@logixjs/react 0.1.0 → 1.0.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.
Files changed (36) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1 -1
  3. package/dist/Hooks.cjs +462 -325
  4. package/dist/Hooks.d.cts +6 -6
  5. package/dist/Hooks.d.ts +6 -6
  6. package/dist/Hooks.js +3 -3
  7. package/dist/{ModuleRef-wZSQ3Wwo.d.cts → ModuleRef-gZmL6Zvb.d.cts} +8 -3
  8. package/dist/{ModuleRef-wZSQ3Wwo.d.ts → ModuleRef-gZmL6Zvb.d.ts} +8 -3
  9. package/dist/ModuleScope.cjs +596 -362
  10. package/dist/ModuleScope.d.cts +4 -4
  11. package/dist/ModuleScope.d.ts +4 -4
  12. package/dist/ModuleScope.js +4 -4
  13. package/dist/Platform.cjs +1 -4
  14. package/dist/Platform.d.cts +1 -2
  15. package/dist/Platform.d.ts +1 -2
  16. package/dist/Platform.js +1 -1
  17. package/dist/ReactPlatform.cjs +543 -309
  18. package/dist/ReactPlatform.d.cts +2 -2
  19. package/dist/ReactPlatform.d.ts +2 -2
  20. package/dist/ReactPlatform.js +5 -5
  21. package/dist/RuntimeProvider.cjs +276 -56
  22. package/dist/RuntimeProvider.js +2 -2
  23. package/dist/{chunk-PYWHL7TA.js → chunk-6NLXTHZ7.js} +8 -8
  24. package/dist/{chunk-UFFCJGSZ.js → chunk-E3ZXST5F.js} +256 -240
  25. package/dist/{chunk-4G7H66OY.js → chunk-KYWW4KMQ.js} +3 -3
  26. package/dist/{chunk-2WFULYPJ.js → chunk-L7KTYBXN.js} +155 -32
  27. package/dist/{chunk-ZANGOPUQ.js → chunk-NKYV44OG.js} +1 -4
  28. package/dist/{chunk-G5MRIFKK.js → chunk-SDQF3WRT.js} +7 -7
  29. package/dist/{chunk-JXAJTWSZ.js → chunk-XSGDBJXD.js} +122 -25
  30. package/dist/index.cjs +564 -333
  31. package/dist/index.d.cts +2 -2
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +7 -7
  34. package/dist/{useDispatch-BnzYVkRE.d.ts → useDispatch-CiDimIYZ.d.ts} +13 -15
  35. package/dist/{useDispatch-CnO5-66H.d.cts → useDispatch-DiwQQAfC.d.cts} +13 -15
  36. package/package.json +12 -4
@@ -5,7 +5,7 @@ import {
5
5
  isDevEnv,
6
6
  stableHash,
7
7
  useLayerBinding
8
- } from "./chunk-2WFULYPJ.js";
8
+ } from "./chunk-L7KTYBXN.js";
9
9
 
10
10
  // src/internal/hooks/useRuntime.ts
11
11
  import { useContext, useEffect, useMemo, useRef } from "react";
