liminal 0.17.13 → 0.17.15

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 (94) hide show
  1. package/Actor.ts +12 -13
  2. package/ActorTransport.ts +6 -4
  3. package/Audition.ts +87 -40
  4. package/CHANGELOG.md +16 -0
  5. package/Client.ts +260 -134
  6. package/ClientDirectory.ts +50 -36
  7. package/ClientHandleEncoders.ts +15 -0
  8. package/Fn.ts +55 -0
  9. package/Method.ts +11 -21
  10. package/Protocol.ts +44 -36
  11. package/Reducer.ts +22 -0
  12. package/Tracing.ts +37 -0
  13. package/browser/BrowserActorNamespace.ts +65 -30
  14. package/dist/Actor.d.ts +1 -1
  15. package/dist/Actor.js +6 -6
  16. package/dist/Actor.js.map +1 -1
  17. package/dist/ActorTransport.d.ts +5 -4
  18. package/dist/Audition.d.ts +16 -9
  19. package/dist/Audition.js +25 -9
  20. package/dist/Audition.js.map +1 -1
  21. package/dist/Client.d.ts +21 -14
  22. package/dist/Client.js +147 -100
  23. package/dist/Client.js.map +1 -1
  24. package/dist/ClientDirectory.d.ts +14 -6
  25. package/dist/ClientDirectory.js +25 -22
  26. package/dist/ClientDirectory.js.map +1 -1
  27. package/dist/ClientHandleEncoders.d.ts +7 -0
  28. package/dist/ClientHandleEncoders.js +2 -0
  29. package/dist/ClientHandleEncoders.js.map +1 -0
  30. package/dist/Fn.d.ts +16 -0
  31. package/dist/Fn.js +2 -0
  32. package/dist/Fn.js.map +1 -0
  33. package/dist/Method.d.ts +9 -14
  34. package/dist/Method.js +0 -1
  35. package/dist/Method.js.map +1 -1
  36. package/dist/Protocol.d.ts +19 -22
  37. package/dist/Protocol.js +20 -15
  38. package/dist/Protocol.js.map +1 -1
  39. package/dist/Reducer.d.ts +11 -0
  40. package/dist/Reducer.js +2 -0
  41. package/dist/Reducer.js.map +1 -0
  42. package/dist/Tracing.d.ts +37 -0
  43. package/dist/Tracing.js +29 -0
  44. package/dist/Tracing.js.map +1 -0
  45. package/dist/browser/BrowserActorNamespace.d.ts +5 -5
  46. package/dist/browser/BrowserActorNamespace.js +41 -20
  47. package/dist/browser/BrowserActorNamespace.js.map +1 -1
  48. package/dist/errors.d.ts +0 -4
  49. package/dist/errors.js.map +1 -1
  50. package/dist/experimental/TaggedTemplateFunction.js +1 -1
  51. package/dist/experimental/TaggedTemplateFunction.js.map +1 -1
  52. package/dist/index.d.ts +3 -3
  53. package/dist/index.js +3 -3
  54. package/dist/index.js.map +1 -1
  55. package/dist/package.json +16 -21
  56. package/dist/tsconfig.tsbuildinfo +1 -1
  57. package/dist/workerd/ActorHandle.d.ts +9 -0
  58. package/dist/workerd/ActorHandle.js +4 -0
  59. package/dist/workerd/ActorHandle.js.map +1 -0
  60. package/dist/workerd/WorkerdActorNamespace.d.ts +18 -18
  61. package/dist/workerd/WorkerdActorNamespace.js +43 -141
  62. package/dist/workerd/WorkerdActorNamespace.js.map +1 -1
  63. package/dist/workerd/WorkerdActorRuntime.d.ts +19 -0
  64. package/dist/workerd/WorkerdActorRuntime.js +204 -0
  65. package/dist/workerd/WorkerdActorRuntime.js.map +1 -0
  66. package/dist/workerd/index.d.ts +2 -0
  67. package/dist/workerd/index.js +2 -0
  68. package/dist/workerd/index.js.map +1 -1
  69. package/errors.ts +0 -6
  70. package/experimental/TaggedTemplateFunction.ts +1 -1
  71. package/index.ts +3 -3
  72. package/package.json +10 -25
  73. package/tsconfig.json +1 -1
  74. package/vitest.config.ts +7 -0
  75. package/workerd/ActorHandle.ts +29 -0
  76. package/workerd/WorkerdActorNamespace.ts +86 -273
  77. package/workerd/WorkerdActorRuntime.ts +422 -0
  78. package/workerd/index.ts +2 -0
  79. package/Accumulator.ts +0 -103
  80. package/F.ts +0 -10
  81. package/_diagnostic.ts +0 -3
  82. package/_util/Mutex.ts +0 -13
  83. package/dist/Accumulator.d.ts +0 -22
  84. package/dist/Accumulator.js +0 -37
  85. package/dist/Accumulator.js.map +0 -1
  86. package/dist/F.d.ts +0 -4
  87. package/dist/F.js +0 -2
  88. package/dist/F.js.map +0 -1
  89. package/dist/_diagnostic.d.ts +0 -4
  90. package/dist/_diagnostic.js +0 -3
  91. package/dist/_diagnostic.js.map +0 -1
  92. package/dist/_util/Mutex.d.ts +0 -7
  93. package/dist/_util/Mutex.js +0 -9
  94. package/dist/_util/Mutex.js.map +0 -1
