@livestore/utils 0.3.0-dev.3 → 0.3.0-dev.30

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 +21 -18
  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 +3 -0
  25. package/dist/effect/Schema/index.d.ts.map +1 -1
  26. package/dist/effect/Schema/index.js +19 -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 +3 -2
  59. package/dist/effect/WebSocket.d.ts.map +1 -1
  60. package/dist/effect/WebSocket.js +45 -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 +11 -0
  65. package/dist/effect/WebSocket.test.js.map +1 -0
  66. package/dist/effect/index.d.ts +5 -3
  67. package/dist/effect/index.d.ts.map +1 -1
  68. package/dist/effect/index.js +7 -3
  69. package/dist/effect/index.js.map +1 -1
  70. package/dist/env.d.ts +2 -0
  71. package/dist/env.d.ts.map +1 -1
  72. package/dist/env.js +8 -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 +6 -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 +21 -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 +4 -3
  101. package/dist/node/mod.d.ts.map +1 -1
  102. package/dist/node/mod.js +37 -10
  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 +63 -40
  112. package/src/bun/mod.ts +12 -0
  113. package/src/effect/BucketQueue.ts +33 -6
  114. package/src/effect/Effect.ts +33 -20
  115. package/src/effect/Logger.ts +23 -1
  116. package/src/effect/Schema/index.ts +27 -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 +15 -0
  124. package/src/effect/WebSocket.ts +75 -36
  125. package/src/effect/index.ts +16 -1
  126. package/src/env.ts +11 -0
  127. package/src/index.ts +6 -2
  128. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +40 -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 +49 -11
  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
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@livestore/utils",
3
- "version": "0.3.0-dev.3",
3
+ "version": "0.3.0-dev.30",
4
+ "type": "module",
4
5
  "sideEffects": [
5
- "./dist/global.js"
6
+ "./dist/global.js",
7
+ "./dist/node-vitest/polyfill.js"
6
8
  ],
