@voidhash/mimic-effect 1.0.0-beta.1 → 1.0.0-beta.10

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 (142) hide show
  1. package/.turbo/turbo-build.log +116 -74
  2. package/dist/ColdStorage.cjs +9 -5
  3. package/dist/ColdStorage.d.cts.map +1 -1
  4. package/dist/ColdStorage.d.mts.map +1 -1
  5. package/dist/ColdStorage.mjs +9 -5
  6. package/dist/ColdStorage.mjs.map +1 -1
  7. package/dist/DocumentInstance.cjs +263 -0
  8. package/dist/DocumentInstance.d.cts +78 -0
  9. package/dist/DocumentInstance.d.cts.map +1 -0
  10. package/dist/DocumentInstance.d.mts +78 -0
  11. package/dist/DocumentInstance.d.mts.map +1 -0
  12. package/dist/DocumentInstance.mjs +264 -0
  13. package/dist/DocumentInstance.mjs.map +1 -0
  14. package/dist/Errors.cjs +10 -1
  15. package/dist/Errors.d.cts +18 -3
  16. package/dist/Errors.d.cts.map +1 -1
  17. package/dist/Errors.d.mts +18 -3
  18. package/dist/Errors.d.mts.map +1 -1
  19. package/dist/Errors.mjs +9 -1
  20. package/dist/Errors.mjs.map +1 -1
  21. package/dist/HotStorage.cjs +39 -12
  22. package/dist/HotStorage.d.cts +17 -1
  23. package/dist/HotStorage.d.cts.map +1 -1
  24. package/dist/HotStorage.d.mts +17 -1
  25. package/dist/HotStorage.d.mts.map +1 -1
  26. package/dist/HotStorage.mjs +39 -12
  27. package/dist/HotStorage.mjs.map +1 -1
  28. package/dist/Metrics.cjs +29 -1
  29. package/dist/Metrics.d.cts +5 -0
  30. package/dist/Metrics.d.cts.map +1 -1
  31. package/dist/Metrics.d.mts +5 -0
  32. package/dist/Metrics.d.mts.map +1 -1
  33. package/dist/Metrics.mjs +26 -1
  34. package/dist/Metrics.mjs.map +1 -1
  35. package/dist/MimicClusterServerEngine.cjs +44 -139
  36. package/dist/MimicClusterServerEngine.d.cts.map +1 -1
  37. package/dist/MimicClusterServerEngine.d.mts +1 -1
  38. package/dist/MimicClusterServerEngine.d.mts.map +1 -1
  39. package/dist/MimicClusterServerEngine.mjs +46 -141
  40. package/dist/MimicClusterServerEngine.mjs.map +1 -1
  41. package/dist/MimicServer.cjs +20 -20
  42. package/dist/MimicServer.d.cts.map +1 -1
  43. package/dist/MimicServer.d.mts.map +1 -1
  44. package/dist/MimicServer.mjs +20 -20
  45. package/dist/MimicServer.mjs.map +1 -1
  46. package/dist/MimicServerEngine.cjs +92 -11
  47. package/dist/MimicServerEngine.d.cts +12 -4
  48. package/dist/MimicServerEngine.d.cts.map +1 -1
  49. package/dist/MimicServerEngine.d.mts +12 -4
  50. package/dist/MimicServerEngine.d.mts.map +1 -1
  51. package/dist/MimicServerEngine.mjs +94 -13
  52. package/dist/MimicServerEngine.mjs.map +1 -1
  53. package/dist/PresenceManager.cjs +5 -5
  54. package/dist/PresenceManager.d.cts.map +1 -1
  55. package/dist/PresenceManager.d.mts.map +1 -1
  56. package/dist/PresenceManager.mjs +5 -5
  57. package/dist/PresenceManager.mjs.map +1 -1
  58. package/dist/Protocol.d.cts +1 -1
  59. package/dist/Protocol.d.mts +1 -1
  60. package/dist/Types.d.cts +9 -2
  61. package/dist/Types.d.cts.map +1 -1
  62. package/dist/Types.d.mts +9 -2
  63. package/dist/Types.d.mts.map +1 -1
  64. package/dist/index.cjs +5 -6
  65. package/dist/index.d.cts +3 -3
  66. package/dist/index.d.mts +3 -3
  67. package/dist/index.mjs +3 -3
  68. package/dist/testing/ColdStorageTestSuite.cjs +508 -0
  69. package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
  70. package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
  71. package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
  72. package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
  73. package/dist/testing/ColdStorageTestSuite.mjs +508 -0
  74. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
  75. package/dist/testing/FailingStorage.cjs +162 -0
  76. package/dist/testing/FailingStorage.d.cts +43 -0
  77. package/dist/testing/FailingStorage.d.cts.map +1 -0
  78. package/dist/testing/FailingStorage.d.mts +43 -0
  79. package/dist/testing/FailingStorage.d.mts.map +1 -0
  80. package/dist/testing/FailingStorage.mjs +163 -0
  81. package/dist/testing/FailingStorage.mjs.map +1 -0
  82. package/dist/testing/HotStorageTestSuite.cjs +820 -0
  83. package/dist/testing/HotStorageTestSuite.d.cts +42 -0
  84. package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
  85. package/dist/testing/HotStorageTestSuite.d.mts +42 -0
  86. package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
  87. package/dist/testing/HotStorageTestSuite.mjs +820 -0
  88. package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
  89. package/dist/testing/StorageIntegrationTestSuite.cjs +487 -0
  90. package/dist/testing/StorageIntegrationTestSuite.d.cts +37 -0
  91. package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
  92. package/dist/testing/StorageIntegrationTestSuite.d.mts +37 -0
  93. package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
  94. package/dist/testing/StorageIntegrationTestSuite.mjs +487 -0
  95. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
  96. package/dist/testing/assertions.cjs +117 -0
  97. package/dist/testing/assertions.mjs +112 -0
  98. package/dist/testing/assertions.mjs.map +1 -0
  99. package/dist/testing/index.cjs +14 -0
  100. package/dist/testing/index.d.cts +6 -0
  101. package/dist/testing/index.d.mts +6 -0
  102. package/dist/testing/index.mjs +7 -0
  103. package/dist/testing/types.cjs +15 -0
  104. package/dist/testing/types.d.cts +90 -0
  105. package/dist/testing/types.d.cts.map +1 -0
  106. package/dist/testing/types.d.mts +90 -0
  107. package/dist/testing/types.d.mts.map +1 -0
  108. package/dist/testing/types.mjs +16 -0
  109. package/dist/testing/types.mjs.map +1 -0
  110. package/package.json +8 -3
  111. package/src/ColdStorage.ts +21 -12
  112. package/src/DocumentInstance.ts +527 -0
  113. package/src/Errors.ts +15 -1
  114. package/src/HotStorage.ts +115 -24
  115. package/src/Metrics.ts +30 -0
  116. package/src/MimicClusterServerEngine.ts +120 -275
  117. package/src/MimicServer.ts +83 -75
  118. package/src/MimicServerEngine.ts +230 -30
  119. package/src/PresenceManager.ts +44 -34
  120. package/src/Types.ts +9 -2
  121. package/src/index.ts +5 -35
  122. package/src/testing/ColdStorageTestSuite.ts +589 -0
  123. package/src/testing/FailingStorage.ts +338 -0
  124. package/src/testing/HotStorageTestSuite.ts +1105 -0
  125. package/src/testing/StorageIntegrationTestSuite.ts +736 -0
  126. package/src/testing/assertions.ts +188 -0
  127. package/src/testing/index.ts +83 -0
  128. package/src/testing/types.ts +100 -0
  129. package/tests/ColdStorage.test.ts +8 -120
  130. package/tests/DocumentInstance.test.ts +669 -0
  131. package/tests/HotStorage.test.ts +7 -126
  132. package/tests/StorageIntegration.test.ts +259 -0
  133. package/tsdown.config.ts +1 -1
  134. package/dist/DocumentManager.cjs +0 -229
  135. package/dist/DocumentManager.d.cts +0 -59
  136. package/dist/DocumentManager.d.cts.map +0 -1
  137. package/dist/DocumentManager.d.mts +0 -59
  138. package/dist/DocumentManager.d.mts.map +0 -1
  139. package/dist/DocumentManager.mjs +0 -227
  140. package/dist/DocumentManager.mjs.map +0 -1
  141. package/src/DocumentManager.ts +0 -506
  142. package/tests/DocumentManager.test.ts +0 -335
