effect 4.0.0-beta.44 → 4.0.0-beta.46

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 (151) hide show
  1. package/dist/Equal.d.ts.map +1 -1
  2. package/dist/Equal.js +16 -0
  3. package/dist/Equal.js.map +1 -1
  4. package/dist/Hash.js +1 -1
  5. package/dist/Hash.js.map +1 -1
  6. package/dist/Semaphore.d.ts +1 -1
  7. package/dist/Semaphore.d.ts.map +1 -1
  8. package/dist/Semaphore.js +1 -3
  9. package/dist/Semaphore.js.map +1 -1
  10. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  11. package/dist/unstable/ai/McpServer.js +24 -21
  12. package/dist/unstable/ai/McpServer.js.map +1 -1
  13. package/dist/unstable/eventlog/Event.d.ts +0 -6
  14. package/dist/unstable/eventlog/Event.d.ts.map +1 -1
  15. package/dist/unstable/eventlog/Event.js +0 -5
  16. package/dist/unstable/eventlog/Event.js.map +1 -1
  17. package/dist/unstable/eventlog/EventGroup.d.ts +0 -2
  18. package/dist/unstable/eventlog/EventGroup.d.ts.map +1 -1
  19. package/dist/unstable/eventlog/EventGroup.js +0 -2
  20. package/dist/unstable/eventlog/EventGroup.js.map +1 -1
  21. package/dist/unstable/eventlog/EventJournal.d.ts +22 -5
  22. package/dist/unstable/eventlog/EventJournal.d.ts.map +1 -1
  23. package/dist/unstable/eventlog/EventJournal.js +126 -67
  24. package/dist/unstable/eventlog/EventJournal.js.map +1 -1
  25. package/dist/unstable/eventlog/EventLog.d.ts +88 -34
  26. package/dist/unstable/eventlog/EventLog.d.ts.map +1 -1
  27. package/dist/unstable/eventlog/EventLog.js +215 -141
  28. package/dist/unstable/eventlog/EventLog.js.map +1 -1
  29. package/dist/unstable/eventlog/EventLogEncryption.d.ts +9 -7
  30. package/dist/unstable/eventlog/EventLogEncryption.d.ts.map +1 -1
  31. package/dist/unstable/eventlog/EventLogEncryption.js +13 -15
  32. package/dist/unstable/eventlog/EventLogEncryption.js.map +1 -1
  33. package/dist/unstable/eventlog/EventLogMessage.d.ts +228 -0
  34. package/dist/unstable/eventlog/EventLogMessage.d.ts.map +1 -0
  35. package/dist/unstable/eventlog/EventLogMessage.js +214 -0
  36. package/dist/unstable/eventlog/EventLogMessage.js.map +1 -0
  37. package/dist/unstable/eventlog/EventLogRemote.d.ts +109 -194
  38. package/dist/unstable/eventlog/EventLogRemote.d.ts.map +1 -1
  39. package/dist/unstable/eventlog/EventLogRemote.js +165 -320
  40. package/dist/unstable/eventlog/EventLogRemote.js.map +1 -1
  41. package/dist/unstable/eventlog/EventLogServer.d.ts +25 -47
  42. package/dist/unstable/eventlog/EventLogServer.d.ts.map +1 -1
  43. package/dist/unstable/eventlog/EventLogServer.js +127 -198
  44. package/dist/unstable/eventlog/EventLogServer.js.map +1 -1
  45. package/dist/unstable/eventlog/EventLogServerEncrypted.d.ts +60 -0
  46. package/dist/unstable/eventlog/EventLogServerEncrypted.d.ts.map +1 -0
  47. package/dist/unstable/eventlog/EventLogServerEncrypted.js +166 -0
  48. package/dist/unstable/eventlog/EventLogServerEncrypted.js.map +1 -0
  49. package/dist/unstable/eventlog/EventLogServerUnencrypted.d.ts +183 -0
  50. package/dist/unstable/eventlog/EventLogServerUnencrypted.d.ts.map +1 -0
  51. package/dist/unstable/eventlog/EventLogServerUnencrypted.js +461 -0
  52. package/dist/unstable/eventlog/EventLogServerUnencrypted.js.map +1 -0
  53. package/dist/unstable/eventlog/EventLogSessionAuth.d.ts +117 -0
  54. package/dist/unstable/eventlog/EventLogSessionAuth.d.ts.map +1 -0
  55. package/dist/unstable/eventlog/EventLogSessionAuth.js +284 -0
  56. package/dist/unstable/eventlog/EventLogSessionAuth.js.map +1 -0
  57. package/dist/unstable/eventlog/{SqlEventLogJournal.d.ts → SqlEventJournal.d.ts} +2 -2
  58. package/dist/unstable/eventlog/SqlEventJournal.d.ts.map +1 -0
  59. package/dist/unstable/eventlog/{SqlEventLogJournal.js → SqlEventJournal.js} +20 -14
  60. package/dist/unstable/eventlog/SqlEventJournal.js.map +1 -0
  61. package/dist/unstable/eventlog/{SqlEventLogServer.d.ts → SqlEventLogServerEncrypted.d.ts} +5 -5
  62. package/dist/unstable/eventlog/SqlEventLogServerEncrypted.d.ts.map +1 -0
  63. package/dist/unstable/eventlog/{SqlEventLogServer.js → SqlEventLogServerEncrypted.js} +65 -24
  64. package/dist/unstable/eventlog/SqlEventLogServerEncrypted.js.map +1 -0
  65. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.d.ts +25 -0
  66. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.d.ts.map +1 -0
  67. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js +354 -0
  68. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js.map +1 -0
  69. package/dist/unstable/eventlog/index.d.ts +22 -2
  70. package/dist/unstable/eventlog/index.d.ts.map +1 -1
  71. package/dist/unstable/eventlog/index.js +22 -2
  72. package/dist/unstable/eventlog/index.js.map +1 -1
  73. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.d.ts +2 -0
  74. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.d.ts.map +1 -0
  75. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.js +89 -0
  76. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.js.map +1 -0
  77. package/dist/unstable/reactivity/AtomHttpApi.d.ts +1 -2
  78. package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
  79. package/dist/unstable/reactivity/AtomHttpApi.js +2 -2
  80. package/dist/unstable/reactivity/AtomHttpApi.js.map +1 -1
  81. package/dist/unstable/reactivity/AtomRpc.d.ts +1 -2
  82. package/dist/unstable/reactivity/AtomRpc.d.ts.map +1 -1
  83. package/dist/unstable/reactivity/AtomRpc.js +3 -3
  84. package/dist/unstable/reactivity/AtomRpc.js.map +1 -1
  85. package/dist/unstable/rpc/Rpc.d.ts +25 -4
  86. package/dist/unstable/rpc/Rpc.d.ts.map +1 -1
  87. package/dist/unstable/rpc/Rpc.js +26 -0
  88. package/dist/unstable/rpc/Rpc.js.map +1 -1
  89. package/dist/unstable/rpc/RpcClient.d.ts +3 -13
  90. package/dist/unstable/rpc/RpcClient.d.ts.map +1 -1
  91. package/dist/unstable/rpc/RpcClient.js +47 -23
  92. package/dist/unstable/rpc/RpcClient.js.map +1 -1
  93. package/dist/unstable/rpc/RpcGroup.d.ts +1 -1
  94. package/dist/unstable/rpc/RpcGroup.d.ts.map +1 -1
  95. package/dist/unstable/rpc/RpcMiddleware.d.ts +2 -2
  96. package/dist/unstable/rpc/RpcMiddleware.d.ts.map +1 -1
  97. package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
  98. package/dist/unstable/rpc/RpcServer.js +3 -2
  99. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  100. package/dist/unstable/rpc/Utils.d.ts +6 -0
  101. package/dist/unstable/rpc/Utils.d.ts.map +1 -1
  102. package/dist/unstable/rpc/Utils.js +44 -0
  103. package/dist/unstable/rpc/Utils.js.map +1 -1
  104. package/dist/unstable/schema/Model.d.ts +2 -2
  105. package/dist/unstable/schema/Model.d.ts.map +1 -1
  106. package/dist/unstable/schema/Model.js +2 -4
  107. package/dist/unstable/schema/Model.js.map +1 -1
  108. package/dist/unstable/schema/VariantSchema.d.ts +1 -1
  109. package/dist/unstable/schema/VariantSchema.d.ts.map +1 -1
  110. package/dist/unstable/schema/VariantSchema.js +1 -12
  111. package/dist/unstable/schema/VariantSchema.js.map +1 -1
  112. package/dist/unstable/workers/Transferable.d.ts +1 -1
  113. package/dist/unstable/workers/Transferable.d.ts.map +1 -1
  114. package/dist/unstable/workers/Transferable.js +1 -1
  115. package/dist/unstable/workers/Transferable.js.map +1 -1
  116. package/package.json +1 -1
  117. package/src/Equal.ts +17 -0
  118. package/src/Hash.ts +2 -2
  119. package/src/Semaphore.ts +2 -4
  120. package/src/unstable/ai/McpServer.ts +24 -22
  121. package/src/unstable/eventlog/Event.ts +0 -8
  122. package/src/unstable/eventlog/EventGroup.ts +0 -4
  123. package/src/unstable/eventlog/EventJournal.ts +144 -76
  124. package/src/unstable/eventlog/EventLog.ts +342 -221
  125. package/src/unstable/eventlog/EventLogEncryption.ts +16 -30
  126. package/src/unstable/eventlog/EventLogMessage.ts +277 -0
  127. package/src/unstable/eventlog/EventLogRemote.ts +261 -408
  128. package/src/unstable/eventlog/EventLogServer.ts +182 -274
  129. package/src/unstable/eventlog/EventLogServerEncrypted.ts +206 -0
  130. package/src/unstable/eventlog/EventLogServerUnencrypted.ts +749 -0
  131. package/src/unstable/eventlog/EventLogSessionAuth.ts +437 -0
  132. package/src/unstable/eventlog/{SqlEventLogJournal.ts → SqlEventJournal.ts} +26 -18
  133. package/src/unstable/eventlog/{SqlEventLogServer.ts → SqlEventLogServerEncrypted.ts} +102 -40
  134. package/src/unstable/eventlog/SqlEventLogServerUnencrypted.ts +500 -0
  135. package/src/unstable/eventlog/index.ts +27 -2
  136. package/src/unstable/eventlog/internal/identityRootSecretDerivation.ts +153 -0
  137. package/src/unstable/reactivity/AtomHttpApi.ts +23 -8
  138. package/src/unstable/reactivity/AtomRpc.ts +16 -5
  139. package/src/unstable/rpc/Rpc.ts +42 -4
  140. package/src/unstable/rpc/RpcClient.ts +59 -24
  141. package/src/unstable/rpc/RpcGroup.ts +1 -1
  142. package/src/unstable/rpc/RpcMiddleware.ts +2 -2
  143. package/src/unstable/rpc/RpcServer.ts +5 -3
  144. package/src/unstable/rpc/Utils.ts +59 -0
  145. package/src/unstable/schema/Model.ts +4 -6
  146. package/src/unstable/schema/VariantSchema.ts +4 -17
  147. package/src/unstable/workers/Transferable.ts +9 -11
  148. package/dist/unstable/eventlog/SqlEventLogJournal.d.ts.map +0 -1
  149. package/dist/unstable/eventlog/SqlEventLogJournal.js.map +0 -1
  150. package/dist/unstable/eventlog/SqlEventLogServer.d.ts.map +0 -1
  151. package/dist/unstable/eventlog/SqlEventLogServer.js.map +0 -1
