@livestore/utils 0.3.0-dev.2 → 0.3.0-dev.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/dist/.tsbuildinfo.json +1 -1
  2. package/dist/Deferred.d.ts.map +1 -1
  3. package/dist/base64.d.ts.map +1 -1
  4. package/dist/bun/mod.d.ts +5 -0
  5. package/dist/bun/mod.d.ts.map +1 -0
  6. package/dist/bun/mod.js +9 -0
  7. package/dist/bun/mod.js.map +1 -0
  8. package/dist/cuid/cuid.browser.d.ts.map +1 -1
  9. package/dist/cuid/cuid.node.d.ts.map +1 -1
  10. package/dist/effect/BucketQueue.d.ts +15 -2
  11. package/dist/effect/BucketQueue.d.ts.map +1 -1
  12. package/dist/effect/BucketQueue.js +24 -6
  13. package/dist/effect/BucketQueue.js.map +1 -1
  14. package/dist/effect/Effect.d.ts +4 -2
  15. package/dist/effect/Effect.d.ts.map +1 -1
  16. package/dist/effect/Effect.js +19 -5
  17. package/dist/effect/Effect.js.map +1 -1
  18. package/dist/effect/Logger.d.ts +2 -0
  19. package/dist/effect/Logger.d.ts.map +1 -1
  20. package/dist/effect/Logger.js +16 -1
  21. package/dist/effect/Logger.js.map +1 -1
  22. package/dist/effect/Scheduler.d.ts.map +1 -1
  23. package/dist/effect/Schema/debug-diff.d.ts.map +1 -1
  24. package/dist/effect/Schema/index.d.ts +1 -0
  25. package/dist/effect/Schema/index.d.ts.map +1 -1
  26. package/dist/effect/Schema/index.js +10 -0
  27. package/dist/effect/Schema/index.js.map +1 -1
  28. package/dist/effect/Schema/msgpack.d.ts +1 -1
  29. package/dist/effect/Schema/msgpack.d.ts.map +1 -1
  30. package/dist/effect/ServiceContext.d.ts.map +1 -1
  31. package/dist/effect/Stream.d.ts.map +1 -1
  32. package/dist/effect/Subscribable.d.ts +75 -0
  33. package/dist/effect/Subscribable.d.ts.map +1 -0
  34. package/dist/effect/Subscribable.js +76 -0
  35. package/dist/effect/Subscribable.js.map +1 -0
  36. package/dist/effect/TaskTracing.d.ts.map +1 -1
  37. package/dist/effect/{WebChannel.d.ts → WebChannel/WebChannel.d.ts} +32 -14
  38. package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -0
  39. package/dist/effect/WebChannel/WebChannel.js +283 -0
  40. package/dist/effect/WebChannel/WebChannel.js.map +1 -0
  41. package/dist/effect/WebChannel/WebChannel.test.d.ts +2 -0
  42. package/dist/effect/WebChannel/WebChannel.test.d.ts.map +1 -0
  43. package/dist/effect/WebChannel/WebChannel.test.js +62 -0
  44. package/dist/effect/WebChannel/WebChannel.test.js.map +1 -0
  45. package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts +5 -6
  46. package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
  47. package/dist/effect/WebChannel/broadcastChannelWithAck.js +12 -9
  48. package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
  49. package/dist/effect/WebChannel/common.d.ts +16 -1
  50. package/dist/effect/WebChannel/common.d.ts.map +1 -1
  51. package/dist/effect/WebChannel/common.js +19 -1
  52. package/dist/effect/WebChannel/common.js.map +1 -1
  53. package/dist/effect/WebChannel/mod.d.ts +4 -0
  54. package/dist/effect/WebChannel/mod.d.ts.map +1 -0
  55. package/dist/effect/WebChannel/mod.js +4 -0
  56. package/dist/effect/WebChannel/mod.js.map +1 -0
  57. package/dist/effect/WebLock.d.ts.map +1 -1
  58. package/dist/effect/WebSocket.d.ts +1 -1
  59. package/dist/effect/WebSocket.d.ts.map +1 -1
  60. package/dist/effect/WebSocket.js +38 -19
  61. package/dist/effect/WebSocket.js.map +1 -1
  62. package/dist/effect/WebSocket.test.d.ts +2 -0
  63. package/dist/effect/WebSocket.test.d.ts.map +1 -0
  64. package/dist/effect/WebSocket.test.js +10 -0
  65. package/dist/effect/WebSocket.test.js.map +1 -0
  66. package/dist/effect/index.d.ts +4 -2
  67. package/dist/effect/index.d.ts.map +1 -1
  68. package/dist/effect/index.js +6 -2
  69. package/dist/effect/index.js.map +1 -1
  70. package/dist/env.d.ts +1 -0
  71. package/dist/env.d.ts.map +1 -1
  72. package/dist/env.js +3 -0
  73. package/dist/env.js.map +1 -1
  74. package/dist/fast-deep-equal.d.ts.map +1 -1
  75. package/dist/guards.d.ts.map +1 -1
  76. package/dist/index.d.ts +5 -4
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/misc.d.ts.map +1 -1
  80. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +0 -1
  81. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +1 -1
  82. package/dist/node/ChildProcessRunner/ChildProcessRunner.js +17 -11
  83. package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +1 -1
  84. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +2 -0
  85. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +1 -0
  86. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +39 -0
  87. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +1 -0
  88. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +75 -0
  89. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +1 -0
  90. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +62 -0
  91. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +1 -0
  92. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts +2 -0
  93. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts.map +1 -0
  94. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +42 -0
  95. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +1 -0
  96. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +1 -4
  97. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +1 -1
  98. package/dist/node/ChildProcessRunner/ChildProcessWorker.js +1 -4
  99. package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +1 -1
  100. package/dist/node/mod.d.ts +3 -2
  101. package/dist/node/mod.d.ts.map +1 -1
  102. package/dist/node/mod.js +36 -9
  103. package/dist/node/mod.js.map +1 -1
  104. package/dist/object/index.d.ts.map +1 -1
  105. package/dist/object/omit.d.ts.map +1 -1
  106. package/dist/object/pick.d.ts.map +1 -1
  107. package/dist/promise.d.ts.map +1 -1
  108. package/dist/set.d.ts.map +1 -1
  109. package/dist/string.d.ts.map +1 -1
  110. package/dist/time.d.ts.map +1 -1
  111. package/package.json +56 -40
  112. package/src/bun/mod.ts +12 -0
  113. package/src/effect/BucketQueue.ts +33 -6
  114. package/src/effect/Effect.ts +31 -6
  115. package/src/effect/Logger.ts +23 -1
  116. package/src/effect/Schema/index.ts +15 -0
  117. package/src/effect/Subscribable.ts +150 -0
  118. package/src/effect/WebChannel/WebChannel.test.ts +106 -0
  119. package/src/effect/WebChannel/WebChannel.ts +477 -0
  120. package/src/effect/WebChannel/broadcastChannelWithAck.ts +86 -83
  121. package/src/effect/WebChannel/common.ts +49 -2
  122. package/src/effect/WebChannel/mod.ts +3 -0
  123. package/src/effect/WebSocket.test.ts +14 -0
  124. package/src/effect/WebSocket.ts +64 -37
  125. package/src/effect/index.ts +10 -1
  126. package/src/env.ts +5 -0
  127. package/src/index.ts +4 -2
  128. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +35 -29
  129. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +52 -0
  130. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +65 -0
  131. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +53 -0
  132. package/src/node/ChildProcessRunner/ChildProcessWorker.ts +3 -6
  133. package/src/node/mod.ts +48 -10
  134. package/tmp/pack.tgz +0 -0
  135. package/tsconfig.json +1 -1
  136. package/dist/effect/WebChannel.d.ts.map +0 -1
  137. package/dist/effect/WebChannel.js +0 -162
  138. package/dist/effect/WebChannel.js.map +0 -1
  139. package/dist/nanoid/index.browser.d.ts +0 -2
  140. package/dist/nanoid/index.browser.d.ts.map +0 -1
  141. package/dist/nanoid/index.browser.js +0 -3
  142. package/dist/nanoid/index.browser.js.map +0 -1
  143. package/src/effect/WebChannel.ts +0 -290
  144. package/src/nanoid/index.browser.ts +0 -2
