effect 4.0.0-beta.44 → 4.0.0-beta.45

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
@@ -0,0 +1,749 @@
1
+ /**
2
+ * @since 4.0.0
3
+ */
4
+ import * as Arr from "../../Array.ts"
5
+ import * as Context from "../../Context.ts"
6
+ import * as Data from "../../Data.ts"
7
+ import * as Effect from "../../Effect.ts"
8
+ import * as Layer from "../../Layer.ts"
9
+ import * as Option from "../../Option.ts"
10
+ import * as PubSub from "../../PubSub.ts"
11
+ import * as RcMap from "../../RcMap.ts"
12
+ import * as Redacted from "../../Redacted.ts"
13
+ import * as Schema from "../../Schema.ts"
14
+ import type * as Scope from "../../Scope.ts"
15
+ import * as Semaphore from "../../Semaphore.ts"
16
+ import * as Stream from "../../Stream.ts"
17
+ import type * as Rpc from "../rpc/Rpc.ts"
18
+ import type * as RpcGroup from "../rpc/RpcGroup.ts"
19
+ import * as RpcServer from "../rpc/RpcServer.ts"
20
+ import type * as Event from "./Event.ts"
21
+ import type * as EventGroup from "./EventGroup.ts"
22
+ import * as EventJournal from "./EventJournal.ts"
23
+ import { Entry, makeEntryIdUnsafe, makeRemoteIdUnsafe, RemoteEntry, type RemoteId } from "./EventJournal.ts"
24
+ import * as EventLog from "./EventLog.ts"
25
+ import {
26
+ ChangesRpc,
27
+ type EventLogAuthentication,
28
+ EventLogProtocolError,
29
+ EventLogRemoteRpcs,
30
+ type StoreId,
31
+ WriteEntriesUnencrypted
32
+ } from "./EventLogMessage.ts"
33
+ import * as EventLogServer from "./EventLogServer.ts"
34
+
35
+ /**
36
+ * @since 4.0.0
37
+ * @category EventLogServerUnencrypted
38
+ */
39
+ export class EventLogServerUnencrypted extends Context.Service<EventLogServerUnencrypted, {
40
+ readonly makeWrite: <Groups extends EventGroup.Any>(
41
+ schema: EventLog.EventLogSchema<Groups>
42
+ ) => <
43
+ Tag extends EventGroup.Events<Groups>["tag"],
44
+ Event extends Event.Any = Event.WithTag<EventGroup.Events<Groups>, Tag>
45
+ >(options: {
46
+ readonly storeId: StoreId
47
+ readonly event: Tag
48
+ readonly payload: Event.Payload<Event>
49
+ }) => Effect.Effect<
50
+ Event.Success<Event>,
51
+ EventLogServerStoreError | Event.Error<Event>
52
+ >
53
+ }>()("effect/eventlog/EventLogServerUnencrypted") {}
54
+
55
+ /**
56
+ * @since 4.0.0
57
+ * @category EventLogServerUnencrypted
58
+ */
59
+ export const makeWrite = <Groups extends EventGroup.Any>(
60
+ schema: EventLog.EventLogSchema<Groups>
61
+ ): Effect.Effect<
62
+ <
63
+ Tag extends EventGroup.Events<Groups>["tag"],
64
+ Event extends Event.Any = Event.WithTag<EventGroup.Events<Groups>, Tag>
65
+ >(options: {
66
+ readonly storeId: StoreId
67
+ readonly event: Tag
68
+ readonly payload: Event.Payload<Event>
69
+ }) => Effect.Effect<
70
+ Event.Success<Event>,
71
+ EventLogServerStoreError | Event.Error<Event>
72
+ >,
73
+ never,
74
+ EventLogServerUnencrypted
75
+ > => EventLogServerUnencrypted.useSync((_) => _.makeWrite(schema))
76
+
77
+ /**
78
+ * @since 4.0.0
79
+ * @category Layers
80
+ */
81
+ export const layerRpcHandlers: Layer.Layer<
82
+ Rpc.ToHandler<RpcGroup.Rpcs<typeof EventLogRemoteRpcs>> | EventLogAuthentication,
83
+ never,
84
+ Storage | StoreMapping | EventLogServerAuthorization | EventLog.Registry
85
+ > = Layer.unwrap(Effect.gen(function*() {
86
+ const storage = yield* Storage
87
+ const mapping = yield* StoreMapping
88
+ const auth = yield* EventLogServerAuthorization
89
+ const registry = yield* EventLog.Registry
90
+ const handler = yield* makeServerHandler
91
+ const remoteId = yield* storage.getId
92
+
93
+ const processEntries = Effect.fnUntraced(function*(options: {
94
+ readonly publicKey: string
95
+ readonly storeId: StoreId
96
+ readonly entries: Arr.NonEmptyReadonlyArray<Entry>
97
+ }) {
98
+ const entries = Arr.sort(options.entries, Entry.Order)
99
+ let history = yield* storage.entriesAfter(options.storeId, entries[0])
100
+ const persistedEntries = Arr.empty<Entry>()
101
+ for (const entry of entries) {
102
+ const [duplicate, conflicts, newHistory] = toConflicts(history, entry)
103
+ if (duplicate) continue
104
+ history = newHistory
105
+ yield* handler({
106
+ publicKey: options.publicKey,
107
+ storeId: options.storeId,
108
+ entry,
109
+ conflicts
110
+ })
111
+ persistedEntries.push(entry)
112
+ }
113
+ yield* storage.write(options.storeId, persistedEntries)
114
+ }, storage.withTransaction)
115
+
116
+ return EventLogServer.layerRpcHandlers({
117
+ remoteId,
118
+ getOrCreateSessionAuthBinding: (publicKey, signingPublicKey) =>
119
+ storage.getOrCreateSessionAuthBinding(publicKey, signingPublicKey),
120
+ onWrite: Effect.fnUntraced(function*(data) {
121
+ const request = yield* WriteEntriesUnencrypted.decode(data).pipe(
122
+ Effect.mapError((_) =>
123
+ new EventLogProtocolError({
124
+ requestTag: "WriteEntries",
125
+ publicKey: undefined,
126
+ code: "InternalServerError",
127
+ message: "Decoding failure"
128
+ })
129
+ )
130
+ )
131
+ if (!Arr.isReadonlyArrayNonEmpty(request.entries)) return
132
+
133
+ const resolvedStoreId = yield* mapping.resolve({
134
+ publicKey: request.publicKey,
135
+ storeId: request.storeId
136
+ }).pipe(
137
+ Effect.mapError((_) =>
138
+ new EventLogProtocolError({
139
+ requestTag: "WriteEntries",
140
+ publicKey: request.publicKey,
141
+ storeId: request.storeId,
142
+ code: "Unauthorized",
143
+ message: _.message
144
+ })
145
+ )
146
+ )
147
+ yield* auth.authorizeWrite({
148
+ publicKey: request.publicKey,
149
+ storeId: resolvedStoreId,
150
+ entries: request.entries
151
+ }).pipe(
152
+ Effect.mapError((_) =>
153
+ new EventLogProtocolError({
154
+ requestTag: "WriteEntries",
155
+ publicKey: request.publicKey,
156
+ storeId: request.storeId,
157
+ code: "Unauthorized",
158
+ message: _.message
159
+ })
160
+ )
161
+ )
162
+ yield* processEntries({
163
+ publicKey: request.publicKey,
164
+ storeId: resolvedStoreId,
165
+ entries: request.entries
166
+ }).pipe(
167
+ Effect.catchCause((_) =>
168
+ Effect.fail(
169
+ new EventLogProtocolError({
170
+ requestTag: "WriteEntries",
171
+ publicKey: request.publicKey,
172
+ code: "InternalServerError",
173
+ message: "Persistence failure"
174
+ })
175
+ )
176
+ ),
177
+ Effect.provideService(EventLog.Identity, makeClientIdentity(request.publicKey))
178
+ )
179
+ }),
180
+ changes: Effect.fnUntraced(function*(request) {
181
+ const storeId = yield* mapping.resolve({
182
+ publicKey: request.publicKey,
183
+ storeId: request.storeId
184
+ })
185
+ yield* auth.authorizeRead({
186
+ publicKey: request.publicKey,
187
+ storeId
188
+ })
189
+ return storage.changes({
190
+ storeId,
191
+ startSequence: request.startSequence,
192
+ compactors: registry.compactors
193
+ }).pipe(
194
+ Stream.mapArrayEffect((entries) => Effect.map(ChangesRpc.encodeUnencrypted(entries), Arr.of))
195
+ )
196
+ }, Stream.unwrap)
197
+ })
198
+ }))
199
+
200
+ /**
201
+ * @since 4.0.0
202
+ * @category errors
203
+ */
204
+ export class EventLogServerStoreError extends Data.TaggedError("EventLogServerStoreError")<{
205
+ readonly reason: "NotFound" | "PersistenceFailure"
206
+ readonly publicKey?: string | undefined
207
+ readonly storeId?: StoreId | undefined
208
+ readonly message?: string | undefined
209
+ }> {}
210
+
211
+ /**
212
+ * @since 4.0.0
213
+ * @category errors
214
+ */
215
+ export class EventLogServerAuthError extends Data.TaggedError("EventLogServerAuthError")<{
216
+ readonly reason: "Unauthorized" | "Forbidden"
217
+ readonly publicKey: string
218
+ readonly storeId?: StoreId | undefined
219
+ readonly message?: string | undefined
220
+ }> {}
221
+
222
+ /**
223
+ * @since 4.0.0
224
+ * @category context
225
+ */
226
+ export class EventLogServerAuthorization extends Context.Service<EventLogServerAuthorization, {
227
+ readonly authorizeWrite: (options: {
228
+ readonly publicKey: string
229
+ readonly storeId: StoreId
230
+ readonly entries: ReadonlyArray<Entry>
231
+ }) => Effect.Effect<void, EventLogServerAuthError>
232
+ readonly authorizeRead: (options: {
233
+ readonly publicKey: string
234
+ readonly storeId: StoreId
235
+ }) => Effect.Effect<void, EventLogServerAuthError>
236
+ readonly authorizeIdentity: (options: {
237
+ readonly publicKey: string
238
+ }) => Effect.Effect<void, EventLogServerAuthError>
239
+ }>()("effect/eventlog/EventLogServerUnencrypted/EventLogServerAuthorization") {}
240
+
241
+ /**
242
+ * @since 4.0.0
243
+ * @category context
244
+ */
245
+ export class StoreMapping extends Context.Service<StoreMapping, {
246
+ readonly resolve: (
247
+ options: {
248
+ readonly publicKey: string
249
+ readonly storeId: StoreId
250
+ }
251
+ ) => Effect.Effect<StoreId, EventLogServerStoreError>
252
+ readonly hasStore: (options: {
253
+ readonly publicKey: string
254
+ readonly storeId: StoreId
255
+ }) => Effect.Effect<boolean, EventLogServerStoreError>
256
+ }>()("effect/eventlog/EventLogServerUnencrypted/StoreMapping") {}
257
+
258
+ const toStoreNotFoundError = (options: {
259
+ readonly storeId: StoreId
260
+ readonly publicKey?: string | undefined
261
+ }) =>
262
+ new EventLogServerStoreError({
263
+ reason: "NotFound",
264
+ publicKey: options.publicKey,
265
+ storeId: options.storeId,
266
+ message: options.publicKey === undefined
267
+ ? `No provisioned store found for store id: ${options.storeId}`
268
+ : `No provisioned store found for public key: ${options.publicKey} and store id: ${options.storeId}`
269
+ })
270
+
271
+ /**
272
+ * @since 4.0.0
273
+ * @category store
274
+ */
275
+ export const layerStoreMappingStatic = (options: {
276
+ readonly storeId: StoreId
277
+ }): Layer.Layer<StoreMapping> =>
278
+ Layer.succeed(StoreMapping, {
279
+ resolve(request) {
280
+ if (request.storeId === options.storeId) {
281
+ return Effect.succeed(options.storeId)
282
+ }
283
+ return Effect.fail(toStoreNotFoundError(request))
284
+ },
285
+ hasStore: ({ storeId }) => Effect.succeed(storeId === options.storeId)
286
+ })
287
+
288
+ /**
289
+ * @since 4.0.0
290
+ * @category storage
291
+ */
292
+ export class Storage extends Context.Service<Storage, {
293
+ readonly getId: Effect.Effect<RemoteId>
294
+ readonly getOrCreateSessionAuthBinding: (
295
+ publicKey: string,
296
+ signingPublicKey: Uint8Array<ArrayBuffer>
297
+ ) => Effect.Effect<Uint8Array<ArrayBuffer>>
298
+ readonly entriesAfter: (storeId: StoreId, entry: Entry) => Effect.Effect<Array<Entry>>
299
+ readonly write: (
300
+ storeId: StoreId,
301
+ entries: ReadonlyArray<Entry>
302
+ ) => Effect.Effect<ReadonlyArray<RemoteEntry>>
303
+ readonly changes: (options: {
304
+ readonly storeId: StoreId
305
+ readonly startSequence: number
306
+ readonly compactors: ReadonlyMap<string, RegisteredCompactor>
307
+ }) => Stream.Stream<RemoteEntry>
308
+ readonly withTransaction: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
309
+ }>()("effect/eventlog/EventLogServerUnencrypted/Storage") {}
310
+
311
+ const makeClientIdentity = (publicKey: string): EventLog.Identity["Service"] => ({
312
+ publicKey,
313
+ privateKey: constEmptyPrivateKey
314
+ })
315
+ const constEmptyPrivateKey = Redacted.make(new Uint8Array(32))
316
+
317
+ const makeServerWriteIdentityPublicKey = (storeId: StoreId): string => `effect-eventlog-server-write:${storeId}`
318
+
319
+ const entriesAfter = (journal: Array<RemoteEntry>, startSequence: number): ReadonlyArray<RemoteEntry> =>
320
+ journal.filter((entry) => entry.remoteSequence > startSequence)
321
+
322
+ const toConflicts = (
323
+ history: ReadonlyArray<Entry>,
324
+ originEntry: Entry
325
+ ): [duplicate: boolean, conflicts: Array<Entry>, newHistory: Array<Entry>] => {
326
+ let duplicate = false
327
+
328
+ for (let i = 0; i < history.length; i++) {
329
+ const entry = history[i]
330
+ if (entry.createdAtMillis < originEntry.createdAtMillis) {
331
+ continue
332
+ } else if (entry.idString === originEntry.idString) {
333
+ duplicate = true
334
+ continue
335
+ }
336
+
337
+ const newHistory = history.slice(i)
338
+ let conflicts: Array<Entry> = []
339
+ for (let j = 0; j < newHistory.length; j++) {
340
+ const scannedEntry = history[j]!
341
+ if (scannedEntry.event === originEntry.event && scannedEntry.primaryKey === originEntry.primaryKey) {
342
+ conflicts.push(scannedEntry)
343
+ }
344
+ }
345
+ return [duplicate, conflicts, newHistory]
346
+ }
347
+
348
+ return [duplicate, [], []]
349
+ }
350
+
351
+ type RegisteredCompactor = {
352
+ readonly events: ReadonlySet<string>
353
+ readonly effect: (options: {
354
+ readonly entries: ReadonlyArray<Entry>
355
+ readonly write: (entry: Entry) => Effect.Effect<void>
356
+ }) => Effect.Effect<void>
357
+ }
358
+
359
+ const representativeSequences = (options: {
360
+ readonly remoteEntries: ReadonlyArray<RemoteEntry>
361
+ readonly compactedCount: number
362
+ }): ReadonlyArray<number> | undefined => {
363
+ if (options.compactedCount === 0) {
364
+ return []
365
+ }
366
+ if (options.compactedCount > options.remoteEntries.length) {
367
+ return undefined
368
+ }
369
+
370
+ const maxSequence = options.remoteEntries[options.remoteEntries.length - 1]!.remoteSequence
371
+ if (options.compactedCount === 1) {
372
+ return [maxSequence]
373
+ }
374
+
375
+ const selected = options.remoteEntries
376
+ .slice(0, options.compactedCount - 1)
377
+ .map((entry) => entry.remoteSequence)
378
+ selected.push(maxSequence)
379
+ for (let i = 1; i < selected.length; i++) {
380
+ if (selected[i]! <= selected[i - 1]!) {
381
+ return undefined
382
+ }
383
+ }
384
+ return selected
385
+ }
386
+
387
+ const toCompactedRemoteEntries = (options: {
388
+ readonly compacted: ReadonlyArray<Entry>
389
+ readonly remoteEntries: ReadonlyArray<RemoteEntry>
390
+ }): ReadonlyArray<RemoteEntry> | undefined => {
391
+ const sequences = representativeSequences({
392
+ remoteEntries: options.remoteEntries,
393
+ compactedCount: options.compacted.length
394
+ })
395
+ if (sequences === undefined) {
396
+ return undefined
397
+ }
398
+
399
+ return options.compacted.map((entry, index) =>
400
+ new RemoteEntry({
401
+ remoteSequence: sequences[index]!,
402
+ entry
403
+ }, { disableChecks: true })
404
+ )
405
+ }
406
+
407
+ /**
408
+ * @since 4.0.0
409
+ * @category compaction
410
+ */
411
+ export const compactBacklog = Effect.fnUntraced(function*(options: {
412
+ readonly remoteEntries: ReadonlyArray<RemoteEntry>
413
+ readonly compactors: ReadonlyMap<string, RegisteredCompactor>
414
+ }) {
415
+ if (options.compactors.size === 0 || options.remoteEntries.length === 0) {
416
+ return options.remoteEntries
417
+ }
418
+
419
+ const compactedRemoteEntries: Array<RemoteEntry> = []
420
+ let index = 0
421
+
422
+ while (index < options.remoteEntries.length) {
423
+ const remoteEntry = options.remoteEntries[index]!
424
+ const compactor = options.compactors.get(remoteEntry.entry.event)
425
+ if (compactor === undefined) {
426
+ compactedRemoteEntries.push(remoteEntry)
427
+ index++
428
+ continue
429
+ }
430
+
431
+ const entries: Array<Entry> = [remoteEntry.entry]
432
+ const remoteGroup: Array<RemoteEntry> = [remoteEntry]
433
+ const compacted: Array<Entry> = []
434
+ index++
435
+
436
+ while (index < options.remoteEntries.length) {
437
+ const nextRemoteEntry = options.remoteEntries[index]!
438
+ const nextCompactor = options.compactors.get(nextRemoteEntry.entry.event)
439
+ if (nextCompactor !== compactor) {
440
+ break
441
+ }
442
+ entries.push(nextRemoteEntry.entry)
443
+ remoteGroup.push(nextRemoteEntry)
444
+ index++
445
+ }
446
+
447
+ yield* compactor.effect({
448
+ entries,
449
+ write(entry) {
450
+ return Effect.sync(() => {
451
+ compacted.push(entry)
452
+ })
453
+ }
454
+ }).pipe(Effect.orDie)
455
+
456
+ const projected = toCompactedRemoteEntries({
457
+ compacted,
458
+ remoteEntries: remoteGroup
459
+ })
460
+
461
+ if (projected === undefined) {
462
+ compactedRemoteEntries.push(...remoteGroup)
463
+ continue
464
+ }
465
+ compactedRemoteEntries.push(...projected)
466
+ }
467
+
468
+ return compactedRemoteEntries
469
+ })
470
+
471
+ /**
472
+ * @since 4.0.0
473
+ * @category storage
474
+ */
475
+ export const makeStorageMemory: Effect.Effect<Storage["Service"], never, Scope.Scope> = Effect.gen(function*() {
476
+ const knownIds = new Map<string, Map<string, number>>()
477
+ const journals = new Map<string, Array<RemoteEntry>>()
478
+ const sessionAuthBindings = new Map<string, Uint8Array<ArrayBuffer>>()
479
+ const remoteId = makeRemoteIdUnsafe()
480
+
481
+ const ensureKnownIds = (storeId: StoreId): Map<string, number> => {
482
+ let storeKnownIds = knownIds.get(storeId)
483
+ if (storeKnownIds) return storeKnownIds
484
+ storeKnownIds = new Map()
485
+ knownIds.set(storeId, storeKnownIds)
486
+ return storeKnownIds
487
+ }
488
+
489
+ const ensureJournal = (storeId: StoreId): Array<RemoteEntry> => {
490
+ let journal = journals.get(storeId)
491
+ if (journal) return journal
492
+ journal = []
493
+ journals.set(storeId, journal)
494
+ return journal
495
+ }
496
+
497
+ const pubsubs = yield* RcMap.make({
498
+ lookup: (_storeId: string) =>
499
+ Effect.acquireRelease(
500
+ PubSub.unbounded<RemoteEntry>(),
501
+ PubSub.shutdown
502
+ ),
503
+ idleTimeToLive: 60000
504
+ })
505
+
506
+ const write = Effect.fnUntraced(function*(storeId: StoreId, entries: ReadonlyArray<Entry>) {
507
+ const sequenceNumbers: Array<number> = []
508
+ const committed: Array<RemoteEntry> = []
509
+ const storeKnownIds = ensureKnownIds(storeId)
510
+ const journal = ensureJournal(storeId)
511
+ let lastSequenceNumber = Arr.last(journal).pipe(
512
+ Option.map((entry) => entry.remoteSequence),
513
+ Option.getOrElse(() => 0)
514
+ )
515
+ if (entries.some((entry) => storeKnownIds.has(entry.idString))) {
516
+ return yield* Effect.die("Duplicate entries")
517
+ }
518
+
519
+ for (const entry of entries) {
520
+ const remoteEntry = new RemoteEntry({
521
+ remoteSequence: ++lastSequenceNumber,
522
+ entry
523
+ }, { disableChecks: true })
524
+
525
+ sequenceNumbers.push(remoteEntry.remoteSequence)
526
+ committed.push(remoteEntry)
527
+ journal.push(remoteEntry)
528
+ storeKnownIds.set(entry.idString, remoteEntry.remoteSequence)
529
+ }
530
+
531
+ const pubsub = yield* RcMap.get(pubsubs, storeId)
532
+ yield* PubSub.publishAll(pubsub, committed)
533
+
534
+ return committed
535
+ }, Effect.scoped)
536
+
537
+ const transactionSemaphore = yield* Semaphore.make(1)
538
+
539
+ return Storage.of({
540
+ getId: Effect.succeed(remoteId),
541
+ getOrCreateSessionAuthBinding: (publicKey, signingPublicKey) =>
542
+ Effect.sync(() => {
543
+ const existing = sessionAuthBindings.get(publicKey)
544
+ if (existing) return existing
545
+ sessionAuthBindings.set(publicKey, signingPublicKey)
546
+ return signingPublicKey
547
+ }),
548
+ entriesAfter: (storeId, entry) =>
549
+ Effect.sync(() => {
550
+ const journal = ensureJournal(storeId)
551
+ return journal.filter((e) => Entry.Order(e.entry, entry) >= 0).map((e) => e.entry)
552
+ }),
553
+ write,
554
+ changes: Effect.fnUntraced(function*({ storeId, startSequence, compactors }) {
555
+ const pubsub = yield* RcMap.get(pubsubs, storeId)
556
+ const subscription = yield* PubSub.subscribe(pubsub)
557
+
558
+ const backlog = yield* compactBacklog({
559
+ remoteEntries: entriesAfter(ensureJournal(storeId), startSequence),
560
+ compactors
561
+ })
562
+ const replayedUpTo = backlog.length > 0 ? backlog[backlog.length - 1].remoteSequence : startSequence
563
+
564
+ return Stream.fromArray(backlog).pipe(
565
+ Stream.concat(
566
+ Stream.fromSubscription(subscription).pipe(
567
+ Stream.filter((entry) => entry.remoteSequence > replayedUpTo)
568
+ )
569
+ )
570
+ )
571
+ }, Stream.unwrap),
572
+ withTransaction: transactionSemaphore.withPermits(1)
573
+ })
574
+ })
575
+
576
+ /**
577
+ * @since 4.0.0
578
+ * @category storage
579
+ */
580
+ export const layerStorageMemory: Layer.Layer<Storage> = Layer.effect(Storage)(makeStorageMemory)
581
+
582
+ /**
583
+ * @since 4.0.0
584
+ * @category constructors
585
+ */
586
+ export const make = Effect.gen(function*() {
587
+ const storage = yield* Storage
588
+ const handler = yield* makeServerHandler
589
+
590
+ return EventLogServerUnencrypted.of({
591
+ makeWrite<Groups extends EventGroup.Any>(schema: EventLog.EventLogSchema<Groups>) {
592
+ const events = new Map<string, Event.AnyWithProps>()
593
+ for (const group of schema.groups as unknown as ReadonlyArray<EventGroup.EventGroup<Event.Any>>) {
594
+ for (const [tag, event] of Object.entries(group.events)) {
595
+ events.set(tag, event)
596
+ }
597
+ }
598
+ return Effect.fnUntraced(function*(options: {
599
+ readonly storeId: StoreId
600
+ readonly event: string
601
+ readonly payload: unknown
602
+ }) {
603
+ const publicKey = makeServerWriteIdentityPublicKey(options.storeId)
604
+ const schemaEvent = events.get(options.event)
605
+ if (schemaEvent === undefined) {
606
+ return yield* Effect.die(`Event schema not found for: "${options.event}"`)
607
+ }
608
+
609
+ const entry = new EventJournal.Entry({
610
+ id: makeEntryIdUnsafe(),
611
+ event: options.event,
612
+ primaryKey: schemaEvent.primaryKey(options.payload),
613
+ payload: yield* Schema.encodeUnknownEffect(schemaEvent.payloadMsgPack)(options.payload).pipe(
614
+ Effect.mapError((_) =>
615
+ new EventLogServerStoreError({
616
+ reason: "PersistenceFailure",
617
+ publicKey: publicKey,
618
+ storeId: options.storeId,
619
+ message: "Failed to encode event"
620
+ })
621
+ )
622
+ ) as Effect.Effect<any, EventLogServerStoreError>
623
+ }, { disableChecks: true })
624
+
625
+ const result = yield* handler({
626
+ publicKey,
627
+ storeId: options.storeId,
628
+ entry,
629
+ conflicts: []
630
+ }).pipe(
631
+ Effect.provideService(EventLog.Identity, makeClientIdentity(publicKey))
632
+ )
633
+
634
+ yield* storage.write(options.storeId, [entry])
635
+
636
+ return result
637
+ }, storage.withTransaction) as any
638
+ }
639
+ })
640
+ })
641
+
642
+ /**
643
+ * @since 4.0.0
644
+ * @category layers
645
+ */
646
+ export const layerServer: Layer.Layer<
647
+ EventLogServerUnencrypted | EventLog.Registry,
648
+ never,
649
+ Storage
650
+ > = Layer.effect(EventLogServerUnencrypted, make).pipe(
651
+ Layer.provideMerge(EventLog.layerRegistry)
652
+ )
653
+
654
+ /**
655
+ * @since 4.0.0
656
+ * @category layers
657
+ */
658
+ export const layer = <Groups extends EventGroup.Any, E, R>(
659
+ _schema: EventLog.EventLogSchema<Groups>,
660
+ layer: Layer.Layer<EventGroup.ToService<Groups>, E, R>
661
+ ): Layer.Layer<
662
+ never,
663
+ E,
664
+ | Exclude<R, EventLogServerUnencrypted | EventLog.Registry>
665
+ | EventLogServerAuthorization
666
+ | RpcServer.Protocol
667
+ | Storage
668
+ | StoreMapping
669
+ > =>
670
+ RpcServer.layer(EventLogRemoteRpcs).pipe(
671
+ Layer.provide(layerRpcHandlers),
672
+ Layer.provide(layer),
673
+ Layer.provide(layerServer)
674
+ )
675
+
676
+ /**
677
+ * @since 4.0.0
678
+ * @category layers
679
+ */
680
+ export const layerNoRpcServer = <Groups extends EventGroup.Any, E, R>(
681
+ _schema: EventLog.EventLogSchema<Groups>,
682
+ layer: Layer.Layer<EventGroup.ToService<Groups>, E, R>
683
+ ): Layer.Layer<
684
+ Rpc.ToHandler<RpcGroup.Rpcs<typeof EventLogRemoteRpcs>> | EventLogAuthentication,
685
+ E,
686
+ | Exclude<R, EventLogServerUnencrypted | EventLog.Registry>
687
+ | EventLogServerAuthorization
688
+ | Storage
689
+ | StoreMapping
690
+ > =>
691
+ layerRpcHandlers.pipe(
692
+ Layer.merge(layer),
693
+ Layer.provide(layerServer)
694
+ )
695
+
696
+ const makeServerHandler = Effect.gen(function*() {
697
+ const registry = yield* EventLog.Registry
698
+
699
+ return Effect.fnUntraced(
700
+ function*(options: {
701
+ readonly publicKey: string
702
+ readonly storeId: StoreId
703
+ readonly entry: Entry
704
+ readonly conflicts: ReadonlyArray<Entry>
705
+ readonly payload?: unknown
706
+ }): Effect.fn.Return<any, any, EventLog.Identity> {
707
+ const handler = registry.handlers.get(options.entry.event)
708
+ if (handler === undefined) {
709
+ return yield* Effect.logDebug(`Event handler not found for: "${options.entry.event}"`)
710
+ }
711
+
712
+ const decodePayload = Schema.decodeUnknownEffect(handler.event.payloadMsgPack)
713
+ const decodedConflicts: Array<{ readonly entry: Entry; readonly payload: unknown }> = []
714
+
715
+ for (const conflict of options.conflicts) {
716
+ decodedConflicts.push({
717
+ entry: conflict,
718
+ payload: yield* decodePayload(conflict.payload).pipe(
719
+ Effect.updateContext((input) => Context.merge(handler.context, input))
720
+ ) as any
721
+ })
722
+ }
723
+
724
+ const payloadEffect = "payload" in options
725
+ ? Effect.succeed(options.payload)
726
+ : decodePayload(options.entry.payload)
727
+
728
+ return yield* payloadEffect.pipe(
729
+ Effect.mapError((_) =>
730
+ new EventLogServerStoreError({
731
+ reason: "PersistenceFailure",
732
+ publicKey: options.publicKey,
733
+ storeId: options.storeId,
734
+ message: "Failed to decode event"
735
+ })
736
+ ),
737
+ Effect.flatMap((payload) =>
738
+ handler.handler({
739
+ storeId: options.storeId,
740
+ payload,
741
+ entry: options.entry,
742
+ conflicts: decodedConflicts
743
+ })
744
+ ),
745
+ Effect.updateContext((input) => Context.merge(handler.context, input))
746
+ ) as Effect.Effect<any, unknown, EventLog.Identity>
747
+ }
748
+ )
749
+ })