@@ -13,32 +13,30 @@ import {
13
13
  HashMap,
14
14
  Layer,
15
15
  Metric,
16
- Option,
17
16
  PubSub,
18
17
  Ref,
18
+ Schedule,
19
19
  Schema,
20
- Scope,
21
20
  Stream,
22
21
  } from "effect";
23
22
  import { Entity, Sharding } from "@effect/cluster";
24
23
  import { Rpc } from "@effect/rpc";
25
- import type { Presence, Primitive, Transaction } from "@voidhash/mimic";
26
- import { ServerDocument } from "@voidhash/mimic/server";
24
+ import { type Primitive, type Transaction } from "@voidhash/mimic";
27
25
  import type {
28
26
  MimicClusterServerEngineConfig,
29
27
  PresenceEntry,
30
28
  PresenceEvent,
31
- PresenceSnapshot,
32
29
  ResolvedClusterConfig,
33
- StoredDocument,
34
- WalEntry,
35
30
  } from "./Types";
36
31
  import type * as Protocol from "./Protocol";
37
32
  import { ColdStorageTag, type ColdStorage } from "./ColdStorage";
38
33
  import { HotStorageTag, type HotStorage } from "./HotStorage";
39
34
  import { MimicAuthServiceTag } from "./MimicAuthService";