@@ -1,5 +1,5 @@
1
- import type { Deferred, Effect, Either, ParseResult, Schema, Stream } from 'effect'
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,52 @@ 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 schemaWithDebugPing = <MsgListen, MsgSend>(
27
+ schema: OutputSchema<MsgListen, MsgSend, any, any>,
28
+ ): OutputSchema<MsgListen | typeof DebugPingMessage.Type, MsgSend | typeof DebugPingMessage.Type, any, any> => ({
29
+ send: Schema.Union(schema.send, DebugPingMessage),
30
+ listen: Schema.Union(schema.listen, DebugPingMessage),
31
+ })
32
+
33
+ export type InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> =
34
+ | Schema.Schema<MsgListen | MsgSend, MsgListenEncoded | MsgSendEncoded>
35
+ | OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>
36
+
37
+ export type OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> = {
38
+ listen: Schema.Schema<MsgListen, MsgListenEncoded>
39
+ send: Schema.Schema<MsgSend, MsgSendEncoded>
40
+ }
41
+
42
+ export const mapSchema = <MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>(
43
+ schema: InputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded>,
44
+ ): OutputSchema<MsgListen, MsgSend, MsgListenEncoded, MsgSendEncoded> =>
45
+ Predicate.hasProperty(schema, 'send') && Predicate.hasProperty(schema, 'listen')
46
+ ? (schemaWithDebugPing(schema) as any)
47
+ : (schemaWithDebugPing({ send: schema, listen: schema }) as any)
48
+
49
+ export const listenToDebugPing =
50
+ (channelName: string) =>
51
+ <MsgListen>(
52
+ stream: Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>, never>,
53
+ ): Stream.Stream<Either.Either<MsgListen, ParseResult.ParseError>, never> =>
54
+ stream.pipe(
55
+ Stream.filterEffect(
56
+ Effect.fn(function* (msg) {
57
+ if (msg._tag === 'Right' && Schema.is(DebugPingMessage)(msg.right)) {
58
+ yield* Effect.logDebug(`WebChannel:ping [${channelName}] ${msg.right.message}`, msg.right.payload)
59
+ return false
60
+ }
61
+ return true
62
+ }),
63
+ ),
64
+ )
@@ -0,0 +1,3 @@
1
+ export * from './WebChannel.js'
2
+ export * from './broadcastChannelWithAck.js'
3
+ export * from './common.js'
@@ -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
+ })
@@ -22,51 +22,78 @@ 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.tryPromise({
26
- try: async () => {
27
- // console.debug('[WebSocket] connecting to', url)
25
+ yield* validateUrl(url)
26
+
27
+ const socket = yield* Effect.async<globalThis.WebSocket, WebSocketError>((cb, signal) => {
28
+ try {
28
29
  const socket = new globalThis.WebSocket(url)
29
30
 
30
31
  if (socket.readyState === globalThis.WebSocket.OPEN) {
31
- return socket
32
+ cb(Effect.succeed(socket))
33
+ return
32
34
  }
33
35
 
34
- return await new Promise<globalThis.WebSocket>((resolve, reject) => {
35
- socket.addEventListener('open', () => resolve(socket), { once: true })
36
- // eslint-disable-next-line unicorn/prefer-add-event-listener
37
- socket.onerror = (event) => reject(event)
36
+ signal.addEventListener('abort', () => {
37
+ socket.close(3000, 'abort signal')
38
38
  })
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
39
 
45
- return new WebSocketError({ cause: errorEvent })
46
- },
47
- }).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
- ),
68
- reconnect ? Effect.retry(reconnect) : identity,
40
+ socket.addEventListener('open', () => cb(Effect.succeed(socket)), { once: true })
41
+
42
+ socket.addEventListener(
43
+ 'error',
44
+ (event) => {
45
+ cb(Effect.fail(new WebSocketError({ cause: event })))
46
+ },
47
+ { once: true },
48
+ )
49
+
50
+ socket.addEventListener(
51
+ 'close',
52
+ (event) => {
53
+ // console.log('makeWebSocket:socket:onclose', event)
54
+ return cb(Effect.fail(new WebSocketError({ cause: event })))
55
+ },
56
+ { once: true },
57
+ )
58
+
59
+ // console.log('makeWebSocket:socket:waiting for open', url)
60
+ } catch (error) {
61
+ cb(Effect.fail(new WebSocketError({ cause: error })))
62
+ }
63
+ }).pipe(reconnect ? Effect.retry(reconnect) : identity)
64
+
65
+ /**
66
+ * Common WebSocket close codes: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close
67
+ * 1000: Normal closure
68
+ * 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.
69
+ * 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.
70
+ * 1011: Internal server error, a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.
71
+ *
72
+ * For reference, here are the valid WebSocket close code ranges:
73
+ * 1000-1999: Reserved for protocol usage
74
+ * 2000-2999: Reserved for WebSocket extensions
75
+ * 3000-3999: Available for libraries and frameworks
76
+ * 4000-4999: Available for applications
77
+ */
78
+ yield* Effect.addFinalizer((exit) =>
79
+ Effect.async<void, WebSocketError>((cb) => {
80
+ try {
81
+ if (Exit.isFailure(exit)) {
82
+ socket.close(3000)
83
+ } else {
84
+ socket.close(1000)
85
+ }
86
+ } catch (error) {
87
+ cb(Effect.fail(new WebSocketError({ cause: error })))
88
+ }
89
+ }).pipe(Effect.orDie),
69
90
  )
70
91
 
71
92
  return socket
72
93
  })
