effect-genserver 0.1.0
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/README.md +3 -0
- package/dist/AtomGenServer.d.ts +26 -0
- package/dist/AtomGenServer.d.ts.map +1 -0
- package/dist/AtomGenServer.js +28 -0
- package/dist/AtomGenServer.js.map +1 -0
- package/dist/ClusterGenServer.d.ts +39 -0
- package/dist/ClusterGenServer.d.ts.map +1 -0
- package/dist/ClusterGenServer.js +31 -0
- package/dist/ClusterGenServer.js.map +1 -0
- package/dist/GenServer.d.ts +139 -0
- package/dist/GenServer.d.ts.map +1 -0
- package/dist/GenServer.js +181 -0
- package/dist/GenServer.js.map +1 -0
- package/dist/RpcGenServer.d.ts +29 -0
- package/dist/RpcGenServer.d.ts.map +1 -0
- package/dist/RpcGenServer.js +32 -0
- package/dist/RpcGenServer.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
- package/src/AtomGenServer.ts +112 -0
- package/src/ClusterGenServer.ts +96 -0
- package/src/GenServer.ts +493 -0
- package/src/RpcGenServer.ts +65 -0
- package/src/index.ts +19 -0
package/src/GenServer.ts
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as Rpc from "effect/unstable/rpc/Rpc"
|
|
5
|
+
import * as RpcGroup from "effect/unstable/rpc/RpcGroup"
|
|
6
|
+
import * as Effect from "effect/Effect"
|
|
7
|
+
import * as Schema from "effect/Schema"
|
|
8
|
+
import * as Layer from "effect/Layer"
|
|
9
|
+
import type * as Scope from "effect/Scope"
|
|
10
|
+
import * as Context from "effect/Context"
|
|
11
|
+
import type * as Option from "effect/Option"
|
|
12
|
+
import * as Persistence from "effect/unstable/persistence/Persistence"
|
|
13
|
+
import * as PubSub from "effect/PubSub"
|
|
14
|
+
import * as MutableRef from "effect/MutableRef"
|
|
15
|
+
import * as Stream from "effect/Stream"
|
|
16
|
+
import * as Semaphore from "effect/Semaphore"
|
|
17
|
+
import { flow, identity, pipe } from "effect/Function"
|
|
18
|
+
import type * as RpcSchema from "effect/unstable/rpc/RpcSchema"
|
|
19
|
+
import * as Latch from "effect/Latch"
|
|
20
|
+
import type { Types } from "effect"
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @since 1.0.0
|
|
24
|
+
* @category Models
|
|
25
|
+
*/
|
|
26
|
+
export interface GenServer<State extends Schema.Top, Rpcs extends Rpc.Any> {
|
|
27
|
+
readonly stateSchema: State
|
|
28
|
+
readonly protocol: RpcGroup.RpcGroup<Rpcs>
|
|
29
|
+
|
|
30
|
+
readonly sender: Effect.Effect<
|
|
31
|
+
<
|
|
32
|
+
const Tag extends Rpcs["_tag"],
|
|
33
|
+
Rpc = Rpc.ExtractTag<Rpcs, Tag>,
|
|
34
|
+
Payload = Rpc.PayloadConstructor<Rpc>,
|
|
35
|
+
>(
|
|
36
|
+
tag: Tag,
|
|
37
|
+
...args: Types.EqualsWith<
|
|
38
|
+
Payload,
|
|
39
|
+
void,
|
|
40
|
+
[payload?: Payload],
|
|
41
|
+
[payload: Payload]
|
|
42
|
+
>
|
|
43
|
+
) => Effect.Effect<void>,
|
|
44
|
+
never,
|
|
45
|
+
SendDiscard
|
|
46
|
+
>
|
|
47
|
+
|
|
48
|
+
of<A extends Handlers<State, Rpcs>>(
|
|
49
|
+
initialState: State["Type"],
|
|
50
|
+
handlers: A,
|
|
51
|
+
): readonly [state: State["Type"], handlers: A]
|
|
52
|
+
|
|
53
|
+
toLayer<A extends Handlers<State, Rpcs>, E, R>(
|
|
54
|
+
build: Effect.Effect<readonly [state: State["Type"], handlers: A], E, R>,
|
|
55
|
+
): Layer.Layer<
|
|
56
|
+
ToHandler<Rpcs> | InitialState,
|
|
57
|
+
E,
|
|
58
|
+
Exclude<R, Scope.Scope> | HandlerServices<A>
|
|
59
|
+
>
|
|
60
|
+
|
|
61
|
+
toLayerPersisted<A extends Handlers<State, Rpcs>, E, R>(
|
|
62
|
+
build: (
|
|
63
|
+
getPersistedState: (
|
|
64
|
+
id: string,
|
|
65
|
+
) => Effect.Effect<
|
|
66
|
+
Option.Option<State["Type"]>,
|
|
67
|
+
Persistence.PersistenceError,
|
|
68
|
+
State["DecodingServices"]
|
|
69
|
+
>,
|
|
70
|
+
) => Effect.Effect<readonly [state: State["Type"], handlers: A], E, R>,
|
|
71
|
+
): Layer.Layer<
|
|
72
|
+
ToHandler<Rpcs> | InitialState,
|
|
73
|
+
E,
|
|
74
|
+
| Exclude<R, Scope.Scope>
|
|
75
|
+
| HandlerServices<A>
|
|
76
|
+
| Persistence.BackingPersistence
|
|
77
|
+
| State["EncodingServices"]
|
|
78
|
+
>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @since 1.0.0
|
|
83
|
+
* @category Initial state
|
|
84
|
+
*/
|
|
85
|
+
export interface InitialState {
|
|
86
|
+
readonly _: unique symbol
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @since 1.0.0
|
|
91
|
+
* @category Handlers
|
|
92
|
+
*/
|
|
93
|
+
export interface Handler<Tag extends string> {
|
|
94
|
+
readonly _: unique symbol
|
|
95
|
+
readonly _tag: Tag
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @since 1.0.0
|
|
100
|
+
* @category Initial state
|
|
101
|
+
*/
|
|
102
|
+
export class SendDiscard extends Context.Service<
|
|
103
|
+
SendDiscard,
|
|
104
|
+
(tag: string, payload: any) => Effect.Effect<void>
|
|
105
|
+
>()("@effectfultech/clanka-utils/GenServer/SendDiscard") {}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @since 1.0.0
|
|
109
|
+
* @category Handlers
|
|
110
|
+
*/
|
|
111
|
+
export type ToHandler<Rpc extends Rpc.Any> = Rpc extends Rpc.Any
|
|
112
|
+
? Handler<Rpc["_tag"]>
|
|
113
|
+
: never
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @since 1.0.0
|
|
117
|
+
* @category Handlers
|
|
118
|
+
*/
|
|
119
|
+
export type Handlers<State extends Schema.Top, Rpcs extends Rpc.Any> = {
|
|
120
|
+
[R in Rpcs as R["_tag"]]: HandlerFn<State, R, any>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @since 1.0.0
|
|
125
|
+
* @category Handlers
|
|
126
|
+
*/
|
|
127
|
+
export type HandlerFn<
|
|
128
|
+
State extends Schema.Top,
|
|
129
|
+
Rpc extends Rpc.Any,
|
|
130
|
+
R,
|
|
131
|
+
> = (options: {
|
|
132
|
+
readonly state: State["Type"]
|
|
133
|
+
readonly payload: Rpc.Payload<Rpc>
|
|
134
|
+
readonly context: Context.Context<never>
|
|
135
|
+
}) => Rpc.WrapperOr<
|
|
136
|
+
Effect.Effect<
|
|
137
|
+
readonly [state: State["Type"], result: Rpc.Success<Rpc>],
|
|
138
|
+
Rpc.Error<Rpc>,
|
|
139
|
+
R
|
|
140
|
+
>
|
|
141
|
+
>
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @since 1.0.0
|
|
145
|
+
* @category Handlers
|
|
146
|
+
*/
|
|
147
|
+
export type HandlerServices<Handlers extends Record<string, any>> =
|
|
148
|
+
keyof Handlers extends infer K
|
|
149
|
+
? K extends keyof Handlers
|
|
150
|
+
? ReturnType<Handlers[K]> extends Effect.Effect<
|
|
151
|
+
infer _A,
|
|
152
|
+
infer _E,
|
|
153
|
+
infer _R
|
|
154
|
+
>
|
|
155
|
+
? _R
|
|
156
|
+
: never
|
|
157
|
+
: never
|
|
158
|
+
: never
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @since 1.0.0
|
|
162
|
+
* @category Constructors
|
|
163
|
+
*/
|
|
164
|
+
export const make = <
|
|
165
|
+
State extends Schema.Top,
|
|
166
|
+
const Rpcs extends ReadonlyArray<Rpc.Any>,
|
|
167
|
+
>(options: {
|
|
168
|
+
readonly state: State
|
|
169
|
+
readonly protocol: Rpcs
|
|
170
|
+
}): GenServer<State, Rpcs[number]> =>
|
|
171
|
+
fromRpcGroup({
|
|
172
|
+
state: options.state,
|
|
173
|
+
group: RpcGroup.make(...options.protocol),
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @since 1.0.0
|
|
178
|
+
* @category Constructors
|
|
179
|
+
*/
|
|
180
|
+
export const fromRpcGroup = <
|
|
181
|
+
State extends Schema.Top,
|
|
182
|
+
Rpcs extends Rpc.Any,
|
|
183
|
+
>(options: {
|
|
184
|
+
readonly state: State
|
|
185
|
+
readonly group: RpcGroup.RpcGroup<Rpcs>
|
|
186
|
+
}): GenServer<State, Rpcs> => {
|
|
187
|
+
const self = Object.create(Proto)
|
|
188
|
+
self.stateSchema = options.state
|
|
189
|
+
self.protocol = options.group
|
|
190
|
+
return self
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const Proto: Omit<GenServer<any, any>, "stateSchema" | "protocol"> = {
|
|
194
|
+
get sender() {
|
|
195
|
+
return SendDiscard.useSync((send) => send as any)
|
|
196
|
+
},
|
|
197
|
+
of(initialState: any, handlers: any) {
|
|
198
|
+
return [initialState, handlers]
|
|
199
|
+
},
|
|
200
|
+
toLayer(
|
|
201
|
+
this: GenServer<any, any>,
|
|
202
|
+
build: Effect.Effect<
|
|
203
|
+
readonly [initialState: any, Handlers<any, any>],
|
|
204
|
+
any,
|
|
205
|
+
any
|
|
206
|
+
>,
|
|
207
|
+
) {
|
|
208
|
+
return Layer.fresh(
|
|
209
|
+
Layer.effectContext(
|
|
210
|
+
Effect.gen(function* () {
|
|
211
|
+
const services = yield* Effect.context()
|
|
212
|
+
const [initialState, handlers] = yield* build
|
|
213
|
+
const handlerMap = new Map<string, any>()
|
|
214
|
+
handlerMap.set(initialStateKey, initialState)
|
|
215
|
+
for (const [tag, handler] of Object.entries(handlers)) {
|
|
216
|
+
handlerMap.set(handlerKey(tag), (options: any) =>
|
|
217
|
+
Rpc.wrapMap(handler(options), Effect.provideContext(services)),
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
return Context.makeUnsafe(handlerMap)
|
|
221
|
+
}),
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
},
|
|
225
|
+
toLayerPersisted(
|
|
226
|
+
this: GenServer<any, any>,
|
|
227
|
+
build: (
|
|
228
|
+
load: (
|
|
229
|
+
id: string,
|
|
230
|
+
) => Effect.Effect<Option.Option<any>, Persistence.PersistenceError, any>,
|
|
231
|
+
) => Effect.Effect<
|
|
232
|
+
readonly [initialState: any, Handlers<any, any>],
|
|
233
|
+
any,
|
|
234
|
+
any
|
|
235
|
+
>,
|
|
236
|
+
options?: {
|
|
237
|
+
readonly storeId?: string | undefined
|
|
238
|
+
},
|
|
239
|
+
) {
|
|
240
|
+
const storeId = options?.storeId ?? "machine"
|
|
241
|
+
const stateFromJson = Schema.toCodecJson(this.stateSchema)
|
|
242
|
+
const decode = Schema.decodeUnknownEffect(stateFromJson)
|
|
243
|
+
const encode = Schema.encodeUnknownEffect(stateFromJson)
|
|
244
|
+
return Layer.fresh(
|
|
245
|
+
Layer.effectContext(
|
|
246
|
+
Effect.gen(function* () {
|
|
247
|
+
const services = yield* Effect.context()
|
|
248
|
+
const persistence = yield* Persistence.BackingPersistence
|
|
249
|
+
const store = yield* persistence.make(storeId)
|
|
250
|
+
let currentId = "default"
|
|
251
|
+
const load = (id: string) =>
|
|
252
|
+
Effect.suspend(() => {
|
|
253
|
+
currentId = id
|
|
254
|
+
return store.get(id)
|
|
255
|
+
}).pipe(
|
|
256
|
+
Effect.flatMap((a) =>
|
|
257
|
+
a ? Effect.asSome(Effect.orDie(decode(a))) : Effect.succeedNone,
|
|
258
|
+
),
|
|
259
|
+
)
|
|
260
|
+
const [initialState, handlers] = yield* build(load)
|
|
261
|
+
const handlerMap = new Map<string, any>([
|
|
262
|
+
initialStateKey,
|
|
263
|
+
initialState,
|
|
264
|
+
])
|
|
265
|
+
for (const [tag, handler] of Object.entries(handlers)) {
|
|
266
|
+
handlerMap.set(handlerKey(tag), (options: any) =>
|
|
267
|
+
Rpc.wrapMap(
|
|
268
|
+
handler(options),
|
|
269
|
+
flow(
|
|
270
|
+
Effect.tap(([state]) =>
|
|
271
|
+
Effect.flatMap(encode(state), (a) =>
|
|
272
|
+
store.set(currentId, a as any, undefined),
|
|
273
|
+
),
|
|
274
|
+
),
|
|
275
|
+
Effect.provideContext(services),
|
|
276
|
+
),
|
|
277
|
+
),
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
return Context.makeUnsafe(handlerMap)
|
|
281
|
+
}),
|
|
282
|
+
),
|
|
283
|
+
)
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const initialStateKey = `effect-genserver/GenServer/InitialState`
|
|
288
|
+
const handlerKey = (tag: string) => `effect-genserver/GenServer/Handler/${tag}`
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @since 1.0.0
|
|
292
|
+
* @category Handlers
|
|
293
|
+
*/
|
|
294
|
+
export const makeHandlers = Effect.fnUntraced(function* <
|
|
295
|
+
State extends Schema.Top,
|
|
296
|
+
Rpcs extends Rpc.Any,
|
|
297
|
+
E,
|
|
298
|
+
R,
|
|
299
|
+
>(
|
|
300
|
+
schema: GenServer<State, Rpcs>,
|
|
301
|
+
layer: Layer.Layer<ToHandler<Rpcs> | InitialState, E, R>,
|
|
302
|
+
): Effect.fn.Return<
|
|
303
|
+
{
|
|
304
|
+
readonly state: MutableRef.MutableRef<State["Type"]>
|
|
305
|
+
readonly pubsub: PubSub.PubSub<State["Type"]>
|
|
306
|
+
readonly handlers: ReadonlyMap<
|
|
307
|
+
string,
|
|
308
|
+
{
|
|
309
|
+
readonly rpc: Rpc.AnyWithProps
|
|
310
|
+
readonly handler: (options: {
|
|
311
|
+
readonly payload: any
|
|
312
|
+
readonly context: Context.Context<never>
|
|
313
|
+
}) => Rpc.WrapperOr<Effect.Effect<any, any>>
|
|
314
|
+
}
|
|
315
|
+
>
|
|
316
|
+
},
|
|
317
|
+
E,
|
|
318
|
+
Exclude<R, SendDiscard> | Scope.Scope
|
|
319
|
+
> {
|
|
320
|
+
const handlers = new Map<
|
|
321
|
+
string,
|
|
322
|
+
{
|
|
323
|
+
readonly rpc: Rpc.AnyWithProps
|
|
324
|
+
readonly handler: (options: {
|
|
325
|
+
readonly payload: any
|
|
326
|
+
readonly context: Context.Context<never>
|
|
327
|
+
}) => Rpc.WrapperOr<Effect.Effect<any, any>>
|
|
328
|
+
}
|
|
329
|
+
>()
|
|
330
|
+
const scope = yield* Effect.scope
|
|
331
|
+
const startLatch = Latch.makeUnsafe(false)
|
|
332
|
+
let started = false
|
|
333
|
+
const sendSemaphore = Semaphore.makeUnsafe(1)
|
|
334
|
+
const stateChanges = RpcStateChanges(schema)
|
|
335
|
+
|
|
336
|
+
const sendDiscard = (tag: string, payload: any) => {
|
|
337
|
+
const eff = started
|
|
338
|
+
? sendDiscardImpl(tag, payload)
|
|
339
|
+
: startLatch.whenOpen(sendDiscardImpl(tag, payload))
|
|
340
|
+
return eff.pipe(Effect.forkIn(scope), Effect.asVoid)
|
|
341
|
+
}
|
|
342
|
+
const sendDiscardImpl = (tag: string, payload: any) =>
|
|
343
|
+
Effect.suspend(() => {
|
|
344
|
+
const entry = handlers.get(tag)
|
|
345
|
+
if (!entry) {
|
|
346
|
+
return Effect.void
|
|
347
|
+
}
|
|
348
|
+
const result = entry.handler({
|
|
349
|
+
payload,
|
|
350
|
+
context: Context.empty(),
|
|
351
|
+
})
|
|
352
|
+
return Rpc.isWrapper(result) ? result.value : result
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
const services = yield* Layer.build(layer).pipe(
|
|
356
|
+
Effect.provideService(SendDiscard, sendDiscard),
|
|
357
|
+
)
|
|
358
|
+
const state = MutableRef.make(
|
|
359
|
+
services.mapUnsafe.get(initialStateKey) as State["Type"],
|
|
360
|
+
)
|
|
361
|
+
const pubsub = yield* PubSub.unbounded<State["Type"]>({
|
|
362
|
+
replay: 1,
|
|
363
|
+
})
|
|
364
|
+
PubSub.publishUnsafe(pubsub, state.current)
|
|
365
|
+
|
|
366
|
+
handlers.set(stateChanges._tag, {
|
|
367
|
+
rpc: stateChanges,
|
|
368
|
+
handler: (_) => Stream.fromPubSub(pubsub) as any,
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
for (const rpc of schema.protocol.requests.values()) {
|
|
372
|
+
const handler = services.mapUnsafe.get(handlerKey(rpc._tag)) as HandlerFn<
|
|
373
|
+
State,
|
|
374
|
+
any,
|
|
375
|
+
never
|
|
376
|
+
>
|
|
377
|
+
handlers.set(rpc._tag, {
|
|
378
|
+
rpc: rpc as any,
|
|
379
|
+
handler: (options) =>
|
|
380
|
+
Rpc.wrapMap(
|
|
381
|
+
handler({
|
|
382
|
+
state: state.current,
|
|
383
|
+
payload: options.payload,
|
|
384
|
+
context: options.context,
|
|
385
|
+
}),
|
|
386
|
+
(effect) =>
|
|
387
|
+
pipe(
|
|
388
|
+
effect,
|
|
389
|
+
Effect.map(([nextState, result]) => {
|
|
390
|
+
if (nextState !== state.current) {
|
|
391
|
+
MutableRef.set(state, nextState)
|
|
392
|
+
PubSub.publishUnsafe(pubsub, nextState)
|
|
393
|
+
}
|
|
394
|
+
return result
|
|
395
|
+
}),
|
|
396
|
+
sendSemaphore.withPermits(1),
|
|
397
|
+
),
|
|
398
|
+
),
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
startLatch.openUnsafe()
|
|
403
|
+
started = true
|
|
404
|
+
|
|
405
|
+
return { handlers, state, pubsub }
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* @since 1.0.0
|
|
410
|
+
* @category RpcServer
|
|
411
|
+
*/
|
|
412
|
+
export const RpcStateChanges = <State extends Schema.Top, Rpcs extends Rpc.Any>(
|
|
413
|
+
server: GenServer<State, Rpcs>,
|
|
414
|
+
): Rpc.Rpc<
|
|
415
|
+
"GenServerChanges",
|
|
416
|
+
Schema.Void,
|
|
417
|
+
RpcSchema.Stream<State, Schema.Never>
|
|
418
|
+
> =>
|
|
419
|
+
Rpc.make("GenServerChanges", {
|
|
420
|
+
success: server.stateSchema,
|
|
421
|
+
stream: true,
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* @since 1.0.0
|
|
426
|
+
* @category Actor
|
|
427
|
+
*/
|
|
428
|
+
export interface Actor<State extends Schema.Top, Rpcs extends Rpc.Any> {
|
|
429
|
+
readonly changes: Stream.Stream<State["Type"]>
|
|
430
|
+
readonly state: MutableRef.MutableRef<State["Type"]>
|
|
431
|
+
send<
|
|
432
|
+
Tag extends Rpcs["_tag"],
|
|
433
|
+
Rpc = Rpc.ExtractTag<Rpcs, Tag>,
|
|
434
|
+
Payload = Rpc.PayloadConstructor<Rpc>,
|
|
435
|
+
>(
|
|
436
|
+
tag: Tag,
|
|
437
|
+
...args: Types.EqualsWith<
|
|
438
|
+
Payload,
|
|
439
|
+
void,
|
|
440
|
+
[payload?: Payload],
|
|
441
|
+
[payload: Payload]
|
|
442
|
+
>
|
|
443
|
+
): Effect.Effect<Rpc.Success<Rpc>, Rpc.Error<Rpc>>
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* @since 1.0.0
|
|
448
|
+
* @category Actor
|
|
449
|
+
*/
|
|
450
|
+
export const makeActor = Effect.fnUntraced(function* <
|
|
451
|
+
State extends Schema.Top,
|
|
452
|
+
Rpcs extends Rpc.Any,
|
|
453
|
+
E,
|
|
454
|
+
R,
|
|
455
|
+
>(
|
|
456
|
+
schema: GenServer<State, Rpcs>,
|
|
457
|
+
layer: Layer.Layer<ToHandler<Rpcs> | InitialState, E, R>,
|
|
458
|
+
options?: {
|
|
459
|
+
readonly requestContext?: Context.Context<never> | undefined
|
|
460
|
+
},
|
|
461
|
+
): Effect.fn.Return<
|
|
462
|
+
Actor<State, Rpcs>,
|
|
463
|
+
E,
|
|
464
|
+
Exclude<R, SendDiscard> | Scope.Scope
|
|
465
|
+
> {
|
|
466
|
+
const requestContext = options?.requestContext ?? Context.empty()
|
|
467
|
+
|
|
468
|
+
const { handlers, state, pubsub } = yield* makeHandlers(schema, layer)
|
|
469
|
+
|
|
470
|
+
const send = <Tag extends Rpcs["_tag"], Rpc = Rpc.ExtractTag<Rpcs, Tag>>(
|
|
471
|
+
tag: Tag,
|
|
472
|
+
payload_?: Rpc.PayloadConstructor<Rpc>,
|
|
473
|
+
) => {
|
|
474
|
+
const entry = handlers.get(tag)
|
|
475
|
+
if (!entry) {
|
|
476
|
+
return Effect.die(`Unknown tag: ${tag}`)
|
|
477
|
+
}
|
|
478
|
+
const effectOrWrap = entry.handler({
|
|
479
|
+
payload:
|
|
480
|
+
payload_ !== undefined
|
|
481
|
+
? entry.rpc.payloadSchema.make(payload_)
|
|
482
|
+
: undefined,
|
|
483
|
+
context: requestContext,
|
|
484
|
+
})
|
|
485
|
+
return Rpc.isWrapper(effectOrWrap) ? effectOrWrap.value : effectOrWrap
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return identity<Actor<State, Rpcs>>({
|
|
489
|
+
changes: Stream.fromPubSub(pubsub),
|
|
490
|
+
state,
|
|
491
|
+
send: send as any,
|
|
492
|
+
})
|
|
493
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import type * as Rpc from "effect/unstable/rpc/Rpc"
|
|
5
|
+
import * as Effect from "effect/Effect"
|
|
6
|
+
import type * as Schema from "effect/Schema"
|
|
7
|
+
import * as Layer from "effect/Layer"
|
|
8
|
+
import * as Context from "effect/Context"
|
|
9
|
+
import type * as RpcMessage from "effect/unstable/rpc/RpcMessage"
|
|
10
|
+
import type * as Headers from "effect/unstable/http/Headers"
|
|
11
|
+
import * as GenServer from "./GenServer.ts"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @since 1.0.0
|
|
15
|
+
* @category RpcServer
|
|
16
|
+
*/
|
|
17
|
+
export const toRpcHandlers = <
|
|
18
|
+
State extends Schema.Top,
|
|
19
|
+
Rpcs extends Rpc.Any,
|
|
20
|
+
E,
|
|
21
|
+
R,
|
|
22
|
+
>(
|
|
23
|
+
schema: GenServer.GenServer<State, Rpcs>,
|
|
24
|
+
layer: Layer.Layer<GenServer.ToHandler<Rpcs> | GenServer.InitialState, E, R>,
|
|
25
|
+
): Layer.Layer<
|
|
26
|
+
Rpc.ToHandler<Rpcs> | Rpc.Handler<"GenServerChanges">,
|
|
27
|
+
E,
|
|
28
|
+
Exclude<R, GenServer.SendDiscard>
|
|
29
|
+
> =>
|
|
30
|
+
Layer.effectContext(
|
|
31
|
+
Effect.gen(function* () {
|
|
32
|
+
const { handlers } = yield* GenServer.makeHandlers(schema, layer)
|
|
33
|
+
const contextMap = new Map<string, Rpc.Handler<string>>()
|
|
34
|
+
const services = yield* Effect.context()
|
|
35
|
+
|
|
36
|
+
for (const { rpc, handler } of handlers.values()) {
|
|
37
|
+
contextMap.set(rpc.key, {
|
|
38
|
+
_: null as any,
|
|
39
|
+
tag: rpc._tag,
|
|
40
|
+
context: services,
|
|
41
|
+
handler: (payload, options) =>
|
|
42
|
+
handler({
|
|
43
|
+
payload,
|
|
44
|
+
context: RpcContext.context(options as any),
|
|
45
|
+
}) as any,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return Context.makeUnsafe(contextMap)
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @since 1.0.0
|
|
55
|
+
* @category RpcServer
|
|
56
|
+
*/
|
|
57
|
+
export class RpcContext extends Context.Service<
|
|
58
|
+
RpcContext,
|
|
59
|
+
{
|
|
60
|
+
readonly client: Rpc.ServerClient
|
|
61
|
+
readonly requestId: RpcMessage.RequestId
|
|
62
|
+
readonly headers: Headers.Headers
|
|
63
|
+
readonly rpc: Rpc.AnyWithProps
|
|
64
|
+
}
|
|
65
|
+
>()("effect-genserver/RpcGenServer/RpcContext") {}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
export * as AtomGenServer from "./AtomGenServer.ts"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @since 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
export * as ClusterGenServer from "./ClusterGenServer.ts"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @since 1.0.0
|
|
13
|
+
*/
|
|
14
|
+
export * as GenServer from "./GenServer.ts"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @since 1.0.0
|
|
18
|
+
*/
|
|
19
|
+
export * as RpcGenServer from "./RpcGenServer.ts"
|