7
- "type": "module",
8
9
  "exports": {
9
10
  ".": {
10
11
  "types": "./dist/index.d.ts",
@@ -13,12 +14,11 @@
13
14
  "./cuid": {
14
15
  "types": "./dist/cuid/cuid.node.d.ts",
15
16
  "node": "./dist/cuid/cuid.node.js",
16
- "react-native": "./dist/cuid/cuid.browser.js",
17
- "browser": "./dist/cuid/cuid.browser.js"
17
+ "browser": "./dist/cuid/cuid.browser.js",
18
+ "react-native": "./dist/cuid/cuid.browser.js"
18
19
  },
19
20
  "./nanoid": {
20
21
  "types": "./dist/nanoid/index.d.ts",
21
- "react-native": "./dist/nanoid/index.browser.js",
22
22
  "default": "./dist/nanoid/index.js"
23
23
  },
24
24
  "./effect": {
@@ -29,6 +29,10 @@
29
29
  "types": "./dist/node/mod.d.ts",
30
30
  "default": "./dist/node/mod.js"
31
31
  },
32
+ "./bun": {
33
+ "types": "./dist/bun/mod.d.ts",
34
+ "default": "./dist/bun/mod.js"
35
+ },
32
36
  "./node-vitest": {
33
37
  "types": "./dist/node-vitest/mod.d.ts",
34
38
  "default": "./dist/node-vitest/mod.js"
@@ -38,7 +42,6 @@
38
42
  "default": "./dist/node-vitest/polyfill.js"
39
43
  }
40
44
  },
41
- "react-native": "./dist/index.js",
42
45
  "types": "./dist/index.d.ts",
43
46
  "typesVersions": {
44
47
  "*": {
@@ -54,6 +57,9 @@
54
57
  "./node": [
55
58
  "./dist/node/mod.d.ts"
56
59
  ],
60
+ "./bun": [
61
+ "./dist/bun/mod.d.ts"
62
+ ],
57
63
  "./node-vitest": [
58
64
  "./dist/node-vitest/mod.d.ts"
59
65
  ],
@@ -63,46 +69,63 @@
63
69
  }
64
70
  },
65
71
  "dependencies": {
66
- "msgpackr": "^1.11.2",
67
- "nanoid": "^5.0.7",
68
- "pretty-bytes": "^6.1.1",
69
- "@livestore/db-schema": "0.3.0-dev.3"
70
- },
71
- "peerDependencies": {
72
- "@effect/cli": "^0.50.2",
73
- "@effect/experimental": "^0.36.1",
74
- "@effect/opentelemetry": "^0.42.1",
75
- "@effect/platform": "^0.72.1",
76
- "@effect/platform-browser": "^0.51.1",
77
- "@effect/platform-bun": "^0.52.1",
78
- "@effect/platform-node": "^0.68.1",
79
- "@effect/vitest": "^0.16.1",
80
- "@opentelemetry/api": "^1.9.0",
81
- "effect": "^3.11.7"
72
+ "@standard-schema/spec": "1.0.0",
73
+ "msgpackr": "1.11.2",
74
+ "nanoid": "5.1.3",
75
+ "pretty-bytes": "6.1.1"
82
76
  },
83
77
  "devDependencies": {
84
- "@effect/cli": "^0.51.1",
85
- "@effect/experimental": "0.36.1",
86
- "@effect/opentelemetry": "0.42.1",
87
- "@effect/platform": "0.72.1",
88
- "@effect/platform-browser": "0.51.1",
89
- "@effect/platform-bun": "0.52.1",
90
- "@effect/platform-node": "0.68.1",
91
- "@effect/vitest": "0.16.1",
78
+ "@effect/cli": "^0.59.8",
79
+ "@effect/cluster": "^0.29.17",
80
+ "@effect/experimental": "^0.44.8",
81
+ "@effect/opentelemetry": "^0.46.4",
82
+ "@effect/platform": "^0.80.8",
83
+ "@effect/platform-browser": "^0.59.8",
84
+ "@effect/platform-bun": "^0.60.17",
85
+ "@effect/platform-node": "^0.76.17",
86
+ "@effect/printer": "^0.42.8",
87
+ "@effect/printer-ansi": "^0.42.8",
88
+ "@effect/rpc": "^0.55.12",
89
+ "@effect/sql": "^0.33.8",
90
+ "@effect/typeclass": "^0.33.8",
91
+ "@effect/vitest": "^0.20.8",
92
92
  "@opentelemetry/api": "^1.9.0",
93
- "@opentelemetry/exporter-metrics-otlp-http": "0.57.0",
94
- "@opentelemetry/exporter-trace-otlp-http": "0.57.0",
95
- "@opentelemetry/sdk-metrics": "1.30.0",
96
- "@opentelemetry/sdk-trace-base": "1.30.0",
97
- "@opentelemetry/sdk-trace-node": "1.30.0",
98
- "@types/node": "22.10.5",
99
- "@types/web": "^0.0.175",
100
- "effect": "3.12.1",
101
- "vitest": "^2.1.4"
93
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.200.0",
94
+ "@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
95
+ "@opentelemetry/resources": "^2.0.0",
96
+ "@opentelemetry/sdk-metrics": "^2.0.0",
97
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
98
+ "@opentelemetry/sdk-trace-node": "^2.0.0",
99
+ "@types/bun": "^1.2.4",
100
+ "@types/jsdom": "^21.1.7",
101
+ "@types/node": "^22.13.10",
102
+ "@types/web": "^0.0.203",
103
+ "effect": "^3.14.8",
104
+ "jsdom": "^26.0.0",
105
+ "vitest": "^3.1.1"
106
+ },
107
+ "peerDependencies": {
108
+ "@effect/cli": "~0.59.8",
109
+ "@effect/cluster": "~0.29.17",
110
+ "@effect/experimental": "~0.44.8",
111
+ "@effect/opentelemetry": "~0.46.4",
112
+ "@effect/platform": "~0.80.8",
113
+ "@effect/platform-browser": "~0.59.8",
114
+ "@effect/platform-bun": "~0.60.17",
115
+ "@effect/platform-node": "~0.76.17",
116
+ "@effect/printer": "~0.42.8",
117
+ "@effect/printer-ansi": "~0.42.8",
118
+ "@effect/rpc": "~0.55.12",
119
+ "@effect/sql": "~0.33.8",
120
+ "@effect/typeclass": "~0.33.8",
121
+ "@effect/vitest": "~0.20.8",
122
+ "@opentelemetry/api": "~1.9.0",
123
+ "effect": "~3.14.8"
102
124
  },
103
125
  "publishConfig": {
104
126
  "access": "public"
105
127
  },
128
+ "react-native": "./dist/index.js",
106
129
  "scripts": {
107
130
  "test": "vitest"
108
131
  }
package/src/bun/mod.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { $ } from 'bun'
2
+ import { Effect } from 'effect'
3
+
4
+ export * as PlatformBun from '@effect/platform-bun'
5
+
6
+ export const cmd = (_: string) =>
7
+ Effect.promise(() => {
8
+ console.log(`Running command: ${_}`)
9
+ return $`${{ raw: _ }}`
10
+ })
11
+
12
+ export { $ } from 'bun'
@@ -1,4 +1,4 @@
1
- import { Array, STM, TRef } from 'effect'
1
+ import { Array, Effect, STM, TRef } from 'effect'
2
2
 
3
3
  export type BucketQueue<A> = TRef.TRef<A[]>
4
4
 
@@ -7,16 +7,43 @@ export const make = <A>(): STM.STM<BucketQueue<A>> => TRef.make<A[]>([])
7
7
  export const offerAll = <A>(self: BucketQueue<A>, elements: ReadonlyArray<A>) =>
8
8
  TRef.update(self, (bucket) => Array.appendAll(bucket, elements))
9
9
 
10
+ export const replace = <A>(self: BucketQueue<A>, elements: ReadonlyArray<A>) => TRef.set(self, elements as A[])
11
+
10
12
  export const clear = <A>(self: BucketQueue<A>) => TRef.set(self, [])
11
13
 
12
- export const takeBetween = <A>(self: BucketQueue<A>, min: number, max: number) =>
14
+ export const takeBetween = <A>(
15
+ bucket: BucketQueue<A>,
16
+ min: number,
17
+ max: number,
18
+ ): STM.STM<ReadonlyArray<A>, never, never> =>
13
19
  STM.gen(function* () {
14
- const bucket = yield* TRef.get(self)
15
- if (bucket.length < min) {
20
+ const bucketValue = yield* TRef.get(bucket)
21
+ if (bucketValue.length < min) {
16
22
  return yield* STM.retry
17
23
  } else {
18
- const elements = bucket.splice(0, Math.min(max, bucket.length))
19
- yield* TRef.set(self, bucket)
24
+ const elements = bucketValue.splice(0, Math.min(max, bucketValue.length))
25
+ yield* TRef.set(bucket, bucketValue)
20
26
  return elements
21
27
  }
22
28
  })
29
+
30
+ export const peekAll = <A>(bucket: BucketQueue<A>) => TRef.get(bucket)
31
+
32
+ /** Returns the elements up to the first element that matches the predicate, the rest is left in the queue
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const [elements, rest] = yield* BucketQueue.takeSplitWhere(bucket, (a) => a > 3)
37
+ * assert.deepStrictEqual(elements, [1, 2, 3])
38
+ * assert.deepStrictEqual(rest, [4, 5, 6])
39
+ * ```
40
+ */
41
+ export const takeSplitWhere = <A>(bucket: BucketQueue<A>, predicate: (a: A) => boolean) =>
42
+ STM.gen(function* () {
43
+ const bucketValue = yield* TRef.get(bucket)
44
+ const [elements, rest] = Array.splitWhere(bucketValue, predicate)
45
+ yield* TRef.set(bucket, rest)
46
+ return elements
47
+ })
48
+
49
+ export const size = <A>(bucket: BucketQueue<A>) => TRef.get(bucket).pipe(Effect.map((_) => _.length))
@@ -1,11 +1,11 @@
1
1
  import * as OtelTracer from '@effect/opentelemetry/Tracer'
2
- import type { Context, Duration, Scope, Stream } from 'effect'
3
- import { Cause, Deferred, Effect, Fiber, FiberRef, HashSet, Logger, pipe } from 'effect'
2
+ import type { Context, Duration, Stream } from 'effect'
3
+ import { Cause, Deferred, Effect, Fiber, FiberRef, HashSet, Logger, pipe, Scope } from 'effect'
4
4
  import type { UnknownException } from 'effect/Cause'
5
5
  import { log } from 'effect/Console'
6
6
  import type { LazyArg } from 'effect/Function'
7
7
 
8
- import { isNonEmptyString, isPromise } from '../index.js'
8
+ import { isPromise } from '../index.js'
9
9
  import { UnknownError } from './Error.js'
10
10
 
11
11
  export * from 'effect/Effect'
@@ -25,6 +25,18 @@ export * from 'effect/Effect'
25
25
  // console.error(message, ...rest)
26
26
  // })
27
27
 
28
+ /** Same as `Effect.scopeWith` but with a `CloseableScope` instead of a `Scope`. */
29
+ export const scopeWithCloseable = <R, E, A>(
30
+ fn: (scope: Scope.CloseableScope) => Effect.Effect<A, E, R | Scope.Scope>,
31
+ ): Effect.Effect<A, E, R | Scope.Scope> =>
32
+ Effect.gen(function* () {
33
+ // const parentScope = yield* Scope.Scope
34
+ // const scope = yield* Scope.fork(parentScope, ExecutionStrategy.sequential)
35
+ const scope = yield* Scope.make()
36
+ yield* Effect.addFinalizer((exit) => Scope.close(scope, exit))
37
+ return yield* fn(scope).pipe(Scope.extend(scope))
38
+ })
39
+
28
40
  export const tryAll = <Res>(
29
41
  fn: () => Res,
30
42
  ): Res extends Effect.Effect<infer A, infer E, never>
@@ -42,17 +54,6 @@ export const tryAll = <Res>(
42
54
  ),
43
55
  ) as any
44
56
 
45
- const getThreadName = () => {
46
- // @ts-expect-error TODO fix types
47
- const globalName = globalThis.name
48
- return isNonEmptyString(globalName)
49
- ? globalName
50
- : // eslint-disable-next-line unicorn/prefer-global-this
51
- typeof window === 'object'
52
- ? 'Main Thread'
53
- : 'unknown-thread'
54
- }
55
-
56
57
  export const acquireReleaseLog = (label: string) =>
57
58
  Effect.acquireRelease(Effect.log(`${label} acquire`), (_, ex) => Effect.log(`${label} release`, ex))
58
59
 
@@ -76,9 +77,8 @@ export const tapCauseLogPretty = <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.
76
77
  Effect.catchTag('NoSuchElementException', (_) => Effect.succeed(undefined)),
77
78
  )