94
+
95
+ const validateUrl = (url: string) =>
96
+ Effect.try({
97
+ try: () => new URL(url),
98
+ catch: (error) => new WebSocketError({ cause: error }),
99
+ })
@@ -49,13 +49,21 @@ export {
49
49
  TRef,
50
50
  Channel,
51
51
  Predicate,
52
+ // Subscribable,
52
53
  pipe,
53
54
  identity,
54
55
  GlobalValue,
55
56
  Match,
56
57
  TestServices,
58
+ Mailbox,
59
+ ExecutionStrategy,
60
+ PrimaryKey,
61
+ Types,
62
+ Cache,
57
63
  } from 'effect'
58
64
 
65
+ export * as StandardSchema from '@standard-schema/spec'
66
+
59
67
  export { dual } from 'effect/Function'
60
68
 
61
69
  export * as Stream from './Stream.js'
@@ -63,10 +71,11 @@ export * as Stream from './Stream.js'
63
71
  export * as BucketQueue from './BucketQueue.js'
64
72
 
65
73
  export * as SubscriptionRef from './SubscriptionRef.js'
74
+ export * as Subscribable from './Subscribable.js'
66
75
 
67
76
  export * as Logger from './Logger.js'
68
77
 
69
- export * as WebChannel from './WebChannel.js'
78
+ export * as WebChannel from './WebChannel/mod.js'
70
79
  export * as WebSocket from './WebSocket.js'