@@ -1,22 +1,35 @@
1
1
  /**
2
2
  * @since 4.0.0
3
3
  */
4
+ import * as Cache from "../../Cache.ts"
4
5
  import * as Context from "../../Context.ts"
5
6
  import * as Data from "../../Data.ts"
6
- import * as Deferred from "../../Deferred.ts"
7
7
  import * as Effect from "../../Effect.ts"
8
- import * as Exit from "../../Exit.ts"
9
8
  import * as Layer from "../../Layer.ts"
9
+ import * as Predicate from "../../Predicate.ts"
10
10
  import * as Queue from "../../Queue.ts"
11
- import * as RcMap from "../../RcMap.ts"
12
- import * as Schedule from "../../Schedule.ts"
13
- import * as Schema from "../../Schema.ts"
11
+ import * as Redacted from "../../Redacted.ts"
12
+ import type * as Schema from "../../Schema.ts"
14
13
  import type * as Scope from "../../Scope.ts"
15
- import * as Msgpack from "../encoding/Msgpack.ts"
16
- import * as Socket from "../socket/Socket.ts"
17
- import { type Entry, EntryId, RemoteEntry, RemoteId } from "./EventJournal.ts"
18
- import type { Identity } from "./EventLog.ts"
19
- import { EncryptedEntry, EncryptedRemoteEntry, EventLogEncryption, layerSubtle } from "./EventLogEncryption.ts"
14
+ import * as RpcClient from "../rpc/RpcClient.ts"
15
+ import type { RpcClientError } from "../rpc/RpcClientError.ts"
16
+ import type * as RpcGroup from "../rpc/RpcGroup.ts"
17
+ import type { Entry, RemoteEntry, RemoteId } from "./EventJournal.ts"
18
+ import { type Identity, Registry } from "./EventLog.ts"
19
+ import { EventLogEncryption, layerSubtle } from "./EventLogEncryption.ts"
20
+ import {
21
+ Authenticate,
22
+ ChangesRpc,
23
+ ChunkedMessage,
24
+ type EventLogProtocolError,
25
+ EventLogRemoteRpcs,
26
+ type HelloResponse,
27
+ type StoreId,
28
+ WriteEntries,
29
+ WriteEntriesUnencrypted
30
+ } from "./EventLogMessage.ts"
31
+ import { encodeSessionAuthPayload, signSessionAuthPayloadBytes } from "./EventLogSessionAuth.ts"
32
+ import { makeGetIdentityRootSecretMaterial } from "./internal/identityRootSecretDerivation.ts"
20
33
 