@@ -0,0 +1,422 @@
1
+ import { DurableObject } from "cloudflare:workers"
2
+ import {
3
+ Layer,
4
+ Effect,
5
+ Scope,
6
+ Schema as S,
7
+ Context,
8
+ ManagedRuntime,
9
+ ConfigProvider,
10
+ Duration,
11
+ flow,
12
+ Option,
13
+ Tracer,
14
+ pipe,
15
+ Exit,
16
+ } from "effect"
17
+ import { DoState } from "effect-workerd"
18
+ import { Clock } from "effect-workerd/platform"
19
+ import { SecWebSocketProtocol } from "effect-workerd/socket_util"
20
+ import { Headers, FetchHttpClient, HttpClient, HttpTraceContext } from "effect/unstable/http"
21
+ import { boundLayer } from "liminal-util/boundLayer"
22
+ import { logCause } from "liminal-util/logCause"
23
+ import * as Spanner from "liminal-util/Spanner"
24
+
25
+ import { type TopFromString, encodeJsonString, decodeJsonString } from "../_util/schema.ts"
26
+ import type { ActorTransport } from "../ActorTransport.ts"
27
+ import * as ClientDirectory from "../ClientDirectory.ts"
28
+ import type { ClientHandle } from "../ClientHandle.ts"
29
+ import type { Handlers, Methods } from "../Method.ts"
30
+ import type { ProtocolDefinition } from "../Protocol.ts"
31
+ import * as Tracing from "../Tracing.ts"
32
+ import { sessionAttributes, SessionId, sessionLink } from "../Tracing.ts"
33
+ import type { ActorNamespace } from "./WorkerdActorNamespace.ts"
34
+
35
+ const span = Spanner.make(import.meta.url)
36
+
37
+ export interface ActorRuntimeDefinition<
38
+ NamespaceSelf,
39
+ NamespaceId extends string,
40
+ Internal extends Methods,
41
+ ActorSelf,
42
+ ActorId extends string,
43
+ Name extends TopFromString,
44
+ AttachmentFields extends S.Struct.Fields,
45
+ ClientSelf,
46
+ ClientId extends string,
47
+ D extends ProtocolDefinition,
48
+ PreludeROut,
49
+ PreludeE,
50
+ RunROut,
51
+ RunE,
52
+ > {
53
+ readonly ""?: this["namespace"]["definition"]["actor"]["definition"]["client"]["protocol"]
54
+
55
+ readonly namespace: ActorNamespace<
56
+ NamespaceSelf,
57
+ NamespaceId,
58
+ Internal,
59
+ ActorSelf,
60
+ ActorId,
61
+ Name,
62
+ AttachmentFields,
63
+ ClientSelf,
64
+ ClientId,
65
+ D
66
+ >
67
+
68
+ readonly prelude: Layer.Layer<
69
+ | PreludeROut
70
+ | NonNullable<this[""]>["F"]["Payload"]["DecodingServices"]
71
+ | NonNullable<this[""]>["F"]["Success"]["EncodingServices"]
72
+ | NonNullable<this[""]>["F"]["Failure"]["EncodingServices"]
73
+ | NonNullable<this[""]>["Event"]["EncodingServices"]
74
+ | S.Struct<AttachmentFields>["DecodingServices"]
75
+ | S.Struct<AttachmentFields>["EncodingServices"]
76
+ | Name["EncodingServices"]
77
+ | Name["DecodingServices"],
78
+ PreludeE,
79
+ HttpClient.HttpClient
80
+ >
81
+
82
+ readonly layer: Layer.Layer<RunROut, RunE, ActorSelf | HttpClient.HttpClient | PreludeROut>
83
+
84
+ readonly external: Handlers<D["external"], ActorSelf | HttpClient.HttpClient | PreludeROut | RunROut | Scope.Scope>
85
+
86
+ readonly internal: Handlers<Internal, ActorSelf | HttpClient.HttpClient | PreludeROut | RunROut | Scope.Scope>
87
+
88
+ readonly hydrate: Effect.Effect<
89
+ S.Struct<D["state"]>["Type"],
90
+ never,
91
+ ActorSelf | HttpClient.HttpClient | PreludeROut | RunROut | Scope.Scope
92
+ >
93
+
94
+ readonly onDisconnect: Effect.Effect<
95
+ void,
96
+ never,
97
+ ActorSelf | HttpClient.HttpClient | PreludeROut | RunROut | Scope.Scope
98
+ >
99
+
100
+ readonly hibernation?: Duration.Input | undefined
101
+ }
102
+
103
+ export const make = <
104
+ NamespaceSelf,
105
+ NamespaceId extends string,
106
+ Internal extends Methods,
107
+ ActorSelf,
108
+ ActorId extends string,
109
+ Name extends TopFromString,
110
+ AttachmentFields extends S.Struct.Fields,
111
+ ClientSelf,
112
+ ClientId extends string,
113
+ D extends ProtocolDefinition,
114
+ PreludeROut,
115
+ PreludeE,
116
+ RunROut,
117
+ RunE,
118
+ >(
119
+ definition: ActorRuntimeDefinition<
120
+ NamespaceSelf,
121
+ NamespaceId,
122
+ Internal,
123
+ ActorSelf,
124
+ ActorId,
125
+ Name,
126
+ AttachmentFields,
127
+ ClientSelf,
128
+ ClientId,
129
+ D,
130
+ PreludeROut,
131
+ PreludeE,
132
+ RunROut,
133
+ RunE
134
+ >,
135
+ ): new (state: DurableObjectState<{}>, env: Cloudflare.Env) => DurableObject => {
136
+ const {
137
+ hibernation,
138
+ prelude,
139
+ external,
140
+ layer,
141
+ hydrate,
142
+ onDisconnect,
143
+ internal,
144
+ namespace: {
145
+ definition: { actor },
146
+ },
147
+ } = definition
148
+ const {
149
+ definition: {
150
+ name: Name,
151
+ client: { protocol: P },
152
+ attachments: AttachmentFields,
153
+ },
154
+ } = actor
155
+
156
+ const Attachments = S.Struct(AttachmentFields)
157
+ const SocketAttachment = S.Struct({
158
+ attachments: S.toCodecJson(Attachments),
159
+ session: Tracing.Session,
160
+ })
161
+ const encodeSocketAttachment = S.encodeEffect(SocketAttachment)
162
+ const decodeSocketAttachment = S.decodeUnknownEffect(SocketAttachment)
163
+ const decodeAttachmentsString = decodeJsonString(Attachments)
164
+ const encodeAuditionSuccess = encodeJsonString(P.Audition.Success)
165
+ const decodeClient = decodeJsonString(P.Client)
166
+ const encodeFSuccess = encodeJsonString(P.F.Success)
167
+ const encodeFFailure = encodeJsonString(P.F.Failure)
168
+ const encodeEvent = encodeJsonString(P.Event)
169
+
170
+ const transport: ActorTransport<
171
+ WebSocket,
172
+ {
173
+ readonly socket: WebSocket
174
+ readonly session: typeof Tracing.Session.Type
175
+ },
176
+ AttachmentFields,
177
+ D
178
+ > = {
179
+ key: ({ socket }) => socket,
180
+ send: ({ socket, session }, event) => {
181
+ const { _tag } = event.event as never
182
+ return Effect.gen(function* () {
183
+ const trace = yield* Tracing.currentTrace
184
+ const encoded = yield* encodeEvent({
185
+ ...event,
186
+ ...(trace && { trace }),
187
+ })
188
+ // @effect-diagnostics-next-line tryCatchInEffectGen:off
189
+ try {
190
+ socket.send(encoded)
191
+ // oxlint-disable-next-line no-unused-vars
192
+ } catch (_e) {}
193
+ }).pipe(
194
+ span("send", {
195
+ attributes: { _tag, ...sessionAttributes(session) },
196
+ kind: "producer",
197
+ links: [sessionLink(session)],
198
+ }),
199
+ )
200
+ },
201
+ close: ({ socket }) => Effect.sync(() => socket.close(1000)),
202
+ snapshot: ({ socket, session }, attachments) =>
203
+ encodeSocketAttachment({ attachments, session }).pipe(
204
+ Effect.andThen((v) => Effect.sync(() => socket.serializeAttachment(v))),
205
+ ),
206
+ }
207
+
208
+ class NameDecoded extends Context.Service<NameDecoded, Name["Type"]>()("liminal/WorkerdActorNamespace/NameDecoded") {}
209
+
210
+ return class extends DurableObject {
211
+ readonly run
212
+ readonly directory = ClientDirectory.make(actor, { transport })
213
+ readonly provideActor = (currentClient: ClientHandle<ActorSelf, AttachmentFields, D>) =>
214
+ flow(
215
+ Effect.provide(
216
+ Layer.provideMerge(
217
+ layer,
218
+ Effect.gen({ self: this }, function* () {
219
+ const name = yield* NameDecoded
220
+ return Layer.succeed(actor, {
221
+ name,
222
+ clients: this.directory.handles,
223
+ currentClient,
224
+ })
225
+ }).pipe(Layer.unwrap),
226
+ ),
227
+ ),
228
+ Effect.scoped,
229
+ )
230
+ constructor(state: DurableObjectState<{}>, env: Cloudflare.Env) {
231
+ super(state, env)
232
+ if (hibernation) {
233
+ Option.andThen(
234
+ Duration.fromInput(hibernation),
235
+ flow(Duration.toMillis, (timeout) => state.setHibernatableWebSocketEventTimeout(timeout)),
236
+ )
237
+ }
238
+
239
+ const Live = Layer.mergeAll(
240
+ FetchHttpClient.layer,
241
+ Layer.succeed(DoState.DoState, state),
242
+ Layer.effect(NameDecoded, S.decodeUnknownEffect(Name)(state.id.name)),
243
+ ).pipe(
244
+ Layer.provideMerge(
245
+ prelude.pipe(
246
+ Layer.provideMerge(
247
+ Layer.mergeAll(FetchHttpClient.layer, ConfigProvider.layer(ConfigProvider.fromUnknown(env))),
248
+ ),
249
+ ),
250
+ ),
251
+ Layer.provideMerge(Clock.layer),
252
+ )
253
+
254
+ const HydrateClientsLive = Effect.gen({ self: this }, function* () {
255
+ for (const socket of state.getWebSockets()) {
256
+ const { attachments, session } = yield* decodeSocketAttachment(socket.deserializeAttachment())
257
+ yield* this.directory
258
+ .register({ socket, session }, attachments)
259
+ .pipe(Effect.linkSpans(Tracer.externalSpan(session.trace), sessionLink(session).attributes))
260
+ }
261
+ }).pipe(span("hydrate"), Layer.effectDiscard)
262
+
263
+ const runtime = ManagedRuntime.make(HydrateClientsLive.pipe(Layer.provideMerge(Live), boundLayer("actor")))
264
+
265
+ this.run = flow(Effect.tapCause(logCause), runtime.runPromise)
266
+ }
267
+
268
+ override fetch(request: Request): Promise<Response> {
269
+ return Effect.gen({ self: this }, function* () {
270
+ const url = new URL(request.url)
271
+ const attachments = yield* decodeAttachmentsString(url.searchParams.get("__liminal_attachments"))
272
+ const { 0: webSocket, 1: server } = new WebSocketPair()
273
+ const state = yield* DoState.DoState
274
+ const session = {
275
+ id: SessionId.make(crypto.randomUUID()),
276
+ trace: yield* Effect.currentSpan.pipe(Effect.map(Tracing.toTraceEnvelope)),
277
+ }
278
+ const currentClient = yield* this.directory.register({ socket: server, session }, attachments)
279
+ state.acceptWebSocket(server)
280
+ const initial = yield* hydrate.pipe(
281
+ this.provideActor(currentClient),
282
+ span("hydrate", {
283
+ attributes: sessionAttributes(session),
284
+ links: [sessionLink(session)],
285
+ }),
286
+ )
287
+ server.send(yield* encodeAuditionSuccess({ _tag: "Audition.Success", initial }))
288
+ return new Response(null, {
289
+ status: 101,
290
+ webSocket,
291
+ headers: { [SecWebSocketProtocol]: "liminal" },
292
+ })
293
+ }).pipe(
294
+ span("fetch", {
295
+ kind: "server",
296
+ parent: pipe(request.headers, Headers.fromInput, HttpTraceContext.fromHeaders, Option.getOrUndefined),
297
+ }),
298
+ this.run,
299
+ )
300
+ }
301
+
302
+ override webSocketMessage(socket: WebSocket, raw: string | ArrayBuffer) {
303
+ Effect.gen({ self: this }, function* () {
304
+ const { client, handle: currentClient } = yield* this.directory.entry(socket)
305
+ const { session } = client
306
+ yield* Effect.annotateCurrentSpan(sessionAttributes(session))
307
+ const message = yield* decodeClient(raw instanceof ArrayBuffer ? new TextDecoder().decode(raw) : raw)
308
+ if (message._tag === "Audition.Payload") {
309
+ return yield* Effect.die(undefined)
310
+ }
311
+ if (message._tag === "Disconnect") {
312
+ yield* currentClient.disconnect
313
+ return yield* onDisconnect.pipe(
314
+ this.provideActor(currentClient),
315
+ span("disconnect", {
316
+ attributes: sessionAttributes(session),
317
+ links: [sessionLink(session)],
318
+ }),
319
+ )
320
+ }
321
+ const { id, payload } = message
322
+ const { _tag, value } = payload as never
323
+ const parent = message.trace ? Tracer.externalSpan(message.trace) : undefined
324
+ const transportSpan = yield* Tracing.parent
325
+ const links = [
326
+ sessionLink(session),
327
+ ...(parent && transportSpan
328
+ ? [
329
+ {
330
+ span: transportSpan,
331
+ attributes: {
332
+ "liminal.link": "transport",
333
+ "liminal.transport": "websocket",
334
+ },
335
+ },
336
+ ]
337
+ : []),
338
+ ]
339
+ yield* external[_tag]!(value).pipe(
340
+ Effect.matchEffect({
341
+ onSuccess: (value) =>
342
+ encodeFSuccess({
343
+ _tag: "F.Success",
344
+ id,
345
+ success: { _tag, value } as never,
346
+ }),
347
+ onFailure: (value) =>
348
+ encodeFFailure({
349
+ _tag: "F.Failure",
350
+ id,
351
+ failure: { _tag, value } as never,
352
+ }),
353
+ }),
354
+ Effect.andThen((v) =>
355
+ Effect.try({
356
+ try: () => socket.send(v),
357
+ catch: () => {},
358
+ }),
359
+ ),
360
+ this.provideActor(currentClient),
361
+ span("handler", {
362
+ attributes: { _tag, ...sessionAttributes(session) },
363
+ kind: "server",
364
+ parent,
365
+ links,
366
+ }),
367
+ )
368
+ }).pipe(span("socket-message"), this.run)
369
+ }
370
+
371
+ override webSocketClose(socket: WebSocket, _code: number, _reason: string, _wasClean: boolean) {
372
+ Effect.gen({ self: this }, function* () {
373
+ const entry = yield* this.directory
374
+ .entry(socket)
375
+ .pipe(Effect.catchTag("NoSuchElementError", () => Effect.undefined))
376
+ if (!entry) {
377
+ return
378
+ }
379
+ const {
380
+ client: { session },
381
+ handle: currentClient,
382
+ } = entry
383
+ yield* Effect.annotateCurrentSpan(sessionAttributes(session))
384
+ yield* this.directory.unregister(socket)
385
+ yield* onDisconnect.pipe(
386
+ this.provideActor(currentClient),
387
+ span("disconnect", {
388
+ attributes: sessionAttributes(session),
389
+ links: [sessionLink(session)],
390
+ }),
391
+ )
392
+ }).pipe(span("socket-close"), this.run)
393
+ }
394
+
395
+ override webSocketError(socket: WebSocket, cause: unknown) {
396
+ Effect.gen({ self: this }, function* () {
397
+ const {
398
+ client: { session },
399
+ handle: currentClient,
400
+ } = yield* this.directory.entry(socket)
401
+ yield* Effect.annotateCurrentSpan(sessionAttributes(session))
402
+ yield* this.directory.unregister(socket)
403
+ yield* onDisconnect.pipe(
404
+ this.provideActor(currentClient),
405
+ span("disconnect", {
406
+ attributes: sessionAttributes(session),
407
+ links: [sessionLink(session)],
408
+ }),
409
+ )
410
+ yield* Effect.annotateLogs(Effect.logDebug("SocketErrored"), { cause })
411
+ }).pipe(span("socket-error"), this.run)
412
+ }
413
+
414
+ async rpc<K extends keyof Internal>(
415
+ method: K,
416
+ payload: Internal[K]["payload"]["Type"],
417
+ ): Promise<Exit.Exit<Internal[K]["success"]["Type"], Internal[K]["failure"]["Type"]>> {
418
+ const handler = internal[method]
419
+ return await handler(payload).pipe(this.provideActor(null!), span("fn-internal"), Effect.exit, this.run)
420
+ }
421
+ }
422
+ }
package/workerd/index.ts CHANGED
@@ -1 +1,3 @@
1
1
  export * as WorkerdActorNamespace from "./WorkerdActorNamespace.ts"