78
79
 
79
- const threadName = getThreadName()
80
80
  const firstErrLine = cause.toString().split('\n')[0]
81
- yield* Effect.logError(`Error on ${threadName}: ${firstErrLine}`, cause).pipe((_) =>
81
+ yield* Effect.logError(firstErrLine, cause).pipe((_) =>
82
82
  span === undefined
83
83
  ? _
84
84
  : Effect.annotateLogs({ spanId: span.spanContext().spanId, traceId: span.spanContext().traceId })(_),
@@ -113,11 +113,11 @@ export const logWarnIfTakesLongerThan =
113
113
  Effect.gen(function* () {
114
114
  const runtime = yield* Effect.runtime<never>()
115
115
 
116
- let timedOut = false
116
+ let tookLongerThanTimer = false
117
117
 
118
118
  const timeoutFiber = Effect.sleep(duration).pipe(
119
119
  Effect.tap(() => {
120
- timedOut = true
120
+ tookLongerThanTimer = true
121
121
  // TODO include span info
122
122
  return Effect.logWarning(`${label}: Took longer than ${duration}ms`)
123
123
  }),
@@ -126,9 +126,22 @@ export const logWarnIfTakesLongerThan =
126
126
  )
127
127
 
128
128
  const start = Date.now()
129
- const res = yield* eff.pipe(Effect.exit)
129
+ const res = yield* eff.pipe(
130
+ Effect.exit,
131
+ Effect.onInterrupt(
132
+ Effect.fn(function* () {
133
+ const end = Date.now()
134
+
135
+ yield* Fiber.interrupt(timeoutFiber)
136
+
137
+ if (tookLongerThanTimer) {
138
+ yield* Effect.logWarning(`${label}: Interrupted after ${end - start}ms`)
139
+ }
140
+ }),
141
+ ),
142
+ )
130
143
 
131
- if (timedOut) {
144
+ if (tookLongerThanTimer) {
132
145
  const end = Date.now()
133
146
  yield* Effect.logWarning(`${label}: Actual duration: ${end - start}ms`)
134
147
  }
@@ -1,4 +1,4 @@
1
- import { Logger } from 'effect'
1
+ import { Cause, HashMap, Logger, LogLevel } from 'effect'
2
2
 
3
3
  export * from 'effect/Logger'
4
4
 
@@ -14,4 +14,26 @@ export const prettyWithThread = (threadName: string) =>
14
14
  Logger.prettyLogger({
15
15
  formatDate: (date) => `${defaultDateFormat(date)} ${threadName}`,
16
16
  }),
17
+ // consoleLogger(threadName),
17
18
  )
19
+
20
+ export const consoleLogger = (threadName: string) =>
21
+ Logger.make(({ message, annotations, date, logLevel, cause }) => {
22
+ const consoleFn =
23
+ logLevel === LogLevel.Debug
24
+ ? console.debug
25
+ : logLevel === LogLevel.Info
26
+ ? console.info
27
+ : logLevel === LogLevel.Warning
28
+ ? console.warn
29
+ : console.error
30
+
31
+ const annotationsObj = Object.fromEntries(HashMap.entries(annotations))
32
+
33
+ const messages = Array.isArray(message) ? message : [message]
34
+ if (Cause.isEmpty(cause) === false) {
35
+ messages.push(Cause.pretty(cause, { renderErrorCause: true }))
36
+ }
37
+
38
+ consoleFn(`[${defaultDateFormat(date)} ${threadName}]`, ...messages, annotationsObj)
39
+ })
@@ -1,8 +1,11 @@
1
1
  import { Transferable } from '@effect/platform'
2
+ import type { SchemaAST } from 'effect'
2
3
  import { Effect, Hash, ParseResult, Schema } from 'effect'
3
4
  import type { ParseError } from 'effect/ParseResult'
4
5
  import type { ParseOptions } from 'effect/SchemaAST'
5
6
 
7
+ import { shouldNeverHappen } from '../../index.js'
8
+
6
9
  export * from 'effect/Schema'
7
10
  export * from './debug-diff.js'
8
11
  export * from './msgpack.js'
@@ -34,6 +37,30 @@ export const encodeWithTransferables =
34
37
  return [encoded, collector.unsafeRead() as Transferable[]]
35
38
  })
36
39
 
40
+ export const decodeSyncDebug: <A, I>(
41
+ schema: Schema.Schema<A, I, never>,
42
+ options?: SchemaAST.ParseOptions,
43
+ ) => (i: I, overrideOptions?: SchemaAST.ParseOptions) => A = (schema, options) => (input, overrideOptions) => {
44
+ const res = Schema.decodeEither(schema, options)(input, overrideOptions)
45
+ if (res._tag === 'Left') {
46
+ return shouldNeverHappen(`decodeSyncDebug failed:`, res.left)
47
+ } else {
48
+ return res.right
49
+ }
50
+ }
51
+
52
+ export const encodeSyncDebug: <A, I>(
53
+ schema: Schema.Schema<A, I, never>,
54
+ options?: SchemaAST.ParseOptions,
55
+ ) => (a: A, overrideOptions?: SchemaAST.ParseOptions) => I = (schema, options) => (input, overrideOptions) => {
56
+ const res = Schema.encodeEither(schema, options)(input, overrideOptions)
57
+ if (res._tag === 'Left') {
58
+ return shouldNeverHappen(`encodeSyncDebug failed:`, res.left)
59
+ } else {
60
+ return res.right
61
+ }
62
+ }
63
+
37
64
  export const swap = <A, I, R>(schema: Schema.Schema<A, I, R>): Schema.Schema<I, A, R> =>
38
65
  Schema.transformOrFail(Schema.typeSchema(schema), Schema.encodedSchema(schema), {
39
66
  decode: ParseResult.encode(schema),
@@ -0,0 +1,150 @@
1
+ // Fork of effect/Subscribable.ts which makes Subscribable yieldable
2
+
3
+ /**
4
+ * @since 2.0.0
5
+ */
6
+
7
+ import type { SubscriptionRef } from 'effect'
8
+ import { Effect, Effectable, Readable, Stream } from 'effect'
9
+ import { dual } from 'effect/Function'
10
+ import { hasProperty } from 'effect/Predicate'
11
+
12
+ /**
13
+ * @since 2.0.0
14
+ * @category type ids
15
+ */
16
+ export const TypeId: unique symbol = Symbol.for('effect/Subscribable')
17
+
18
+ /**
19
+ * @since 2.0.0
20
+ * @category type ids
21
+ */
22
+ export type TypeId = typeof TypeId
23
+
24
+ /**
25
+ * @since 2.0.0
26
+ * @category models
27
+ */
28
+ export interface Subscribable<A, E = never, R = never> extends Readable.Readable<A, E, R>, Effect.Effect<A, E, R> {
29
+ readonly [TypeId]: TypeId
30
+ readonly changes: Stream.Stream<A, E, R>
31
+ }
32
+
33
+ /**
34
+ * @since 2.0.0
35
+ * @category refinements
36
+ */
37
+ export const isSubscribable = (u: unknown): u is Subscribable<unknown, unknown, unknown> => hasProperty(u, TypeId)
38
+
39
+ // const Proto: Omit<Subscribable<any>, 'get' | 'changes'> = {
40
+ // [Readable.TypeId]: Readable.TypeId,
41
+ // [TypeId]: TypeId,
42
+ // pipe() {
43
+ // return pipeArguments(this, arguments)
44
+ // },
45
+ // }
46
+
47
+ class SubscribableImpl<in out A> extends Effectable.Class<A> implements Subscribable<A> {
48
+ // @ts-expect-error type symbol
49
+ readonly [TypeId] = TypeId
50
+ // @ts-expect-error type symbol
51
+ readonly [Readable.TypeId] = Readable.TypeId
52
+ constructor(
53
+ readonly get: Effect.Effect<A>,
54
+ readonly changes: Stream.Stream<A>,
55
+ ) {
56
+ super()
57
+ }
58
+ // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
59
+ commit() {
60
+ return this.get
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @since 2.0.0
66
+ * @category constructors
67
+ */
68
+ // export const make = <A, E, R>(options: {
69
+ // readonly get: Effect.Effect<A, E, R>
70
+ // readonly changes: Stream.Stream<A, E, R>
71
+ // }): Subscribable<A, E, R> => Object.assign(Object.create(Proto), options)
72
+
73
+ export const make = <A, E, R>(options: {
74
+ readonly get: Effect.Effect<A, E, R>
75
+ readonly changes: Stream.Stream<A, E, R>
76
+ }): Subscribable<A, E, R> => new SubscribableImpl(options.get as any, options.changes as any) as Subscribable<A, E, R>
77
+
78
+ export const fromSubscriptionRef = <A>(ref: SubscriptionRef.SubscriptionRef<A>): Subscribable<A> =>
79
+ make({
80
+ get: ref.get,
81
+ changes: ref.changes,
82
+ })
83
+
84
+ /**
85
+ * @since 2.0.0
86
+ * @category combinators
87
+ */
88
+ export const map: {
89
+ /**
90
+ * @since 2.0.0
91
+ * @category combinators
92
+ */
93
+ <A, B>(f: (a: NoInfer<A>) => B): <E, R>(fa: Subscribable<A, E, R>) => Subscribable<B, E, R>
94
+ /**
95
+ * @since 2.0.0
96
+ * @category combinators
97
+ */
98
+ <A, E, R, B>(self: Subscribable<A, E, R>, f: (a: NoInfer<A>) => B): Subscribable<B, E, R>
99
+ } = dual(
100
+ 2,
101
+ <A, E, R, B>(self: Subscribable<A, E, R>, f: (a: NoInfer<A>) => B): Subscribable<B, E, R> =>
102
+ make({
103
+ get: Effect.map(self.get, f),
104
+ changes: Stream.map(self.changes, f),
105
+ }),
106
+ )
107
+
108
+ /**
109
+ * @since 2.0.0
110
+ * @category combinators
111
+ */
112
+ export const mapEffect: {
113
+ /**
114
+ * @since 2.0.0
115
+ * @category combinators
116
+ */
117
+ <A, B, E2, R2>(
118
+ f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>,
119
+ ): <E, R>(fa: Subscribable<A, E, R>) => Subscribable<B, E | E2, R | R2>
120
+ /**
121
+ * @since 2.0.0
122
+ * @category combinators
123
+ */
124
+ <A, E, R, B, E2, R2>(
125
+ self: Subscribable<A, E, R>,
126
+ f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>,
127
+ ): Subscribable<B, E | E2, R | R2>
128
+ } = dual(
129
+ 2,
130
+ <A, E, R, B, E2, R2>(
131
+ self: Subscribable<A, E, R>,
132
+ f: (a: NoInfer<A>) => Effect.Effect<B, E2, R2>,
133
+ ): Subscribable<B, E | E2, R | R2> =>
134
+ make({
135
+ get: Effect.flatMap(self.get, f),
136
+ changes: Stream.mapEffect(self.changes, f),
137
+ }),
138
+ )
139
+
140
+ /**
141
+ * @since 2.0.0
142
+ * @category constructors
143
+ */
144
+ export const unwrap = <A, E, R, E1, R1>(
145
+ effect: Effect.Effect<Subscribable<A, E, R>, E1, R1>,
146
+ ): Subscribable<A, E | E1, R | R1> =>
147
+ make({
148
+ get: Effect.flatMap(effect, (s) => s.get),
149
+ changes: Stream.unwrap(Effect.map(effect, (s) => s.changes)),
150
+ })
@@ -0,0 +1,106 @@
1
+ import * as Vitest from '@effect/vitest'
2
+ import { Effect, Schema, Stream } from 'effect'
3
+ import { JSDOM } from 'jsdom'
4
+
5
+ import * as WebChannel from './WebChannel.js'
6
+
7
+ Vitest.describe('WebChannel', () => {
8
+ Vitest.describe('windowChannel', () => {
9
+ Vitest.scopedLive('should work with 2 windows', () =>
10
+ Effect.gen(function* () {
11
+ const windowA = new JSDOM().window as unknown as globalThis.Window
12
+ const windowB = new JSDOM().window as unknown as globalThis.Window
13
+
14
+ const codeSideA = Effect.gen(function* () {
15
+ const channelToB = yield* WebChannel.windowChannel2({
16
+ listenWindow: windowA,
17
+ sendWindow: windowB,
18
+ ids: { own: 'a', other: 'b' },
19
+ schema: Schema.Number,
20
+ })
21
+
22
+ const msgFromBFiber = yield* channelToB.listen.pipe(
23
+ Stream.flatten(),
24
+ Stream.runHead,
25
+ Effect.flatten,
26
+ Effect.fork,
27
+ )
28
+
29
+ yield* channelToB.send(1)
30
+
31
+ Vitest.expect(yield* msgFromBFiber).toEqual(2)
32
+ })
33
+
34
+ const codeSideB = Effect.gen(function* () {
35
+ const channelToA = yield* WebChannel.windowChannel2({
36
+ listenWindow: windowB,
37
+ sendWindow: windowA,
38
+ ids: { own: 'b', other: 'a' },
39
+ schema: Schema.Number,
40
+ })
41
+
42
+ const msgFromAFiber = yield* channelToA.listen.pipe(
43
+ Stream.flatten(),
44
+ Stream.runHead,
45
+ Effect.flatten,
46
+ Effect.fork,
47
+ )
48
+
49
+ yield* channelToA.send(2)
50
+
51
+ Vitest.expect(yield* msgFromAFiber).toEqual(1)
52
+ })
53
+
54
+ yield* Effect.all([codeSideA, codeSideB], { concurrency: 'unbounded' })
55
+ }),
56
+ )
57
+
58
+ Vitest.scopedLive('should work with the same window', () =>
59
+ Effect.gen(function* () {
60
+ const window = new JSDOM().window as unknown as globalThis.Window
61
+
62
+ const codeSideA = Effect.gen(function* () {
63
+ const channelToB = yield* WebChannel.windowChannel2({
64
+ listenWindow: window,
65
+ sendWindow: window,
66
+ ids: { own: 'a', other: 'b' },
67
+ schema: Schema.Number,
68
+ })
69
+
70
+ const msgFromBFiber = yield* channelToB.listen.pipe(
71
+ Stream.flatten(),
72
+ Stream.runHead,
73
+ Effect.flatten,
74
+ Effect.fork,
75
+ )
76
+
77
+ yield* channelToB.send(1)
78
+
79
+ Vitest.expect(yield* msgFromBFiber).toEqual(2)
80
+ })
81
+
82
+ const codeSideB = Effect.gen(function* () {
83
+ const channelToA = yield* WebChannel.windowChannel2({
84
+ listenWindow: window,
85
+ sendWindow: window,
86
+ ids: { own: 'b', other: 'a' },
87
+ schema: Schema.Number,
88
+ })
89
+
90
+ const msgFromAFiber = yield* channelToA.listen.pipe(
91
+ Stream.flatten(),
92
+ Stream.runHead,
93
+ Effect.flatten,
94
+ Effect.fork,
95
+ )
96
+
97
+ yield* channelToA.send(2)
98
+
99
+ Vitest.expect(yield* msgFromAFiber).toEqual(1)
100
+ })
101
+
102
+ yield* Effect.all([codeSideA, codeSideB], { concurrency: 'unbounded' })
103
+ }),
104
+ )
105
+ })
106
+ })