@@ -138,12 +138,12 @@ var shallow = (previous, next) => {
138
138
  // src/internal/hooks/useSelector.ts
139
139
  import { useContext as useContext3, useEffect as useEffect3, useMemo as useMemo3 } from "react";
140
140
  import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector";
141
- import * as Logix2 from "@logixjs/core";
141
+ import * as Logix4 from "@logixjs/core";
142
142
 
143
143
  // src/internal/hooks/useModuleRuntime.ts
144
- import { useEffect as useEffect2, useMemo as useMemo2, useContext as useContext2 } from "react";
145
- import * as Logix from "@logixjs/core";
146
- import { Scope } from "effect";
144
+ import { useEffect as useEffect2, useMemo as useMemo2, useContext as useContext2, useRef as useRef2 } from "react";
145
+ import * as Logix2 from "@logixjs/core";
146
+ import { Effect as Effect2, Scope } from "effect";
147
147
 
148
148
  // src/internal/store/ModuleRef.ts
149
149
  var isModuleRef = (value) => typeof value === "object" && value !== null && "runtime" in value && "actions" in value && "dispatch" in value;
@@ -234,11 +234,26 @@ var applyHandleExtend = (tag, runtime, base) => {
234
234
  return { ...base, ...next };
235
235
  };
236
236
 
237
+ // src/internal/provider/runtimeDebugBridge.ts
238
+ import { Effect } from "effect";
239
+ import * as Logix from "@logixjs/core";
240
+ var readRuntimeDiagnosticsLevel = (runtime) => {
241
+ try {
242
+ return runtime.runSync(Effect.service(Logix.Debug.internal.currentDiagnosticsLevel).pipe(Effect.orDie));
243
+ } catch {
244
+ return isDevEnv() ? "light" : "off";
245
+ }
246
+ };
247
+ var emitRuntimeDebugEventBestEffort = (runtime, event) => {
248
+ runtime.runFork(event);
249
+ };
250
+
237
251
  // src/internal/hooks/useModuleRuntime.ts
238
252
  var isModuleRuntime = (value) => typeof value === "object" && value !== null && "dispatch" in value && "getState" in value;
239
253
  function useModuleRuntime(handle) {
240
254
  const runtime = useRuntime();
241
255
  const runtimeContext = useContext2(RuntimeContext);
256
+ const moduleTagResolveTraceRef = useRef2(void 0);
242
257
  if (!runtimeContext) {
243
258
  throw new RuntimeProviderNotFoundError("useModuleRuntime");
244
259
  }
@@ -259,87 +274,117 @@ function useModuleRuntime(handle) {
259
274
  const preloadKey = runtimeContext.policy.preload?.keysByTagId.get(tokenId);
260
275
  const key = preloadKey ?? `tag:${tokenId}`;
261
276
  const mode = runtimeContext.policy.moduleTagMode;
262
- const factory = (scope) => tag.pipe(Scope.extend(scope));
263
- return mode === "suspend" ? cache.read(key, factory, void 0, tokenId, {
277
+ const startedAtMs = performance.now();
278
+ const factory = (scope) => Scope.provide(scope)(Effect2.service(tag).pipe(Effect2.orDie));
279
+ const resolvedRuntime = mode === "suspend" ? cache.read(key, factory, void 0, tokenId, {
264
280
  entrypoint: "react.useModuleRuntime",
265
281
  policyMode: runtimeContext.policy.mode,
266
- yield: runtimeContext.policy.yield
282
+ yield: runtimeContext.policy.yield,
283
+ optimisticSyncBudgetMs: runtimeContext.policy.syncBudgetMs
267
284
  }) : cache.readSync(key, factory, void 0, tokenId, {
268
285
  entrypoint: "react.useModuleRuntime",
269
286
  policyMode: runtimeContext.policy.mode,
270
287
  warnSyncBlockingThresholdMs: 5
271
288
  });
289
+ moduleTagResolveTraceRef.current = {
290
+ tokenId,
291
+ durationMs: Math.round((performance.now() - startedAtMs) * 100) / 100,
292
+ cacheMode: mode
293
+ };
294
+ return resolvedRuntime;
272
295
  }, [cache, runtimeContext.policy, handle]);
273
296
  useEffect2(() => {
274
297
  if (!isTagHandle) {
275
298
  return;
276
299
  }
277
- if (!isDevEnv() && !Logix.Debug.isDevtoolsEnabled()) {
300
+ const diagnosticsLevel = readRuntimeDiagnosticsLevel(runtime);
301
+ if (diagnosticsLevel === "off") {
278
302
  return;
279
303
  }
280
304
  const tokenId = handle?.id ?? "ModuleTag";
281
- const effect = Logix.Debug.record({
305
+ const trace = moduleTagResolveTraceRef.current;
306
+ const effect = Logix2.Debug.record({
282
307
  type: "trace:react.moduleTag.resolve",
283
308
  moduleId: resolved.moduleId,
284
309
  instanceId: resolved.instanceId,
285
310
  data: {
286
311
  mode: runtimeContext.policy.moduleTagMode,
287
312
  tokenId,
288
- yieldStrategy: runtimeContext.policy.yield.strategy
313
+ yieldStrategy: runtimeContext.policy.yield.strategy,
314
+ durationMs: trace?.durationMs,
315
+ cacheMode: trace?.cacheMode ?? runtimeContext.policy.moduleTagMode
289
316
  }
290
317
  });
291
- runtime.runFork(effect);
318
+ emitRuntimeDebugEventBestEffort(runtime, effect);
292
319
  }, [runtime, runtimeContext.policy, resolved, handle, isTagHandle]);
293
320
  return resolved;
294
321
  }
295
322
 
296
- // src/internal/store/ModuleRuntimeExternalStore.ts
297
- import { Effect as Effect2, Fiber, Stream } from "effect";
323
+ // src/internal/store/RuntimeExternalStore.ts
324
+ import * as Logix3 from "@logixjs/core";
325
+ import { Fiber, Stream } from "effect";
298
326
  var storesByRuntime = /* @__PURE__ */ new WeakMap();
299
327
  var getStoreMapForRuntime = (runtime) => {
300
328
  const cached = storesByRuntime.get(runtime);
301
329
  if (cached) return cached;
302
- const next = /* @__PURE__ */ new WeakMap();
330
+ const next = /* @__PURE__ */ new Map();
303
331
  storesByRuntime.set(runtime, next);
304
332
  return next;
305
333
  };
306
- var getModuleRuntimeExternalStore = (runtime, moduleRuntime, options) => {
307
- const byModule = getStoreMapForRuntime(runtime);
308
- const cached = byModule.get(moduleRuntime);
334
+ var makeModuleInstanceKey = (moduleId, instanceId) => `${moduleId}::${instanceId}`;
335
+ var makeReadQueryTopicKey = (moduleInstanceKey, selectorId) => `${moduleInstanceKey}::rq:${selectorId}`;
336
+ var getRuntimeStore = (runtime) => Logix3.InternalContracts.getRuntimeStore(runtime);
337
+ var getHostScheduler = (runtime) => Logix3.InternalContracts.getHostScheduler(runtime);
338
+ var getOrCreateStore = (runtime, topicKey, make) => {
339
+ const map = getStoreMapForRuntime(runtime);
340
+ const cached = map.get(topicKey);
309
341
  if (cached) {
310
342
  return cached;
311
343
  }
312
- let currentState;
344
+ const created = make();
345
+ map.set(topicKey, created);
346
+ return created;
347
+ };
348
+ var removeStore = (runtime, topicKey) => {
349
+ const map = storesByRuntime.get(runtime);
350
+ if (!map) return;
351
+ map.delete(topicKey);
352
+ };
353
+ var makeTopicExternalStore = (args) => {
354
+ const { runtime, runtimeStore, topicKey } = args;
355
+ const hostScheduler = getHostScheduler(runtime);
356
+ let currentVersion;
357
+ let hasSnapshot = false;
358
+ let currentSnapshot;
313
359
  const listeners = /* @__PURE__ */ new Set();
314
- const lowPriorityDelayMs = options?.lowPriorityDelayMs ?? 16;
315
- const lowPriorityMaxDelayMs = options?.lowPriorityMaxDelayMs ?? 50;
360
+ let unsubscribeFromRuntimeStore;
361
+ let teardownScheduled = false;
362
+ let teardownToken = 0;
363
+ const lowPriorityDelayMs = args.options?.lowPriorityDelayMs ?? 16;
364
+ const lowPriorityMaxDelayMs = args.options?.lowPriorityMaxDelayMs ?? 50;
316
365
  let notifyScheduled = false;
317
366
  let notifyScheduledLow = false;
318
- let lowTimeoutId;
319
- let lowMaxTimeoutId;
320
- let lowRafId;
367
+ let lowCancelDelay;
368
+ let lowCancelMaxDelay;
369
+ let lowCancelRaf;
321
370
  const cancelLow = () => {
322
371
  if (!notifyScheduledLow) return;
323
372
  notifyScheduledLow = false;
324
- if (lowTimeoutId != null) {
325
- clearTimeout(lowTimeoutId);
326
- lowTimeoutId = void 0;
327
- }
328
- if (lowMaxTimeoutId != null) {
329
- clearTimeout(lowMaxTimeoutId);
330
- lowMaxTimeoutId = void 0;
331
- }
332
- const cancel = globalThis.cancelAnimationFrame;
333
- if (cancel && typeof lowRafId === "number") {
334
- cancel(lowRafId);
335
- lowRafId = void 0;
336
- }
373
+ lowCancelDelay?.();
374
+ lowCancelDelay = void 0;
375
+ lowCancelMaxDelay?.();
376
+ lowCancelMaxDelay = void 0;
377
+ lowCancelRaf?.();
378
+ lowCancelRaf = void 0;
337
379
  };
338
380
  const flushNotify = () => {
339
381
  notifyScheduled = false;
340
382
  cancelLow();
341
383
  for (const listener of listeners) {
342
- listener();
384
+ try {
385
+ listener();
386
+ } catch {
387
+ }
343
388
  }
344
389
  };
345
390
  const scheduleNotify = (priority) => {
@@ -351,209 +396,154 @@ var getModuleRuntimeExternalStore = (runtime, moduleRuntime, options) => {
351
396
  if (!notifyScheduledLow) return;
352
397
  flushNotify();
353
398
  };
354
- const raf = globalThis.requestAnimationFrame;
355
- if (raf) {
356
- lowRafId = raf(flush);
399
+ const scheduleRaf = () => {
400
+ if (!notifyScheduledLow) return;
401
+ lowCancelRaf = hostScheduler.scheduleAnimationFrame(flush);
402
+ };
403
+ if (lowPriorityDelayMs <= 0) {
404
+ scheduleRaf();
357
405
  } else {
358
- lowTimeoutId = setTimeout(flush, lowPriorityDelayMs);
406
+ lowCancelDelay = hostScheduler.scheduleTimeout(lowPriorityDelayMs, scheduleRaf);
359
407
  }
360
- lowMaxTimeoutId = setTimeout(flush, lowPriorityMaxDelayMs);
408
+ lowCancelMaxDelay = hostScheduler.scheduleTimeout(lowPriorityMaxDelayMs, flush);
361
409
  return;
362
410
  }
363
411
  cancelLow();
364
412
  if (notifyScheduled) return;
365
413
  notifyScheduled = true;
366
- queueMicrotask(flushNotify);
414
+ hostScheduler.scheduleMicrotask(flushNotify);
415
+ };
416
+ const onRuntimeStoreChange = () => {
417
+ try {
418
+ scheduleNotify(runtimeStore.getTopicPriority(topicKey));
419
+ } catch {
420
+ }
367
421
  };
368
- let fiber;
369
422
  const ensureSubscription = () => {
370
- if (fiber) return;
371
- fiber = runtime.runFork(
372
- Stream.runForEach(
373
- moduleRuntime.changesWithMeta((state) => state),
374
- ({ value: state, meta }) => Effect2.sync(() => {
375
- currentState = state;
376
- scheduleNotify(meta.priority);
377
- })
378
- )
379
- );
423
+ if (unsubscribeFromRuntimeStore) return;
424
+ unsubscribeFromRuntimeStore = runtimeStore.subscribeTopic(topicKey, onRuntimeStoreChange);
380
425
  };
381
426
  const refreshSnapshotIfStale = () => {
382
- if (currentState === void 0) {
383
- return;
384
- }
427
+ if (!hasSnapshot) return;
385
428
  try {
386
- const latest = runtime.runSync(moduleRuntime.getState);
387
- if (currentState === void 0 || !Object.is(currentState, latest)) {
388
- currentState = latest;
389
- scheduleNotify("normal");
429
+ const version = runtimeStore.getTopicVersion(topicKey);
430
+ if (currentVersion !== version) {
431
+ scheduleNotify(runtimeStore.getTopicPriority(topicKey));
390
432
  }
391
433
  } catch {
392
434
  }
393
435
  };
394
436
  const getSnapshot = () => {
395
- if (currentState !== void 0) return currentState;
396
- currentState = runtime.runSync(moduleRuntime.getState);
397
- return currentState;
398
- };
399
- const subscribe = (listener) => {
400
- listeners.add(listener);
401
- ensureSubscription();
402
- refreshSnapshotIfStale();
403
- return () => {
404
- listeners.delete(listener);
405
- if (listeners.size > 0) return;
406
- const running = fiber;
407
- if (!running) return;
408
- fiber = void 0;
409
- cancelLow();
410
- runtime.runFork(Fiber.interrupt(running));
411
- };
437
+ const version = runtimeStore.getTopicVersion(topicKey);
438
+ if (hasSnapshot && currentVersion === version) {
439
+ return currentSnapshot;
440
+ }
441
+ const next = args.readSnapshot();
442
+ currentVersion = version;
443
+ hasSnapshot = true;
444
+ currentSnapshot = next;
445
+ return next;
412
446
  };
413
- const store = { getSnapshot, subscribe };
414
- byModule.set(moduleRuntime, store);
415
- return store;
416
- };
417
-
418
- // src/internal/store/ModuleRuntimeSelectorExternalStore.ts
419
- import { Effect as Effect3, Fiber as Fiber2, Stream as Stream2 } from "effect";
420
- var storesByRuntime2 = /* @__PURE__ */ new WeakMap();
421
- var getStoreMapForRuntime2 = (runtime) => {
422
- const cached = storesByRuntime2.get(runtime);
423
- if (cached) return cached;
424
- const next = /* @__PURE__ */ new WeakMap();
425
- storesByRuntime2.set(runtime, next);
426
- return next;
427
- };
428
- var getOrCreateSelectorMapForModule = (byModule, moduleRuntime) => {
429
- const cached = byModule.get(moduleRuntime);
430
- if (cached) return cached;
431
- const next = /* @__PURE__ */ new Map();
432
- byModule.set(moduleRuntime, next);
433
- return next;
434
- };
435
- var equalsValue = (readQuery, a, b) => {
436
- if (readQuery.equalsKind === "custom" && typeof readQuery.equals === "function") {
437
- return readQuery.equals(a, b);
438
- }
439
- if (readQuery.equalsKind === "shallowStruct") {
440
- return shallow(a, b);
441
- }
442
- return Object.is(a, b);
443
- };
444
- var getModuleRuntimeSelectorExternalStore = (runtime, moduleRuntime, selectorReadQuery, options) => {
445
- const byModule = getStoreMapForRuntime2(runtime);
446
- const bySelector = getOrCreateSelectorMapForModule(byModule, moduleRuntime);
447
- const cached = bySelector.get(selectorReadQuery.selectorId);
448
- if (cached) {
449
- return cached;
450
- }
451
- let currentValue;
452
- const listeners = /* @__PURE__ */ new Set();
453
- const lowPriorityDelayMs = options?.lowPriorityDelayMs ?? 16;
454
- const lowPriorityMaxDelayMs = options?.lowPriorityMaxDelayMs ?? 50;
455
- let notifyScheduled = false;
456
- let notifyScheduledLow = false;
457
- let lowTimeoutId;
458
- let lowMaxTimeoutId;
459
- let lowRafId;
460
- const cancelLow = () => {
461
- if (!notifyScheduledLow) return;
462
- notifyScheduledLow = false;
463
- if (lowTimeoutId != null) {
464
- clearTimeout(lowTimeoutId);
465
- lowTimeoutId = void 0;
466
- }
467
- if (lowMaxTimeoutId != null) {
468
- clearTimeout(lowMaxTimeoutId);
469
- lowMaxTimeoutId = void 0;
470
- }
471
- const cancel = globalThis.cancelAnimationFrame;
472
- if (cancel && typeof lowRafId === "number") {
473
- cancel(lowRafId);
474
- lowRafId = void 0;
475
- }
447
+ const cancelScheduledTeardown = () => {
448
+ if (!teardownScheduled) return;
449
+ teardownScheduled = false;
450
+ teardownToken += 1;
476
451
  };
477
- const flushNotify = () => {
478
- notifyScheduled = false;
479
- cancelLow();
480
- for (const listener of listeners) {
481
- listener();
482
- }
483
- };
484
- const scheduleNotify = (priority) => {
485
- if (priority === "low") {
486
- if (notifyScheduled) return;
487
- if (notifyScheduledLow) return;
488
- notifyScheduledLow = true;
489
- const flush = () => {
490
- if (!notifyScheduledLow) return;
491
- flushNotify();
492
- };
493
- const raf = globalThis.requestAnimationFrame;
494
- if (raf) {
495
- lowRafId = raf(flush);
496
- } else {
497
- lowTimeoutId = setTimeout(flush, lowPriorityDelayMs);
498
- }
499
- lowMaxTimeoutId = setTimeout(flush, lowPriorityMaxDelayMs);
500
- return;
452
+ const finalizeTeardown = () => {
453
+ if (listeners.size > 0) return;
454
+ try {
455
+ args.onLastListener?.();
456
+ } catch {
501
457
  }
458
+ const unsub = unsubscribeFromRuntimeStore;
459
+ unsubscribeFromRuntimeStore = void 0;
502
460
  cancelLow();
503
- if (notifyScheduled) return;
504
- notifyScheduled = true;
505
- queueMicrotask(flushNotify);
506
- };
507
- let fiber;
508
- const ensureSubscription = () => {
509
- if (fiber) return;
510
- fiber = runtime.runFork(
511
- Stream2.runForEach(
512
- moduleRuntime.changesReadQueryWithMeta(selectorReadQuery),
513
- ({ value, meta }) => Effect3.sync(() => {
514
- currentValue = value;
515
- scheduleNotify(meta.priority);
516
- })
517
- )
518
- );
519
- };
520
- const refreshSnapshotIfStale = () => {
521
- if (currentValue === void 0) {
522
- return;
523
- }
524
461
  try {
525
- const state = runtime.runSync(moduleRuntime.getState);
526
- const next = selectorReadQuery.select(state);
527
- if (currentValue === void 0 || !equalsValue(selectorReadQuery, currentValue, next)) {
528
- currentValue = next;
529
- scheduleNotify("normal");
530
- }
462
+ unsub?.();
531
463
  } catch {
532
464
  }
465
+ removeStore(runtime, topicKey);
533
466
  };
534
- const getSnapshot = () => {
535
- if (currentValue !== void 0) return currentValue;
536
- const state = runtime.runSync(moduleRuntime.getState);
537
- currentValue = selectorReadQuery.select(state);
538
- return currentValue;
467
+ const scheduleTeardown = () => {
468
+ if (teardownScheduled) return;
469
+ teardownScheduled = true;
470
+ const token = ++teardownToken;
471
+ hostScheduler.scheduleMicrotask(() => {
472
+ if (!teardownScheduled || token !== teardownToken) return;
473
+ teardownScheduled = false;
474
+ finalizeTeardown();
475
+ });
539
476
  };
540
477
  const subscribe = (listener) => {
478
+ cancelScheduledTeardown();
479
+ const isFirst = listeners.size === 0;
541
480
  listeners.add(listener);
542
481
  ensureSubscription();
543
482
  refreshSnapshotIfStale();
483
+ if (isFirst) {
484
+ try {
485
+ args.onFirstListener?.();
486
+ } catch {
487
+ }
488
+ }
544
489
  return () => {
545
490
  listeners.delete(listener);
546
491
  if (listeners.size > 0) return;
547
- const running = fiber;
548
- if (!running) return;
549
- fiber = void 0;
550
- cancelLow();
551
- runtime.runFork(Fiber2.interrupt(running));
492
+ scheduleTeardown();
552
493
  };
553
494
  };
554
- const store = { getSnapshot, subscribe };
555
- bySelector.set(selectorReadQuery.selectorId, store);
556
- return store;
495
+ return { getSnapshot, getServerSnapshot: getSnapshot, subscribe };
496
+ };
497
+ var getRuntimeModuleExternalStore = (runtime, moduleRuntime, options) => {
498
+ const moduleInstanceKey = makeModuleInstanceKey(moduleRuntime.moduleId, moduleRuntime.instanceId);
499
+ const runtimeStore = getRuntimeStore(runtime);
500
+ return getOrCreateStore(
501
+ runtime,
502
+ moduleInstanceKey,
503
+ () => makeTopicExternalStore({
504
+ runtime,
505
+ runtimeStore,
506
+ topicKey: moduleInstanceKey,
507
+ readSnapshot: () => {
508
+ const state = runtimeStore.getModuleState(moduleInstanceKey);
509
+ if (state !== void 0) return state;
510
+ return runtime.runSync(moduleRuntime.getState);
511
+ },
512
+ options
513
+ })
514
+ );
515
+ };
516
+ var getRuntimeReadQueryExternalStore = (runtime, moduleRuntime, selectorReadQuery, options) => {
517
+ const moduleInstanceKey = makeModuleInstanceKey(moduleRuntime.moduleId, moduleRuntime.instanceId);
518
+ const topicKey = makeReadQueryTopicKey(moduleInstanceKey, selectorReadQuery.selectorId);
519
+ const runtimeStore = getRuntimeStore(runtime);
520
+ let readQueryDrainFiber;
521
+ return getOrCreateStore(
522
+ runtime,
523
+ topicKey,
524
+ () => makeTopicExternalStore({
525
+ runtime,
526
+ runtimeStore,
527
+ topicKey,
528
+ readSnapshot: () => {
529
+ const state = runtimeStore.getModuleState(moduleInstanceKey);
530
+ const current = state ?? runtime.runSync(moduleRuntime.getState);
531
+ return selectorReadQuery.select(current);
532
+ },
533
+ options,
534
+ onFirstListener: () => {
535
+ if (readQueryDrainFiber) return;
536
+ const effect = Stream.runDrain(moduleRuntime.changesReadQueryWithMeta(selectorReadQuery));
537
+ readQueryDrainFiber = runtime.runFork(effect);
538
+ },
539
+ onLastListener: () => {
540
+ const fiber = readQueryDrainFiber;
541
+ if (!fiber) return;
542
+ readQueryDrainFiber = void 0;
543
+ runtime.runFork(Fiber.interrupt(fiber));
544
+ }
545
+ })
546
+ );
557
547
  };
558
548
 
559
549
  // src/internal/hooks/useSelector.ts
@@ -566,7 +556,7 @@ function useSelector(handle, selector, equalityFn) {
566
556
  const moduleRuntime = useModuleRuntime(handle);
567
557
  const actualSelector = selector ?? ((state) => state);
568
558
  const selectorReadQuery = useMemo3(
569
- () => typeof selector === "function" ? Logix2.ReadQuery.compile(selector) : void 0,
559
+ () => typeof selector === "function" ? Logix4.ReadQuery.compile(selector) : void 0,
570
560
  [selector]
571
561
  );
572
562
  const actualEqualityFn = useMemo3(() => {
@@ -574,17 +564,12 @@ function useSelector(handle, selector, equalityFn) {
574
564
  if (typeof selector !== "function") return Object.is;
575
565
  return selectorReadQuery?.equalsKind === "shallowStruct" ? shallow : Object.is;
576
566
  }, [equalityFn, selector, selectorReadQuery?.equalsKind]);
577
- const useStaticLane = typeof selector === "function" && selectorReadQuery?.lane === "static";
567
+ const selectorTopicEligible = typeof selector === "function" && selectorReadQuery?.lane === "static" && selectorReadQuery.readsDigest != null && selectorReadQuery.fallbackReason == null;
578
568
  const store = useMemo3(
579
- () => useStaticLane && selectorReadQuery ? getModuleRuntimeSelectorExternalStore(
580
- runtime,
581
- moduleRuntime,
582
- selectorReadQuery,
583
- {
584
- lowPriorityDelayMs: runtimeContext.reactConfigSnapshot.lowPriorityDelayMs,
585
- lowPriorityMaxDelayMs: runtimeContext.reactConfigSnapshot.lowPriorityMaxDelayMs
586
- }
587
- ) : getModuleRuntimeExternalStore(
569
+ () => selectorTopicEligible && selectorReadQuery ? getRuntimeReadQueryExternalStore(runtime, moduleRuntime, selectorReadQuery, {
570
+ lowPriorityDelayMs: runtimeContext.reactConfigSnapshot.lowPriorityDelayMs,
571
+ lowPriorityMaxDelayMs: runtimeContext.reactConfigSnapshot.lowPriorityMaxDelayMs
572
+ }) : getRuntimeModuleExternalStore(
588
573
  runtime,
589
574
  moduleRuntime,
590
575
  {
@@ -598,18 +583,18 @@ function useSelector(handle, selector, equalityFn) {
598
583
  runtimeContext.reactConfigSnapshot.lowPriorityDelayMs,
599
584
  runtimeContext.reactConfigSnapshot.lowPriorityMaxDelayMs,
600
585
  selectorReadQuery,
601
- useStaticLane
586
+ selectorTopicEligible
602
587
  ]
603
588
  );
604
589
  const selected = useSyncExternalStoreWithSelector(
605
590
  store.subscribe,
606
591
  store.getSnapshot,
607
- store.getSnapshot,
608
- useStaticLane ? (snapshot) => snapshot : (snapshot) => actualSelector(snapshot),
592
+ store.getServerSnapshot ?? store.getSnapshot,
593
+ selectorTopicEligible ? (snapshot) => snapshot : (snapshot) => actualSelector(snapshot),
609
594
  actualEqualityFn
610
595
  );
611
596
  useEffect3(() => {
612
- if (!isDevEnv() && !Logix2.Debug.isDevtoolsEnabled()) {
597
+ if (!isDevEnv() && !Logix4.Debug.isDevtoolsEnabled()) {
613
598
  return;
614
599
  }
615
600
  const instanceId = moduleRuntime.instanceId;
@@ -625,7 +610,7 @@ function useSelector(handle, selector, equalityFn) {
625
610
  const rawDebugKey = meta.debugKey;
626
611
  selectorKey = typeof rawDebugKey === "string" && rawDebugKey.length > 0 ? rawDebugKey : typeof selector.name === "string" && selector.name.length > 0 ? selector.name : void 0;
627
612
  }
628
- const effect = Logix2.Debug.record({
613
+ const effect = Logix4.Debug.record({
629
614
  type: "trace:react-selector",
630
615
  moduleId: moduleRuntime.moduleId,
631
616
  instanceId,
@@ -649,11 +634,11 @@ function useSelector(handle, selector, equalityFn) {
649
634
 
650
635
  // src/internal/hooks/useModule.ts
651
636
  import React2 from "react";
652
- import * as Logix4 from "@logixjs/core";
653
- import { Context, Effect as Effect4, Layer as Layer2 } from "effect";
637
+ import * as Logix6 from "@logixjs/core";
638
+ import { Effect as Effect4, Layer as Layer2, ServiceMap } from "effect";
654
639
 
655
640
  // src/internal/store/resolveImportedModuleRef.ts
656
- import * as Logix3 from "@logixjs/core";
641
+ import * as Logix5 from "@logixjs/core";
657
642
  var getOrCreateWeakMap = (map, key, make) => {
658
643
  const cached = map.get(key);
659
644
  if (cached) return cached;
@@ -677,7 +662,7 @@ var resolveImportedModuleRef = (runtime, parentRuntime, module) => {
677
662
  if (cached) {
678
663
  return cached;
679
664
  }
680
- const importsScope = Logix3.InternalContracts.getImportsScope(parentRuntime);
665
+ const importsScope = Logix5.InternalContracts.getImportsScope(parentRuntime);
681
666
  const childRuntime = importsScope.get(module);
682
667
  if (childRuntime) {
683
668
  const dispatch = Object.assign(
@@ -763,8 +748,8 @@ var useStableId = () => {
763
748
 
764
749
  // src/internal/hooks/useModule.ts
765
750
  var isModuleImpl = (handle) => Boolean(handle) && typeof handle === "object" && handle._tag === "ModuleImpl";
766
- var isModule = (handle) => Logix4.Module.hasImpl(handle);
767
- var isModuleDef = (handle) => Logix4.Module.is(handle) && handle._kind === "ModuleDef";
751
+ var isModule = (handle) => Logix6.Module.hasImpl(handle);
752
+ var isModuleDef = (handle) => Logix6.Module.is(handle) && handle._kind === "ModuleDef";
768
753
  function useModule(handle, selectorOrOptions, equalityFn) {
769
754
  const runtimeBase = useRuntime();
770
755
  const runtimeContext = React2.useContext(RuntimeContext);
@@ -788,6 +773,7 @@ function useModule(handle, selectorOrOptions, equalityFn) {
788
773
  }
789
774
  }
790
775
  let runtime;
776
+ const moduleImplResolveTraceRef = React2.useRef(void 0);
791
777
  if (isModuleImpl(normalizedHandle)) {
792
778
  const cache = React2.useMemo(
793
779
  () => getModuleCache(runtimeBase, runtimeContext.reactConfigSnapshot, runtimeContext.configVersion),
@@ -816,9 +802,9 @@ function useModule(handle, selectorOrOptions, equalityFn) {
816
802
  const key = depsHash ? `${baseKey}:${depsHash}` : baseKey;
817
803
  const ownerId = moduleId;
818
804
  const baseFactory = React2.useMemo(
819
- () => (scope) => Layer2.buildWithScope(normalizedHandle.layer, scope).pipe(
805
+ () => (scope) => Layer2.buildWithScope(Layer2.fresh(normalizedHandle.layer), scope).pipe(
820
806
  Effect4.map(
821
- (context) => Context.get(context, normalizedHandle.module)
807
+ (context) => ServiceMap.get(context, normalizedHandle.module)
822
808
  )
823
809
  ),
824
810
  [normalizedHandle]
@@ -828,26 +814,56 @@ function useModule(handle, selectorOrOptions, equalityFn) {
828
814
  return baseFactory;
829
815
  }
830
816
  return (scope) => baseFactory(scope).pipe(
831
- Effect4.timeoutFail({
832
- duration: initTimeoutMs,
833
- onTimeout: () => new Error(`[useModule] Module "${ownerId}" initialization timed out after ${initTimeoutMs}ms`)
834
- })
817
+ Effect4.timeoutOption(initTimeoutMs),
818
+ Effect4.flatMap(
819
+ (maybe) => maybe._tag === "Some" ? Effect4.succeed(maybe.value) : Effect4.die(new Error(`[useModule] Module "${ownerId}" initialization timed out after ${initTimeoutMs}ms`))
820
+ )
835
821
  );
836
822
  }, [baseFactory, suspend, initTimeoutMs, ownerId]);
823
+ const moduleResolveStartedAt = performance.now();
837
824
  const moduleRuntime = suspend ? cache.read(key, factory, gcTime, ownerId, {
838
825
  entrypoint: "react.useModule",
839
826
  policyMode: runtimeContext.policy.mode,
840
- yield: runtimeContext.policy.yield
827
+ yield: runtimeContext.policy.yield,
828
+ optimisticSyncBudgetMs: runtimeContext.policy.syncBudgetMs
841
829
  }) : cache.readSync(key, factory, gcTime, ownerId, {
842
830
  entrypoint: "react.useModule",
843
831
  policyMode: runtimeContext.policy.mode,
844
832
  warnSyncBlockingThresholdMs: 5
845
833
  });
834
+ moduleImplResolveTraceRef.current = {
835
+ moduleId,
836
+ cacheMode: suspend ? "suspend" : "sync",
837
+ durationMs: Math.round((performance.now() - moduleResolveStartedAt) * 100) / 100
838
+ };
846
839
  React2.useEffect(() => cache.retain(key), [cache, key]);
847
840
  runtime = moduleRuntime;
848
841
  } else {
849
842
  runtime = useModuleRuntime(normalizedHandle);
850
843
  }
844
+ React2.useEffect(() => {
845
+ if (!isModuleImpl(normalizedHandle)) {
846
+ return;
847
+ }
848
+ const diagnosticsLevel = readRuntimeDiagnosticsLevel(runtimeBase);
849
+ if (diagnosticsLevel === "off") {
850
+ return;
851
+ }
852
+ const trace = moduleImplResolveTraceRef.current;
853
+ if (!trace) {
854
+ return;
855
+ }
856
+ const effect = Logix6.Debug.record({
857
+ type: "trace:react.moduleImpl.resolve",
858
+ moduleId: trace.moduleId,
859
+ instanceId: runtime.instanceId,
860
+ data: {
861
+ cacheMode: trace.cacheMode,
862
+ durationMs: trace.durationMs
863
+ }
864
+ });
865
+ emitRuntimeDebugEventBestEffort(runtimeBase, effect);
866
+ }, [runtimeBase, runtime, normalizedHandle]);
851
867
  React2.useEffect(() => {
852
868
  if (!isModuleImpl(normalizedHandle)) {
853
869
  return;
@@ -857,22 +873,22 @@ function useModule(handle, selectorOrOptions, equalityFn) {
857
873
  if (!label) {
858
874
  return;
859
875
  }
860
- const effect = Logix4.Debug.record({
876
+ const effect = Logix6.Debug.record({
861
877
  type: "trace:instanceLabel",
862
878
  moduleId: normalizedHandle.module.id,
863
879
  instanceId: runtime.instanceId,
864
880
  data: { label }
865
881
  });
866
- runtimeBase.runFork(effect);
882
+ emitRuntimeDebugEventBestEffort(runtimeBase, effect);
867
883
  }, [runtimeBase, runtime, normalizedHandle, options]);
868
884
  React2.useEffect(() => {
869
- if (!isDevEnv() && !Logix4.Debug.isDevtoolsEnabled()) {
885
+ if (!isDevEnv() && !Logix6.Debug.isDevtoolsEnabled()) {
870
886
  return;
871
887
  }
872
888
  if (!runtime.instanceId) {
873
889
  return;
874
890
  }
875
- const effect = Logix4.Debug.record({
891
+ const effect = Logix6.Debug.record({
876
892
  type: "trace:react-render",
877
893
  moduleId: runtime.moduleId,
878
894
  instanceId: runtime.instanceId,
@@ -882,7 +898,7 @@ function useModule(handle, selectorOrOptions, equalityFn) {
882
898
  }
883
899
  });
884
900
  runtimeBase.runFork(effect);
885
- }, [runtimeBase, runtime]);
901
+ });
886
902
  if (selector) {
887
903
  if (isModuleImpl(normalizedHandle)) {
888
904
  return useSelector(runtime, selector, equalityFn);