21
34
  /**
22
35
  * @since 4.0.0
@@ -24,217 +37,21 @@ import { EncryptedEntry, EncryptedRemoteEntry, EventLogEncryption, layerSubtle }
24
37
  */
25
38
  export class EventLogRemote extends Context.Service<EventLogRemote, {
26
39
  readonly id: RemoteId
27
- readonly changes: (
28
- identity: Identity["Service"],
29
- startSequence: number
30
- ) => Effect.Effect<Queue.Dequeue<RemoteEntry>, never, Scope.Scope>
31
- readonly write: (identity: Identity["Service"], entries: ReadonlyArray<Entry>) => Effect.Effect<void>
40
+ readonly changes: (options: {
41
+ readonly identity: Identity["Service"]
42
+ readonly storeId: StoreId
43
+ readonly startSequence: number
44
+ }) => Effect.Effect<Queue.Dequeue<RemoteEntry, EventLogRemoteError>, never, Scope.Scope>
45
+ readonly write: (options: {
46
+ readonly identity: Identity["Service"]
47
+ readonly storeId: StoreId
48
+ readonly entries: ReadonlyArray<Entry>
49
+ }) => Effect.Effect<void, EventLogRemoteError>
50
+ readonly whenAuthenticated: <A, E, R>(
51
+ effect: Effect.Effect<A, E, R>
52
+ ) => Effect.Effect<A, E | EventLogRemoteError, R | Identity>
32
53
  }>()("effect/eventlog/EventLogRemote") {}
