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.
- package/dist/Equal.d.ts.map +1 -1
- package/dist/Equal.js +16 -0
- package/dist/Equal.js.map +1 -1
- package/dist/Hash.js +1 -1
- package/dist/Hash.js.map +1 -1
- package/dist/Semaphore.d.ts +1 -1
- package/dist/Semaphore.d.ts.map +1 -1
- package/dist/Semaphore.js +1 -3
- package/dist/Semaphore.js.map +1 -1
- package/dist/unstable/ai/McpServer.d.ts.map +1 -1
- package/dist/unstable/ai/McpServer.js +24 -21
- package/dist/unstable/ai/McpServer.js.map +1 -1
- package/dist/unstable/eventlog/Event.d.ts +0 -6
- package/dist/unstable/eventlog/Event.d.ts.map +1 -1
- package/dist/unstable/eventlog/Event.js +0 -5
- package/dist/unstable/eventlog/Event.js.map +1 -1
- package/dist/unstable/eventlog/EventGroup.d.ts +0 -2
- package/dist/unstable/eventlog/EventGroup.d.ts.map +1 -1
- package/dist/unstable/eventlog/EventGroup.js +0 -2
- package/dist/unstable/eventlog/EventGroup.js.map +1 -1
- package/dist/unstable/eventlog/EventJournal.d.ts +22 -5
- package/dist/unstable/eventlog/EventJournal.d.ts.map +1 -1
- package/dist/unstable/eventlog/EventJournal.js +126 -67
- package/dist/unstable/eventlog/EventJournal.js.map +1 -1
- package/dist/unstable/eventlog/EventLog.d.ts +88 -34
- package/dist/unstable/eventlog/EventLog.d.ts.map +1 -1
- package/dist/unstable/eventlog/EventLog.js +215 -141
- package/dist/unstable/eventlog/EventLog.js.map +1 -1
- package/dist/unstable/eventlog/EventLogEncryption.d.ts +9 -7
- package/dist/unstable/eventlog/EventLogEncryption.d.ts.map +1 -1
- package/dist/unstable/eventlog/EventLogEncryption.js +13 -15
- package/dist/unstable/eventlog/EventLogEncryption.js.map +1 -1
- package/dist/unstable/eventlog/EventLogMessage.d.ts +228 -0
- package/dist/unstable/eventlog/EventLogMessage.d.ts.map +1 -0
- package/dist/unstable/eventlog/EventLogMessage.js +214 -0
- package/dist/unstable/eventlog/EventLogMessage.js.map +1 -0
- package/dist/unstable/eventlog/EventLogRemote.d.ts +109 -194
- package/dist/unstable/eventlog/EventLogRemote.d.ts.map +1 -1
- package/dist/unstable/eventlog/EventLogRemote.js +165 -320
- package/dist/unstable/eventlog/EventLogRemote.js.map +1 -1
- package/dist/unstable/eventlog/EventLogServer.d.ts +25 -47
- package/dist/unstable/eventlog/EventLogServer.d.ts.map +1 -1
- package/dist/unstable/eventlog/EventLogServer.js +127 -198
- package/dist/unstable/eventlog/EventLogServer.js.map +1 -1
- package/dist/unstable/eventlog/EventLogServerEncrypted.d.ts +60 -0
- package/dist/unstable/eventlog/EventLogServerEncrypted.d.ts.map +1 -0
- package/dist/unstable/eventlog/EventLogServerEncrypted.js +166 -0
- package/dist/unstable/eventlog/EventLogServerEncrypted.js.map +1 -0
- package/dist/unstable/eventlog/EventLogServerUnencrypted.d.ts +183 -0
- package/dist/unstable/eventlog/EventLogServerUnencrypted.d.ts.map +1 -0
- package/dist/unstable/eventlog/EventLogServerUnencrypted.js +461 -0
- package/dist/unstable/eventlog/EventLogServerUnencrypted.js.map +1 -0
- package/dist/unstable/eventlog/EventLogSessionAuth.d.ts +117 -0
- package/dist/unstable/eventlog/EventLogSessionAuth.d.ts.map +1 -0
- package/dist/unstable/eventlog/EventLogSessionAuth.js +284 -0
- package/dist/unstable/eventlog/EventLogSessionAuth.js.map +1 -0
- package/dist/unstable/eventlog/{SqlEventLogJournal.d.ts → SqlEventJournal.d.ts} +2 -2
- package/dist/unstable/eventlog/SqlEventJournal.d.ts.map +1 -0
- package/dist/unstable/eventlog/{SqlEventLogJournal.js → SqlEventJournal.js} +20 -14
- package/dist/unstable/eventlog/SqlEventJournal.js.map +1 -0
- package/dist/unstable/eventlog/{SqlEventLogServer.d.ts → SqlEventLogServerEncrypted.d.ts} +5 -5
- package/dist/unstable/eventlog/SqlEventLogServerEncrypted.d.ts.map +1 -0
- package/dist/unstable/eventlog/{SqlEventLogServer.js → SqlEventLogServerEncrypted.js} +65 -24
- package/dist/unstable/eventlog/SqlEventLogServerEncrypted.js.map +1 -0
- package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.d.ts +25 -0
- package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.d.ts.map +1 -0
- package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js +354 -0
- package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js.map +1 -0
- package/dist/unstable/eventlog/index.d.ts +22 -2
- package/dist/unstable/eventlog/index.d.ts.map +1 -1
- package/dist/unstable/eventlog/index.js +22 -2
- package/dist/unstable/eventlog/index.js.map +1 -1
- package/dist/unstable/eventlog/internal/identityRootSecretDerivation.d.ts +2 -0
- package/dist/unstable/eventlog/internal/identityRootSecretDerivation.d.ts.map +1 -0
- package/dist/unstable/eventlog/internal/identityRootSecretDerivation.js +89 -0
- package/dist/unstable/eventlog/internal/identityRootSecretDerivation.js.map +1 -0
- package/dist/unstable/reactivity/AtomHttpApi.d.ts +1 -2
- package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomHttpApi.js +2 -2
- package/dist/unstable/reactivity/AtomHttpApi.js.map +1 -1
- package/dist/unstable/reactivity/AtomRpc.d.ts +1 -2
- package/dist/unstable/reactivity/AtomRpc.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomRpc.js +3 -3
- package/dist/unstable/reactivity/AtomRpc.js.map +1 -1
- package/dist/unstable/rpc/Rpc.d.ts +25 -4
- package/dist/unstable/rpc/Rpc.d.ts.map +1 -1
- package/dist/unstable/rpc/Rpc.js +26 -0
- package/dist/unstable/rpc/Rpc.js.map +1 -1
- package/dist/unstable/rpc/RpcClient.d.ts +3 -13
- package/dist/unstable/rpc/RpcClient.d.ts.map +1 -1
- package/dist/unstable/rpc/RpcClient.js +47 -23
- package/dist/unstable/rpc/RpcClient.js.map +1 -1
- package/dist/unstable/rpc/RpcGroup.d.ts +1 -1
- package/dist/unstable/rpc/RpcGroup.d.ts.map +1 -1
- package/dist/unstable/rpc/RpcMiddleware.d.ts +2 -2
- package/dist/unstable/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
- package/dist/unstable/rpc/RpcServer.js +3 -2
- package/dist/unstable/rpc/RpcServer.js.map +1 -1
- package/dist/unstable/rpc/Utils.d.ts +6 -0
- package/dist/unstable/rpc/Utils.d.ts.map +1 -1
- package/dist/unstable/rpc/Utils.js +44 -0
- package/dist/unstable/rpc/Utils.js.map +1 -1
- package/dist/unstable/schema/Model.d.ts +2 -2
- package/dist/unstable/schema/Model.d.ts.map +1 -1
- package/dist/unstable/schema/Model.js +2 -4
- package/dist/unstable/schema/Model.js.map +1 -1
- package/dist/unstable/schema/VariantSchema.d.ts +1 -1
- package/dist/unstable/schema/VariantSchema.d.ts.map +1 -1
- package/dist/unstable/schema/VariantSchema.js +1 -12
- package/dist/unstable/schema/VariantSchema.js.map +1 -1
- package/dist/unstable/workers/Transferable.d.ts +1 -1
- package/dist/unstable/workers/Transferable.d.ts.map +1 -1
- package/dist/unstable/workers/Transferable.js +1 -1
- package/dist/unstable/workers/Transferable.js.map +1 -1
- package/package.json +1 -1
- package/src/Equal.ts +17 -0
- package/src/Hash.ts +2 -2
- package/src/Semaphore.ts +2 -4
- package/src/unstable/ai/McpServer.ts +24 -22
- package/src/unstable/eventlog/Event.ts +0 -8
- package/src/unstable/eventlog/EventGroup.ts +0 -4
- package/src/unstable/eventlog/EventJournal.ts +144 -76
- package/src/unstable/eventlog/EventLog.ts +342 -221
- package/src/unstable/eventlog/EventLogEncryption.ts +16 -30
- package/src/unstable/eventlog/EventLogMessage.ts +277 -0
- package/src/unstable/eventlog/EventLogRemote.ts +261 -408
- package/src/unstable/eventlog/EventLogServer.ts +182 -274
- package/src/unstable/eventlog/EventLogServerEncrypted.ts +206 -0
- package/src/unstable/eventlog/EventLogServerUnencrypted.ts +749 -0
- package/src/unstable/eventlog/EventLogSessionAuth.ts +437 -0
- package/src/unstable/eventlog/{SqlEventLogJournal.ts → SqlEventJournal.ts} +26 -18
- package/src/unstable/eventlog/{SqlEventLogServer.ts → SqlEventLogServerEncrypted.ts} +102 -40
- package/src/unstable/eventlog/SqlEventLogServerUnencrypted.ts +500 -0
- package/src/unstable/eventlog/index.ts +27 -2
- package/src/unstable/eventlog/internal/identityRootSecretDerivation.ts +153 -0
- package/src/unstable/reactivity/AtomHttpApi.ts +23 -8
- package/src/unstable/reactivity/AtomRpc.ts +16 -5
- package/src/unstable/rpc/Rpc.ts +42 -4
- package/src/unstable/rpc/RpcClient.ts +59 -24
- package/src/unstable/rpc/RpcGroup.ts +1 -1
- package/src/unstable/rpc/RpcMiddleware.ts +2 -2
- package/src/unstable/rpc/RpcServer.ts +5 -3
- package/src/unstable/rpc/Utils.ts +59 -0
- package/src/unstable/schema/Model.ts +4 -6
- package/src/unstable/schema/VariantSchema.ts +4 -17
- package/src/unstable/workers/Transferable.ts +9 -11
- package/dist/unstable/eventlog/SqlEventLogJournal.d.ts.map +0 -1
- package/dist/unstable/eventlog/SqlEventLogJournal.js.map +0 -1
- package/dist/unstable/eventlog/SqlEventLogServer.d.ts.map +0 -1
- 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 - 1
|
|
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
|
+
})
|