71
80
 
72
81
  export * as SchemaAST from 'effect/SchemaAST'
package/src/env.ts CHANGED
@@ -26,6 +26,11 @@ export const isDevEnv = () => {
26
26
  return false
27
27
  }
28
28
 
29
+ // export const TRACE_VERBOSE = true
29
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
33
+
34
+ const envTruish = (env: string | undefined) => env !== undefined && env !== 'false' && env !== '0'
35
+
36
+ export const IS_CI = envTruish(env('CI'))
package/src/index.ts CHANGED
@@ -14,12 +14,12 @@ 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
19
  import { isDevEnv } from './env.js'
19
20
  import { objectToString } from './misc.js'
20
21
 
21
22
  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
23
 
24
24
  export type TypeEq<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
25
25
 
@@ -30,6 +30,8 @@ export type AssertTrue<T extends true> = T
30
30
  export type Writeable<T> = { -readonly [P in keyof T]: T[P] }
31
31
  export type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> }
32
32
 
33
+ export type Nullable<T> = { [K in keyof T]: T[K] | null }
34
+
33
35
  export type Primitive = null | undefined | string | number | boolean | symbol | bigint
34
36
 
35
37
  export type LiteralUnion<LiteralType, BaseType extends Primitive> = LiteralType | (BaseType & Record<never, never>)
@@ -134,7 +136,7 @@ export const unwrapThunk = <T>(_: T | (() => T)): T => {
134
136
  }
135
137
  }
136
138
 
