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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/dist/Equal.d.ts.map +1 -1
  2. package/dist/Equal.js +16 -0
  3. package/dist/Equal.js.map +1 -1
  4. package/dist/Hash.js +1 -1
  5. package/dist/Hash.js.map +1 -1
  6. package/dist/Semaphore.d.ts +1 -1
  7. package/dist/Semaphore.d.ts.map +1 -1
  8. package/dist/Semaphore.js +1 -3
  9. package/dist/Semaphore.js.map +1 -1
  10. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  11. package/dist/unstable/ai/McpServer.js +24 -21
  12. package/dist/unstable/ai/McpServer.js.map +1 -1
  13. package/dist/unstable/eventlog/Event.d.ts +0 -6
  14. package/dist/unstable/eventlog/Event.d.ts.map +1 -1
  15. package/dist/unstable/eventlog/Event.js +0 -5
  16. package/dist/unstable/eventlog/Event.js.map +1 -1
  17. package/dist/unstable/eventlog/EventGroup.d.ts +0 -2
  18. package/dist/unstable/eventlog/EventGroup.d.ts.map +1 -1
  19. package/dist/unstable/eventlog/EventGroup.js +0 -2
  20. package/dist/unstable/eventlog/EventGroup.js.map +1 -1
  21. package/dist/unstable/eventlog/EventJournal.d.ts +22 -5
  22. package/dist/unstable/eventlog/EventJournal.d.ts.map +1 -1
  23. package/dist/unstable/eventlog/EventJournal.js +126 -67
  24. package/dist/unstable/eventlog/EventJournal.js.map +1 -1
  25. package/dist/unstable/eventlog/EventLog.d.ts +88 -34
  26. package/dist/unstable/eventlog/EventLog.d.ts.map +1 -1
  27. package/dist/unstable/eventlog/EventLog.js +215 -141
  28. package/dist/unstable/eventlog/EventLog.js.map +1 -1
  29. package/dist/unstable/eventlog/EventLogEncryption.d.ts +9 -7
  30. package/dist/unstable/eventlog/EventLogEncryption.d.ts.map +1 -1
  31. package/dist/unstable/eventlog/EventLogEncryption.js +13 -15
  32. package/dist/unstable/eventlog/EventLogEncryption.js.map +1 -1
  33. package/dist/unstable/eventlog/EventLogMessage.d.ts +228 -0
  34. package/dist/unstable/eventlog/EventLogMessage.d.ts.map +1 -0
  35. package/dist/unstable/eventlog/EventLogMessage.js +214 -0
  36. package/dist/unstable/eventlog/EventLogMessage.js.map +1 -0
  37. package/dist/unstable/eventlog/EventLogRemote.d.ts +109 -194
  38. package/dist/unstable/eventlog/EventLogRemote.d.ts.map +1 -1
  39. package/dist/unstable/eventlog/EventLogRemote.js +165 -320
  40. package/dist/unstable/eventlog/EventLogRemote.js.map +1 -1
  41. package/dist/unstable/eventlog/EventLogServer.d.ts +25 -47
  42. package/dist/unstable/eventlog/EventLogServer.d.ts.map +1 -1
  43. package/dist/unstable/eventlog/EventLogServer.js +127 -198
  44. package/dist/unstable/eventlog/EventLogServer.js.map +1 -1
  45. package/dist/unstable/eventlog/EventLogServerEncrypted.d.ts +60 -0
  46. package/dist/unstable/eventlog/EventLogServerEncrypted.d.ts.map +1 -0
  47. package/dist/unstable/eventlog/EventLogServerEncrypted.js +166 -0
  48. package/dist/unstable/eventlog/EventLogServerEncrypted.js.map +1 -0
  49. package/dist/unstable/eventlog/EventLogServerUnencrypted.d.ts +183 -0
  50. package/dist/unstable/eventlog/EventLogServerUnencrypted.d.ts.map +1 -0
  51. package/dist/unstable/eventlog/EventLogServerUnencrypted.js +461 -0
  52. package/dist/unstable/eventlog/EventLogServerUnencrypted.js.map +1 -0
  53. package/dist/unstable/eventlog/EventLogSessionAuth.d.ts +117 -0
  54. package/dist/unstable/eventlog/EventLogSessionAuth.d.ts.map +1 -0
  55. package/dist/unstable/eventlog/EventLogSessionAuth.js +284 -0
  56. package/dist/unstable/eventlog/EventLogSessionAuth.js.map +1 -0
  57. package/dist/unstable/eventlog/{SqlEventLogJournal.d.ts → SqlEventJournal.d.ts} +2 -2
  58. package/dist/unstable/eventlog/SqlEventJournal.d.ts.map +1 -0
  59. package/dist/unstable/eventlog/{SqlEventLogJournal.js → SqlEventJournal.js} +20 -14
  60. package/dist/unstable/eventlog/SqlEventJournal.js.map +1 -0
  61. package/dist/unstable/eventlog/{SqlEventLogServer.d.ts → SqlEventLogServerEncrypted.d.ts} +5 -5
  62. package/dist/unstable/eventlog/SqlEventLogServerEncrypted.d.ts.map +1 -0
  63. package/dist/unstable/eventlog/{SqlEventLogServer.js → SqlEventLogServerEncrypted.js} +65 -24
  64. package/dist/unstable/eventlog/SqlEventLogServerEncrypted.js.map +1 -0
  65. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.d.ts +25 -0
  66. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.d.ts.map +1 -0
  67. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js +354 -0
  68. package/dist/unstable/eventlog/SqlEventLogServerUnencrypted.js.map +1 -0
  69. package/dist/unstable/eventlog/index.d.ts +22 -2
  70. package/dist/unstable/eventlog/index.d.ts.map +1 -1
  71. package/dist/unstable/eventlog/index.js +22 -2
  72. package/dist/unstable/eventlog/index.js.map +1 -1
  73. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.d.ts +2 -0
  74. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.d.ts.map +1 -0
  75. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.js +89 -0
  76. package/dist/unstable/eventlog/internal/identityRootSecretDerivation.js.map +1 -0
  77. package/dist/unstable/reactivity/AtomHttpApi.d.ts +1 -2
  78. package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
  79. package/dist/unstable/reactivity/AtomHttpApi.js +2 -2
  80. package/dist/unstable/reactivity/AtomHttpApi.js.map +1 -1
  81. package/dist/unstable/reactivity/AtomRpc.d.ts +1 -2
  82. package/dist/unstable/reactivity/AtomRpc.d.ts.map +1 -1
  83. package/dist/unstable/reactivity/AtomRpc.js +3 -3
  84. package/dist/unstable/reactivity/AtomRpc.js.map +1 -1
  85. package/dist/unstable/rpc/Rpc.d.ts +25 -4
  86. package/dist/unstable/rpc/Rpc.d.ts.map +1 -1
  87. package/dist/unstable/rpc/Rpc.js +26 -0
  88. package/dist/unstable/rpc/Rpc.js.map +1 -1
  89. package/dist/unstable/rpc/RpcClient.d.ts +3 -13
  90. package/dist/unstable/rpc/RpcClient.d.ts.map +1 -1
  91. package/dist/unstable/rpc/RpcClient.js +47 -23
  92. package/dist/unstable/rpc/RpcClient.js.map +1 -1
  93. package/dist/unstable/rpc/RpcGroup.d.ts +1 -1
  94. package/dist/unstable/rpc/RpcGroup.d.ts.map +1 -1
  95. package/dist/unstable/rpc/RpcMiddleware.d.ts +2 -2
  96. package/dist/unstable/rpc/RpcMiddleware.d.ts.map +1 -1
  97. package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
  98. package/dist/unstable/rpc/RpcServer.js +3 -2
  99. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  100. package/dist/unstable/rpc/Utils.d.ts +6 -0
  101. package/dist/unstable/rpc/Utils.d.ts.map +1 -1
  102. package/dist/unstable/rpc/Utils.js +44 -0
  103. package/dist/unstable/rpc/Utils.js.map +1 -1
  104. package/dist/unstable/schema/Model.d.ts +2 -2
  105. package/dist/unstable/schema/Model.d.ts.map +1 -1
  106. package/dist/unstable/schema/Model.js +2 -4
  107. package/dist/unstable/schema/Model.js.map +1 -1
  108. package/dist/unstable/schema/VariantSchema.d.ts +1 -1
  109. package/dist/unstable/schema/VariantSchema.d.ts.map +1 -1
  110. package/dist/unstable/schema/VariantSchema.js +1 -12
  111. package/dist/unstable/schema/VariantSchema.js.map +1 -1
  112. package/dist/unstable/workers/Transferable.d.ts +1 -1
  113. package/dist/unstable/workers/Transferable.d.ts.map +1 -1
  114. package/dist/unstable/workers/Transferable.js +1 -1
  115. package/dist/unstable/workers/Transferable.js.map +1 -1
  116. package/package.json +1 -1
  117. package/src/Equal.ts +17 -0
  118. package/src/Hash.ts +2 -2
  119. package/src/Semaphore.ts +2 -4
  120. package/src/unstable/ai/McpServer.ts +24 -22
  121. package/src/unstable/eventlog/Event.ts +0 -8
  122. package/src/unstable/eventlog/EventGroup.ts +0 -4
  123. package/src/unstable/eventlog/EventJournal.ts +144 -76
  124. package/src/unstable/eventlog/EventLog.ts +342 -221
  125. package/src/unstable/eventlog/EventLogEncryption.ts +16 -30
  126. package/src/unstable/eventlog/EventLogMessage.ts +277 -0
  127. package/src/unstable/eventlog/EventLogRemote.ts +261 -408
  128. package/src/unstable/eventlog/EventLogServer.ts +182 -274
  129. package/src/unstable/eventlog/EventLogServerEncrypted.ts +206 -0
  130. package/src/unstable/eventlog/EventLogServerUnencrypted.ts +749 -0
  131. package/src/unstable/eventlog/EventLogSessionAuth.ts +437 -0
  132. package/src/unstable/eventlog/{SqlEventLogJournal.ts → SqlEventJournal.ts} +26 -18
  133. package/src/unstable/eventlog/{SqlEventLogServer.ts → SqlEventLogServerEncrypted.ts} +102 -40
  134. package/src/unstable/eventlog/SqlEventLogServerUnencrypted.ts +500 -0
  135. package/src/unstable/eventlog/index.ts +27 -2
  136. package/src/unstable/eventlog/internal/identityRootSecretDerivation.ts +153 -0
  137. package/src/unstable/reactivity/AtomHttpApi.ts +23 -8
  138. package/src/unstable/reactivity/AtomRpc.ts +16 -5
  139. package/src/unstable/rpc/Rpc.ts +42 -4
  140. package/src/unstable/rpc/RpcClient.ts +59 -24
  141. package/src/unstable/rpc/RpcGroup.ts +1 -1
  142. package/src/unstable/rpc/RpcMiddleware.ts +2 -2
  143. package/src/unstable/rpc/RpcServer.ts +5 -3
  144. package/src/unstable/rpc/Utils.ts +59 -0
  145. package/src/unstable/schema/Model.ts +4 -6
  146. package/src/unstable/schema/VariantSchema.ts +4 -17
  147. package/src/unstable/workers/Transferable.ts +9 -11
  148. package/dist/unstable/eventlog/SqlEventLogJournal.d.ts.map +0 -1
  149. package/dist/unstable/eventlog/SqlEventLogJournal.js.map +0 -1
  150. package/dist/unstable/eventlog/SqlEventLogServer.d.ts.map +0 -1
  151. package/dist/unstable/eventlog/SqlEventLogServer.js.map +0 -1