2
+ export * as WorkerdActorRuntime from "./WorkerdActorRuntime.ts"
3
+ export * from "./ActorHandle.ts"
package/Accumulator.ts DELETED
@@ -1,103 +0,0 @@
1
- import { Deferred, Types, Option, Ref, PubSub, Stream, Effect, Context, Layer, Semaphore } from "effect"
2
-
3
- import { diagnostic } from "./_diagnostic.ts"
4
-
5
- const { debug } = diagnostic("Accumulator")
6
-
7
- const TypeId = "~liminal/Accumulator" as const
8
-
9
- export interface Service<State> {
10
- readonly ref: Ref.Ref<State>
11
-
12
- readonly pubsub: PubSub.PubSub<State>
13
- }
14
-
15
- export interface AccumulatorLayerConfig<Item, E, R, State, E2, R2, E3, R3> {
16
- readonly source: Stream.Stream<Item, E, R>
17
-
18
- readonly reduce: (item: Item) => (state: State) => Effect.Effect<State, E2, R2>
19
-
20
- readonly initial: (item: Item) => Effect.Effect<Option.Option<State>, E3, R3>
21
- }
22
-
23
- export type Reduce<State, Item, K extends Types.Tags<Item> = Types.Tags<Item>, E = never, R = never> = (
24
- item: Types.ExtractTag<Item, K>,
25
- ) => (accumulator: State) => Effect.Effect<State, E, R>
26
-
27
- export interface Accumulator<Self, Id extends string, State> extends Context.Service<Self, Service<State>> {
28
- new (_: never): Context.ServiceClass.Shape<Id, Service<State>>
29
-
30
- readonly [TypeId]: typeof TypeId
31
-
32
- readonly get: Effect.Effect<State, never, Self>
33
-
34
- readonly stream: Stream.Stream<State, never, Self>
35
-
36
- readonly reducer: <Item>() => <K extends Types.Tags<Item>, E, R>(
37
- _tag: K,
38
- f: Reduce<State, Item, K, E, R>,
39
- ) => Reduce<State, Item, K, E, R>
40
-
41
- readonly layer: <Item, E, R, E2, R2, E3, R3>(
42
- config: AccumulatorLayerConfig<Item, E, R, State, E2, R2, E3, R3>,
43
- ) => Layer.Layer<Self, E | E2 | E3, R | R2 | R3>
44
- }
45
-
46
- export const Service =
47
- <Self, State>() =>
48
- <Id extends string>(id: Id): Accumulator<Self, Id, State> => {
49
- const tag = Context.Service<Self, Service<State>>()(id)
50
-
51
- const get = tag.asEffect().pipe(
52
- Effect.map(({ ref }) => ref),
53
- Effect.flatMap(Ref.get),
54
- )
55
-
56
- const stream = tag.asEffect().pipe(
57
- Effect.map(({ pubsub }) => Stream.fromPubSub(pubsub)),
58
- Stream.unwrap,
59
- )
60
-
61
- const reducer =
62
- <Item>() =>
63
- <K extends Types.Tags<Item>, E, R>(_tag: K, f: Reduce<State, Item, K, E, R>): Reduce<State, Item, K, E, R> =>
64
- f
65
-
66
- const layer = <Item, E, R, E2, R2, E3, R3>({
67
- source,
68
- initial,
69
- reduce,
70
- }: AccumulatorLayerConfig<Item, E, R, State, E2, R2, E3, R3>): Layer.Layer<Self, E | E2 | E3, R | R2 | R3> =>
71
- Effect.gen(function* () {
72
- const semaphore = yield* Semaphore.make(1)
73
- const deferred = yield* Deferred.make<State>()
74
- const pubsub = yield* PubSub.unbounded<State>({ replay: 1 })
75
- yield* source.pipe(
76
- Stream.runForEach(
77
- Effect.fn(function* (item) {
78
- if (!(yield* Deferred.isDone(deferred))) {
79
- const match = yield* initial(item)
80
- if (Option.isSome(match)) {
81
- const { value } = match
82
- yield* Deferred.succeed(deferred, value)
83
- yield* debug("InitializedState", { state: value })
84
- }
85
- return
86
- }
87
- const current = yield* Ref.get(ref)
88
- const reduced = yield* reduce(item)(current)
89
- yield* Ref.set(ref, reduced)
90
- yield* PubSub.publish(pubsub, reduced)
91
- yield* debug("ReducedState", { item, previous: current, current: reduced })
92
- }, semaphore.withPermits(1)),
93
- ),
94
- Effect.forkScoped,
95
- )
96
- const initial_ = yield* Deferred.await(deferred)
97
- const ref = yield* Ref.make(initial_)
98
- yield* PubSub.publish(pubsub, initial_)
99
- return { ref, pubsub }
100
- }).pipe(Layer.effect(tag))
101
-
102
- return Object.assign(tag, { [TypeId]: TypeId, get, stream, reducer, layer })
103
- }
package/F.ts DELETED
@@ -1,10 +0,0 @@
1
- import { Effect } from "effect"
2
-
3
- import type { FError } from "./errors.ts"
4
- import type { ProtocolDefinition } from "./Protocol.ts"
5
-
6
- export type F<Self, D extends ProtocolDefinition> = <Method extends keyof D["methods"]>(
7
- method: Method,
8
- ) => (
9
- payload: D["methods"][Method]["payload"]["Type"],
10
- ) => Effect.Effect<D["methods"][Method]["success"]["Type"], FError<D>, Self>
package/_diagnostic.ts DELETED
@@ -1,3 +0,0 @@
1
- import { factory } from "liminal-util/Diagnostic"
2
-
3
- export const diagnostic = factory("liminal")
package/_util/Mutex.ts DELETED
@@ -1,13 +0,0 @@
1
- import { Context, Effect, Layer, Semaphore } from "effect"
2
-
3
- export class Mutex extends Context.Service<
4
- Mutex,
5
- <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
6
- >()("liminal/Mutex") {}
7
-
8
- export const layer = Effect.gen(function* () {
9
- const mutex = yield* Semaphore.make(1)
10
- return mutex.withPermits(1)
11
- }).pipe(Layer.effect(Mutex))
12
-
13
- export const task = <A, E, R>(effect: Effect.Effect<A, E, R>) => Mutex.asEffect().pipe(Effect.flatMap((f) => f(effect)))
@@ -1,22 +0,0 @@
1
- import { Types, Option, Ref, PubSub, Stream, Effect, Context, Layer } from "effect";
2
- declare const TypeId: "~liminal/Accumulator";
3
- export interface Service<State> {
4
- readonly ref: Ref.Ref<State>;
5
- readonly pubsub: PubSub.PubSub<State>;
6
- }
7
- export interface AccumulatorLayerConfig<Item, E, R, State, E2, R2, E3, R3> {
8
- readonly source: Stream.Stream<Item, E, R>;
9
- readonly reduce: (item: Item) => (state: State) => Effect.Effect<State, E2, R2>;
10
- readonly initial: (item: Item) => Effect.Effect<Option.Option<State>, E3, R3>;
11
- }
12
- export type Reduce<State, Item, K extends Types.Tags<Item> = Types.Tags<Item>, E = never, R = never> = (item: Types.ExtractTag<Item, K>) => (accumulator: State) => Effect.Effect<State, E, R>;
13
- export interface Accumulator<Self, Id extends string, State> extends Context.Service<Self, Service<State>> {
14
- new (_: never): Context.ServiceClass.Shape<Id, Service<State>>;
15
- readonly [TypeId]: typeof TypeId;
16
- readonly get: Effect.Effect<State, never, Self>;
17
- readonly stream: Stream.Stream<State, never, Self>;
18
- readonly reducer: <Item>() => <K extends Types.Tags<Item>, E, R>(_tag: K, f: Reduce<State, Item, K, E, R>) => Reduce<State, Item, K, E, R>;
19
- readonly layer: <Item, E, R, E2, R2, E3, R3>(config: AccumulatorLayerConfig<Item, E, R, State, E2, R2, E3, R3>) => Layer.Layer<Self, E | E2 | E3, R | R2 | R3>;
20
- }
21
- export declare const Service: <Self, State>() => <Id extends string>(id: Id) => Accumulator<Self, Id, State>;
22
- export {};
@@ -1,37 +0,0 @@
1
- import { Deferred, Types, Option, Ref, PubSub, Stream, Effect, Context, Layer, Semaphore } from "effect";
2
- import { diagnostic } from "./_diagnostic.js";
3
- const { debug } = diagnostic("Accumulator");
4
- const TypeId = "~liminal/Accumulator";
5
- export const Service = () => (id) => {
6
- const tag = Context.Service()(id);
7
- const get = tag.asEffect().pipe(Effect.map(({ ref }) => ref), Effect.flatMap(Ref.get));
8
- const stream = tag.asEffect().pipe(Effect.map(({ pubsub }) => Stream.fromPubSub(pubsub)), Stream.unwrap);
9
- const reducer = () => (_tag, f) => f;
10
- const layer = ({ source, initial, reduce, }) => Effect.gen(function* () {
11
- const semaphore = yield* Semaphore.make(1);
12
- const deferred = yield* Deferred.make();
13
- const pubsub = yield* PubSub.unbounded({ replay: 1 });
14
- yield* source.pipe(Stream.runForEach(Effect.fn(function* (item) {
15
- if (!(yield* Deferred.isDone(deferred))) {
16
- const match = yield* initial(item);
17
- if (Option.isSome(match)) {
18
- const { value } = match;
19
- yield* Deferred.succeed(deferred, value);
20
- yield* debug("InitializedState", { state: value });
21
- }
22
- return;
23
- }
24
- const current = yield* Ref.get(ref);
25
- const reduced = yield* reduce(item)(current);
26
- yield* Ref.set(ref, reduced);
27
- yield* PubSub.publish(pubsub, reduced);
28
- yield* debug("ReducedState", { item, previous: current, current: reduced });
29
- }, semaphore.withPermits(1))), Effect.forkScoped);
30
- const initial_ = yield* Deferred.await(deferred);
31
- const ref = yield* Ref.make(initial_);
32
- yield* PubSub.publish(pubsub, initial_);
33
- return { ref, pubsub };
34
- }).pipe(Layer.effect(tag));
35
- return Object.assign(tag, { [TypeId]: TypeId, get, stream, reducer, layer });
36
- };
37
- //# sourceMappingURL=Accumulator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Accumulator.js","sourceRoot":"","sources":["../Accumulator.ts"],"names":[],"mappings":"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"}
package/dist/F.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import { Effect } from "effect";
2
- import type { FError } from "./errors.ts";
3
- import type { ProtocolDefinition } from "./Protocol.ts";
4
- export type F<Self, D extends ProtocolDefinition> = <Method extends keyof D["methods"]>(method: Method) => (payload: D["methods"][Method]["payload"]["Type"]) => Effect.Effect<D["methods"][Method]["success"]["Type"], FError<D>, Self>;
package/dist/F.js DELETED
@@ -1,2 +0,0 @@
1
- import { Effect } from "effect";
2
- //# sourceMappingURL=F.js.map
package/dist/F.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"F.js","sourceRoot":"","sources":["../F.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA"}
@@ -1,4 +0,0 @@
1
- export declare const diagnostic: (module: string) => {
2
- debug: (event: string, annotations?: Record<string, unknown>) => import("effect/Effect").Effect<void, never, never>;
3
- span: (operation: string, options?: import("effect/Tracer").SpanOptions | undefined) => <A, E, R>(effect: import("effect/Effect").Effect<A, E, R>) => import("effect/Effect").Effect<A, E, Exclude<R, import("effect/Tracer").ParentSpan>>;
4
- };
@@ -1,3 +0,0 @@
1
- import { factory } from "liminal-util/Diagnostic";
2
- export const diagnostic = factory("liminal");
3
- //# sourceMappingURL=_diagnostic.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_diagnostic.js","sourceRoot":"","sources":["../_diagnostic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AAEjD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA"}
@@ -1,7 +0,0 @@
1
- import { Context, Effect, Layer } from "effect";
2
- declare const Mutex_base: Context.ServiceClass<Mutex, "liminal/Mutex", <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>>;
3
- export declare class Mutex extends Mutex_base {
4
- }
5
- export declare const layer: Layer.Layer<Mutex, never, never>;
6
- export declare const task: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R | Mutex>;
7
- export {};
@@ -1,9 +0,0 @@
1
- import { Context, Effect, Layer, Semaphore } from "effect";
2
- export class Mutex extends Context.Service()("liminal/Mutex") {
3
- }
4
- export const layer = Effect.gen(function* () {
5
- const mutex = yield* Semaphore.make(1);
6
- return mutex.withPermits(1);
7
- }).pipe(Layer.effect(Mutex));
8
- export const task = (effect) => Mutex.asEffect().pipe(Effect.flatMap((f) => f(effect)));
9
- //# sourceMappingURL=Mutex.js.map