137
- export type NullableFieldsToOptional<T> = PrettifyFlat<
139
+ export type NullableFieldsToOptional<T> = Types.Simplify<
138
140
  Partial<T> & {
139
141
  [K in keyof T as null extends T[K] ? K : never]?: Exclude<T[K], null>
140
142
  } & {
@@ -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 { FiberId } 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'
@@ -15,7 +14,7 @@ import * as Scope from 'effect/Scope'
15
14
 
16
15
  const platformRunnerImpl = Runner.PlatformRunner.of({
17
16
  [Runner.PlatformRunnerTypeId]: Runner.PlatformRunnerTypeId,
18
- start<I, O>() {
17
+ start<I, O>(closeLatch: Deferred.Deferred<void, WorkerError>) {
19
18
  return Effect.gen(function* () {
20
19
  if (!process.send) {
21
20
  return yield* new WorkerError({ reason: 'spawn', cause: new Error('not in a child process') })
@@ -27,37 +26,44 @@ const platformRunnerImpl = Runner.PlatformRunner.of({
27
26
  }
28
27
  const send = (_portId: number, message: O, _transfers?: ReadonlyArray<unknown>) =>
29
28
  Effect.sync(() => port.postMessage([1, message] /*, transfers as any*/))
30
- const run = <A, E, R>(handler: (portId: number, message: I) => Effect.Effect<A, E, R>) =>
31
- Effect.uninterruptibleMask((restore) =>
32
- Effect.gen(function* () {
33
- const runtime = (yield* Effect.runtime<R | Scope.Scope>()).pipe(
34
- Runtime.updateContext(Context.omit(Scope.Scope)),
35
- ) as Runtime.Runtime<R>
36
- const fiberSet = yield* FiberSet.make<any, WorkerError | E>()
37
- const runFork = Runtime.runFork(runtime)
38
- port.on('message', (message: Runner.BackingRunner.Message<I>) => {
39
- if (message[0] === 0) {
40
- FiberSet.unsafeAdd(fiberSet, runFork(restore(handler(0, message[1]))))
41
- } else {
42
- Deferred.unsafeDone(fiberSet.deferred, Exit.interrupt(FiberId.none))
43
- port.close()
44
- }
45
- })
46
- port.on('messageerror', (cause) => {
47
- Deferred.unsafeDone(fiberSet.deferred, new WorkerError({ reason: 'decode', cause }))
48
- })
49
- port.on('error', (cause) => {
50
- Deferred.unsafeDone(fiberSet.deferred, new WorkerError({ reason: 'unknown', cause }))
51
- })
52
- port.postMessage([0])
53
- return (yield* restore(FiberSet.join(fiberSet))) as never
54
- }).pipe(Effect.scoped),
55
- )
29
+
30
+ const run = Effect.fnUntraced(function* <A, E, R>(
31
+ handler: (portId: number, message: I) => Effect.Effect<A, E, R>,
32
+ ) {
33
+ const runtime = (yield* Effect.interruptible(Effect.runtime<R | Scope.Scope>())).pipe(
34
+ Runtime.updateContext(Context.omit(Scope.Scope)),
35
+ ) as Runtime.Runtime<R>
36
+ const fiberSet = yield* FiberSet.make<any, WorkerError | E>()
37
+ const runFork = Runtime.runFork(runtime)
38
+ const onExit = (exit: Exit.Exit<any, E>) => {
39
+ if (exit._tag === 'Failure') {
40
+ // Deferred.unsafeDone(closeLatch, Exit.die(Cause.squash(exit.cause)))
41
+ Deferred.unsafeDone(closeLatch, Exit.die(exit.cause))
42
+ }
43
+ }
44
+ port.on('message', (message: Runner.BackingRunner.Message<I>) => {
45
+ // console.log('message', message)
46
+ if (message[0] === 0) {
47
+ const fiber = runFork(handler(0, message[1]))
48
+ fiber.addObserver(onExit)
49
+ FiberSet.unsafeAdd(fiberSet, fiber)
50
+ } else {
51
+ Deferred.unsafeDone(closeLatch, Exit.void)
52
+ port.close()
53
+ }
54
+ })
55
+ port.on('messageerror', (cause) => {
56
+ Deferred.unsafeDone(closeLatch, new WorkerError({ reason: 'decode', cause }))
57
+ })
58
+ port.on('error', (cause) => {
59
+ Deferred.unsafeDone(closeLatch, new WorkerError({ reason: 'unknown', cause }))
60
+ })
61
+ port.postMessage([0])
62
+ })
56
63
 
57
64
  return { run, send }
58
65
  })
59
66
  },
60
67
  })
61
68
 
62
- /** @internal */
63
69
  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>
@@ -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'
@@ -23,8 +23,8 @@ const platformWorkerImpl = Worker.makePlatform<ChildProcess.ChildProcess>()({
23
23
  childProcess.send([1])
24
24
  return Deferred.await(exitDeferred)
25
25
  }).pipe(
26
- Effect.interruptible,
27
26
  Effect.timeout(5000),
27
+ Effect.interruptible,
28
28
  Effect.catchAllCause(() => Effect.sync(() => childProcess.kill())),
29
29
  ),
30
30
  ),
@@ -55,12 +55,9 @@ const platformWorkerImpl = Worker.makePlatform<ChildProcess.ChildProcess>()({
55
55
  },
56
56
  })
57
57
 
58
- /** @internal */
59
58
  export const layerWorker = Layer.succeed(Worker.PlatformWorker, platformWorkerImpl)
60
59
 
61
- /** @internal */
62
60
  export const layerManager = Layer.provide(Worker.layerManager, layerWorker)
63
61
 
64
- /** @internal */
65
62
  export const layer = (spawn: (id: number) => ChildProcess.ChildProcess) =>
66
63
  Layer.merge(layerManager, Worker.layerSpawner(spawn))