40
35
  import { MimicServerEngineTag, type MimicServerEngine } from "./MimicServerEngine";
41
- import type { SubmitResult } from "./DocumentManager";
36
+ import {
37
+ DocumentInstance,
38
+ type DocumentInstance as DocumentInstanceInterface,
39
+ } from "./DocumentInstance";
42
40
  import * as Metrics from "./Metrics";
43
41
 
44
42
  // =============================================================================
@@ -49,6 +47,7 @@ const DEFAULT_MAX_IDLE_TIME = Duration.minutes(5);
49
47
  const DEFAULT_MAX_TRANSACTION_HISTORY = 1000;
50
48
  const DEFAULT_SNAPSHOT_INTERVAL = Duration.minutes(5);
51
49
  const DEFAULT_SNAPSHOT_THRESHOLD = 100;
50
+ const DEFAULT_SNAPSHOT_IDLE_TIMEOUT = Duration.seconds(30);
52
51
  const DEFAULT_SHARD_GROUP = "mimic-documents";
53
52
 
54
53
  // =============================================================================
@@ -172,16 +171,12 @@ const MimicDocumentEntity = Entity.make("MimicDocument", [
172
171
  // =============================================================================
173
172
 
174
173
  /**
175
- * Document state managed by the entity
174
+ * Entity state that wraps DocumentInstance and adds presence management
176
175
  */
177
- interface EntityDocumentState<TSchema extends Primitive.AnyPrimitive> {
178
- readonly document: ServerDocument.ServerDocument<TSchema>;
179
- readonly broadcastPubSub: PubSub.PubSub<Protocol.ServerMessage>;
176
+ interface EntityState<TSchema extends Primitive.AnyPrimitive> {
177
+ readonly instance: DocumentInstanceInterface<TSchema>;
180
178
  readonly presences: HashMap.HashMap<string, PresenceEntry>;
181
179
  readonly presencePubSub: PubSub.PubSub<PresenceEvent>;
182
- readonly lastSnapshotVersion: number;
183
- readonly lastSnapshotTime: number;
184
- readonly transactionsSinceSnapshot: number;
185
180
  }
186
181
 
187
182
  // =============================================================================
@@ -216,6 +211,9 @@ const resolveClusterConfig = <TSchema extends Primitive.AnyPrimitive>(
216
211
  : DEFAULT_SNAPSHOT_INTERVAL,
217
212
  transactionThreshold:
218
213
  config.snapshot?.transactionThreshold ?? DEFAULT_SNAPSHOT_THRESHOLD,
214
+ idleTimeout: config.snapshot?.idleTimeout
215
+ ? Duration.decode(config.snapshot.idleTimeout)
216
+ : DEFAULT_SNAPSHOT_IDLE_TIMEOUT,
219
217
  },
220
218
  shardGroup: config.shardGroup ?? DEFAULT_SHARD_GROUP,
221
219
  });
@@ -257,258 +255,99 @@ const createEntityHandler = <TSchema extends Primitive.AnyPrimitive>(
257
255
  coldStorage: ColdStorage,
258
256
  hotStorage: HotStorage
259
257
  ) =>
260
- Effect.gen(function* () {
258
+ Effect.fn("cluster.entity.handler.create")(function* () {
261
259
  // Get entity address to determine documentId
262
260
  const address = yield* Entity.CurrentAddress;
263
261
  const documentId = address.entityId;
264
262
 
265
- // Current schema version (hard-coded to 1 for now)
266
- const SCHEMA_VERSION = 1;
267
-
268
- // Compute initial state
269
- const computeInitialState = (): Effect.Effect<
270
- Primitive.InferSetInput<TSchema> | undefined
271
- > => {
272
- if (config.initial === undefined) {
273
- return Effect.succeed(undefined);
274
- }
275
-
276
- if (typeof config.initial === "function") {
277
- return (
278
- config.initial as (ctx: {
279
- documentId: string;
280
- }) => Effect.Effect<Primitive.InferSetInput<TSchema>>
281
- )({ documentId });
282
- }
283
-
284
- return Effect.succeed(
285
- config.initial as Primitive.InferSetInput<TSchema>
286
- );
287
- };
288
-
289
- // Load snapshot from ColdStorage
290
- const storedDoc = yield* Effect.catchAll(
291
- coldStorage.load(documentId),
292
- () => Effect.succeed(undefined)
293
- );
294
-
295
- let initialState: Primitive.InferSetInput<TSchema> | undefined;
296
- let initialVersion = 0;
297
-
298
- if (storedDoc) {
299
- initialState =
300
- storedDoc.state as Primitive.InferSetInput<TSchema>;
301
- initialVersion = storedDoc.version;
302
- } else {
303
- initialState = yield* computeInitialState();
304
- }
263
+ // Create DocumentInstance (fatal if unavailable - entity cannot start)
264
+ const instance = yield* DocumentInstance.make(
265
+ documentId,
266
+ {
267
+ schema: config.schema,
268
+ initial: config.initial,
269
+ maxTransactionHistory: config.maxTransactionHistory,
270
+ snapshot: config.snapshot,
271
+ },
272
+ coldStorage,
273
+ hotStorage
274
+ ).pipe(Effect.orDie);
305
275
 
306
- // Create PubSubs for broadcasting
307
- const broadcastPubSub = yield* PubSub.unbounded<Protocol.ServerMessage>();
276
+ // Create presence PubSub and state ref
308
277
  const presencePubSub = yield* PubSub.unbounded<PresenceEvent>();
309
-
310
- // Create state ref
311
- const stateRef = yield* Ref.make<EntityDocumentState<TSchema>>({
312
- document: undefined as unknown as ServerDocument.ServerDocument<TSchema>,
313
- broadcastPubSub,
278
+ const stateRef = yield* Ref.make<EntityState<TSchema>>({
279
+ instance,
314
280
  presences: HashMap.empty(),
315
281
  presencePubSub,
316
- lastSnapshotVersion: initialVersion,
317
- lastSnapshotTime: Date.now(),
318
- transactionsSinceSnapshot: 0,
319
- });
320
-
321
- // Create ServerDocument with callbacks
322
- const document = ServerDocument.make({
323
- schema: config.schema,
324
- initialState,
325
- initialVersion,
326
- maxTransactionHistory: config.maxTransactionHistory,
327
- onBroadcast: (message: ServerDocument.TransactionMessage) => {
328
- Effect.runSync(
329
- PubSub.publish(broadcastPubSub, {
330
- type: "transaction",
331
- transaction: message.transaction,
332
- version: message.version,
333
- } as Protocol.ServerMessage)
334
- );
335
- },
336
- onRejection: (transactionId: string, reason: string) => {
337
- Effect.runSync(
338
- PubSub.publish(broadcastPubSub, {
339
- type: "error",
340
- transactionId,
341
- reason,
342
- } as Protocol.ServerMessage)
343
- );
344
- },
345
- });
346
-
347
- // Update state with document
348
- yield* Ref.update(stateRef, (s) => ({ ...s, document }));
349
-
350
- // Replay WAL entries
351
- const walEntries = yield* Effect.catchAll(
352
- hotStorage.getEntries(documentId, initialVersion),
353
- () => Effect.succeed([] as WalEntry[])
354
- );
355
-
356
- for (const entry of walEntries) {
357
- const result = document.submit(entry.transaction);
358
- if (!result.success) {
359
- yield* Effect.logWarning("Skipping corrupted WAL entry", {
360
- documentId,
361
- version: entry.version,
362
- reason: result.reason,
363
- });
364
- }
365
- }
366
-
367
- // Track metrics
368
- if (storedDoc) {
369
- yield* Metric.increment(Metrics.documentsRestored);
370
- } else {
371
- yield* Metric.increment(Metrics.documentsCreated);
372
- }
373
- yield* Metric.incrementBy(Metrics.documentsActive, 1);
374
-
375
- /**
376
- * Save snapshot to ColdStorage
377
- */
378
- const saveSnapshot = Effect.gen(function* () {
379
- const state = yield* Ref.get(stateRef);
380
- const docState = state.document.get();
381
- const version = state.document.getVersion();
382
-
383
- if (docState === undefined) {
384
- return;
385
- }
386
-
387
- const storedDocument: StoredDocument = {
388
- state: docState,
389
- version,
390
- schemaVersion: SCHEMA_VERSION,
391
- savedAt: Date.now(),
392
- };
393
-
394
- const snapshotStartTime = Date.now();
395
-
396
- yield* Effect.catchAll(
397
- coldStorage.save(documentId, storedDocument),
398
- (e) =>
399
- Effect.logError("Failed to save snapshot", { documentId, error: e })
400
- );
401
-
402
- const snapshotDuration = Date.now() - snapshotStartTime;
403
- yield* Metric.increment(Metrics.storageSnapshots);
404
- yield* Metric.update(Metrics.storageSnapshotLatency, snapshotDuration);
405
-
406
- yield* Effect.catchAll(hotStorage.truncate(documentId, version), (e) =>
407
- Effect.logError("Failed to truncate WAL", { documentId, error: e })
408
- );
409
-
410
- yield* Ref.update(stateRef, (s) => ({
411
- ...s,
412
- lastSnapshotVersion: version,
413
- lastSnapshotTime: Date.now(),
414
- transactionsSinceSnapshot: 0,
415
- }));
416
- });
417
-
418
- /**
419
- * Check if snapshot should be triggered
420
- */
421
- const checkSnapshotTriggers = Effect.gen(function* () {
422
- const state = yield* Ref.get(stateRef);
423
- const now = Date.now();
424
-
425
- const intervalMs = Duration.toMillis(config.snapshot.interval);
426
- const threshold = config.snapshot.transactionThreshold;
427
-
428
- if (state.transactionsSinceSnapshot >= threshold) {
429
- yield* saveSnapshot;
430
- return;
431
- }
432
-
433
- if (now - state.lastSnapshotTime >= intervalMs) {
434
- yield* saveSnapshot;
435
- return;
436
- }
437
282
  });
438
283
 
439
284
  // Cleanup on entity finalization
440
285
  yield* Effect.addFinalizer(() =>
441
- Effect.gen(function* () {
442
- // Save final snapshot before entity is garbage collected
443
- yield* saveSnapshot;
286
+ Effect.fn("cluster.entity.finalize")(function* () {
287
+ // Best effort save - don't fail shutdown if storage is unavailable
288
+ yield* Effect.catchAll(instance.saveSnapshot(), (e) =>
289
+ Effect.logError("Failed to save snapshot during entity finalization", {
290
+ documentId,
291
+ error: e,
292
+ })
293
+ );
444
294
  yield* Metric.incrementBy(Metrics.documentsActive, -1);
445
295
  yield* Metric.increment(Metrics.documentsEvicted);
446
296
  yield* Effect.logDebug("Entity finalized", { documentId });
447
- })
297
+ })()
448
298
  );
449
299
 
450
- // Return RPC handlers
451
- return {
452
- Submit: Effect.fnUntraced(function* ({ payload }) {
453
- const submitStartTime = Date.now();
454
- const state = yield* Ref.get(stateRef);
455
-
456
- // Decode transaction
457
- const transaction = decodeTransaction(payload.transaction);
458
-
459
- // Submit to ServerDocument
460
- const result = state.document.submit(transaction);
461
-
462
- // Track latency
463
- const latency = Date.now() - submitStartTime;
464
- yield* Metric.update(Metrics.transactionsLatency, latency);
465
-
466
- if (result.success) {
467
- yield* Metric.increment(Metrics.transactionsProcessed);
468
-
469
- // Append to WAL
470
- const walEntry: WalEntry = {
471
- transaction,
472
- version: result.version,
473
- timestamp: Date.now(),
474
- };
475
-
476
- yield* Effect.catchAll(hotStorage.append(documentId, walEntry), (e) =>
477
- Effect.logError("Failed to append to WAL", {
300
+ // Start periodic snapshot fiber for this entity
301
+ const idleTimeoutMs = Duration.toMillis(config.snapshot.idleTimeout);
302
+ if (idleTimeoutMs > 0) {
303
+ const snapshotLoop = Effect.fn("cluster.entity.snapshot.loop")(function* () {
304
+ const needs = yield* instance.needsSnapshot();
305
+ if (needs) {
306
+ yield* Effect.catchAll(instance.saveSnapshot(), (e) =>
307
+ Effect.logWarning("Periodic snapshot failed in cluster entity", {
478
308
  documentId,
479
309
  error: e,
480
310
  })
481
311
  );
312
+ yield* Metric.increment(Metrics.storageIdleSnapshots);
313
+ }
314
+ });
482
315
 
483
- yield* Metric.increment(Metrics.storageWalAppends);
484
-
485
- // Increment transaction count
486
- yield* Ref.update(stateRef, (s) => ({
487
- ...s,
488
- transactionsSinceSnapshot: s.transactionsSinceSnapshot + 1,
489
- }));
316
+ // Run every idleTimeout
317
+ yield* snapshotLoop().pipe(
318
+ Effect.repeat(Schedule.spaced(config.snapshot.idleTimeout)),
319
+ Effect.fork
320
+ );
321
+ }
490
322
 
491
- // Check snapshot triggers
492
- yield* checkSnapshotTriggers;
493
- } else {
494
- yield* Metric.increment(Metrics.transactionsRejected);
495
- }
323
+ // Return RPC handlers
324
+ return {
325
+ Submit: Effect.fn("cluster.document.transaction.submit")(function* ({
326
+ payload,
327
+ }) {
328
+ // Decode transaction
329
+ const transaction = decodeTransaction(payload.transaction);
496
330
 
497
- return result;
331
+ // Use DocumentInstance's submit method, catching storage errors
332
+ return yield* instance.submit(transaction).pipe(
333
+ Effect.catchAll((error) =>
334
+ Effect.succeed({
335
+ success: false as const,
336
+ reason: `Storage error: ${String(error)}`,
337
+ })
338
+ )
339
+ );
498
340
  }),
499
341
 
500
- GetSnapshot: Effect.fnUntraced(function* () {
501
- const state = yield* Ref.get(stateRef);
502
- return state.document.getSnapshot();
342
+ GetSnapshot: Effect.fn("cluster.document.snapshot.get")(function* () {
343
+ return instance.getSnapshot();
503
344
  }),
504
345
 
505
- Touch: Effect.fnUntraced(function* () {
506
- // Entity touch is handled automatically by the cluster framework
507
- // Just update last activity time conceptually
508
- return void 0;
346
+ Touch: Effect.fn("cluster.document.touch")(function* () {
347
+ yield* instance.touch();
509
348
  }),
510
349
 
511
- SetPresence: Effect.fnUntraced(function* ({ payload }) {
350
+ SetPresence: Effect.fn("cluster.presence.set")(function* ({ payload }) {
512
351
  const { connectionId, entry } = payload;
513
352
 
514
353
  yield* Ref.update(stateRef, (s) => ({
@@ -529,7 +368,9 @@ const createEntityHandler = <TSchema extends Primitive.AnyPrimitive>(
529
368
  yield* PubSub.publish(state.presencePubSub, event);
530
369
  }),
531
370
 
532
- RemovePresence: Effect.fnUntraced(function* ({ payload }) {
371
+ RemovePresence: Effect.fn("cluster.presence.remove")(function* ({
372
+ payload,
373
+ }) {
533
374
  const { connectionId } = payload;
534
375
  const state = yield* Ref.get(stateRef);
535
376
 
@@ -551,16 +392,18 @@ const createEntityHandler = <TSchema extends Primitive.AnyPrimitive>(
551
392
  yield* PubSub.publish(state.presencePubSub, event);
552
393
  }),
553
394
 
554
- GetPresenceSnapshot: Effect.fnUntraced(function* () {
555
- const state = yield* Ref.get(stateRef);
556
- const presences: Record<string, PresenceEntry> = {};
557
- for (const [id, entry] of state.presences) {
558
- presences[id] = entry;
395
+ GetPresenceSnapshot: Effect.fn("cluster.presence.snapshot.get")(
396
+ function* () {
397
+ const state = yield* Ref.get(stateRef);
398
+ const presences: Record<string, PresenceEntry> = {};
399
+ for (const [id, entry] of state.presences) {
400
+ presences[id] = entry;
401
+ }
402
+ return { presences };
559
403
  }
560
- return { presences };
561
- }),
404
+ ),
562
405
  };
563
- });
406
+ })();
564
407
 
565
408
  // =============================================================================
566
409
  // Subscription Store (for managing subscriptions at the gateway level)
@@ -585,7 +428,7 @@ class SubscriptionStoreTag extends Context.Tag(
585
428
 
586
429
  const subscriptionStoreLayer = Layer.effect(
587
430
  SubscriptionStoreTag,
588
- Effect.gen(function* () {
431
+ Effect.fn("cluster.subscriptions.layer.create")(function* () {
589
432
  const documentPubSubs = yield* Ref.make(
590
433
  HashMap.empty<string, PubSub.PubSub<Protocol.ServerMessage>>()
591
434
  );
@@ -594,37 +437,39 @@ const subscriptionStoreLayer = Layer.effect(
594
437
  );
595
438
 
596
439
  return {
597
- getOrCreatePubSub: (documentId: string) =>
598
- Effect.gen(function* () {
599
- const current = yield* Ref.get(documentPubSubs);
600
- const existing = HashMap.get(current, documentId);
601
- if (existing._tag === "Some") {
602
- return existing.value;
603
- }
440
+ getOrCreatePubSub: Effect.fn(
441
+ "cluster.subscriptions.pubsub.get-or-create"
442
+ )(function* (documentId: string) {
443
+ const current = yield* Ref.get(documentPubSubs);
444
+ const existing = HashMap.get(current, documentId);
445
+ if (existing._tag === "Some") {
446
+ return existing.value;
447
+ }
604
448
 
605
- const pubsub = yield* PubSub.unbounded<Protocol.ServerMessage>();
606
- yield* Ref.update(documentPubSubs, (map) =>
607
- HashMap.set(map, documentId, pubsub)
608
- );
609
- return pubsub;
610
- }),
611
-
612
- getOrCreatePresencePubSub: (documentId: string) =>
613
- Effect.gen(function* () {
614
- const current = yield* Ref.get(presencePubSubs);
615
- const existing = HashMap.get(current, documentId);
616
- if (existing._tag === "Some") {
617
- return existing.value;
618
- }
449
+ const pubsub = yield* PubSub.unbounded<Protocol.ServerMessage>();
450
+ yield* Ref.update(documentPubSubs, (map) =>
451
+ HashMap.set(map, documentId, pubsub)
452
+ );
453
+ return pubsub;
454
+ }),
619
455
 
620
- const pubsub = yield* PubSub.unbounded<PresenceEvent>();
621
- yield* Ref.update(presencePubSubs, (map) =>
622
- HashMap.set(map, documentId, pubsub)
623
- );
624
- return pubsub;
625
- }),
456
+ getOrCreatePresencePubSub: Effect.fn(
457
+ "cluster.subscriptions.presence-pubsub.get-or-create"
458
+ )(function* (documentId: string) {
459
+ const current = yield* Ref.get(presencePubSubs);
460
+ const existing = HashMap.get(current, documentId);
461
+ if (existing._tag === "Some") {
462
+ return existing.value;
463
+ }
464
+
465
+ const pubsub = yield* PubSub.unbounded<PresenceEvent>();
466
+ yield* Ref.update(presencePubSubs, (map) =>
467
+ HashMap.set(map, documentId, pubsub)
468
+ );
469
+ return pubsub;
470
+ }),
626
471
  };
627
- })
472
+ })()
628
473
  );
629
474
 
630
475
  // =============================================================================