@livestore/utils 0.3.0-dev.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo.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 +5 -0
- package/dist/bun/mod.d.ts.map +1 -0
- package/dist/bun/mod.js +10 -0
- package/dist/bun/mod.js.map +1 -0
- 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 +24 -7
- package/dist/effect/BucketQueue.js.map +1 -1
- package/dist/effect/Effect.d.ts +4 -2
- package/dist/effect/Effect.d.ts.map +1 -1
- package/dist/effect/Effect.js +21 -18
- 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 +3 -0
- package/dist/effect/Schema/index.d.ts.map +1 -1
- package/dist/effect/Schema/index.js +19 -0
- package/dist/effect/Schema/index.js.map +1 -1
- package/dist/effect/Schema/msgpack.d.ts +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 +3 -0
- package/dist/effect/Subscribable.d.ts.map +1 -1
- package/dist/effect/Subscribable.js +8 -3
- package/dist/effect/Subscribable.js.map +1 -1
- package/dist/effect/TaskTracing.d.ts.map +1 -1
- package/dist/effect/{WebChannel.d.ts → WebChannel/WebChannel.d.ts} +39 -17
- package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -0
- package/dist/effect/WebChannel/WebChannel.js +300 -0
- package/dist/effect/WebChannel/WebChannel.js.map +1 -0
- package/dist/effect/WebChannel/WebChannel.test.d.ts +2 -0
- package/dist/effect/WebChannel/WebChannel.test.d.ts.map +1 -0
- package/dist/effect/WebChannel/WebChannel.test.js +62 -0
- package/dist/effect/WebChannel/WebChannel.test.js.map +1 -0
- package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts +5 -6
- package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
- package/dist/effect/WebChannel/broadcastChannelWithAck.js +12 -9
- package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
- package/dist/effect/WebChannel/common.d.ts +29 -1
- package/dist/effect/WebChannel/common.d.ts.map +1 -1
- package/dist/effect/WebChannel/common.js +26 -1
- package/dist/effect/WebChannel/common.js.map +1 -1
- package/dist/effect/WebChannel/mod.d.ts +4 -0
- package/dist/effect/WebChannel/mod.d.ts.map +1 -0
- package/dist/effect/WebChannel/mod.js +4 -0
- package/dist/effect/WebChannel/mod.js.map +1 -0
- package/dist/effect/WebLock.d.ts.map +1 -1
- package/dist/effect/WebSocket.d.ts +3 -2
- package/dist/effect/WebSocket.d.ts.map +1 -1
- package/dist/effect/WebSocket.js +45 -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 +11 -0
- package/dist/effect/WebSocket.test.js.map +1 -0
- package/dist/effect/index.d.ts +5 -3
- package/dist/effect/index.d.ts.map +1 -1
- package/dist/effect/index.js +6 -5
- package/dist/effect/index.js.map +1 -1
- package/dist/env.d.ts +4 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +7 -12
- 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 +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -8
- package/dist/index.js.map +1 -1
- package/dist/misc.d.ts +3 -0
- package/dist/misc.d.ts.map +1 -1
- package/dist/misc.js +23 -0
- package/dist/misc.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js +21 -11
- 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 -4
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js +1 -4
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +1 -1
- package/dist/node/mod.d.ts +3 -16
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +24 -75
- 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 +51 -43
- package/src/bun/mod.ts +13 -0
- package/src/effect/BucketQueue.ts +32 -7
- package/src/effect/Effect.ts +33 -20
- package/src/effect/Logger.ts +23 -1
- package/src/effect/Schema/index.ts +27 -0
- package/src/effect/Subscribable.ts +12 -0
- package/src/effect/WebChannel/WebChannel.test.ts +106 -0
- package/src/effect/WebChannel/WebChannel.ts +502 -0
- package/src/effect/WebChannel/broadcastChannelWithAck.ts +86 -83
- package/src/effect/WebChannel/common.ts +61 -2
- package/src/effect/WebChannel/mod.ts +3 -0
- package/src/effect/WebSocket.test.ts +15 -0
- package/src/effect/WebSocket.ts +75 -36
- package/src/effect/index.ts +31 -2
- package/src/env.ts +9 -13
- package/src/index.ts +6 -12
- package/src/misc.ts +31 -0
- package/src/node/ChildProcessRunner/ChildProcessRunner.ts +40 -29
- 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 +3 -6
- package/src/node/mod.ts +30 -103
- package/dist/effect/WebChannel.d.ts.map +0 -1
- package/dist/effect/WebChannel.js +0 -175
- package/dist/effect/WebChannel.js.map +0 -1
- package/dist/nanoid/index.browser.d.ts +0 -2
- package/dist/nanoid/index.browser.d.ts.map +0 -1
- package/dist/nanoid/index.browser.js +0 -3
- package/dist/nanoid/index.browser.js.map +0 -1
- package/src/effect/WebChannel.ts +0 -305
- package/src/nanoid/index.browser.ts +0 -2
- package/tmp/effect-deferred-repro.ts +0 -29
- package/tmp/effect-semaphore-repro.ts +0 -93
- package/tsconfig.json +0 -10
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Deferred,
|
|
2
|
-
import { Predicate } from 'effect'
|
|
1
|
+
import type { Deferred, Either, ParseResult } from 'effect'
|
|
2
|
+
import { Effect, Predicate, Schema, Stream } from 'effect'
|
|
3
3
|
|
|
4
4
|
export const WebChannelSymbol = Symbol('WebChannel')
|
|
5
5
|
export type WebChannelSymbol = typeof WebChannelSymbol
|
|
@@ -13,5 +13,64 @@ export interface WebChannel<MsgListen, MsgSend, E = never> {
|
|
|
13
13
|
listen: Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>, E>
|
|
14
14
|
supportsTransferables: boolean
|
|
15
15
|
closedDeferred: Deferred.Deferred<void>
|
|
16
|
+
shutdown: Effect.Effect<void>
|
|
16
17
|
schema: { listen: Schema.Schema<MsgListen, any>; send: Schema.Schema<MsgSend, any> }
|
|
18
|
+
debugInfo?: Record<string, any>
|
|
17
19
|
}
|
|
20
|
+
|
|
21
|
+
export const DebugPingMessage = Schema.TaggedStruct('WebChannel.DebugPing', {
|
|
22
|
+
message: Schema.String,
|
|
23
|
+
payload: Schema.optional(Schema.String),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export const WebChannelPing = Schema.TaggedStruct('WebChannel.Ping', {
|
|
27
|
+
requestId: Schema.String,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export const WebChannelPong = Schema.TaggedStruct('WebChannel.Pong', {
|
|
31
|
+
requestId: Schema.String,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const WebChannelHeartbeat = Schema.Union(WebChannelPing, WebChannelPong)
|
|
35
|
+
|
|
36
|
+
type WebChannelMessages = typeof DebugPingMessage.Type | typeof WebChannelPing.Type | typeof WebChannelPong.Type
|
|
37
|
+
|
|
38
|
+
export const schemaWithWebChannelMessages = <MsgListen, MsgSend>(
|
|
39
|
+
schema: OutputSchema<MsgListen, MsgSend, any, any>,
|
|
40
|
+
): OutputSchema<MsgListen | WebChannelMessages, MsgSend | WebChannelMessages, any, any> => ({
|
|
41
|
+
send: Schema.Union(schema.send, DebugPingMessage, WebChannelPing, WebChannelPong),
|
|
42
|
+
listen: Schema.Union(schema.listen, DebugPingMessage, WebChannelPing, WebChannelPong),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export type InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> =
|
|
46
|
+
| Schema.Schema<MsgListen | MsgSend, MsgListenEncoded | MsgSendEncoded>
|
|
47
|
+
| OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>
|
|
48
|
+
|
|
49
|
+
export type OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> = {
|
|
50
|
+
listen: Schema.Schema<MsgListen, MsgListenEncoded>
|
|
51
|
+
send: Schema.Schema<MsgSend, MsgSendEncoded>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const mapSchema = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>(
|
|
55
|
+
schema: InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>,
|
|
56
|
+
): OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> =>
|
|
57
|
+
Predicate.hasProperty(schema, 'send') && Predicate.hasProperty(schema, 'listen')
|
|
58
|
+
? (schemaWithWebChannelMessages(schema) as any)
|
|
59
|
+
: (schemaWithWebChannelMessages({ send: schema, listen: schema }) as any)
|
|
60
|
+
|
|
61
|
+
export const listenToDebugPing =
|
|
62
|
+
(channelName: string) =>
|
|
63
|
+
<MsgListen>(
|
|
64
|
+
stream: Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>, never>,
|
|
65
|
+
): Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>, never> =>
|
|
66
|
+
stream.pipe(
|
|
67
|
+
Stream.filterEffect(
|
|
68
|
+
Effect.fn(function* (msg) {
|
|
69
|
+
if (msg._tag === 'Right' && Schema.is(DebugPingMessage)(msg.right)) {
|
|
70
|
+
yield* Effect.logDebug(`WebChannel:ping [${channelName}] ${msg.right.message}`, msg.right.payload)
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
return true
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FetchHttpClient } from '@effect/platform'
|
|
2
|
+
import * as Vitest from '@effect/vitest'
|
|
3
|
+
import { Effect, Exit } from 'effect'
|
|
4
|
+
|
|
5
|
+
import { makeWebSocket } from './WebSocket.js'
|
|
6
|
+
|
|
7
|
+
Vitest.describe('WebSocket', () => {
|
|
8
|
+
Vitest.scopedLive(
|
|
9
|
+
'should create a WebSocket connection',
|
|
10
|
+
Effect.fn(function* () {
|
|
11
|
+
const exit = yield* makeWebSocket({ url: 'ws://localhost:1000' }).pipe(Effect.timeout(500), Effect.exit)
|
|
12
|
+
Vitest.expect(Exit.isFailure(exit)).toBe(true)
|
|
13
|
+
}, Effect.provide(FetchHttpClient.layer)),
|
|
14
|
+
)
|
|
15
|
+
})
|
package/src/effect/WebSocket.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HttpClient } from '@effect/platform'
|
|
1
2
|
import type { Schedule, Scope } from 'effect'
|
|
2
3
|
import { Effect, Exit, identity, Schema } from 'effect'
|
|
3
4
|
|
|
@@ -20,53 +21,91 @@ export const makeWebSocket = ({
|
|
|
20
21
|
}: {
|
|
21
22
|
url: string
|
|
22
23
|
reconnect?: Schedule.Schedule<unknown> | false
|
|
23
|
-
}): Effect.Effect<globalThis.WebSocket, WebSocketError, Scope.Scope> =>
|
|
24
|
+
}): Effect.Effect<globalThis.WebSocket, WebSocketError, Scope.Scope | HttpClient.HttpClient> =>
|
|
24
25
|
Effect.gen(function* () {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
yield* validateUrl(url)
|
|
27
|
+
|
|
28
|
+
const socket = yield* Effect.async<globalThis.WebSocket, WebSocketError>((cb, signal) => {
|
|
29
|
+
try {
|
|
28
30
|
const socket = new globalThis.WebSocket(url)
|
|
29
31
|
|
|
30
32
|
if (socket.readyState === globalThis.WebSocket.OPEN) {
|
|
31
|
-
|
|
33
|
+
cb(Effect.succeed(socket))
|
|
34
|
+
return
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
socket.
|
|
36
|
-
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
|
37
|
-
socket.onerror = (event) => reject(event)
|
|
37
|
+
signal.addEventListener('abort', () => {
|
|
38
|
+
socket.close(3000, 'abort signal')
|
|
38
39
|
})
|
|
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
40
|
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
socket.addEventListener('open', () => cb(Effect.succeed(socket)), { once: true })
|
|
42
|
+
|
|
43
|
+
socket.addEventListener(
|
|
44
|
+
'error',
|
|
45
|
+
(event) => {
|
|
46
|
+
cb(Effect.fail(new WebSocketError({ cause: event })))
|
|
47
|
+
},
|
|
48
|
+
{ once: true },
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
socket.addEventListener(
|
|
52
|
+
'close',
|
|
53
|
+
(event) => {
|
|
54
|
+
// console.log('makeWebSocket:socket:onclose', event)
|
|
55
|
+
return cb(Effect.fail(new WebSocketError({ cause: event })))
|
|
56
|
+
},
|
|
57
|
+
{ once: true },
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// console.log('makeWebSocket:socket:waiting for open', url)
|
|
61
|
+
} catch (error) {
|
|
62
|
+
cb(Effect.fail(new WebSocketError({ cause: error })))
|
|
63
|
+
}
|
|
47
64
|
}).pipe(
|
|
48
|
-
|
|
49
|
-
* Common WebSocket close codes: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close
|
|
50
|
-
* 1000: Normal closure
|
|
51
|
-
* 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.
|
|
52
|
-
* 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.
|
|
53
|
-
* 1011: Internal server error, a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.
|
|
54
|
-
*
|
|
55
|
-
* For reference, here are the valid WebSocket close code ranges:
|
|
56
|
-
* 1000-1999: Reserved for protocol usage
|
|
57
|
-
* 2000-2999: Reserved for WebSocket extensions
|
|
58
|
-
* 3000-3999: Available for libraries and frameworks
|
|
59
|
-
* 4000-4999: Available for applications
|
|
60
|
-
*/
|
|
61
|
-
Effect.acquireRelease((socket, exit) =>
|
|
62
|
-
Effect.sync(() =>
|
|
63
|
-
Exit.isFailure(exit)
|
|
64
|
-
? socket.close(3000, `closing webmesh websocket connection due to error: ${exit.cause.toString()}`)
|
|
65
|
-
: socket.close(1000, 'closing webmesh websocket connection gracefully'),
|
|
66
|
-
),
|
|
67
|
-
),
|
|
65
|
+
Effect.tapErrorTag('WebSocketError', () => tryLogWebsocketConnectError(url)),
|
|
68
66
|
reconnect ? Effect.retry(reconnect) : identity,
|
|
69
67
|
)
|
|
70
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Common WebSocket close codes: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close
|
|
71
|
+
* 1000: Normal closure
|
|
72
|
+
* 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.
|
|
73
|
+
* 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.
|
|
74
|
+
* 1011: Internal server error, a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.
|
|
75
|
+
*
|
|
76
|
+
* For reference, here are the valid WebSocket close code ranges:
|
|
77
|
+
* 1000-1999: Reserved for protocol usage
|
|
78
|
+
* 2000-2999: Reserved for WebSocket extensions
|
|
79
|
+
* 3000-3999: Available for libraries and frameworks
|
|
80
|
+
* 4000-4999: Available for applications
|
|
81
|
+
*/
|
|
82
|
+
yield* Effect.addFinalizer(
|
|
83
|
+
Effect.fn(function* (exit) {
|
|
84
|
+
try {
|
|
85
|
+
if (Exit.isFailure(exit)) {
|
|
86
|
+
socket.close(3000)
|
|
87
|
+
} else {
|
|
88
|
+
socket.close(1000)
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
yield* Effect.die(new WebSocketError({ cause: error }))
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
|
|
71
96
|
return socket
|
|
72
97
|
})
|
|
98
|
+
|
|
99
|
+
const validateUrl = (url: string) =>
|
|
100
|
+
Effect.try({
|
|
101
|
+
try: () => new URL(url),
|
|
102
|
+
catch: (error) => new WebSocketError({ cause: error }),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const tryLogWebsocketConnectError = (url: string) =>
|
|
106
|
+
Effect.gen(function* () {
|
|
107
|
+
const client = yield* HttpClient.HttpClient
|
|
108
|
+
const res = yield* client.get(url)
|
|
109
|
+
const responseBody = yield* res.text
|
|
110
|
+
yield* Effect.logError(`Failed to connect to '${url}' (status: ${res.status}). Error:`, responseBody)
|
|
111
|
+
}).pipe(Effect.ignore)
|
package/src/effect/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export {
|
|
|
37
37
|
SortedMap,
|
|
38
38
|
HashMap,
|
|
39
39
|
HashSet,
|
|
40
|
+
ManagedRuntime,
|
|
40
41
|
MutableHashSet,
|
|
41
42
|
MutableHashMap,
|
|
42
43
|
TQueue,
|
|
@@ -56,8 +57,14 @@ export {
|
|
|
56
57
|
Match,
|
|
57
58
|
TestServices,
|
|
58
59
|
Mailbox,
|
|
60
|
+
ExecutionStrategy,
|
|
61
|
+
PrimaryKey,
|
|
62
|
+
Types,
|
|
63
|
+
Cache,
|
|
59
64
|
} from 'effect'
|
|
60
65
|
|
|
66
|
+
export * as StandardSchema from '@standard-schema/spec'
|
|
67
|
+
|
|
61
68
|
export { dual } from 'effect/Function'
|
|
62
69
|
|
|
63
70
|
export * as Stream from './Stream.js'
|
|
@@ -69,7 +76,7 @@ export * as Subscribable from './Subscribable.js'
|
|
|
69
76
|
|
|
70
77
|
export * as Logger from './Logger.js'
|
|
71
78
|
|
|
72
|
-
export * as WebChannel from './WebChannel.js'
|
|
79
|
+
export * as WebChannel from './WebChannel/mod.js'
|
|
73
80
|
export * as WebSocket from './WebSocket.js'
|
|
74
81
|
|
|
75
82
|
export * as SchemaAST from 'effect/SchemaAST'
|
|
@@ -78,9 +85,21 @@ export { ParseResult, Pretty } from 'effect'
|
|
|
78
85
|
export type { Serializable, SerializableWithResult } from 'effect/Schema'
|
|
79
86
|
export * as Schema from './Schema/index.js'
|
|
80
87
|
export * as OtelTracer from '@effect/opentelemetry/Tracer'
|
|
81
|
-
// export * as OtelResource from '@effect/opentelemetry/Resource'
|
|
82
88
|
export * as TaskTracing from './TaskTracing.js'
|
|
83
89
|
|
|
90
|
+
export {
|
|
91
|
+
Rpc,
|
|
92
|
+
RpcGroup,
|
|
93
|
+
RpcClient,
|
|
94
|
+
RpcMessage,
|
|
95
|
+
RpcSchema,
|
|
96
|
+
RpcMiddleware,
|
|
97
|
+
RpcServer,
|
|
98
|
+
RpcSerialization,
|
|
99
|
+
RpcTest,
|
|
100
|
+
RpcWorker,
|
|
101
|
+
} from '@effect/rpc'
|
|
102
|
+
|
|
84
103
|
export {
|
|
85
104
|
Transferable,
|
|
86
105
|
FileSystem,
|
|
@@ -95,6 +114,16 @@ export {
|
|
|
95
114
|
HttpClientResponse,
|
|
96
115
|
FetchHttpClient,
|
|
97
116
|
Socket,
|
|
117
|
+
UrlParams,
|
|
118
|
+
HttpServerRequest,
|
|
119
|
+
Headers,
|
|
120
|
+
HttpMiddleware,
|
|
121
|
+
HttpRouter,
|
|
122
|
+
HttpServerResponse,
|
|
123
|
+
Command,
|
|
124
|
+
CommandExecutor,
|
|
125
|
+
KeyValueStore,
|
|
126
|
+
Error as PlatformError,
|
|
98
127
|
} from '@effect/platform'
|
|
99
128
|
export { BrowserWorker, BrowserWorkerRunner } from '@effect/platform-browser'
|
|
100
129
|
|
package/src/env.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { envTruish } from './misc.js'
|
|
2
|
+
|
|
1
3
|
export const env = (name: string): string | undefined => {
|
|
2
4
|
if (typeof process !== 'undefined' && process.env !== undefined) {
|
|
3
5
|
return process.env[name]
|
|
@@ -12,20 +14,14 @@ export const env = (name: string): string | undefined => {
|
|
|
12
14
|
return undefined
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
export const
|
|
16
|
-
|
|
17
|
-
return process.env.NODE_ENV !== 'production'
|
|
18
|
-
}
|
|
17
|
+
// export const TRACE_VERBOSE = true
|
|
18
|
+
export const TRACE_VERBOSE = env('LS_TRACE_VERBOSE') !== undefined || env('VITE_LS_TRACE_VERBOSE') !== undefined
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (import.meta.env !== undefined) {
|
|
23
|
-
return import.meta.env.DEV
|
|
24
|
-
}
|
|
20
|
+
/** Only set when developing LiveStore itself. */
|
|
21
|
+
export const LS_DEV = envTruish(env('LS_DEV')) || envTruish(env('VITE_LS_DEV'))
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
}
|
|
23
|
+
export const IS_CI = envTruish(env('CI'))
|
|
28
24
|
|
|
29
|
-
export const
|
|
25
|
+
export const IS_BUN = typeof Bun !== 'undefined'
|
|
30
26
|
|
|
31
|
-
export const
|
|
27
|
+
export const IS_REACT_NATIVE = typeof navigator !== 'undefined' && navigator.product === 'ReactNative'
|
package/src/index.ts
CHANGED
|
@@ -14,12 +14,11 @@ export * as base64 from './base64.js'
|
|
|
14
14
|
export { default as prettyBytes } from 'pretty-bytes'
|
|
15
15
|
|
|
16
16
|
import type * as otel from '@opentelemetry/api'
|
|
17
|
+
import type { Types } from 'effect'
|
|
17
18
|
|
|
18
|
-
import { isDevEnv } from './env.js'
|
|
19
19
|
import { objectToString } from './misc.js'
|
|
20
20
|
|
|
21
21
|
export type Prettify<T> = T extends infer U ? { [K in keyof U]: Prettify<U[K]> } : never
|
|
22
|
-
export type PrettifyFlat<T> = T extends infer U ? { [K in keyof U]: U[K] } : never
|
|
23
22
|
|
|
24
23
|
export type TypeEq<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
|
|
25
24
|
|
|
@@ -30,12 +29,16 @@ export type AssertTrue<T extends true> = T
|
|
|
30
29
|
export type Writeable<T> = { -readonly [P in keyof T]: T[P] }
|
|
31
30
|
export type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> }
|
|
32
31
|
|
|
32
|
+
export type Nullable<T> = { [K in keyof T]: T[K] | null }
|
|
33
|
+
|
|
33
34
|
export type Primitive = null | undefined | string | number | boolean | symbol | bigint
|
|
34
35
|
|
|
35
36
|
export type LiteralUnion<LiteralType, BaseType extends Primitive> = LiteralType | (BaseType & Record<never, never>)
|
|
36
37
|
|
|
37
38
|
export type GetValForKey<T, K> = K extends keyof T ? T[K] : never
|
|
38
39
|
|
|
40
|
+
export type SingleOrReadonlyArray<T> = T | ReadonlyArray<T>
|
|
41
|
+
|
|
39
42
|
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
40
43
|
|
|
41
44
|
export const ref = <T>(val: T): { current: T } => ({ current: val })
|
|
@@ -88,15 +91,6 @@ export function casesHandled(unexpectedCase: never): never {
|
|
|
88
91
|
throw new Error(`A case was not handled for value: ${truncate(objectToString(unexpectedCase), 1000)}`)
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
export const shouldNeverHappen = (msg?: string, ...args: any[]): never => {
|
|
92
|
-
console.error(msg, ...args)
|
|
93
|
-
if (isDevEnv()) {
|
|
94
|
-
debugger
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
throw new Error(`This should never happen: ${msg}`)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
94
|
export const assertNever = (failIfFalse: boolean, msg?: string): void => {
|
|
101
95
|
if (failIfFalse === false) {
|
|
102
96
|
debugger
|
|
@@ -134,7 +128,7 @@ export const unwrapThunk = <T>(_: T | (() => T)): T => {
|
|
|
134
128
|
}
|
|
135
129
|
}
|
|
136
130
|
|
|
137
|
-
export type NullableFieldsToOptional<T> =
|
|
131
|
+
export type NullableFieldsToOptional<T> = Types.Simplify<
|
|
138
132
|
Partial<T> & {
|
|
139
133
|
[K in keyof T as null extends T[K] ? K : never]?: Exclude<T[K], null>
|
|
140
134
|
} & {
|
package/src/misc.ts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
export const isDevEnv = () => {
|
|
2
|
+
if (typeof process !== 'undefined' && process.env !== undefined) {
|
|
3
|
+
return process.env.NODE_ENV !== 'production'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// TODO re-enable the full guard code once `import.meta` is supported in Expo
|
|
7
|
+
// if (import.meta !== undefined && import.meta.env !== undefined) {
|
|
8
|
+
if (import.meta.env !== undefined) {
|
|
9
|
+
return import.meta.env.DEV
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// @ts-expect-error Only exists in Expo / RN
|
|
13
|
+
if (typeof globalThis !== 'undefined' && globalThis.__DEV__) {
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
1
20
|
export const objectToString = (error: any): string => {
|
|
2
21
|
const str = error?.toString()
|
|
3
22
|
if (str !== '[object Object]') return str
|
|
@@ -24,3 +43,15 @@ export const tryAsFunctionAndNew = <TArg, TResult>(
|
|
|
24
43
|
return fnOrConstructor(arg)
|
|
25
44
|
}
|
|
26
45
|
}
|
|
46
|
+
|
|
47
|
+
export const envTruish = (env: string | undefined) =>
|
|
48
|
+
env !== undefined && env.toLowerCase() !== 'false' && env.toLowerCase() !== '0'
|
|
49
|
+
|
|
50
|
+
export const shouldNeverHappen = (msg?: string, ...args: any[]): never => {
|
|
51
|
+
console.error(msg, ...args)
|
|
52
|
+
if (isDevEnv()) {
|
|
53
|
+
debugger
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw new Error(`This should never happen: ${msg}`)
|
|
57
|
+
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import process from 'node:process'
|
|
3
3
|
|
|
4
4
|
import { WorkerError } from '@effect/platform/WorkerError'
|
|
5
|
+
import type { CloseLatch } from '@effect/platform/WorkerRunner'
|
|
5
6
|
import * as Runner from '@effect/platform/WorkerRunner'
|
|
6
|
-
import
|
|
7
|
+
import * as Cause from 'effect/Cause'
|
|
7
8
|
import * as Context from 'effect/Context'
|
|
8
9
|
import * as Deferred from 'effect/Deferred'
|
|
9
10
|
import * as Effect from 'effect/Effect'
|
|
@@ -15,7 +16,7 @@ import * as Scope from 'effect/Scope'
|
|
|
15
16
|
|
|
16
17
|
const platformRunnerImpl = Runner.PlatformRunner.of({
|
|
17
18
|
[Runner.PlatformRunnerTypeId]: Runner.PlatformRunnerTypeId,
|
|
18
|
-
start<I, O>() {
|
|
19
|
+
start<I, O>(closeLatch: typeof CloseLatch.Service) {
|
|
19
20
|
return Effect.gen(function* () {
|
|
20
21
|
if (!process.send) {
|
|
21
22
|
return yield* new WorkerError({ reason: 'spawn', cause: new Error('not in a child process') })
|
|
@@ -27,37 +28,47 @@ const platformRunnerImpl = Runner.PlatformRunner.of({
|
|
|
27
28
|
}
|
|
28
29
|
const send = (_portId: number, message: O, _transfers?: ReadonlyArray<unknown>) =>
|
|
29
30
|
Effect.sync(() => port.postMessage([1, message] /*, transfers as any*/))
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
31
|
+
|
|
32
|
+
const run = Effect.fnUntraced(function* <A, E, R>(
|
|
33
|
+
handler: (portId: number, message: I) => Effect.Effect<A, E, R> | void,
|
|
34
|
+
) {
|
|
35
|
+
const runtime = (yield* Effect.interruptible(Effect.runtime<R | Scope.Scope>())).pipe(
|
|
36
|
+
Runtime.updateContext(Context.omit(Scope.Scope)),
|
|
37
|
+
) as Runtime.Runtime<R>
|
|
38
|
+
const fiberSet = yield* FiberSet.make<any, WorkerError | E>()
|
|
39
|
+
const runFork = Runtime.runFork(runtime)
|
|
40
|
+
const onExit = (exit: Exit.Exit<any, E>) => {
|
|
41
|
+
if (exit._tag === 'Failure' && !Cause.isInterruptedOnly(exit.cause)) {
|
|
42
|
+
// Deferred.unsafeDone(closeLatch, Exit.die(Cause.squash(exit.cause)))
|
|
43
|
+
Deferred.unsafeDone(closeLatch, Exit.die(exit.cause))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
port.on('message', (message: Runner.BackingRunner.Message<I>) => {
|
|
47
|
+
// console.log('message', message)
|
|
48
|
+
if (message[0] === 0) {
|
|
49
|
+
const result = handler(0, message[1])
|
|
50
|
+
if (Effect.isEffect(result)) {
|
|
51
|
+
const fiber = runFork(result)
|
|
52
|
+
fiber.addObserver(onExit)
|
|
53
|
+
FiberSet.unsafeAdd(fiberSet, fiber)
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
Deferred.unsafeDone(closeLatch, Exit.void)
|
|
57
|
+
port.close()
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
port.on('messageerror', (cause) => {
|
|
61
|
+
Deferred.unsafeDone(closeLatch, new WorkerError({ reason: 'decode', cause }))
|
|
62
|
+
})
|
|
63
|
+
port.on('error', (cause) => {
|
|
64
|
+
Deferred.unsafeDone(closeLatch, new WorkerError({ reason: 'unknown', cause }))
|
|
65
|
+
})
|
|
66
|
+
port.postMessage([0])
|
|
67
|
+
})
|
|
56
68
|
|
|
57
69
|
return { run, send }
|
|
58
70
|
})
|
|
59
71
|
},
|
|
60
72
|
})
|
|
61
73
|
|
|
62
|
-
/** @internal */
|
|
63
74
|
export const layer = Layer.succeed(Runner.PlatformRunner, platformRunnerImpl)
|
|
@@ -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>
|