@livestore/utils 0.3.0-dev.16 → 0.3.0-dev.18
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/Deferred.d.ts.map +1 -1
- package/dist/base64.d.ts.map +1 -1
- package/dist/bun/mod.d.ts.map +1 -1
- package/dist/cuid/cuid.browser.d.ts.map +1 -1
- package/dist/cuid/cuid.node.d.ts.map +1 -1
- package/dist/effect/BucketQueue.d.ts +15 -3
- package/dist/effect/BucketQueue.d.ts.map +1 -1
- package/dist/effect/BucketQueue.js +25 -7
- package/dist/effect/BucketQueue.js.map +1 -1
- package/dist/effect/Effect.d.ts.map +1 -1
- package/dist/effect/Effect.js +10 -4
- package/dist/effect/Effect.js.map +1 -1
- package/dist/effect/Logger.d.ts +2 -0
- package/dist/effect/Logger.d.ts.map +1 -1
- package/dist/effect/Logger.js +16 -1
- package/dist/effect/Logger.js.map +1 -1
- package/dist/effect/Scheduler.d.ts.map +1 -1
- package/dist/effect/Schema/debug-diff.d.ts.map +1 -1
- package/dist/effect/Schema/index.d.ts.map +1 -1
- package/dist/effect/Schema/msgpack.d.ts.map +1 -1
- package/dist/effect/ServiceContext.d.ts.map +1 -1
- package/dist/effect/Stream.d.ts.map +1 -1
- package/dist/effect/Subscribable.d.ts.map +1 -1
- package/dist/effect/TaskTracing.d.ts.map +1 -1
- package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
- package/dist/effect/WebChannel/WebChannel.js +4 -2
- package/dist/effect/WebChannel/WebChannel.js.map +1 -1
- package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
- package/dist/effect/WebChannel/common.d.ts.map +1 -1
- package/dist/effect/WebChannel/common.js.map +1 -1
- package/dist/effect/WebLock.d.ts.map +1 -1
- package/dist/effect/WebSocket.d.ts.map +1 -1
- package/dist/effect/WebSocket.js +33 -19
- package/dist/effect/WebSocket.js.map +1 -1
- package/dist/effect/WebSocket.test.d.ts +2 -0
- package/dist/effect/WebSocket.test.d.ts.map +1 -0
- package/dist/effect/WebSocket.test.js +10 -0
- package/dist/effect/WebSocket.test.js.map +1 -0
- package/dist/env.d.ts +1 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +2 -1
- package/dist/env.js.map +1 -1
- package/dist/fast-deep-equal.d.ts.map +1 -1
- package/dist/guards.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/misc.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js +3 -7
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +2 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +1 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +39 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +1 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +75 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +1 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +62 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +1 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts +2 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts.map +1 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +42 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +1 -0
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +1 -1
- package/dist/node/mod.d.ts +2 -1
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +26 -4
- package/dist/node/mod.js.map +1 -1
- package/dist/object/index.d.ts.map +1 -1
- package/dist/object/omit.d.ts.map +1 -1
- package/dist/object/pick.d.ts.map +1 -1
- package/dist/promise.d.ts.map +1 -1
- package/dist/set.d.ts.map +1 -1
- package/dist/string.d.ts.map +1 -1
- package/dist/time.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/effect/BucketQueue.ts +30 -7
- package/src/effect/Effect.ts +17 -4
- package/src/effect/Logger.ts +23 -1
- package/src/effect/WebChannel/WebChannel.ts +4 -2
- package/src/effect/WebChannel/common.ts +1 -1
- package/src/effect/WebSocket.test.ts +14 -0
- package/src/effect/WebSocket.ts +56 -37
- package/src/env.ts +2 -1
- package/src/node/ChildProcessRunner/ChildProcessRunner.ts +3 -7
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +52 -0
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +65 -0
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +53 -0
- package/src/node/ChildProcessRunner/ChildProcessWorker.ts +2 -2
- package/src/node/mod.ts +31 -4
- package/tmp/pack.tgz +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { Array, STM, TRef } from 'effect'
|
|
1
|
+
import { Array, Effect, STM, TRef } from 'effect'
|
|
2
|
+
|
|
3
|
+
import { ReadonlyArray } from './index.js'
|
|
2
4
|
|
|
3
5
|
export type BucketQueue<A> = TRef.TRef<A[]>
|
|
4
6
|
|
|
@@ -7,22 +9,43 @@ export const make = <A>(): STM.STM<BucketQueue<A>> => TRef.make<A[]>([])
|
|
|
7
9
|
export const offerAll = <A>(self: BucketQueue<A>, elements: ReadonlyArray<A>) =>
|
|
8
10
|
TRef.update(self, (bucket) => Array.appendAll(bucket, elements))
|
|
9
11
|
|
|
12
|
+
export const replace = <A>(self: BucketQueue<A>, elements: ReadonlyArray<A>) => TRef.set(self, elements as A[])
|
|
13
|
+
|
|
10
14
|
export const clear = <A>(self: BucketQueue<A>) => TRef.set(self, [])
|
|
11
15
|
|
|
12
16
|
export const takeBetween = <A>(
|
|
13
|
-
|
|
17
|
+
bucket: BucketQueue<A>,
|
|
14
18
|
min: number,
|
|
15
19
|
max: number,
|
|
16
20
|
): STM.STM<ReadonlyArray<A>, never, never> =>
|
|
17
21
|
STM.gen(function* () {
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
22
|
+
const bucketValue = yield* TRef.get(bucket)
|
|
23
|
+
if (bucketValue.length < min) {
|
|
20
24
|
return yield* STM.retry
|
|
21
25
|
} else {
|
|
22
|
-
const elements =
|
|
23
|
-
yield* TRef.set(
|
|
26
|
+
const elements = bucketValue.splice(0, Math.min(max, bucketValue.length))
|
|
27
|
+
yield* TRef.set(bucket, bucketValue)
|
|
24
28
|
return elements
|
|
25
29
|
}
|
|
26
30
|
})
|
|
27
31
|
|
|
28
|
-
export const
|
|
32
|
+
export const peekAll = <A>(bucket: BucketQueue<A>) => TRef.get(bucket)
|
|
33
|
+
|
|
34
|
+
/** Returns the elements up to the first element that matches the predicate, the rest is left in the queue
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const [elements, rest] = yield* BucketQueue.takeSplitWhere(bucket, (a) => a > 3)
|
|
39
|
+
* assert.deepStrictEqual(elements, [1, 2, 3])
|
|
40
|
+
* assert.deepStrictEqual(rest, [4, 5, 6])
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export const takeSplitWhere = <A>(bucket: BucketQueue<A>, predicate: (a: A) => boolean) =>
|
|
44
|
+
STM.gen(function* () {
|
|
45
|
+
const bucketValue = yield* TRef.get(bucket)
|
|
46
|
+
const [elements, rest] = ReadonlyArray.splitWhere(bucketValue, predicate)
|
|
47
|
+
yield* TRef.set(bucket, rest)
|
|
48
|
+
return elements
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const size = <A>(bucket: BucketQueue<A>) => TRef.get(bucket).pipe(Effect.map((_) => _.length))
|
package/src/effect/Effect.ts
CHANGED
|
@@ -125,11 +125,11 @@ export const logWarnIfTakesLongerThan =
|
|
|
125
125
|
Effect.gen(function* () {
|
|
126
126
|
const runtime = yield* Effect.runtime<never>()
|
|
127
127
|
|
|
128
|
-
let
|
|
128
|
+
let tookLongerThanTimer = false
|
|
129
129
|
|
|
130
130
|
const timeoutFiber = Effect.sleep(duration).pipe(
|
|
131
131
|
Effect.tap(() => {
|
|
132
|
-
|
|
132
|
+
tookLongerThanTimer = true
|
|
133
133
|
// TODO include span info
|
|
134
134
|
return Effect.logWarning(`${label}: Took longer than ${duration}ms`)
|
|
135
135
|
}),
|
|
@@ -138,9 +138,22 @@ export const logWarnIfTakesLongerThan =
|
|
|
138
138
|
)
|
|
139
139
|
|
|
140
140
|
const start = Date.now()
|
|
141
|
-
const res = yield* eff.pipe(
|
|
141
|
+
const res = yield* eff.pipe(
|
|
142
|
+
Effect.exit,
|
|
143
|
+
Effect.onInterrupt(
|
|
144
|
+
Effect.fn(function* () {
|
|
145
|
+
const end = Date.now()
|
|
146
|
+
|
|
147
|
+
yield* Fiber.interrupt(timeoutFiber)
|
|
148
|
+
|
|
149
|
+
if (tookLongerThanTimer) {
|
|
150
|
+
yield* Effect.logWarning(`${label}: Interrupted after ${end - start}ms`)
|
|
151
|
+
}
|
|
152
|
+
}),
|
|
153
|
+
),
|
|
154
|
+
)
|
|
142
155
|
|
|
143
|
-
if (
|
|
156
|
+
if (tookLongerThanTimer) {
|
|
144
157
|
const end = Date.now()
|
|
145
158
|
yield* Effect.logWarning(`${label}: Actual duration: ${end - start}ms`)
|
|
146
159
|
}
|
package/src/effect/Logger.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Logger } from 'effect'
|
|
1
|
+
import { Cause, HashMap, Logger, LogLevel } from 'effect'
|
|
2
2
|
|
|
3
3
|
export * from 'effect/Logger'
|
|
4
4
|
|
|
@@ -14,4 +14,26 @@ export const prettyWithThread = (threadName: string) =>
|
|
|
14
14
|
Logger.prettyLogger({
|
|
15
15
|
formatDate: (date) => `${defaultDateFormat(date)} ${threadName}`,
|
|
16
16
|
}),
|
|
17
|
+
// consoleLogger(threadName),
|
|
17
18
|
)
|
|
19
|
+
|
|
20
|
+
export const consoleLogger = (threadName: string) =>
|
|
21
|
+
Logger.make(({ message, annotations, date, logLevel, cause }) => {
|
|
22
|
+
const consoleFn =
|
|
23
|
+
logLevel === LogLevel.Debug
|
|
24
|
+
? console.debug
|
|
25
|
+
: logLevel === LogLevel.Info
|
|
26
|
+
? console.info
|
|
27
|
+
: logLevel === LogLevel.Warning
|
|
28
|
+
? console.warn
|
|
29
|
+
: console.error
|
|
30
|
+
|
|
31
|
+
const annotationsObj = Object.fromEntries(HashMap.entries(annotations))
|
|
32
|
+
|
|
33
|
+
const messages = Array.isArray(message) ? message : [message]
|
|
34
|
+
if (Cause.isEmpty(cause) === false) {
|
|
35
|
+
messages.push(Cause.pretty(cause, { renderErrorCause: true }))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
consoleFn(`[${defaultDateFormat(date)} ${threadName}]`, ...messages, annotationsObj)
|
|
39
|
+
})
|
|
@@ -177,11 +177,13 @@ export const messagePortChannelWithAck: {
|
|
|
177
177
|
const ChannelRequest = Schema.TaggedStruct('ChannelRequest', {
|
|
178
178
|
id: Schema.String,
|
|
179
179
|
payload: Schema.Union(schema.listen, schema.send),
|
|
180
|
-
})
|
|
180
|
+
}).annotations({ title: 'webmesh.ChannelRequest' })
|
|
181
181
|
const ChannelRequestAck = Schema.TaggedStruct('ChannelRequestAck', {
|
|
182
182
|
reqId: Schema.String,
|
|
183
|
+
}).annotations({ title: 'webmesh.ChannelRequestAck' })
|
|
184
|
+
const ChannelMessage = Schema.Union(ChannelRequest, ChannelRequestAck).annotations({
|
|
185
|
+
title: 'webmesh.ChannelMessage',
|
|
183
186
|
})
|
|
184
|
-
const ChannelMessage = Schema.Union(ChannelRequest, ChannelRequestAck)
|
|
185
187
|
type ChannelMessage = typeof ChannelMessage.Type
|
|
186
188
|
|
|
187
189
|
const debugInfo = {
|
|
@@ -43,7 +43,7 @@ export const mapSchema = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>(
|
|
|
43
43
|
schema: InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>,
|
|
44
44
|
): OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> =>
|
|
45
45
|
Predicate.hasProperty(schema, 'send') && Predicate.hasProperty(schema, 'listen')
|
|
46
|
-
? schemaWithDebugPing(schema)
|
|
46
|
+
? (schemaWithDebugPing(schema) as any)
|
|
47
47
|
: (schemaWithDebugPing({ send: schema, listen: schema }) as any)
|
|
48
48
|
|
|
49
49
|
export const listenToDebugPing = (channelName: string) => {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as Vitest from '@effect/vitest'
|
|
2
|
+
import { Effect, Exit } from 'effect'
|
|
3
|
+
|
|
4
|
+
import { makeWebSocket } from './WebSocket.js'
|
|
5
|
+
|
|
6
|
+
Vitest.describe('WebSocket', () => {
|
|
7
|
+
Vitest.scopedLive(
|
|
8
|
+
'should create a WebSocket connection',
|
|
9
|
+
Effect.fn(function* () {
|
|
10
|
+
const exit = yield* makeWebSocket({ url: 'ws://localhost:1000' }).pipe(Effect.timeout(500), Effect.exit)
|
|
11
|
+
Vitest.expect(Exit.isFailure(exit)).toBe(true)
|
|
12
|
+
}),
|
|
13
|
+
)
|
|
14
|
+
})
|
package/src/effect/WebSocket.ts
CHANGED
|
@@ -22,50 +22,69 @@ export const makeWebSocket = ({
|
|
|
22
22
|
reconnect?: Schedule.Schedule<unknown> | false
|
|
23
23
|
}): Effect.Effect<globalThis.WebSocket, WebSocketError, Scope.Scope> =>
|
|
24
24
|
Effect.gen(function* () {
|
|
25
|
-
const socket = yield* Effect.
|
|
26
|
-
try
|
|
27
|
-
// console.debug('[WebSocket] connecting to', url)
|
|
25
|
+
const socket = yield* Effect.async<globalThis.WebSocket, WebSocketError>((cb, signal) => {
|
|
26
|
+
try {
|
|
28
27
|
const socket = new globalThis.WebSocket(url)
|
|
29
28
|
|
|
30
29
|
if (socket.readyState === globalThis.WebSocket.OPEN) {
|
|
31
|
-
|
|
30
|
+
cb(Effect.succeed(socket))
|
|
31
|
+
return
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
socket.
|
|
36
|
-
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
|
37
|
-
socket.onerror = (event) => reject(event)
|
|
34
|
+
signal.addEventListener('abort', () => {
|
|
35
|
+
socket.close(3000, 'abort signal')
|
|
38
36
|
})
|
|
39
|
-
},
|
|
40
|
-
catch: (errorEvent: any) => {
|
|
41
|
-
if (errorEvent.currentTarget != null && errorEvent.currentTarget instanceof globalThis.WebSocket) {
|
|
42
|
-
errorEvent.currentTarget.close(3000, `closing websocket connection due to error: ${errorEvent.toString()}`)
|
|
43
|
-
}
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
|
|
38
|
+
socket.addEventListener('open', () => cb(Effect.succeed(socket)), { once: true })
|
|
39
|
+
|
|
40
|
+
socket.addEventListener(
|
|
41
|
+
'error',
|
|
42
|
+
(event) => {
|
|
43
|
+
cb(Effect.fail(new WebSocketError({ cause: event })))
|
|
44
|
+
},
|
|
45
|
+
{ once: true },
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
socket.addEventListener(
|
|
49
|
+
'close',
|
|
50
|
+
(event) => {
|
|
51
|
+
// console.log('makeWebSocket:socket:onclose', event)
|
|
52
|
+
return cb(Effect.fail(new WebSocketError({ cause: event })))
|
|
53
|
+
},
|
|
54
|
+
{ once: true },
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// console.log('makeWebSocket:socket:waiting for open', url)
|
|
58
|
+
} catch (error) {
|
|
59
|
+
cb(Effect.fail(new WebSocketError({ cause: error })))
|
|
60
|
+
}
|
|
61
|
+
}).pipe(reconnect ? Effect.retry(reconnect) : identity)
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Common WebSocket close codes: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close
|
|
65
|
+
* 1000: Normal closure
|
|
66
|
+
* 1001: Endpoint is going away, a server is terminating the connection because it has received a request that indicates the client is ending the connection.
|
|
67
|
+
* 1002: Protocol error, a server is terminating the connection because it has received data on the connection that was not consistent with the type of the connection.
|
|
68
|
+
* 1011: Internal server error, a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.
|
|
69
|
+
*
|
|
70
|
+
* For reference, here are the valid WebSocket close code ranges:
|
|
71
|
+
* 1000-1999: Reserved for protocol usage
|
|
72
|
+
* 2000-2999: Reserved for WebSocket extensions
|
|
73
|
+
* 3000-3999: Available for libraries and frameworks
|
|
74
|
+
* 4000-4999: Available for applications
|
|
75
|
+
*/
|
|
76
|
+
yield* Effect.addFinalizer((exit) =>
|
|
77
|
+
Effect.async<void, WebSocketError>((cb) => {
|
|
78
|
+
try {
|
|
79
|
+
if (Exit.isFailure(exit)) {
|
|
80
|
+
socket.close(3000)
|
|
81
|
+
} else {
|
|
82
|
+
socket.close(1000)
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
cb(Effect.fail(new WebSocketError({ cause: error })))
|
|
86
|
+
}
|
|
87
|
+
}).pipe(Effect.orDie),
|
|
69
88
|
)
|
|
70
89
|
|
|
71
90
|
return socket
|
package/src/env.ts
CHANGED
|
@@ -26,7 +26,8 @@ export const isDevEnv = () => {
|
|
|
26
26
|
return false
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export const TRACE_VERBOSE =
|
|
29
|
+
export const TRACE_VERBOSE = true
|
|
30
|
+
// export const TRACE_VERBOSE = env('LS_TRACE_VERBOSE') !== undefined || env('VITE_LS_TRACE_VERBOSE') !== undefined
|
|
30
31
|
|
|
31
32
|
export const LS_DEV = env('LS_DEV') !== undefined || env('VITE_LS_DEV') !== undefined
|
|
32
33
|
|
|
@@ -3,7 +3,6 @@ import process from 'node:process'
|
|
|
3
3
|
|
|
4
4
|
import { WorkerError } from '@effect/platform/WorkerError'
|
|
5
5
|
import * as Runner from '@effect/platform/WorkerRunner'
|
|
6
|
-
import { Cause } from 'effect'
|
|
7
6
|
import * as Context from 'effect/Context'
|
|
8
7
|
import * as Deferred from 'effect/Deferred'
|
|
9
8
|
import * as Effect from 'effect/Effect'
|
|
@@ -38,10 +37,12 @@ const platformRunnerImpl = Runner.PlatformRunner.of({
|
|
|
38
37
|
const runFork = Runtime.runFork(runtime)
|
|
39
38
|
const onExit = (exit: Exit.Exit<any, E>) => {
|
|
40
39
|
if (exit._tag === 'Failure') {
|
|
41
|
-
Deferred.unsafeDone(closeLatch, Exit.die(Cause.squash(exit.cause)))
|
|
40
|
+
// Deferred.unsafeDone(closeLatch, Exit.die(Cause.squash(exit.cause)))
|
|
41
|
+
Deferred.unsafeDone(closeLatch, Exit.die(exit.cause))
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
port.on('message', (message: Runner.BackingRunner.Message<I>) => {
|
|
45
|
+
// console.log('message', message)
|
|
45
46
|
if (message[0] === 0) {
|
|
46
47
|
const fiber = runFork(handler(0, message[1]))
|
|
47
48
|
fiber.addObserver(onExit)
|
|
@@ -49,11 +50,6 @@ const platformRunnerImpl = Runner.PlatformRunner.of({
|
|
|
49
50
|
} else {
|
|
50
51
|
Deferred.unsafeDone(closeLatch, Exit.void)
|
|
51
52
|
port.close()
|
|
52
|
-
// TODO get rid of this timeout (needs help from Tim Smart)
|
|
53
|
-
setTimeout(() => {
|
|
54
|
-
// eslint-disable-next-line unicorn/no-process-exit
|
|
55
|
-
process.exit(0)
|
|
56
|
-
}, 1000)
|
|
57
53
|
}
|
|
58
54
|
})
|
|
59
55
|
port.on('messageerror', (cause) => {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// import * as WorkerThreads from 'node:worker_threads'
|
|
2
|
+
import * as ChildProcess from 'node:child_process'
|
|
3
|
+
|
|
4
|
+
import * as EffectWorker from '@effect/platform/Worker'
|
|
5
|
+
import { assert, describe, it } from '@effect/vitest'
|
|
6
|
+
import { Chunk, Effect, Stream } from 'effect'
|
|
7
|
+
|
|
8
|
+
import * as ChildProcessWorker from '../ChildProcessWorker.js'
|
|
9
|
+
import type { WorkerMessage } from './schema.js'
|
|
10
|
+
import { GetPersonById, GetUserById, InitialMessage, Person, User } from './schema.js'
|
|
11
|
+
|
|
12
|
+
const WorkerLive = ChildProcessWorker.layer(() =>
|
|
13
|
+
ChildProcess.fork(
|
|
14
|
+
new URL('../../../../dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js', import.meta.url),
|
|
15
|
+
),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// const WorkerLive = NodeWorker.layer(
|
|
19
|
+
// () =>
|
|
20
|
+
// new WorkerThreads.Worker(
|
|
21
|
+
// new URL('../../../../dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js', import.meta.url),
|
|
22
|
+
// ),
|
|
23
|
+
// )
|
|
24
|
+
|
|
25
|
+
describe('ChildProcessRunner', { timeout: 10_000 }, () => {
|
|
26
|
+
it('Serialized', () =>
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
const pool = yield* EffectWorker.makePoolSerialized({ size: 1 })
|
|
29
|
+
const people = yield* pool.execute(new GetPersonById({ id: 123 })).pipe(Stream.runCollect)
|
|
30
|
+
assert.deepStrictEqual(Chunk.toReadonlyArray(people), [
|
|
31
|
+
new Person({ id: 123, name: 'test', data: new Uint8Array([1, 2, 3]) }),
|
|
32
|
+
new Person({ id: 123, name: 'ing', data: new Uint8Array([4, 5, 6]) }),
|
|
33
|
+
])
|
|
34
|
+
}).pipe(Effect.scoped, Effect.provide(WorkerLive), Effect.runPromise))
|
|
35
|
+
|
|
36
|
+
it('Serialized with initialMessage', () =>
|
|
37
|
+
Effect.gen(function* () {
|
|
38
|
+
const pool = yield* EffectWorker.makePoolSerialized<WorkerMessage>({
|
|
39
|
+
size: 1,
|
|
40
|
+
initialMessage: () => new InitialMessage({ name: 'custom', data: new Uint8Array([1, 2, 3]) }),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
let user = yield* pool.executeEffect(new GetUserById({ id: 123 }))
|
|
44
|
+
user = yield* pool.executeEffect(new GetUserById({ id: 123 }))
|
|
45
|
+
assert.deepStrictEqual(user, new User({ id: 123, name: 'custom' }))
|
|
46
|
+
const people = yield* pool.execute(new GetPersonById({ id: 123 })).pipe(Stream.runCollect)
|
|
47
|
+
assert.deepStrictEqual(Chunk.toReadonlyArray(people), [
|
|
48
|
+
new Person({ id: 123, name: 'test', data: new Uint8Array([1, 2, 3]) }),
|
|
49
|
+
new Person({ id: 123, name: 'ing', data: new Uint8Array([4, 5, 6]) }),
|
|
50
|
+
])
|
|
51
|
+
}).pipe(Effect.scoped, Effect.provide(WorkerLive), Effect.runPromise))
|
|
52
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// import * as Transferable from '@effect/platform/Transferable'
|
|
2
|
+
import * as Schema from 'effect/Schema'
|
|
3
|
+
|
|
4
|
+
export class User extends Schema.Class<User>('User')({
|
|
5
|
+
id: Schema.Number,
|
|
6
|
+
name: Schema.String,
|
|
7
|
+
}) {}
|
|
8
|
+
|
|
9
|
+
export class GetUserById extends Schema.TaggedRequest<GetUserById>()('GetUserById', {
|
|
10
|
+
failure: Schema.Never,
|
|
11
|
+
success: User,
|
|
12
|
+
payload: {
|
|
13
|
+
id: Schema.Number,
|
|
14
|
+
},
|
|
15
|
+
}) {}
|
|
16
|
+
|
|
17
|
+
export class Person extends Schema.Class<Person>('Person')({
|
|
18
|
+
id: Schema.Number,
|
|
19
|
+
name: Schema.String,
|
|
20
|
+
// data: Transferable.Uint8Array,
|
|
21
|
+
data: Schema.Uint8Array,
|
|
22
|
+
}) {}
|
|
23
|
+
|
|
24
|
+
export class GetPersonById extends Schema.TaggedRequest<GetPersonById>()('GetPersonById', {
|
|
25
|
+
failure: Schema.Never,
|
|
26
|
+
success: Person,
|
|
27
|
+
payload: {
|
|
28
|
+
id: Schema.Number,
|
|
29
|
+
},
|
|
30
|
+
}) {}
|
|
31
|
+
|
|
32
|
+
export class RunnerInterrupt extends Schema.TaggedRequest<RunnerInterrupt>()('RunnerInterrupt', {
|
|
33
|
+
failure: Schema.Never,
|
|
34
|
+
success: Schema.Void,
|
|
35
|
+
payload: {},
|
|
36
|
+
}) {}
|
|
37
|
+
|
|
38
|
+
export class InitialMessage extends Schema.TaggedRequest<InitialMessage>()('InitialMessage', {
|
|
39
|
+
failure: Schema.Never,
|
|
40
|
+
success: Schema.Void,
|
|
41
|
+
payload: {
|
|
42
|
+
name: Schema.String,
|
|
43
|
+
data: Schema.Uint8Array,
|
|
44
|
+
// data: Transferable.Uint8Array,
|
|
45
|
+
},
|
|
46
|
+
}) {}
|
|
47
|
+
|
|
48
|
+
export class GetSpan extends Schema.TaggedRequest<GetSpan>()('GetSpan', {
|
|
49
|
+
failure: Schema.Never,
|
|
50
|
+
success: Schema.Struct({
|
|
51
|
+
name: Schema.String,
|
|
52
|
+
traceId: Schema.String,
|
|
53
|
+
spanId: Schema.String,
|
|
54
|
+
parent: Schema.Option(
|
|
55
|
+
Schema.Struct({
|
|
56
|
+
traceId: Schema.String,
|
|
57
|
+
spanId: Schema.String,
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
60
|
+
}),
|
|
61
|
+
payload: {},
|
|
62
|
+
}) {}
|
|
63
|
+
|
|
64
|
+
export const WorkerMessage = Schema.Union(GetUserById, GetPersonById, InitialMessage, GetSpan, RunnerInterrupt)
|
|
65
|
+
export type WorkerMessage = Schema.Schema.Type<typeof WorkerMessage>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as Runner from '@effect/platform/WorkerRunner'
|
|
2
|
+
import { Context, Effect, Layer, Option, Stream } from 'effect'
|
|
3
|
+
|
|
4
|
+
// import { NodeRuntime, NodeWorkerRunner } from '@effect/platform-node'
|
|
5
|
+
import { PlatformNode } from '../../mod.js'
|
|
6
|
+
import * as ChildProcessRunner from '../ChildProcessRunner.js'
|
|
7
|
+
import { Person, User, WorkerMessage } from './schema.js'
|
|
8
|
+
|
|
9
|
+
interface Name {
|
|
10
|
+
readonly _: unique symbol
|
|
11
|
+
}
|
|
12
|
+
const Name = Context.GenericTag<Name, string>('Name')
|
|
13
|
+
|
|
14
|
+
const WorkerLive = Runner.layerSerialized(WorkerMessage, {
|
|
15
|
+
GetPersonById: (req) => {
|
|
16
|
+
return Stream.make(
|
|
17
|
+
new Person({ id: req.id, name: 'test', data: new Uint8Array([1, 2, 3]) }),
|
|
18
|
+
new Person({ id: req.id, name: 'ing', data: new Uint8Array([4, 5, 6]) }),
|
|
19
|
+
)
|
|
20
|
+
},
|
|
21
|
+
GetUserById: (req) => Effect.map(Name, (name) => new User({ id: req.id, name })),
|
|
22
|
+
// InitialMessage: (req) => Layer.succeed(Name, req.name),
|
|
23
|
+
InitialMessage: (req) =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
yield* Effect.addFinalizer(() => Effect.log('closing worker scope'))
|
|
26
|
+
return Layer.succeed(Name, req.name)
|
|
27
|
+
}).pipe(Layer.unwrapScoped),
|
|
28
|
+
// InitialMessage: (req) =>
|
|
29
|
+
// Layer.scoped(
|
|
30
|
+
// Name,
|
|
31
|
+
// Effect.gen(function* () {
|
|
32
|
+
// yield* Effect.addFinalizer(() => Effect.log('closing worker scope'))
|
|
33
|
+
// return req.name
|
|
34
|
+
// }),
|
|
35
|
+
// ),
|
|
36
|
+
GetSpan: (_) =>
|
|
37
|
+
Effect.gen(function* (_) {
|
|
38
|
+
const span = yield* _(Effect.currentSpan, Effect.orDie)
|
|
39
|
+
return {
|
|
40
|
+
traceId: span.traceId,
|
|
41
|
+
spanId: span.spanId,
|
|
42
|
+
name: span.name,
|
|
43
|
+
parent: Option.map(span.parent, (span) => ({
|
|
44
|
+
traceId: span.traceId,
|
|
45
|
+
spanId: span.spanId,
|
|
46
|
+
})),
|
|
47
|
+
}
|
|
48
|
+
}).pipe(Effect.withSpan('GetSpan')),
|
|
49
|
+
RunnerInterrupt: () => Effect.interrupt,
|
|
50
|
+
}).pipe(Layer.provide(ChildProcessRunner.layer))
|
|
51
|
+
// }).pipe(Layer.provide(PlatformNode.NodeWorkerRunner.layer))
|
|
52
|
+
|
|
53
|
+
PlatformNode.NodeRuntime.runMain(Runner.launch(WorkerLive))
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* eslint-disable prefer-arrow/prefer-arrow-functions */
|
|
2
2
|
|
|
3
|
-
import type * as ChildProcess from 'node:child_process'
|
|
4
|
-
|
|
5
3
|
import * as Worker from '@effect/platform/Worker'
|
|
6
4
|
import { WorkerError } from '@effect/platform/WorkerError'
|
|
5
|
+
// eslint-disable-next-line unicorn/prefer-node-protocol
|
|
6
|
+
import type * as ChildProcess from 'child_process'
|
|
7
7
|
import * as Deferred from 'effect/Deferred'
|
|
8
8
|
import * as Effect from 'effect/Effect'
|
|
9
9
|
import * as Exit from 'effect/Exit'
|
package/src/node/mod.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as http from 'node:http'
|
|
2
|
+
|
|
1
3
|
import * as OtelNodeSdk from '@effect/opentelemetry/NodeSdk'
|
|
2
4
|
import type * as otel from '@opentelemetry/api'
|
|
3
5
|
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
|
|
@@ -8,7 +10,7 @@ import { Config, Effect, Layer } from 'effect'
|
|
|
8
10
|
import type { ParentSpan } from 'effect/Tracer'
|
|
9
11
|
|
|
10
12
|
import { tapCauseLogPretty } from '../effect/Effect.js'
|
|
11
|
-
import { OtelTracer } from '../effect/index.js'
|
|
13
|
+
import { OtelTracer, UnknownError } from '../effect/index.js'
|
|
12
14
|
import { makeNoopTracer } from '../NoopTracer.js'
|
|
13
15
|
|
|
14
16
|
// import { tapCauseLogPretty } from '../effect/Effect.js'
|
|
@@ -30,6 +32,31 @@ export * as ChildProcessWorker from './ChildProcessRunner/ChildProcessWorker.js'
|
|
|
30
32
|
|
|
31
33
|
// export const OtelLiveHttp = (args: any): Layer.Layer<never> => Layer.empty
|
|
32
34
|
|
|
35
|
+
export const getFreePort = Effect.async<number, UnknownError>((cb, signal) => {
|
|
36
|
+
const server = http.createServer()
|
|
37
|
+
|
|
38
|
+
signal.addEventListener('abort', () => {
|
|
39
|
+
server.close()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Listen on port 0 to get an available port
|
|
43
|
+
server.listen(0, () => {
|
|
44
|
+
const address = server.address()
|
|
45
|
+
|
|
46
|
+
if (address && typeof address === 'object') {
|
|
47
|
+
const port = address.port
|
|
48
|
+
server.close(() => cb(Effect.succeed(port)))
|
|
49
|
+
} else {
|
|
50
|
+
server.close(() => cb(Effect.fail(new UnknownError({ cause: 'Failed to get a free port' }))))
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Error handling in case the server encounters an error
|
|
55
|
+
server.on('error', (err) => {
|
|
56
|
+
server.close(() => cb(Effect.fail(new UnknownError({ cause: err }))))
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
33
60
|
export const OtelLiveDummy: Layer.Layer<OtelTracer.OtelTracer> = Layer.suspend(() => {
|
|
34
61
|
const OtelTracerLive = Layer.succeed(OtelTracer.OtelTracer, makeNoopTracer())
|
|
35
62
|
|
|
@@ -91,12 +118,12 @@ export const logTraceUiUrlForSpan = (printMsg?: (url: string) => string) => (spa
|
|
|
91
118
|
getTracingBackendUrl(span).pipe(
|
|
92
119
|
Effect.tap((url) => {
|
|
93
120
|
if (url === undefined) {
|
|
94
|
-
|
|
121
|
+
return Effect.logWarning('No tracing backend url found')
|
|
95
122
|
} else {
|
|
96
123
|
if (printMsg) {
|
|
97
|
-
|
|
124
|
+
return Effect.log(printMsg(url))
|
|
98
125
|
} else {
|
|
99
|
-
|
|
126
|
+
return Effect.log(`Trace URL: ${url}`)
|
|
100
127
|
}
|
|
101
128
|
}
|
|
102
129
|
}),
|
package/tmp/pack.tgz
ADDED
|
Binary file
|