33
54
 
34
- /**
35
- * @since 4.0.0
36
- * @category protocol
37
- */
38
- export class Hello extends Schema.Class<Hello>("effect/eventlog/EventLogRemote/Hello")({
39
- _tag: Schema.tag("Hello"),
40
- remoteId: RemoteId
41
- }) {}
42
-
43
- /**
44
- * @since 4.0.0
45
- * @category protocol
46
- */
47
- export class ChunkedMessage extends Schema.Class<ChunkedMessage>("effect/eventlog/EventLogRemote/ChunkedMessage")({
48
- _tag: Schema.tag("ChunkedMessage"),
49
- id: Schema.Number,
50
- part: Schema.Tuple([Schema.Number, Schema.Number]),
51
- data: Schema.Uint8Array
52
- }) {
53
- /**
54
- * @since 4.0.0
55
- */
56
- static split(id: number, data: Uint8Array): ReadonlyArray<ChunkedMessage> {
57
- const parts = Math.ceil(data.byteLength / constChunkSize)
58
- const result: Array<ChunkedMessage> = new Array(parts)
59
- for (let i = 0; i < parts; i++) {
60
- const start = i * constChunkSize
61
- const end = Math.min((i + 1) * constChunkSize, data.byteLength)
62
- result[i] = new ChunkedMessage({
63
- _tag: "ChunkedMessage",
64
- id,
65
- part: [i, parts],
66
- data: data.subarray(start, end)
67
- })
68
- }
69
- return result
70
- }
71
-
72
- /**
73
- * @since 4.0.0
74
- */
75
- static join(
76
- map: Map<number, {
77
- readonly parts: Array<Uint8Array>
78
- count: number
79
- bytes: number
80
- }>,
81
- part: ChunkedMessage
82
- ): Uint8Array | undefined {
83
- const [index, total] = part.part
84
- let entry = map.get(part.id)
85
- if (!entry) {
86
- entry = {
87
- parts: new Array(total),
88
- count: 0,
89
- bytes: 0
90
- }
91
- map.set(part.id, entry)
92
- }
93
- entry.parts[index] = part.data
94
- entry.count++
95
- entry.bytes += part.data.byteLength
96
- if (entry.count !== total) {
97
- return
98
- }
99
- const data = new Uint8Array(entry.bytes)
100
- let offset = 0
101
- for (const part of entry.parts) {
102
- data.set(part, offset)
103
- offset += part.byteLength
104
- }
105
- map.delete(part.id)
106
- return data
107
- }
108
- }
109
-
110
- /**
111
- * @since 4.0.0
112
- * @category protocol
113
- */
114
- export class WriteEntries extends Schema.Class<WriteEntries>("effect/eventlog/EventLogRemote/WriteEntries")({
115
- _tag: Schema.tag("WriteEntries"),
116
- publicKey: Schema.String,
117
- id: Schema.Number,
118
- iv: Schema.Uint8Array,
119
- encryptedEntries: Schema.Array(EncryptedEntry)
120
- }) {}
121
-
122
- /**
123
- * @since 4.0.0
124
- * @category protocol
125
- */
126
- export class Ack extends Schema.Class<Ack>("effect/eventlog/EventLogRemote/Ack")({
127
- _tag: Schema.tag("Ack"),
128
- id: Schema.Number,
129
- sequenceNumbers: Schema.Array(Schema.Number)
130
- }) {}
131
-
132
- /**
133
- * @since 4.0.0
134
- * @category protocol
135
- */
136
- export class RequestChanges extends Schema.Class<RequestChanges>("effect/eventlog/EventLogRemote/RequestChanges")({
137
- _tag: Schema.tag("RequestChanges"),
138
- publicKey: Schema.String,
139
- startSequence: Schema.Number
140
- }) {}
141
-
142
- /**
143
- * @since 4.0.0
144
- * @category protocol
145
- */
146
- export class Changes extends Schema.Class<Changes>("effect/eventlog/EventLogRemote/Changes")({
147
- _tag: Schema.tag("Changes"),
148
- publicKey: Schema.String,
149
- entries: Schema.Array(EncryptedRemoteEntry)
150
- }) {}
151
-
152
- /**
153
- * @since 4.0.0
154
- * @category protocol
155
- */
156
- export class StopChanges extends Schema.Class<StopChanges>("effect/eventlog/EventLogRemote/StopChanges")({
157
- _tag: Schema.tag("StopChanges"),
158
- publicKey: Schema.String
159
- }) {}
160
-
161
- /**
162
- * @since 4.0.0
163
- * @category protocol
164
- */
165
- export class Ping extends Schema.Class<Ping>("effect/eventlog/EventLogRemote/Ping")({
166
- _tag: Schema.tag("Ping"),
167
- id: Schema.Number
168
- }) {}
169
-
170
- /**
171
- * @since 4.0.0
172
- * @category protocol
173
- */
174
- export class Pong extends Schema.Class<Pong>("effect/eventlog/EventLogRemote/Pong")({
175
- _tag: Schema.tag("Pong"),
176
- id: Schema.Number
177
- }) {}
178
-
179
- /**
180
- * @since 4.0.0
181
- * @category protocol
182
- */
183
- export const ProtocolRequest = Schema.Union([WriteEntries, RequestChanges, StopChanges, ChunkedMessage, Ping])
184
-
185
- /**
186
- * @since 4.0.0
187
- * @category protocol
188
- */
189
- export const ProtocolRequestMsgpack = Msgpack.schema(ProtocolRequest)
190
-
191
- /**
192
- * @since 4.0.0
193
- * @category protocol
194
- */
195
- export const decodeRequest = Schema.decodeUnknownEffect(ProtocolRequestMsgpack)
196
-
197
- /**
198
- * @since 4.0.0
199
- * @category protocol
200
- */
201
- export const encodeRequest = Schema.encodeUnknownEffect(ProtocolRequestMsgpack)
202
-
203
- /**
204
- * @since 4.0.0
205
- * @category protocol
206
- */
207
- export const ProtocolResponse = Schema.Union([Hello, Ack, Changes, ChunkedMessage, Pong])
208
-
209
- /**
210
- * @since 4.0.0
211
- * @category protocol
212
- */
213
- export const ProtocolResponseMsgpack = Msgpack.schema(ProtocolResponse)
214
-
215
- /**
216
- * @since 4.0.0
217
- * @category protocol
218
- */
219
- export const decodeResponse = Schema.decodeUnknownEffect(ProtocolResponseMsgpack)
220
-
221
- /**
222
- * @since 4.0.0
223
- * @category protocol
224
- */
225
- export const encodeResponse = Schema.encodeUnknownEffect(ProtocolResponseMsgpack)
226
-
227
- /**
228
- * @since 4.0.0
229
- * @category change
230
- */
231
- export class RemoteAdditions extends Schema.Class<RemoteAdditions>("effect/eventlog/EventLogRemote/RemoteAdditions")({
232
- _tag: Schema.tag("RemoteAdditions"),
233
- entries: Schema.Array(RemoteEntry)
234
- }) {}
235
-
236
- const constChunkSize = 512_000
237
-
238
55
  /**
239
56
  * @since 4.0.0
240
57
  * @category errors
@@ -244,230 +61,266 @@ export class EventLogRemoteError extends Data.TaggedError("EventLogRemoteError")
244
61
  readonly cause: unknown
245
62
  }> {}
246
63
 
64
+ const getIdentityRootSecretMaterial = makeGetIdentityRootSecretMaterial(globalThis.crypto)
65
+
66
+ const makeAuthenticate = Effect.fnUntraced(function*(options: {
67
+ readonly identity: Identity["Service"]
68
+ readonly hello: HelloResponse
69
+ }) {
70
+ const rootSecretMaterial = yield* getIdentityRootSecretMaterial(options.identity)
71
+ const payload = yield* encodeSessionAuthPayload({
72
+ remoteId: options.hello.remoteId,
73
+ challenge: options.hello.challenge,
74
+ publicKey: options.identity.publicKey,
75
+ signingPublicKey: rootSecretMaterial.signingPublicKey
76
+ })
77
+ const signature = yield* signSessionAuthPayloadBytes({
78
+ payload,
79
+ signingPrivateKey: Redacted.value(rootSecretMaterial.signingPrivateKey)
80
+ })
81
+
82
+ return new Authenticate({
83
+ publicKey: options.identity.publicKey,
84
+ signingPublicKey: rootSecretMaterial.signingPublicKey,
85
+ signature,
86
+ algorithm: "Ed25519"
87
+ })
88
+ })
89
+
247
90
  /**
248
91
  * @since 4.0.0
249
- * @category entry
92
+ * @category RpcClient
250
93
  */