package/src/Equal.ts CHANGED
@@ -317,6 +317,11 @@ function compareObjects(self: object, that: object): boolean {
317
317
  return false
318
318
  }
319
319
  return compareArrays(self, that)
320
+ } else if (ArrayBuffer.isView(self)) {
321
+ if (!ArrayBuffer.isView(that) || self.byteLength !== that.byteLength) {
322
+ return false
323
+ }
324
+ return compareTypedArrays(self as Uint8Array, that as Uint8Array)
320
325
  } else if (self instanceof Map) {
321
326
  if (!(that instanceof Map) || self.size !== that.size) {
322
327
  return false
@@ -370,6 +375,18 @@ function compareArrays(self: Array<unknown>, that: Array<unknown>): boolean {
370
375
  return true
371
376
  }
372
377
 
378
+ function compareTypedArrays(self: Uint8Array, that: Uint8Array): boolean {
379
+ if (self.length !== that.length) {
380
+ return false
381
+ }
382
+ for (let i = 0; i < self.length; i++) {
383
+ if (self[i] !== that[i]) {
384
+ return false
385
+ }
386
+ }
387
+ return true
388
+ }
389
+
373
390
  function compareRecords(
374
391
  self: Record<PropertyKey, unknown>,
375
392
  that: Record<PropertyKey, unknown>
package/src/Hash.ts CHANGED
@@ -116,8 +116,8 @@ export const hash: <A>(self: A) => number = <A>(self: A) => {
116
116
  return self[symbol]()
117
117
  } else if (typeof self === "function") {
118
118
  return random(self)
119
- } else if (Array.isArray(self)) {
120
- return array(self)
119
+ } else if (Array.isArray(self) || ArrayBuffer.isView(self)) {
120
+ return array(self as any)
121
121
  } else if (self instanceof Map) {
122
122
  return hashMap(self)
123
123
  } else if (self instanceof Set) {
package/src/Semaphore.ts CHANGED
@@ -55,7 +55,7 @@ export interface Semaphore {
55
55
  * If insufficient permits are available, the function will wait until they
56
56
  * are released by other tasks.
57
57
  */
58
- withPermit<A, E, R>(this: Semaphore, self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
58
+ withPermit<A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
59
59
 
60
60
  /**
61
61
  * Runs an effect only if the specified number of permits are immediately
@@ -220,9 +220,7 @@ class SemaphoreImpl implements Semaphore {
220
220
  )
221
221
  }
222
222
 
223
- get withPermit(): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> {
224
- return this.withPermits(1)
225
- }
223
+ readonly withPermit = this.withPermits(1)
226
224
 
227
225
  withPermitsIfAvailable(n: number) {
228
226
  return <A, E, R>(self: Effect.Effect<A, E, R>) =>
@@ -24,7 +24,7 @@ import { appendPreResponseHandlerUnsafe } from "../http/HttpEffect.ts"
24
24
  import * as HttpRouter from "../http/HttpRouter.ts"
25
25
  import * as HttpServerRequest from "../http/HttpServerRequest.ts"
26
26
  import * as HttpServerResponse from "../http/HttpServerResponse.ts"
27
- import type * as Rpc from "../rpc/Rpc.ts"
27
+ import * as Rpc from "../rpc/Rpc.ts"
28
28
  import * as RpcClient from "../rpc/RpcClient.ts"
29
29
  import type * as RpcGroup from "../rpc/RpcGroup.ts"
30
30
  import * as RpcMessage from "../rpc/RpcMessage.ts"
@@ -353,9 +353,11 @@ export const run: (options: {
353
353
  Effect.provideServiceEffect(
354
354
  RpcClient.Protocol,
355
355
  RpcClient.Protocol.make(Effect.fnUntraced(function*(writeResponse) {
356
- write = writeResponse
356
+ let cid = 0
357
+ write = (message) => writeResponse(cid, message)
357
358
  return {
358
- send(request, _transferables) {
359
+ send(id, request, _transferables) {
360
+ cid = id
359
361
  return protocol.send(clientId, {
360
362
  ...request,
361
363
  headers: undefined,
@@ -377,8 +379,8 @@ export const run: (options: {
377
379
  idleTimeToLive: 10000
378
380
  })
379
381
 
380
- const clientMiddleware = McpServerClientMiddleware.of((effect, { clientId, headers, rpc }) => {
381
- const initializePayload = getInitializedClient(clientSessions, clientId, headers)
382
+ const clientMiddleware = McpServerClientMiddleware.of((effect, { client, headers, rpc }) => {
383
+ const initializePayload = getInitializedClient(clientSessions, client.id, headers)
382
384
  const isInitialize = rpc._tag === "initialize"
383
385
  if (!isInitialize && !initializePayload) {
384
386
  return Effect.die(new Error(`Mcp-Session-Id does not exist`))
@@ -387,9 +389,9 @@ export const run: (options: {
387
389
  effect,
388
390
  McpServerClient,
389
391
  McpServerClient.of({
390
- clientId,
392
+ clientId: client.id,
391
393
  initializePayload: initializePayload!,
392
- getClient: RcMap.get(clients, clientId).pipe(
394
+ getClient: RcMap.get(clients, client.id).pipe(
393
395
  Effect.map(({ client }) => client)
394
396
  )
395
397
  })
@@ -429,7 +431,7 @@ export const run: (options: {
429
431
  ? handler.handler(request.payload, {
430
432
  rpc,
431
433
  requestId: RpcMessage.RequestId(request.id),
432
- clientId,
434
+ client: new Rpc.ServerClient(clientId),
433
435
  headers: Headers.fromInput(request.headers)
434
436
  }) as Effect.Effect<void>
435
437
  : Effect.void
@@ -1176,7 +1178,7 @@ const layerHandlers = (serverInfo: {
1176
1178
  return ClientRpcs.of({
1177
1179
  // Requests
1178
1180
  ping: () => Effect.succeed({}),
1179
- initialize(params, { clientId }) {
1181
+ initialize(params, { client }) {
1180
1182
  const requestedVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(params.protocolVersion)
1181
1183
  ? params.protocolVersion
1182
1184
  : LATEST_PROTOCOL_VERSION
@@ -1215,7 +1217,7 @@ const layerHandlers = (serverInfo: {
1215
1217
  [mcpProtocolVersionHeader]: requestedVersion
1216
1218
  })))
1217
1219
  } else {
1218
- options.clientSessions.set(String(clientId), params)
1220
+ options.clientSessions.set(String(client.id), params)
1219
1221
  }
1220
1222
  return Effect.succeed({
1221
1223
  capabilities,
@@ -1255,15 +1257,15 @@ const layerHandlers = (serverInfo: {
1255
1257
  server.getPromptResult(r).pipe(
1256
1258
  Effect.provideService(CurrentLogLevel, currentLogLevel)
1257
1259
  ),
1258
- "prompts/list": (_, { clientId, headers }) =>
1260
+ "prompts/list": (_, { client, headers }) =>
1259
1261
  Effect.sync(() => {
1260
- const client = getInitializedClient(options.clientSessions, clientId, headers)
1261
- return new ListPromptsResult({ prompts: filterByClient(client, server.prompts, "prompt") })
1262
+ const initialized = getInitializedClient(options.clientSessions, client.id, headers)
1263
+ return new ListPromptsResult({ prompts: filterByClient(initialized, server.prompts, "prompt") })
1262
1264
  }),
1263
- "resources/list": (_, { clientId, headers }) =>
1265
+ "resources/list": (_, { client, headers }) =>
1264
1266
  Effect.sync(() => {
1265
- const client = getInitializedClient(options.clientSessions, clientId, headers)
1266
- return new ListResourcesResult({ resources: filterByClient(client, server.resources, "resource") })
1267
+ const initialized = getInitializedClient(options.clientSessions, client.id, headers)
1268
+ return new ListResourcesResult({ resources: filterByClient(initialized, server.resources, "resource") })
1267
1269
  }),
1268
1270
  "resources/read": ({ uri }) =>
1269
1271
  server.findResource(uri).pipe(
@@ -1273,22 +1275,22 @@ const layerHandlers = (serverInfo: {
1273
1275
  InternalError.notImplemented.asEffect(),
1274
1276
  "resources/unsubscribe": () =>
1275
1277
  InternalError.notImplemented.asEffect(),
1276
- "resources/templates/list": (_, { clientId, headers }) =>
1278
+ "resources/templates/list": (_, { client, headers }) =>
1277
1279
  Effect.sync(() => {
1278
- const client = getInitializedClient(options.clientSessions, clientId, headers)
1280
+ const initialized = getInitializedClient(options.clientSessions, client.id, headers)
1279
1281
  return new ListResourceTemplatesResult({
1280
- resourceTemplates: filterByClient(client, server.resourceTemplates, "template")
1282
+ resourceTemplates: filterByClient(initialized, server.resourceTemplates, "template")
1281
1283
  })
1282
1284
  }),
1283
1285
  "tools/call": (r) =>
1284
1286
  server.callTool(r).pipe(
1285
1287
  Effect.provideService(CurrentLogLevel, currentLogLevel)
1286
1288
  ),
1287
- "tools/list": (_, { clientId, headers }) =>
1289
+ "tools/list": (_, { client, headers }) =>
1288
1290
  Effect.sync(() => {
1289
- const client = getInitializedClient(options.clientSessions, clientId, headers)
1291
+ const initialized = getInitializedClient(options.clientSessions, client.id, headers)
1290
1292
  return new ListToolsResult({
1291
- tools: filterByClient(client, server.tools, "tool")
1293
+ tools: filterByClient(initialized, server.tools, "tool")
1292
1294
  })
1293
1295
  }),
1294
1296
 
@@ -38,7 +38,6 @@ export interface Event<
38
38
  > {
39
39
  readonly [TypeId]: TypeId
40
40
  readonly tag: Tag
41
- readonly key: string
42
41
  readonly primaryKey: (payload: Schema.Schema.Type<Payload>) => string
43
42
  readonly payload: Payload
44
43
  readonly payloadMsgPack: Msgpack.schema<Payload>
@@ -62,7 +61,6 @@ export interface EventHandler<in out Tag extends string> {
62
61
  export interface Any {
63
62
  readonly [TypeId]: TypeId
64
63
  readonly tag: string
65
- readonly key: string
66
64
  readonly primaryKey: (payload: any) => string
67
65
  readonly payload: Schema.Top
68
66
  readonly payloadMsgPack: Msgpack.schema<Schema.Top>
@@ -312,7 +310,6 @@ export function make(options: {
312
310
  const error = options.error ?? Schema.Never
313
311
  return Object.assign(Object.create(Proto), {
314
312
  tag: options.tag,
315
- key: serviceKey(options.tag),
316
313
  primaryKey: options.primaryKey,
317
314
  payload,
318
315
  payloadMsgPack: Msgpack.schema(payload),
@@ -338,8 +335,3 @@ export function addError(event: Any, error: Schema.Top): Any {
338
335
  error: Schema.Union([event.error, error])
339
336
  })
340
337
  }
341
-
342
- /**
343
- * @since 4.0.0
344
- */
345
- export const serviceKey = (tag: string): string => `effect/eventlog/Event/${tag}`
@@ -37,8 +37,6 @@ export const isEventGroup = (u: unknown): u is Any => Predicate.hasProperty(u, T
37
37
  export interface EventGroup<
38
38
  out Events extends Event.Any = Event.Any
39
39
  > extends Pipeable {
40
- readonly _?: symbol
41
- readonly __type?: Events | undefined
42
40
  readonly [TypeId]: TypeId
43
41
  readonly events: Record.ReadonlyRecord<string, Events>
44
42
 
@@ -112,8 +110,6 @@ const makeProto = <
112
110
  const EventGroupClass = (_: never) => {}
113
111
  const group = Object.assign(EventGroupClass, {
114
112
  [TypeId]: TypeId,
115
- _: Symbol.for("~effect/eventlog/EventGroup"),
116
- __type: undefined,
117
113
  events: options.events,
118
114
  add<
119
115
  Tag extends string,
@@ -8,10 +8,13 @@ import * as Data from "../../Data.ts"
8
8
  import * as DateTime from "../../DateTime.ts"
9
9
  import * as Effect from "../../Effect.ts"
10
10
  import * as Layer from "../../Layer.ts"
11
+ import * as Order from "../../Order.ts"
11
12
  import * as PubSub from "../../PubSub.ts"
12
13
  import * as Schema from "../../Schema.ts"
13
14
  import type { Scope } from "../../Scope.ts"
15
+ import * as Semaphore from "../../Semaphore.ts"
14
16
  import * as Msgpack from "../encoding/Msgpack.ts"
17
+ import type { StoreId } from "./EventLogMessage.ts"
15
18
 
16
19
  /**
17
20
  * @since 4.0.0
@@ -44,17 +47,16 @@ export class EventJournal extends Context.Service<EventJournal, {
44
47
  readonly remoteId: RemoteId
45
48
  readonly entries: ReadonlyArray<RemoteEntry>
46
49
  readonly compact?:
47
- | ((uncommitted: ReadonlyArray<RemoteEntry>) => Effect.Effect<
48
- ReadonlyArray<[compacted: ReadonlyArray<Entry>, remoteEntries: ReadonlyArray<RemoteEntry>]>,
49
- EventJournalError
50
- >)
50
+ | ((uncommitted: ReadonlyArray<RemoteEntry>) => Effect.Effect<ReadonlyArray<Entry>, EventJournalError>)
51
51
  | undefined
52
52
  readonly effect: (options: {
53
53
  readonly entry: Entry
54
54
  readonly conflicts: ReadonlyArray<Entry>
55
55
  }) => Effect.Effect<void, EventJournalError>
56
56
  }
57
- ) => Effect.Effect<void, EventJournalError>
57
+ ) => Effect.Effect<{
58
+ readonly duplicateEntries: ReadonlyArray<Entry>
59
+ }, EventJournalError>
58
60
 
59
61
  /**
60
62
  * Return the uncommitted entries for a remote source.
@@ -78,6 +80,11 @@ export class EventJournal extends Context.Service<EventJournal, {
78
80
  * Remove all data
79
81
  */
80
82
  readonly destroy: Effect.Effect<void, EventJournalError>
83
+
84
+ /**
85
+ * Run an effect with a lock on the journal.
86
+ */
87
+ readonly withLock: (storeId: StoreId) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
81
88
  }>()("effect/eventlog/EventJournal") {}
82
89
 
83
90
  const TypeId = "effect/eventlog/EventJournal/EventJournalError" as const
@@ -142,13 +149,28 @@ export type EntryIdTypeId = "effect/eventlog/EventJournal/EntryId"
142
149
  * @since 4.0.0
143
150
  * @category entry
144
151
  */
145
- export type EntryId = Uint8Array & Brand<EntryIdTypeId>
152
+ export type EntryId = Uint8Array<ArrayBuffer> & Brand<EntryIdTypeId>
153
+
154
+ /**
155
+ * @since 4.0.0
156
+ * @category entry
157
+ */
158
+ export const EntryId = (Schema.Uint8Array as Schema.instanceOf<Uint8Array<ArrayBuffer>>).pipe(
159
+ Schema.brand(EntryIdTypeId)
160
+ )
146
161
 
147
162
  /**
148
163
  * @since 4.0.0
149
164
  * @category entry
150
165
  */
151
- export const EntryId = Schema.Uint8Array.pipe(Schema.brand(EntryIdTypeId))
166
+ export const EntryIdOrder = Order.make<EntryId>((a, b) => {
167
+ for (let i = 0; i < 16; i++) {
168
+ if (a[i] !== b[i]) {
169
+ return (a[i] - b[i]) < 0 ? -1 : 1
170
+ }
171
+ }
172
+ return 0
173
+ })
152
174
 
153
175
  /**
154
176
  * @since 4.0.0
@@ -192,6 +214,11 @@ export class Entry extends Schema.Class<Entry>("effect/eventlog/EventJournal/Ent
192
214
  */
193
215
  static decodeArray = Schema.decodeUnknownEffect(Entry.arrayMsgpack)
194
216
 
217
+ /**
218
+ * @since 4.0.0
219
+ */
220
+ static Order = Order.make<Entry>((a, b) => EntryIdOrder(a.id, b.id))
221
+
195
222
  /**
196
223
  * @since 4.0.0
197
224
  */
@@ -232,6 +259,15 @@ export const makeMemory: Effect.Effect<EventJournal["Service"]> = Effect.gen(fun
232
259
  const byId = new Map<string, Entry>()
233
260
  const remotes = new Map<string, { sequence: number; missing: Array<Entry> }>()
234
261
  const pubsub = yield* PubSub.unbounded<Entry>()
262
+ const storeSemaphores = new Map<StoreId, Semaphore.Semaphore>()
263
+ const withLock = (storeId: StoreId) => {
264
+ let semaphore = storeSemaphores.get(storeId)
265
+ if (!semaphore) {
266
+ semaphore = Semaphore.makeUnsafe(1)
267
+ storeSemaphores.set(storeId, semaphore)
268
+ }
269
+ return semaphore.withPermit
270
+ }
235
271
 
236
272
  const ensureRemote = (remoteId: RemoteId) => {
237
273
  const remoteIdString = Uuid.stringify(remoteId)
@@ -271,8 +307,10 @@ export const makeMemory: Effect.Effect<EventJournal["Service"]> = Effect.gen(fun
271
307
  const remote = ensureRemote(options.remoteId)
272
308
  const uncommittedRemotes: Array<RemoteEntry> = []
273
309
  const uncommitted: Array<Entry> = []
310
+ const duplicateEntries: Array<Entry> = []
274
311
  for (const remoteEntry of options.entries) {
275
312
  if (byId.has(remoteEntry.entry.idString)) {
313
+ duplicateEntries.push(remoteEntry.entry)
276
314
  if (remoteEntry.remoteSequence > remote.sequence) {
277
315
  remote.sequence = remoteEntry.remoteSequence
278
316
  }
@@ -282,36 +320,38 @@ export const makeMemory: Effect.Effect<EventJournal["Service"]> = Effect.gen(fun
282
320
  uncommitted.push(remoteEntry.entry)
283
321
  }
284
322
 
285
- const brackets = options.compact
323
+ const compacted = options.compact
286
324
  ? yield* options.compact(uncommittedRemotes)
287
- : [[uncommitted, uncommittedRemotes]] as const
288
-
289
- for (const [compacted, remoteEntries] of brackets) {
290
- for (const originEntry of compacted) {
291
- const entryMillis = entryIdMillis(originEntry.id)
292
- const conflicts: Array<Entry> = []
293
- for (let i = journal.length - 1; i >= -1; i--) {
294
- const entry = journal[i]
295
- if (entry !== undefined && entry.createdAtMillis > entryMillis) {
296
- continue
297
- }
298
- for (let j = i + 2; j < journal.length; j++) {
299
- const scannedEntry = journal[j]!
300
- if (scannedEntry.event === originEntry.event && scannedEntry.primaryKey === originEntry.primaryKey) {
301
- conflicts.push(scannedEntry)
302
- }
325
+ : uncommitted
326
+
327
+ for (const originEntry of compacted) {
328
+ const entryMillis = entryIdMillis(originEntry.id)
329
+ const conflicts: Array<Entry> = []
330
+ for (let i = journal.length - 1; i >= -1; i--) {
331
+ const entry = journal[i]
332
+ if (entry !== undefined && entry.createdAtMillis > entryMillis) {
333
+ continue
334
+ }
335
+ for (let j = i + 2; j < journal.length; j++) {
336
+ const scannedEntry = journal[j]!
337
+ if (scannedEntry.event === originEntry.event && scannedEntry.primaryKey === originEntry.primaryKey) {
338
+ conflicts.push(scannedEntry)
303
339
  }
304
- yield* options.effect({ entry: originEntry, conflicts })
305
- break
306
340
  }
341
+ yield* options.effect({ entry: originEntry, conflicts })
342
+ break
307
343
  }
308
- for (const remoteEntry of remoteEntries) {
309
- journal.push(remoteEntry.entry)
310
- if (remoteEntry.remoteSequence > remote.sequence) {
311
- remote.sequence = remoteEntry.remoteSequence
312
- }
344
+ }
345
+ for (const remoteEntry of uncommittedRemotes) {
346
+ journal.push(remoteEntry.entry)
347
+ byId.set(remoteEntry.entry.idString, remoteEntry.entry)
348
+ if (remoteEntry.remoteSequence > remote.sequence) {
349
+ remote.sequence = remoteEntry.remoteSequence
313
350
  }
314
- journal.sort((a, b) => a.createdAtMillis - b.createdAtMillis)
351
+ }
352
+ journal.sort((a, b) => a.createdAtMillis - b.createdAtMillis)
353
+ return {
354
+ duplicateEntries
315
355
  }
316
356
  }),
317
357
  withRemoteUncommited: (remoteId, f) =>
@@ -338,7 +378,8 @@ export const makeMemory: Effect.Effect<EventJournal["Service"]> = Effect.gen(fun
338
378
  journal.length = 0
339
379
  byId.clear()
340
380
  remotes.clear()
341
- })
381
+ }),
382
+ withLock
342
383
  })
343
384
  })
344
385
 
@@ -413,6 +454,7 @@ export const makeIndexedDb = (options?: {
413
454
  writeFromRemote: Effect.fnUntraced(function*(options) {
414
455
  const uncommitted: Array<Entry> = []
415
456
  const uncommittedRemotes: Array<RemoteEntry> = []
457
+ const duplicateEntries: Array<Entry> = []
416
458
 
417
459
  yield* Effect.callback<void, EventJournalError>((resume) => {
418
460
  const tx = db.transaction(["entries", "remotes"], "readwrite")
@@ -426,6 +468,7 @@ export const makeIndexedDb = (options?: {
426
468
  const entryIdKey = entry.id as IDBValidKey
427
469
  entries.get(entryIdKey).onsuccess = (event) => {
428
470
  if (event.target && "result" in event.target && event.target.result) {
471
+ duplicateEntries.push(entry)
429
472
  remotes.put({
430
473
  remoteId: options.remoteId,
431
474
  entryId: entry.id,
@@ -445,58 +488,58 @@ export const makeIndexedDb = (options?: {
445
488
  return Effect.sync(() => tx.abort())
446
489
  })
447
490
 
448
- const brackets = options.compact
491
+ const compacted = options.compact
449
492
  ? yield* options.compact(uncommittedRemotes)
450
- : [[uncommitted, uncommittedRemotes]] as const
451
-
452
- for (const [compacted, remoteEntries] of brackets) {
453
- for (const originEntry of compacted) {
454
- const conflicts: Array<Entry> = []
455
- yield* Effect.callback<void, EventJournalError>((resume) => {
456
- const tx = db.transaction("entries", "readonly")
457
- const entries = tx.objectStore("entries")
458
- const cursorRequest = entries.index("id").openCursor(
459
- IDBKeyRange.lowerBound(originEntry.id as IDBValidKey, true),
460
- "next"
461
- )
462
- cursorRequest.onsuccess = () => {
463
- const cursor = cursorRequest.result
464
- if (!cursor) return
465
- const decodedEntry = decodeEntryIdb(cursor.value)
466
- if (
467
- decodedEntry.event === originEntry.event &&
468
- decodedEntry.primaryKey === originEntry.primaryKey
469
- ) {
470
- conflicts.push(decodedEntry)
471
- }
472
- cursor.continue()
473
- }
474
- tx.oncomplete = () => resume(Effect.void)
475
- tx.onerror = () =>
476
- resume(Effect.fail(new EventJournalError({ method: "writeFromRemote", cause: tx.error })))
477
- return Effect.sync(() => tx.abort())
478
- })
479
-
480
- yield* options.effect({ entry: originEntry, conflicts })
481
- }
493
+ : uncommitted
482
494
 
495
+ for (const originEntry of compacted) {
496
+ const conflicts: Array<Entry> = []
483
497
  yield* Effect.callback<void, EventJournalError>((resume) => {
484
- const tx = db.transaction(["entries", "remotes"], "readwrite")
498
+ const tx = db.transaction("entries", "readonly")
485
499
  const entries = tx.objectStore("entries")
486
- const remotes = tx.objectStore("remotes")
487
- for (const remoteEntry of remoteEntries) {
488
- entries.add(encodeEntryIdb(remoteEntry.entry))
489
- remotes.put({
490
- remoteId: options.remoteId,
491
- entryId: remoteEntry.entry.id,
492
- sequence: remoteEntry.remoteSequence
493
- })
500
+ const cursorRequest = entries.index("id").openCursor(
501
+ IDBKeyRange.lowerBound(originEntry.id as IDBValidKey, true),
502
+ "next"
503
+ )
504
+ cursorRequest.onsuccess = () => {
505
+ const cursor = cursorRequest.result
506
+ if (!cursor) return
507
+ const decodedEntry = decodeEntryIdb(cursor.value)
508
+ if (
509
+ decodedEntry.event === originEntry.event &&
510
+ decodedEntry.primaryKey === originEntry.primaryKey
511
+ ) {
512
+ conflicts.push(decodedEntry)
513
+ }
514
+ cursor.continue()
494
515
  }
495
516
  tx.oncomplete = () => resume(Effect.void)
496
517
  tx.onerror = () =>
497
518
  resume(Effect.fail(new EventJournalError({ method: "writeFromRemote", cause: tx.error })))
498
519
  return Effect.sync(() => tx.abort())
499
520
  })
521
+
522
+ yield* options.effect({ entry: originEntry, conflicts })
523
+ }
524
+
525
+ yield* Effect.callback<void, EventJournalError>((resume) => {
526
+ const tx = db.transaction(["entries", "remotes"], "readwrite")
527
+ const entries = tx.objectStore("entries")
528
+ const remotes = tx.objectStore("remotes")
529
+ for (const remoteEntry of uncommittedRemotes) {
530
+ entries.add(encodeEntryIdb(remoteEntry.entry))
531
+ remotes.put({
532
+ remoteId: options.remoteId,
533
+ entryId: remoteEntry.entry.id,
534
+ sequence: remoteEntry.remoteSequence
535
+ })
536
+ }
537
+ tx.oncomplete = () => resume(Effect.void)
538
+ tx.onerror = () => resume(Effect.fail(new EventJournalError({ method: "writeFromRemote", cause: tx.error })))
539
+ return Effect.sync(() => tx.abort())
540
+ })
541
+ return {
542
+ duplicateEntries
500
543
  }
501
544
  }),
502
545
  withRemoteUncommited: (remoteId, f) =>
@@ -570,10 +613,35 @@ export const makeIndexedDb = (options?: {
570
613
  changes: PubSub.subscribe(pubsub),
571
614
  destroy: Effect.sync(() => {
572
615
  indexedDB.deleteDatabase(database)
573
- })
616
+ }),
617
+ withLock: yield* makeBrowserWithLock(database)
574
618
  })
575
619
  })
576
620
 
621
+ const makeBrowserWithLock = Effect.fnUntraced(function*(key: string) {
622
+ if (typeof navigator !== "undefined" && "locks" in navigator) {
623
+ return (storeId: StoreId) => <A, E, R>(self: Effect.Effect<A, E, R>) =>
624
+ Effect.callback<A, E, R>((resume, signal) => {
625
+ navigator.locks.request(`${key}/${storeId}`, { signal }, () =>
626
+ new Promise<void>((resolve) => {
627
+ resume(Effect.onExit(self, () => {
628
+ resolve()
629
+ return Effect.void
630
+ }))
631
+ })).catch((defect) => resume(Effect.die(defect)))
632
+ })
633
+ }
634
+ const semaphores = new Map<StoreId, Semaphore.Semaphore>()
635
+ return (storeId: StoreId) => {
636
+ let semaphore = semaphores.get(storeId)
637
+ if (!semaphore) {
638
+ semaphore = Semaphore.makeUnsafe(1)
639
+ semaphores.set(storeId, semaphore)
640
+ }
641
+ return semaphore.withPermit
642
+ }
643
+ })
644
+
577
645
  const decodeEntryIdb = Schema.decodeSync(Entry)
578
646
  const encodeEntryIdb = Schema.encodeSync(Entry)
579
647
  const EntryIdbArray = Schema.Array(Entry)