@livestore/utils 0.2.0 → 0.3.0-dev.1

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 (104) hide show
  1. package/dist/.tsbuildinfo.json +1 -1
  2. package/dist/NoopTracer.js +1 -1
  3. package/dist/NoopTracer.js.map +1 -1
  4. package/dist/base64.d.ts +1 -1
  5. package/dist/base64.d.ts.map +1 -1
  6. package/dist/base64.js.map +1 -1
  7. package/dist/effect/BucketQueue.d.ts +7 -0
  8. package/dist/effect/BucketQueue.d.ts.map +1 -0
  9. package/dist/effect/BucketQueue.js +16 -0
  10. package/dist/effect/BucketQueue.js.map +1 -0
  11. package/dist/effect/Effect.d.ts +6 -1
  12. package/dist/effect/Effect.d.ts.map +1 -1
  13. package/dist/effect/Effect.js +24 -10
  14. package/dist/effect/Effect.js.map +1 -1
  15. package/dist/effect/Logger.d.ts +3 -0
  16. package/dist/effect/Logger.d.ts.map +1 -0
  17. package/dist/effect/Logger.js +10 -0
  18. package/dist/effect/Logger.js.map +1 -0
  19. package/dist/effect/Schema/index.d.ts.map +1 -1
  20. package/dist/effect/Schema/index.js +1 -1
  21. package/dist/effect/Schema/index.js.map +1 -1
  22. package/dist/effect/Schema/msgpack.d.ts +1 -1
  23. package/dist/effect/Schema/msgpack.d.ts.map +1 -1
  24. package/dist/effect/TaskTracing.d.ts +5 -0
  25. package/dist/effect/TaskTracing.d.ts.map +1 -0
  26. package/dist/effect/TaskTracing.js +38 -0
  27. package/dist/effect/TaskTracing.js.map +1 -0
  28. package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts +13 -0
  29. package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -0
  30. package/dist/effect/WebChannel/broadcastChannelWithAck.js +88 -0
  31. package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -0
  32. package/dist/effect/WebChannel/common.d.ts +16 -0
  33. package/dist/effect/WebChannel/common.d.ts.map +1 -0
  34. package/dist/effect/WebChannel/common.js +4 -0
  35. package/dist/effect/WebChannel/common.js.map +1 -0
  36. package/dist/effect/WebChannel.d.ts +55 -25
  37. package/dist/effect/WebChannel.d.ts.map +1 -1
  38. package/dist/effect/WebChannel.js +133 -15
  39. package/dist/effect/WebChannel.js.map +1 -1
  40. package/dist/effect/WebLock.d.ts +4 -0
  41. package/dist/effect/WebLock.d.ts.map +1 -1
  42. package/dist/effect/WebLock.js +61 -0
  43. package/dist/effect/WebLock.js.map +1 -1
  44. package/dist/effect/WebSocket.d.ts +19 -0
  45. package/dist/effect/WebSocket.d.ts.map +1 -0
  46. package/dist/effect/WebSocket.js +53 -0
  47. package/dist/effect/WebSocket.js.map +1 -0
  48. package/dist/effect/index.d.ts +6 -2
  49. package/dist/effect/index.d.ts.map +1 -1
  50. package/dist/effect/index.js +10 -2
  51. package/dist/effect/index.js.map +1 -1
  52. package/dist/env.d.ts +5 -0
  53. package/dist/env.d.ts.map +1 -0
  54. package/dist/env.js +25 -0
  55. package/dist/env.js.map +1 -0
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +3 -12
  59. package/dist/index.js.map +1 -1
  60. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +5 -0
  61. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +1 -0
  62. package/dist/node/ChildProcessRunner/ChildProcessRunner.js +55 -0
  63. package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +1 -0
  64. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +10 -0
  65. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +1 -0
  66. package/dist/node/ChildProcessRunner/ChildProcessWorker.js +46 -0
  67. package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +1 -0
  68. package/dist/node/mod.d.ts +22 -0
  69. package/dist/node/mod.d.ts.map +1 -0
  70. package/dist/node/mod.js +83 -0
  71. package/dist/node/mod.js.map +1 -0
  72. package/dist/node-vitest/mod.d.ts +2 -0
  73. package/dist/node-vitest/mod.d.ts.map +1 -0
  74. package/dist/node-vitest/mod.js +2 -0
  75. package/dist/node-vitest/mod.js.map +1 -0
  76. package/dist/node-vitest/polyfill.d.ts +2 -0
  77. package/dist/node-vitest/polyfill.d.ts.map +1 -0
  78. package/dist/node-vitest/polyfill.js +3 -0
  79. package/dist/node-vitest/polyfill.js.map +1 -0
  80. package/dist/object/index.d.ts.map +1 -1
  81. package/dist/object/pick.d.ts.map +1 -1
  82. package/package.json +49 -15
  83. package/src/NoopTracer.ts +1 -1
  84. package/src/base64.ts +1 -1
  85. package/src/effect/BucketQueue.ts +22 -0
  86. package/src/effect/Effect.ts +41 -17
  87. package/src/effect/Logger.ts +17 -0
  88. package/src/effect/Schema/index.ts +1 -1
  89. package/src/effect/TaskTracing.ts +43 -0
  90. package/src/effect/WebChannel/broadcastChannelWithAck.ts +126 -0
  91. package/src/effect/WebChannel/common.ts +17 -0
  92. package/src/effect/WebChannel.ts +223 -49
  93. package/src/effect/WebLock.ts +75 -0
  94. package/src/effect/WebSocket.ts +72 -0
  95. package/src/effect/index.ts +14 -1
  96. package/src/env.ts +31 -0
  97. package/src/index.ts +3 -11
  98. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +63 -0
  99. package/src/node/ChildProcessRunner/ChildProcessWorker.ts +66 -0
  100. package/src/node/mod.ts +115 -0
  101. package/src/node-vitest/mod.ts +1 -0
  102. package/src/node-vitest/polyfill.ts +1 -0
  103. package/tmp/effect-deferred-repro.ts +29 -0
  104. package/tmp/effect-semaphore-repro.ts +93 -0