251
- export const RemoteEntryChange = Schema.Tuple([RemoteId, Schema.Array(EntryId)])
94
+ export class EventLogRemoteClient extends Context.Service<
95
+ EventLogRemoteClient,
96
+ RpcClient.RpcClient<RpcGroup.Rpcs<typeof EventLogRemoteRpcs>, RpcClientError>
97
+ >()(
98
+ "effect/unstable/eventlog/EventLogRemote/EventLogRemoteClient"
99
+ ) {
100
+ static readonly layer = Layer.effect(
101
+ EventLogRemoteClient,
102
+ RpcClient.make(EventLogRemoteRpcs, {
103
+ disableTracing: true
104
+ })
105
+ )
106
+ }
252
107
 
253
108
  /**
254
109
  * @since 4.0.0
255
110
  * @category constructors
256
111
  */
257
- export const fromSocket = (options?: {
258
- readonly disablePing?: boolean
259
- }): Effect.Effect<EventLogRemote["Service"], never, Scope.Scope | EventLogEncryption | Socket.Socket> =>
260
- Effect.gen(function*() {
261
- const socket = yield* Socket.Socket
262
- const encryption = yield* EventLogEncryption
263
- const writeRaw = yield* socket.writer
112
+ export const makeWith = Effect.fnUntraced(function*({ encodeWrite, decodeChanges }: {
113
+ readonly encodeWrite: (options: {
114
+ readonly identity: Identity["Service"]
115
+ readonly entries: ReadonlyArray<Entry>
116
+ readonly storeId: StoreId
117
+ }) => Effect.Effect<Uint8Array<ArrayBuffer>, Schema.SchemaError>
118
+ readonly decodeChanges: (
119
+ identity: Identity["Service"],
120
+ data: Uint8Array<ArrayBuffer>
121
+ ) => Effect.Effect<ReadonlyArray<RemoteEntry>, Schema.SchemaError>
122
+ }): Effect.fn.Return<EventLogRemote["Service"], EventLogRemoteError, Scope.Scope | EventLogRemoteClient | Registry> {
123
+ const client = yield* EventLogRemoteClient
124
+ const registry = yield* Registry
125
+
126
+ let hello: HelloResponse | null = yield* client["EventLog.Hello"]().pipe(
127
+ Effect.mapError((cause) => new EventLogRemoteError({ method: "hello", cause }))
128
+ )
264
129
 
265
- const writeRequest = (request: typeof ProtocolRequest.Type) =>
266
- Effect.gen(function*() {
267
- const data = yield* encodeRequest(request)
268
- if (request._tag !== "WriteEntries" || data.byteLength <= constChunkSize) {
269
- return yield* writeRaw(data)
270
- }
271
- const id = request.id
272
- for (const part of ChunkedMessage.split(id, data)) {
273
- yield* writeRaw(yield* encodeRequest(part))
274
- }
130
+ const identities = new Map<string, Identity["Service"]>()
131
+ const ensureIdentity = (identity: Identity["Service"]) => {
132
+ let entry = identities.get(identity.publicKey)
133
+ if (!entry) {
134
+ entry = identity
135
+ identities.set(identity.publicKey, entry)
136
+ }
137
+ return entry
138
+ }
139
+
140
+ const authCache = yield* Cache.make({
141
+ lookup: Effect.fnUntraced(function*(publicKey: string) {
142
+ const identity = identities.get(publicKey)!
143
+ hello ??= yield* client["EventLog.Hello"]().pipe(
144
+ Effect.mapError((cause) => new EventLogRemoteError({ method: "hello", cause }))
145
+ )
146
+ const authenticate = yield* makeAuthenticate({
147
+ identity,
148
+ hello
275
149
  })
150
+ yield* client["EventLog.Authenticate"](authenticate)
151
+ }, Effect.mapError((cause) => new EventLogRemoteError({ method: "authenticate", cause }))),
152
+ capacity: Number.MAX_SAFE_INTEGER
153
+ })
276
154
 
277
- let pendingCounter = 0
278
- const pending = new Map<number, {
279
- readonly entries: ReadonlyArray<Entry>
280
- readonly deferred: Deferred.Deferred<void>
281
- readonly publicKey: string
282
- }>()
283
- const chunks = new Map<number, {
284
- readonly parts: Array<Uint8Array>
285
- count: number
286
- bytes: number
287
- }>()
155
+ const ensureAuthenticated = (identity: Identity["Service"]) => {
156
+ ensureIdentity(identity)
157
+ return Cache.get(authCache, identity.publicKey)
158
+ }
288
159
 
289
- const subscriptions = yield* RcMap.make({
290
- lookup: (publicKey: string) =>
291
- Effect.acquireRelease(
292
- Queue.make<RemoteEntry>(),
293
- (queue) =>
294
- Queue.shutdown(queue).pipe(
295
- Effect.andThen(Effect.ignore(writeRequest(new StopChanges({ publicKey }))))
296
- )
160
+ const retryForbidden = <A, E, R>(
161
+ effect: Effect.Effect<A, E, R>,
162
+ options: {
163
+ readonly identity: Identity["Service"]
164
+ }
165
+ ) =>
166
+ Effect.retry(effect, {
167
+ while(e) {
168
+ hello = null
169
+ const isForbidden = Predicate.isTagged(e, "EventLogProtocolError") &&
170
+ (e as any as EventLogProtocolError).code === "Forbidden"
171
+ return Cache.invalidate(authCache, options.identity.publicKey).pipe(
172
+ Effect.as(isForbidden)
297
173
  )
174
+ },
175
+ times: 5
298
176
  })
299
- const identities = new Map<string, Identity["Service"]>()
300
- const badPing = yield* Deferred.make<never, Error>()
301
- const remoteId = yield* Deferred.make<RemoteId>()
302
177
 
303
- let latestPing = 0
304
- let latestPong = 0
178
+ let chunkedIdCounter = 0
305
179
 
306
- if (options?.disablePing !== true) {
307
- yield* Effect.suspend(() => {
308
- if (latestPing !== latestPong) {
309
- return Deferred.fail(badPing, new Error("Ping timeout"))
180
+ const remote = EventLogRemote.of({
181
+ id: hello.remoteId,
182
+ write: Effect.fnUntraced(
183
+ function*(options) {
184
+ yield* ensureAuthenticated(options.identity)
185
+ const encoded = yield* encodeWrite(options)
186
+ if (encoded.byteLength <= ChunkedMessage.chunkSize) {
187
+ return yield* client["EventLog.WriteSingle"]({ data: encoded })
188
+ }
189
+ for (const part of ChunkedMessage.split(chunkedIdCounter++, encoded)) {
190
+ yield* client["EventLog.WriteChunked"](part)
191
+ }
192
+ },
193
+ retryForbidden,
194
+ Effect.mapError((cause) => new EventLogRemoteError({ method: "write", cause }))
195
+ ),
196
+ changes: Effect.fnUntraced(function*(options) {
197
+ const outgoing = yield* Queue.make<RemoteEntry, EventLogRemoteError>()
198
+
199
+ yield* Effect.gen(function*() {
200
+ yield* ensureAuthenticated(options.identity)
201
+
202
+ const chunkedState = ChunkedMessage.initialJoinState()
203
+ const incoming = yield* client["EventLog.Changes"]({
204
+ publicKey: options.identity.publicKey,
205
+ storeId: options.storeId,
206
+ startSequence: options.startSequence
207
+ }, { asQueue: true })
208
+
209
+ while (true) {
210
+ const parts = yield* Queue.takeAll(incoming)
211
+ for (let i = 0; i < parts.length; i++) {
212
+ const part = parts[i]
213
+ if (part._tag === "Single") {
214
+ yield* Queue.offerAll(outgoing, yield* decodeChanges(options.identity, part.data))
215
+ continue
216
+ }
217
+ const data = ChunkedMessage.join(chunkedState, part)
218
+ if (!data) continue
219
+ yield* Queue.offerAll(outgoing, yield* decodeChanges(options.identity, data))
220
+ }
310
221
  }
311
- return writeRequest(new Ping({ id: ++latestPing }))
312
222
  }).pipe(
313
- Effect.delay("10 seconds"),
314
- Effect.ignore,
315
- Effect.forever,
316
- Effect.interruptible,
223
+ (effect) => retryForbidden(effect, options),
224
+ Effect.mapError((cause) => {
225
+ if (cause._tag === "EventLogRemoteError") {
226
+ return cause
227
+ }
228
+ return new EventLogRemoteError({
229
+ method: "changes",
230
+ cause
231
+ })
232
+ }),
233
+ Effect.catchCause((cause) => Queue.failCause(outgoing, cause)),
317
234
  Effect.forkScoped
318
235
  )
319
- }
320
236
 
321
- const handleMessage = (res: typeof ProtocolResponse.Type): Effect.Effect<void, unknown, Scope.Scope> => {
322
- switch (res._tag) {
323
- case "Hello": {
324
- return Deferred.succeed(remoteId, res.remoteId).pipe(Effect.asVoid)
325
- }
326
- case "Ack": {
327
- return Effect.gen(function*() {
328
- const entry = pending.get(res.id)
329
- if (!entry) return
330
- pending.delete(res.id)
331
- const { deferred, entries, publicKey } = entry
332
- const remoteEntries = res.sequenceNumbers.map((sequenceNumber, i) => {
333
- const entry = entries[i]
334
- return new RemoteEntry({
335
- remoteSequence: sequenceNumber,
336
- entry
337
- })
338
- })
339
- const queue = yield* RcMap.get(subscriptions, publicKey)
340
- yield* Queue.offerAll(queue, remoteEntries)
341
- yield* Deferred.done(deferred, Exit.void)
342
- }).pipe(Effect.scoped)
343
- }
344
- case "Pong": {
345
- latestPong = res.id
346
- if (res.id === latestPing) {
347
- return Effect.void
348
- }
349
- return Deferred.fail(badPing, new Error("Pong id mismatch")).pipe(
350
- Effect.asVoid
351
- )
352
- }
353
- case "Changes": {
354
- return Effect.gen(function*() {
355
- const queue = yield* RcMap.get(subscriptions, res.publicKey)
356
- const identity = identities.get(res.publicKey)
357
- if (!identity) {
358
- return
359
- }
360
- const entries = yield* encryption.decrypt(identity, res.entries)
361
- yield* Queue.offerAll(queue, entries)
362
- }).pipe(Effect.scoped)
363
- }
364
- case "ChunkedMessage": {
365
- const data = ChunkedMessage.join(chunks, res)
366
- if (!data) return Effect.void
367
- return Effect.scoped(
368
- Effect.flatMap(decodeResponse(data), handleMessage)
369
- )
370
- }
371
- }
372
- }
237
+ return outgoing
238
+ }),
239
+ whenAuthenticated: (effect) =>
240
+ IdentityService.use((identity) => Effect.flatMap(ensureAuthenticated(identity), () => effect))
241
+ })
373
242
 
374
- yield* socket.run((data) => Effect.flatMap(decodeResponse(data), handleMessage)).pipe(
375
- Effect.raceFirst(Deferred.await(badPing)),
376
- Effect.tapCause(Effect.logDebug),
377
- Effect.retry({
378
- schedule: Schedule.exponential(100).pipe(
379
- Schedule.either(Schedule.spaced(5000))
380
- )
381
- }),
382
- Effect.annotateLogs({
383
- service: "EventLogRemote",
384
- method: "fromSocket"
385
- }),
386
- Effect.forkScoped,
387
- Effect.interruptible
388
- )
243
+ yield* registry.registerRemote(remote)
389
244
 
390
- const id = yield* Deferred.await(remoteId)
245
+ return remote
246
+ })
391
247
 
392
- return {
393
- id,
394
- write: (identity, entries) =>
395
- Effect.gen(function*() {
396
- const encrypted = yield* encryption.encrypt(identity, entries)
397
- const deferred = yield* Deferred.make<void>()
398
- const id = pendingCounter++
399
- pending.set(id, {
400
- entries,
401
- deferred,
402
- publicKey: identity.publicKey
403
- })
404
- yield* Effect.orDie(writeRequest(
405
- new WriteEntries({
406
- publicKey: identity.publicKey,
407
- id,
408
- iv: encrypted.iv,
409
- encryptedEntries: encrypted.encryptedEntries.map((encryptedEntry, i) => ({
410
- entryId: entries[i].id,
411
- encryptedEntry
412
- }))
413
- })
414
- ))
415
- yield* Deferred.await(deferred)
416
- }),
417
- changes: (identity, startSequence) =>
418
- Effect.gen(function*() {
419
- const queue = yield* RcMap.get(subscriptions, identity.publicKey)
420
- identities.set(identity.publicKey, identity)
421
- yield* Effect.orDie(writeRequest(
422
- new RequestChanges({
423
- publicKey: identity.publicKey,
424
- startSequence
425
- })
426
- ))
427
- return queue
428
- })
429
- }
430
- })
248
+ class IdentityService extends Context.Service<Identity, Identity["Service"]>()(
249
+ "effect/eventlog/EventLog/Identity" satisfies Identity["key"]
250
+ ) {}
431
251
 
432
252
  /**
433
253
  * @since 4.0.0
434
254
  * @category constructors
435
255
  */
436
- export const fromWebSocket = (
437
- url: string,
438
- options?: {
439
- readonly disablePing?: boolean
440
- }
441
- ): Effect.Effect<EventLogRemote["Service"], never, Scope.Scope | EventLogEncryption | Socket.WebSocketConstructor> =>
442
- Effect.gen(function*() {
443
- const socket = yield* Socket.makeWebSocket(url)
444
- return yield* fromSocket(options).pipe(
445
- Effect.provideService(Socket.Socket, socket)
446
- )
256
+ export const makeEncrypted = Effect.gen(function*(): Effect.fn.Return<
257
+ EventLogRemote["Service"],
258
+ EventLogRemoteError,
259
+ Scope.Scope | EventLogRemoteClient | EventLogEncryption | Registry
260
+ > {
261
+ const encryption = yield* EventLogEncryption
262
+
263
+ return yield* makeWith({
264
+ encodeWrite: (options) =>
265
+ encryption.encrypt(options.identity, options.entries).pipe(
266
+ Effect.flatMap((msg) =>
267
+ new WriteEntries({
268
+ publicKey: options.identity.publicKey,
269
+ storeId: options.storeId,
270
+ iv: msg.iv,
271
+ encryptedEntries: msg.encryptedEntries.map((entry, i) => ({
272
+ entryId: options.entries[i].id,
273
+ encryptedEntry: entry
274
+ }))
275
+ }).encoded
276
+ )
277
+ ),
278
+ decodeChanges: (identity, data) =>
279
+ ChangesRpc.decodeEncrypted(data).pipe(
280
+ Effect.flatMap((entries) => encryption.decrypt(identity, entries))
281
+ )
447
282
  })
283
+ })
448
284
 
449
285
  /**
450
286
  * @since 4.0.0
451
- * @category layers
287
+ * @category constructors
452
288
  */
453
- export const layerWebSocket = (
454
- url: string,
455
- options?: {
456
- readonly disablePing?: boolean
457
- }
458
- ): Layer.Layer<never, never, Socket.WebSocketConstructor | EventLogEncryption> =>
459
- Layer.effectDiscard(fromWebSocket(url, options))
289
+ export const makeUnencrypted: Effect.Effect<
290
+ EventLogRemote["Service"],
291
+ EventLogRemoteError,
292
+ Scope.Scope | EventLogRemoteClient | Registry
293
+ > = makeWith({
294
+ encodeWrite: (options) =>
295
+ new WriteEntriesUnencrypted({
296
+ publicKey: options.identity.publicKey,
297
+ storeId: options.storeId,
298
+ entries: options.entries
299
+ }).encoded,
300
+ decodeChanges: (_identity, data) => ChangesRpc.decodeUnencrypted(data)
301
+ })
460
302
 
461
303
  /**
462
304
  * @since 4.0.0
463
- * @category layers
305
+ * @category Layers
464
306
  */
465
- export const layerWebSocketBrowser = (
466
- url: string,
467
- options?: {
468
- readonly disablePing?: boolean
469
- }
470
- ): Layer.Layer<never> =>
471
- layerWebSocket(url, options).pipe(
472
- Layer.provide([layerSubtle, Socket.layerWebSocketConstructorGlobal])
473
- )
307
+ export const layerEncrypted: Layer.Layer<
308
+ EventLogRemote,
309
+ EventLogRemoteError,
310
+ RpcClient.Protocol | Registry
311
+ > = Layer.effect(EventLogRemote, makeEncrypted).pipe(
312
+ Layer.provide(EventLogRemoteClient.layer),
313
+ Layer.provide(layerSubtle)
314
+ )
315
+
316
+ /**
317
+ * @since 4.0.0
318
+ * @category Layers
319
+ */
320
+ export const layerUnencrypted: Layer.Layer<
321
+ EventLogRemote,
322
+ EventLogRemoteError,
323
+ RpcClient.Protocol | Registry
324
+ > = Layer.effect(EventLogRemote, makeUnencrypted).pipe(
325
+ Layer.provide(EventLogRemoteClient.layer)
326
+ )