@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.
- package/dist/.tsbuildinfo.json +1 -1
- package/dist/NoopTracer.js +1 -1
- package/dist/NoopTracer.js.map +1 -1
- package/dist/base64.d.ts +1 -1
- package/dist/base64.d.ts.map +1 -1
- package/dist/base64.js.map +1 -1
- package/dist/effect/BucketQueue.d.ts +7 -0
- package/dist/effect/BucketQueue.d.ts.map +1 -0
- package/dist/effect/BucketQueue.js +16 -0
- package/dist/effect/BucketQueue.js.map +1 -0
- package/dist/effect/Effect.d.ts +6 -1
- package/dist/effect/Effect.d.ts.map +1 -1
- package/dist/effect/Effect.js +24 -10
- package/dist/effect/Effect.js.map +1 -1
- package/dist/effect/Logger.d.ts +3 -0
- package/dist/effect/Logger.d.ts.map +1 -0
- package/dist/effect/Logger.js +10 -0
- package/dist/effect/Logger.js.map +1 -0
- package/dist/effect/Schema/index.d.ts.map +1 -1
- package/dist/effect/Schema/index.js +1 -1
- 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/TaskTracing.d.ts +5 -0
- package/dist/effect/TaskTracing.d.ts.map +1 -0
- package/dist/effect/TaskTracing.js +38 -0
- package/dist/effect/TaskTracing.js.map +1 -0
- package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts +13 -0
- package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -0
- package/dist/effect/WebChannel/broadcastChannelWithAck.js +88 -0
- package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -0
- package/dist/effect/WebChannel/common.d.ts +16 -0
- package/dist/effect/WebChannel/common.d.ts.map +1 -0
- package/dist/effect/WebChannel/common.js +4 -0
- package/dist/effect/WebChannel/common.js.map +1 -0
- package/dist/effect/WebChannel.d.ts +55 -25
- package/dist/effect/WebChannel.d.ts.map +1 -1
- package/dist/effect/WebChannel.js +133 -15
- package/dist/effect/WebChannel.js.map +1 -1
- package/dist/effect/WebLock.d.ts +4 -0
- package/dist/effect/WebLock.d.ts.map +1 -1
- package/dist/effect/WebLock.js +61 -0
- package/dist/effect/WebLock.js.map +1 -1
- package/dist/effect/WebSocket.d.ts +19 -0
- package/dist/effect/WebSocket.d.ts.map +1 -0
- package/dist/effect/WebSocket.js +53 -0
- package/dist/effect/WebSocket.js.map +1 -0
- package/dist/effect/index.d.ts +6 -2
- package/dist/effect/index.d.ts.map +1 -1
- package/dist/effect/index.js +10 -2
- package/dist/effect/index.js.map +1 -1
- package/dist/env.d.ts +5 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +25 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -12
- package/dist/index.js.map +1 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +5 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +1 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js +55 -0
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +1 -0
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +10 -0
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +1 -0
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js +46 -0
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +1 -0
- package/dist/node/mod.d.ts +22 -0
- package/dist/node/mod.d.ts.map +1 -0
- package/dist/node/mod.js +83 -0
- package/dist/node/mod.js.map +1 -0
- package/dist/node-vitest/mod.d.ts +2 -0
- package/dist/node-vitest/mod.d.ts.map +1 -0
- package/dist/node-vitest/mod.js +2 -0
- package/dist/node-vitest/mod.js.map +1 -0
- package/dist/node-vitest/polyfill.d.ts +2 -0
- package/dist/node-vitest/polyfill.d.ts.map +1 -0
- package/dist/node-vitest/polyfill.js +3 -0
- package/dist/node-vitest/polyfill.js.map +1 -0
- package/dist/object/index.d.ts.map +1 -1
- package/dist/object/pick.d.ts.map +1 -1
- package/package.json +49 -15
- package/src/NoopTracer.ts +1 -1
- package/src/base64.ts +1 -1
- package/src/effect/BucketQueue.ts +22 -0
- package/src/effect/Effect.ts +41 -17
- package/src/effect/Logger.ts +17 -0
- package/src/effect/Schema/index.ts +1 -1
- package/src/effect/TaskTracing.ts +43 -0
- package/src/effect/WebChannel/broadcastChannelWithAck.ts +126 -0
- package/src/effect/WebChannel/common.ts +17 -0
- package/src/effect/WebChannel.ts +223 -49
- package/src/effect/WebLock.ts +75 -0
- package/src/effect/WebSocket.ts +72 -0
- package/src/effect/index.ts +14 -1
- package/src/env.ts +31 -0
- package/src/index.ts +3 -11
- package/src/node/ChildProcessRunner/ChildProcessRunner.ts +63 -0
- package/src/node/ChildProcessRunner/ChildProcessWorker.ts +66 -0
- package/src/node/mod.ts +115 -0
- package/src/node-vitest/mod.ts +1 -0
- package/src/node-vitest/polyfill.ts +1 -0
- package/tmp/effect-deferred-repro.ts +29 -0
- package/tmp/effect-semaphore-repro.ts +93 -0
package/src/effect/index.ts
CHANGED
|
@@ -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 (
|
|
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))
|
package/src/node/mod.ts
ADDED
|
@@ -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
|
+
)
|