@@ -39,9 +39,11 @@ export {
39
39
  HashSet,
40
40
  MutableHashSet,
41
41
  MutableHashMap,
42
+ TQueue,
42
43
  Option,
43
44
  LogLevel,
44
- Logger,
45
+ // Logger,
46
+ Config,
45
47
  Layer,
46
48
  STM,
47
49
  TRef,
@@ -51,15 +53,21 @@ export {
51
53
  identity,
52
54
  GlobalValue,
53
55
  Match,
56
+ TestServices,
54
57
  } from 'effect'
55
58
 
56
59
  export { dual } from 'effect/Function'
57
60
 
58
61
  export * as Stream from './Stream.js'
59
62
 
63
+ export * as BucketQueue from './BucketQueue.js'
64
+
60
65
  export * as SubscriptionRef from './SubscriptionRef.js'
61
66
 
67
+ export * as Logger from './Logger.js'
68
+
62
69
  export * as WebChannel from './WebChannel.js'
70
+ export * as WebSocket from './WebSocket.js'
63
71
 
64
72
  export * as SchemaAST from 'effect/SchemaAST'
65
73
  export { TreeFormatter } from 'effect/ParseResult'
@@ -67,6 +75,8 @@ export { ParseResult, Pretty } from 'effect'
67
75
  export type { Serializable, SerializableWithResult } from 'effect/Schema'
68
76
  export * as Schema from './Schema/index.js'
69
77
  export * as OtelTracer from '@effect/opentelemetry/Tracer'
78
+ // export * as OtelResource from '@effect/opentelemetry/Resource'
79
+ export * as TaskTracing from './TaskTracing.js'
70
80
 
71
81
  export {
72
82
  Transferable,
@@ -81,9 +91,12 @@ export {
81
91
  HttpClientRequest,
82
92
  HttpClientResponse,
83
93
  FetchHttpClient,
94
+ Socket,
84
95
  } from '@effect/platform'
85
96
  export { BrowserWorker, BrowserWorkerRunner } from '@effect/platform-browser'
86
97
 
98
+ // export { DevTools as EffectDevtools } from '@effect/experimental'
99
+
87
100
  export * as Effect from './Effect.js'
88
101
  export * as Schedule from './Schedule.js'
89
102
  export * as Scheduler from './Scheduler.js'
package/src/env.ts ADDED
@@ -0,0 +1,31 @@
1
+ export const env = (name: string): string | undefined => {
2
+ if (typeof process !== 'undefined' && process.env !== undefined) {
3
+ return process.env[name]
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[name]
10
+ }
11
+
12
+ return undefined
13
+ }
14
+
15
+ export const isDevEnv = () => {
16
+ if (typeof process !== 'undefined' && process.env !== undefined) {
17
+ return process.env.NODE_ENV !== 'production'
18
+ }
19
+
20
+ // TODO re-enable the full guard code once `import.meta` is supported in Expo
21
+ // if (import.meta !== undefined && import.meta.env !== undefined) {
22
+ if (import.meta.env !== undefined) {
23
+ return import.meta.env.DEV
24
+ }
25
+
26
+ return false
27
+ }
28
+
29
+ export const TRACE_VERBOSE = env('LS_TRACE_VERBOSE') !== undefined || env('VITE_LS_TRACE_VERBOSE') !== undefined
30
+
31
+ export const LS_DEV = env('LS_DEV') !== undefined || env('VITE_LS_DEV') !== undefined
package/src/index.ts CHANGED
@@ -8,12 +8,14 @@ export * from './set.js'
8
8
  export * from './browser.js'
9
9
  export * from './Deferred.js'
10
10
  export * from './misc.js'
11
+ export * from './env.js'
11
12
  export * from './fast-deep-equal.js'
12
13
  export * as base64 from './base64.js'
13
14
  export { default as prettyBytes } from 'pretty-bytes'
14
15
 
15
16
  import type * as otel from '@opentelemetry/api'
16
17
 
18
+ import { isDevEnv } from './env.js'
17
19
  import { objectToString } from './misc.js'
18
20
 
19
21
  export type Prettify<T> = T extends infer U ? { [K in keyof U]: Prettify<U[K]> } : never
@@ -88,7 +90,7 @@ export function casesHandled(unexpectedCase: never): never {
88
90
 
89
91
  export const shouldNeverHappen = (msg?: string, ...args: any[]): never => {
90
92
  console.error(msg, ...args)
91
- if (isDev()) {
93
+ if (isDevEnv()) {
92
94
  debugger
93
95
  }
94
96
 
@@ -228,13 +230,3 @@ export const isPromise = (value: any): value is Promise<unknown> => typeof value
228
230
  export const isIterable = <T>(value: any): value is Iterable<T> => typeof value?.[Symbol.iterator] === 'function'
229
231
 
230
232
  export { objectToString as errorToString } from './misc.js'
231
-
232
- const isDev = memoizeByRef(() => {
233
- if (import.meta.env !== undefined) {
234
- return import.meta.env.DEV || import.meta.env.VITE_DEV
235
- } else if (typeof process !== 'undefined' && process.env !== undefined) {
236
- return process.env.DEV
237
- } else {
238
- return false
239
- }
240
- })
@@ -0,0 +1,63 @@
1
+ /* eslint-disable prefer-arrow/prefer-arrow-functions */
2
+ import process from 'node:process'
3
+
4
+ import { WorkerError } from '@effect/platform/WorkerError'
5
+ import * as Runner from '@effect/platform/WorkerRunner'
6
+ import { FiberId } from 'effect'
7
+ import * as Context from 'effect/Context'
8
+ import * as Deferred from 'effect/Deferred'
9
+ import * as Effect from 'effect/Effect'
10
+ import * as Exit from 'effect/Exit'
11
+ import * as FiberSet from 'effect/FiberSet'
12
+ import * as Layer from 'effect/Layer'
13
+ import * as Runtime from 'effect/Runtime'
14
+ import * as Scope from 'effect/Scope'
15
+
16
+ const platformRunnerImpl = Runner.PlatformRunner.of({
17
+ [Runner.PlatformRunnerTypeId]: Runner.PlatformRunnerTypeId,
18
+ start<I, O>() {
19
+ return Effect.gen(function* () {
20
+ if (!process.send) {
21
+ return yield* new WorkerError({ reason: 'spawn', cause: new Error('not in a child process') })
22
+ }
23
+ const port = {
24
+ postMessage: (message: any) => process.send!(message),
25
+ on: (event: string, handler: (message: any) => void) => process.on(event, handler),
26
+ close: () => process.disconnect(),
27
+ }
28
+ const send = (_portId: number, message: O, _transfers?: ReadonlyArray<unknown>) =>
29
+ 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
+ )
56
+
57
+ return { run, send }
58
+ })
59
+ },
60
+ })
61
+
62
+ /** @internal */
63
+ export const layer = Layer.succeed(Runner.PlatformRunner, platformRunnerImpl)
@@ -0,0 +1,66 @@
1
+ /* eslint-disable prefer-arrow/prefer-arrow-functions */
2
+
3
+ import type * as ChildProcess from 'node:child_process'
4
+
5
+ import * as Worker from '@effect/platform/Worker'
6
+ import { WorkerError } from '@effect/platform/WorkerError'
7
+ import * as Deferred from 'effect/Deferred'
8
+ import * as Effect from 'effect/Effect'
9
+ import * as Exit from 'effect/Exit'
10
+ import * as Layer from 'effect/Layer'
11
+ import * as Scope from 'effect/Scope'
12
+
13
+ const platformWorkerImpl = Worker.makePlatform<ChildProcess.ChildProcess>()({
14
+ setup({ scope, worker: childProcess }) {
15
+ return Effect.flatMap(Deferred.make<void, WorkerError>(), (exitDeferred) => {
16
+ childProcess.on('exit', () => {
17
+ Deferred.unsafeDone(exitDeferred, Exit.void)
18
+ })
19
+ return Effect.as(
20
+ Scope.addFinalizer(
21
+ scope,
22
+ Effect.suspend(() => {
23
+ childProcess.send([1])
24
+ return Deferred.await(exitDeferred)
25
+ }).pipe(
26
+ Effect.interruptible,
27
+ Effect.timeout(5000),
28
+ Effect.catchAllCause(() => Effect.sync(() => childProcess.kill())),
29
+ ),
30
+ ),
31
+ {
32
+ postMessage: (message: any) => childProcess.send(message),
33
+ on: (event: string, handler: (message: any) => void) => childProcess.on(event, handler),
34
+ },
35
+ )
36
+ })
37
+ },
38
+ listen({ deferred, emit, port }) {
39
+ port.on('message', (message) => {
40
+ emit(message)
41
+ })
42
+ port.on('messageerror', (cause) => {
43
+ Deferred.unsafeDone(deferred, new WorkerError({ reason: 'decode', cause }))
44
+ })
45
+ port.on('error', (cause) => {
46
+ Deferred.unsafeDone(deferred, new WorkerError({ reason: 'unknown', cause }))
47
+ })
48
+ port.on('exit', (code) => {
49
+ Deferred.unsafeDone(
50
+ deferred,
51
+ new WorkerError({ reason: 'unknown', cause: new Error(`exited with code ${code}`) }),
52
+ )
53
+ })
54
+ return Effect.void
55
+ },
56
+ })
57
+
58
+ /** @internal */
59
+ export const layerWorker = Layer.succeed(Worker.PlatformWorker, platformWorkerImpl)
60
+
61
+ /** @internal */
62
+ export const layerManager = Layer.provide(Worker.layerManager, layerWorker)
63
+
64
+ /** @internal */
65
+ export const layer = (spawn: (id: number) => ChildProcess.ChildProcess) =>
66
+ Layer.merge(layerManager, Worker.layerSpawner(spawn))
@@ -0,0 +1,115 @@
1
+ import * as OtelNodeSdk from '@effect/opentelemetry/NodeSdk'
2
+ import type * as otel from '@opentelemetry/api'
3
+ import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
4
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
5
+ import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
6
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
7
+ import { Config, Effect, Layer } from 'effect'
8
+ import type { ParentSpan } from 'effect/Tracer'
9
+
10
+ import { tapCauseLogPretty } from '../effect/Effect.js'
11
+ import type { OtelTracer } from '../effect/index.js'
12
+
13
+ // import { tapCauseLogPretty } from '../effect/Effect.js'
14
+
15
+ export * as Cli from '@effect/cli'
16
+ export * as PlatformBun from '@effect/platform-bun'
17
+ export * as PlatformNode from '@effect/platform-node'
18
+ export * as SocketServer from '@effect/experimental/SocketServer'
19
+ export * as OtelResource from '@effect/opentelemetry/Resource'
20
+
21
+ export { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
22
+ export { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
23
+ export * as OtelNodeSdk from '@effect/opentelemetry/NodeSdk'
24
+
25
+ export * as ChildProcessRunner from './ChildProcessRunner/ChildProcessRunner.js'
26
+ export * as ChildProcessWorker from './ChildProcessRunner/ChildProcessWorker.js'
27
+
28
+ // Enable debug logging for OpenTelemetry
29
+ // otel.diag.setLogger(new otel.DiagConsoleLogger(), otel.DiagLogLevel.ERROR)
30
+
31
+ // export const OtelLiveHttp = (args: any): Layer.Layer<never> => Layer.empty
32
+
33
+ export const OtelLiveHttp = ({
34
+ serviceName,
35
+ rootSpanName,
36
+ skipLogUrl,
37
+ }: { serviceName?: string; rootSpanName?: string; skipLogUrl?: boolean } = {}): Layer.Layer<
38
+ OtelTracer.OtelTracer | ParentSpan,
39
+ never,
40
+ never
41
+ > =>
42
+ Effect.gen(function* () {
43
+ const config = yield* Config.all({
44
+ exporterUrlTracing: Config.string('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'),
45
+ exporterUrlMracing: Config.string('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'),
46
+ serviceName: serviceName
47
+ ? Config.succeed(serviceName)
48
+ : Config.string('OTEL_SERVICE_NAME').pipe(Config.withDefault('overtone-node-utils-default-service')),
49
+ rootSpanName: rootSpanName
50
+ ? Config.succeed(rootSpanName)
51
+ : Config.string('OTEL_ROOT_SPAN_NAME').pipe(Config.withDefault('RootSpan')),
52
+ }).pipe(tapCauseLogPretty, Effect.orDie)
53
+
54
+ const resource = { serviceName: config.serviceName }
55
+
56
+ // METRICS
57
+ const metricExporter = new OTLPMetricExporter({ url: config.exporterUrlMracing })
58
+
59
+ const metricReader = new PeriodicExportingMetricReader({
60
+ exporter: metricExporter,
61
+ exportIntervalMillis: 1000,
62
+ })
63
+
64
+ // TRACING
65
+ const OtelLive = OtelNodeSdk.layer(() => ({
66
+ resource,
67
+ metricReader,
68
+ spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter({ url: config.exporterUrlTracing, headers: {} })),
69
+ }))
70
+
71
+ const RootSpanLive = Layer.span(config.rootSpanName, {
72
+ attributes: { config },
73
+ onEnd: skipLogUrl ? undefined : (span: any) => logTraceUiUrlForSpan()(span.span),
74
+ })
75
+
76
+ return RootSpanLive.pipe(Layer.provideMerge(OtelLive))
77
+ }).pipe(Layer.unwrapEffect) as any
78
+
79
+ export const logTraceUiUrlForSpan = (printMsg?: (url: string) => string) => (span: otel.Span) =>
80
+ getTracingBackendUrl(span).pipe(
81
+ Effect.tap((url) => {
82
+ if (url === undefined) {
83
+ console.warn('No tracing backend url found')
84
+ } else {
85
+ if (printMsg) {
86
+ console.log(printMsg(url))
87
+ } else {
88
+ console.log(`Trace URL: ${url}`)
89
+ }
90
+ }
91
+ }),
92
+ )
93
+
94
+ export const getTracingBackendUrl = (span: otel.Span) =>
95
+ Effect.gen(function* () {
96
+ const endpoint = yield* Config.string('TRACING_UI_ENDPOINT').pipe(Config.option, Effect.orDie)
97
+ if (endpoint._tag === 'None') return
98
+
99
+ const traceId = span.spanContext().traceId
100
+
101
+ // Grafana + Tempo
102
+
103
+ const grafanaEndpoint = endpoint.value
104
+ const searchParams = new URLSearchParams({
105
+ orgId: '1',
106
+ left: JSON.stringify({
107
+ datasource: 'tempo',
108
+ queries: [{ query: traceId, queryType: 'traceql', refId: 'A' }],
109
+ range: { from: 'now-1h', to: 'now' },
110
+ }),
111
+ })
112
+
113
+ // TODO make dynamic via env var
114
+ return `${grafanaEndpoint}/explore?${searchParams.toString()}`
115
+ })
@@ -0,0 +1 @@
1
+ export * as Vitest from '@effect/vitest'
@@ -0,0 +1 @@
1
+ process.stdout.isTTY = true
@@ -0,0 +1,29 @@
1
+ /// <reference lib="es2024" />
2
+
3
+ import { Effect, Logger } from 'effect'
4
+
5
+ const main = Effect.gen(function* () {
6
+ yield* Effect.addFinalizer((_) => Effect.log('finalizer', _))
7
+
8
+ // const finished = yield* Deferred.make<void>()
9
+
10
+ const def = Promise.withResolvers()
11
+
12
+ setTimeout(() => {
13
+ def.resolve(undefined)
14
+ }, 1000)
15
+
16
+ console.log('awaiting')
17
+
18
+ // const fiber = yield* Deferred.await(finished).pipe(Effect.fork)
19
+ // yield* Fiber.join(fiber)
20
+ // yield* finished
21
+ yield* Effect.promise(() => def.promise)
22
+ console.log('finished')
23
+ })
24
+
25
+ main
26
+ .pipe(Effect.scoped, Effect.provide(Logger.pretty), Effect.tapErrorCause(Effect.logError), Effect.runPromise)
27
+ .finally(() => {
28
+ console.log('finally')
29
+ })
@@ -0,0 +1,93 @@
1
+ /// <reference lib="es2024" />
2
+ import * as NodePlatform from '@effect/platform-node'
3
+ import { Deferred, Effect, FiberSet, Logger, Ref, Scheduler } from 'effect'
4
+
5
+ const main = Effect.gen(function* () {
6
+ const semaphore = yield* Effect.makeSemaphore(1)
7
+
8
+ const fiberset = yield* FiberSet.make()
9
+
10
+ // setInterval(() => {
11
+ // // semaphore.release(1)
12
+ // }, 1_000_000)
13
+
14
+ const stateRef = yield* Ref.make<'open' | 'closed'>('open')
15
+
16
+ // let counter = 0
17
+ // const nextExpectedCounter = 0
18
+ // const def = Promise.withResolvers()
19
+
20
+ const finished = yield* Deferred.make<void>()
21
+
22
+ const MAX = 100
23
+
24
+ const doWorkWithSemaphore = (counter: number) =>
25
+ Effect.gen(function* () {
26
+ // yield* Effect.log(`doWorkWithSemaphore: ${counter} - waiting for semaphore`)
27
+ // yield* semaphore.take(1)
28
+ // if (counter !== nextExpectedCounter) {
29
+ // throw new Error(`Expected counter ${nextExpectedCounter} but got ${counter}`)
30
+ // }
31
+ // nextExpectedCounter = counter + 1
32
+ yield* Effect.log(`doWorkWithSemaphore: ${counter} - got semaphore`)
33
+ // setTimeout(() => {}, 120)
34
+ yield* Effect.sleep(40)
35
+ // yield* Effect.yieldNow()
36
+ yield* Effect.log(`doWorkWithSemaphore: ${counter} - releasing semaphore`)
37
+ yield* Ref.set(stateRef, 'open')
38
+ yield* semaphore.release(1)
39
+
40
+ if (counter === MAX - 1) {
41
+ yield* Deferred.succeed(finished, undefined)
42
+ // def.resolve(undefined)
43
+ }
44
+ }).pipe(Effect.scoped)
45
+
46
+ for (let i = 0; i < MAX; i++) {
47
+ yield* doWorkWithSemaphore(i).pipe(FiberSet.run(fiberset))
48
+ // yield* Effect.sleep(20)
49
+ yield* Effect.gen(function* () {
50
+ const state = yield* Ref.get(stateRef)
51
+ if (state === 'open') {
52
+ yield* Effect.logWarning(`loop: ${i} - semaphore is open, taking`)
53
+ } else {
54
+ yield* Effect.log(`loop: ${i} - semaphore is closed, waiting`)
55
+ }
56
+
57
+ yield* semaphore.take(1)
58
+ yield* Ref.set(stateRef, 'closed')
59
+
60
+ yield* doWorkWithSemaphore(i).pipe(Effect.fork)
61
+
62
+ const sleepTime = Math.random() * 40
63
+ yield* Effect.log(`loop: ${i} - sleeping for ${sleepTime}ms`)
64
+ yield* Effect.sleep(sleepTime)
65
+
66
+ // yield* Effect.yieldNow()
67
+ }).pipe(FiberSet.run(fiberset))
68
+ }
69
+
70
+ // yield* Effect.never
71
+
72
+ yield* finished
73
+ // yield* Effect.promise(() => def.promise)
74
+ // console.log('finished')
75
+ })
76
+
77
+ export const messageChannel = (shouldYield: Scheduler.Scheduler['shouldYield'] = Scheduler.defaultShouldYield) =>
78
+ Scheduler.makeBatched((task) => {
79
+ const messageChannel = new MessageChannel()
80
+
81
+ messageChannel.port1.postMessage(undefined)
82
+
83
+ // eslint-disable-next-line unicorn/prefer-add-event-listener
84
+ messageChannel.port2.onmessage = task
85
+ }, shouldYield)
86
+
87
+ main.pipe(
88
+ Effect.scoped,
89
+ Effect.provide(Logger.pretty),
90
+ Effect.tapErrorCause(Effect.logError),
91
+ Effect.withScheduler(messageChannel()),
92
+ NodePlatform.NodeRuntime.runMain,
93
+ )