@logixjs/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +64 -0
  2. package/dist/Hooks.cjs +2194 -0
  3. package/dist/Hooks.d.cts +77 -0
  4. package/dist/Hooks.d.ts +77 -0
  5. package/dist/Hooks.js +27 -0
  6. package/dist/ModuleRef-wZSQ3Wwo.d.cts +73 -0
  7. package/dist/ModuleRef-wZSQ3Wwo.d.ts +73 -0
  8. package/dist/ModuleScope.cjs +2695 -0
  9. package/dist/ModuleScope.d.cts +62 -0
  10. package/dist/ModuleScope.d.ts +62 -0
  11. package/dist/ModuleScope.js +9 -0
  12. package/dist/Platform.cjs +60 -0
  13. package/dist/Platform.d.cts +6 -0
  14. package/dist/Platform.d.ts +6 -0
  15. package/dist/Platform.js +6 -0
  16. package/dist/ReactPlatform.cjs +2813 -0
  17. package/dist/ReactPlatform.d.cts +40 -0
  18. package/dist/ReactPlatform.d.ts +40 -0
  19. package/dist/ReactPlatform.js +11 -0
  20. package/dist/RuntimeProvider.cjs +1618 -0
  21. package/dist/RuntimeProvider.d.cts +66 -0
  22. package/dist/RuntimeProvider.d.ts +66 -0
  23. package/dist/RuntimeProvider.js +8 -0
  24. package/dist/chunk-2WFULYPJ.js +856 -0
  25. package/dist/chunk-4G7H66OY.js +54 -0
  26. package/dist/chunk-G5MRIFKK.js +95 -0
  27. package/dist/chunk-JXAJTWSZ.js +773 -0
  28. package/dist/chunk-PYWHL7TA.js +351 -0
  29. package/dist/chunk-UFFCJGSZ.js +981 -0
  30. package/dist/chunk-VFWWMK67.js +0 -0
  31. package/dist/chunk-ZANGOPUQ.js +34 -0
  32. package/dist/global.d.cjs +1 -0
  33. package/dist/global.d.d.cts +9 -0
  34. package/dist/global.d.d.ts +9 -0
  35. package/dist/global.d.js +0 -0
  36. package/dist/index.cjs +3118 -0
  37. package/dist/index.d.cts +10 -0
  38. package/dist/index.d.ts +10 -0
  39. package/dist/index.js +44 -0
  40. package/dist/useDispatch-BnzYVkRE.d.ts +81 -0
  41. package/dist/useDispatch-CnO5-66H.d.cts +81 -0
  42. package/package.json +58 -0
@@ -0,0 +1,856 @@
1
+ // src/internal/provider/ReactContext.ts
2
+ import { createContext } from "react";
3
+ var RuntimeContext = createContext(null);
4
+
5
+ // src/internal/provider/env.ts
6
+ import { getNodeEnv, isDevEnv } from "@logixjs/core/Env";
7
+
8
+ // src/internal/provider/runtimeBindings.ts
9
+ import { useEffect, useRef, useState } from "react";
10
+ import { Layer, Exit, Context, Effect, Cause, FiberRef, Scope } from "effect";
11
+ import * as Logix from "@logixjs/core";
12
+ var toErrorString = (error) => error instanceof Error ? error.stack ?? error.message : String(error);
13
+ var debugScopeCloseFailure = (error) => {
14
+ if (!isDevEnv()) return;
15
+ console.debug("[RuntimeProvider] Scope.close failed", toErrorString(error));
16
+ };
17
+ var useLayerBinding = (runtime, layer, enabled, onError) => {
18
+ const activeBindingRef = useRef(null);
19
+ const [state, setState] = useState(() => ({
20
+ binding: null,
21
+ isLoading: enabled && !!layer,
22
+ runtime,
23
+ layer,
24
+ enabled
25
+ }));
26
+ useEffect(() => {
27
+ if (!enabled || !layer) {
28
+ const current2 = activeBindingRef.current;
29
+ if (current2) {
30
+ activeBindingRef.current = null;
31
+ void runtime.runPromise(Scope.close(current2.scope, Exit.void)).catch(debugScopeCloseFailure);
32
+ }
33
+ setState((prev) => {
34
+ if (prev.binding === null && prev.isLoading === false && prev.layer === void 0 && prev.enabled === enabled) {
35
+ return prev;
36
+ }
37
+ return {
38
+ binding: null,
39
+ isLoading: false,
40
+ runtime: prev.runtime,
41
+ layer: void 0,
42
+ enabled
43
+ };
44
+ });
45
+ return;
46
+ }
47
+ const current = activeBindingRef.current;
48
+ if (current && current.runtime === runtime && current.layer === layer && current.enabled === enabled) {
49
+ setState({
50
+ binding: current,
51
+ isLoading: false,
52
+ runtime,
53
+ layer,
54
+ enabled
55
+ });
56
+ return;
57
+ }
58
+ const previousBinding = current ?? state.binding;
59
+ if (isDevEnv() && previousBinding && previousBinding.layer !== layer && enabled && layer) {
60
+ console.warn(
61
+ "[RuntimeProvider] Rebuilding layer due to a new layer reference. Memoize the Layer in the caller to avoid repeated rebuilds and resource churn."
62
+ );
63
+ }
64
+ if (current) {
65
+ activeBindingRef.current = null;
66
+ void runtime.runPromise(Scope.close(current.scope, Exit.void)).catch(debugScopeCloseFailure);
67
+ }
68
+ let cancelled = false;
69
+ setState({
70
+ binding: null,
71
+ isLoading: true,
72
+ runtime,
73
+ layer,
74
+ enabled
75
+ });
76
+ const newScope = Effect.runSync(Scope.make());
77
+ const buildEffect = Effect.gen(function* () {
78
+ const context = yield* Layer.buildWithScope(layer, newScope);
79
+ const applyEnv = (effect) => Effect.mapInputContext(
80
+ Scope.extend(effect, newScope),
81
+ (parent) => Context.merge(parent, context)
82
+ );
83
+ const loggers = yield* applyEnv(FiberRef.get(FiberRef.currentLoggers));
84
+ const logLevel = yield* applyEnv(FiberRef.get(FiberRef.currentLogLevel));
85
+ const debugSinks = yield* applyEnv(
86
+ FiberRef.get(Logix.Debug.internal.currentDebugSinks)
87
+ );
88
+ return { context, loggers, logLevel, debugSinks };
89
+ });
90
+ const assignBinding = (result) => {
91
+ if (cancelled) {
92
+ return runtime.runPromise(Scope.close(newScope, Exit.void)).catch(debugScopeCloseFailure);
93
+ }
94
+ const previous = activeBindingRef.current;
95
+ const newBinding = {
96
+ context: result.context,
97
+ loggers: result.loggers,
98
+ logLevel: result.logLevel,
99
+ debugSinks: result.debugSinks,
100
+ scope: newScope,
101
+ runtime,
102
+ layer,
103
+ enabled
104
+ };
105
+ activeBindingRef.current = newBinding;
106
+ setState({
107
+ binding: newBinding,
108
+ isLoading: false,
109
+ runtime,
110
+ layer,
111
+ enabled
112
+ });
113
+ if (previous) {
114
+ return runtime.runPromise(Scope.close(previous.scope, Exit.void)).catch(debugScopeCloseFailure);
115
+ }
116
+ return Promise.resolve();
117
+ };
118
+ let builtSync = false;
119
+ try {
120
+ const result = runtime.runSync(buildEffect);
121
+ builtSync = true;
122
+ void assignBinding(result);
123
+ } catch {
124
+ }
125
+ if (!builtSync) {
126
+ void runtime.runPromise(buildEffect).then(assignBinding).catch((error) => {
127
+ if (onError) {
128
+ const cause = Cause.die(error);
129
+ runtime.runFork(
130
+ onError(cause, { source: "provider", phase: "provider.layer.build" }).pipe(
131
+ Effect.catchAllCause(() => Effect.void)
132
+ )
133
+ );
134
+ }
135
+ void runtime.runPromise(Scope.close(newScope, Exit.void)).catch(debugScopeCloseFailure);
136
+ if (!cancelled) {
137
+ console.error("[RuntimeProvider] Failed to build layer", error);
138
+ setState({
139
+ binding: null,
140
+ isLoading: false,
141
+ runtime,
142
+ layer,
143
+ enabled
144
+ });
145
+ }
146
+ });
147
+ }
148
+ return () => {
149
+ cancelled = true;
150
+ const current2 = activeBindingRef.current;
151
+ if (current2) {
152
+ activeBindingRef.current = null;
153
+ void runtime.runPromise(Scope.close(current2.scope, Exit.void)).catch(debugScopeCloseFailure);
154
+ return;
155
+ }
156
+ void runtime.runPromise(Scope.close(newScope, Exit.void)).catch(debugScopeCloseFailure);
157
+ };
158
+ }, [runtime, layer, enabled, onError]);
159
+ const isCurrentBinding = state.binding !== null && state.runtime === runtime && state.layer === layer && state.enabled === enabled && enabled && !!layer;
160
+ return {
161
+ binding: isCurrentBinding ? state.binding : null,
162
+ isLoading: isCurrentBinding ? state.isLoading : enabled && !!layer
163
+ };
164
+ };
165
+ var createRuntimeAdapter = (runtime, contexts, scopes, loggerSets, logLevels, debugSinks) => {
166
+ if (contexts.length === 0 && scopes.length === 0 && loggerSets.length === 0 && logLevels.length === 0 && debugSinks.length === 0) {
167
+ return runtime;
168
+ }
169
+ const applyContexts = (effect) => (
170
+ // First inherit Provider scopes via scope.extend (preserving FiberRef/Logger changes),
171
+ // then merge Context via mapInputContext (inner overrides outer).
172
+ contexts.reduceRight(
173
+ (acc, ctx) => Effect.mapInputContext(
174
+ acc,
175
+ (parent) => Context.merge(parent, ctx)
176
+ ),
177
+ scopes.reduceRight(
178
+ (acc, scope) => Scope.extend(acc, scope),
179
+ effect
180
+ )
181
+ )
182
+ );
183
+ const applyLoggers = (effect) => {
184
+ const last = loggerSets.length > 0 ? loggerSets[loggerSets.length - 1] : null;
185
+ const logLevel = logLevels.length > 0 ? logLevels[logLevels.length - 1] : null;
186
+ const sinks = debugSinks.length > 0 ? debugSinks[debugSinks.length - 1] : null;
187
+ let result = effect;
188
+ if (last) {
189
+ result = Effect.locally(FiberRef.currentLoggers, last)(result);
190
+ }
191
+ if (logLevel) {
192
+ result = Effect.locally(FiberRef.currentLogLevel, logLevel)(result);
193
+ }
194
+ if (sinks && sinks.length > 0) {
195
+ result = Effect.locally(
196
+ Logix.Debug.internal.currentDebugSinks,
197
+ sinks
198
+ )(result);
199
+ }
200
+ return result;
201
+ };
202
+ const adapted = {
203
+ ...runtime,
204
+ runFork: (effect, options) => runtime.runFork(applyLoggers(applyContexts(effect)), options),
205
+ runPromise: (effect, options) => runtime.runPromise(applyLoggers(applyContexts(effect)), options),
206
+ runPromiseExit: (effect, options) => runtime.runPromiseExit(applyLoggers(applyContexts(effect)), options),
207
+ runSync: (effect) => runtime.runSync(applyLoggers(applyContexts(effect))),
208
+ runSyncExit: (effect) => runtime.runSyncExit(applyLoggers(applyContexts(effect))),
209
+ runCallback: (effect, options) => runtime.runCallback(applyLoggers(applyContexts(effect)), options)
210
+ };
211
+ return adapted;
212
+ };
213
+
214
+ // src/internal/store/ModuleCache.ts
215
+ import * as Logix3 from "@logixjs/core";
216
+ import { Cause as Cause2, Effect as Effect3, Exit as Exit2, Fiber, Option, Scope as Scope2 } from "effect";
217
+
218
+ // src/internal/store/perfWorkloads.ts
219
+ import * as Logix2 from "@logixjs/core";
220
+ import { Effect as Effect2, Schema } from "effect";
221
+ var PerfCounterStateSchema = Schema.Struct({
222
+ value: Schema.Number
223
+ });
224
+ var PerfCounterActions = {
225
+ inc: Schema.Void
226
+ };
227
+ var PerfListScopeRowSchema = Schema.Struct({
228
+ id: Schema.String,
229
+ warehouseId: Schema.String
230
+ });
231
+ var PerfListScopeStateSchema = Schema.Struct({
232
+ items: Schema.Array(PerfListScopeRowSchema),
233
+ digest: Schema.String,
234
+ errors: Schema.Any
235
+ });
236
+ var GLOBAL_YIELD_BUDGET_MEMORY_KEY = "__LOGIX_REACT_YIELD_BUDGET_MEMORY__";
237
+ var getGlobalYieldBudgetMemory = () => {
238
+ const root = globalThis;
239
+ const existing = root[GLOBAL_YIELD_BUDGET_MEMORY_KEY];
240
+ if (existing && existing.byRuntime instanceof WeakMap) {
241
+ return existing;
242
+ }
243
+ const created = { byRuntime: /* @__PURE__ */ new WeakMap() };
244
+ root[GLOBAL_YIELD_BUDGET_MEMORY_KEY] = created;
245
+ return created;
246
+ };
247
+ var getStatsMapForRuntime = (runtime) => {
248
+ const global = getGlobalYieldBudgetMemory();
249
+ const cached = global.byRuntime.get(runtime);
250
+ if (cached) return cached;
251
+ const created = /* @__PURE__ */ new Map();
252
+ global.byRuntime.set(runtime, created);
253
+ return created;
254
+ };
255
+ var quantile = (sorted, q) => {
256
+ if (sorted.length === 0) return Number.NaN;
257
+ const clamped = Math.min(1, Math.max(0, q));
258
+ const idx = Math.floor(clamped * (sorted.length - 1));
259
+ return sorted[idx];
260
+ };
261
+ var p95 = (samplesMs) => {
262
+ if (samplesMs.length === 0) return Number.NaN;
263
+ const sorted = samplesMs.slice().sort((a, b) => a - b);
264
+ return quantile(sorted, 0.95);
265
+ };
266
+ var YieldBudgetMemory = {
267
+ shouldYield(args) {
268
+ const budgetMs = args.policy?.onlyWhenOverBudgetMs;
269
+ if (budgetMs === void 0) {
270
+ return { shouldYield: true, reason: "budgetDisabled", p95Ms: void 0 };
271
+ }
272
+ const byKey = getStatsMapForRuntime(args.runtime);
273
+ const stats = byKey.get(args.workloadKey) ?? { seen: false, samplesMs: [] };
274
+ if (!byKey.has(args.workloadKey)) {
275
+ byKey.set(args.workloadKey, stats);
276
+ }
277
+ if (!stats.seen) {
278
+ stats.seen = true;
279
+ return { shouldYield: true, reason: "firstRun", p95Ms: void 0 };
280
+ }
281
+ if (stats.samplesMs.length === 0) {
282
+ return { shouldYield: true, reason: "insufficientSamples", p95Ms: void 0 };
283
+ }
284
+ const p95Ms = p95(stats.samplesMs);
285
+ if (!Number.isFinite(p95Ms)) {
286
+ return { shouldYield: true, reason: "insufficientSamples", p95Ms: void 0 };
287
+ }
288
+ return p95Ms > budgetMs ? { shouldYield: true, reason: "overBudget", p95Ms } : { shouldYield: false, reason: "underBudget", p95Ms };
289
+ },
290
+ record(args) {
291
+ if (!Number.isFinite(args.durationMs) || args.durationMs < 0) return;
292
+ const byKey = getStatsMapForRuntime(args.runtime);
293
+ const stats = byKey.get(args.workloadKey) ?? { seen: true, samplesMs: [] };
294
+ if (!byKey.has(args.workloadKey)) {
295
+ byKey.set(args.workloadKey, stats);
296
+ }
297
+ stats.samplesMs.push(args.durationMs);
298
+ if (stats.samplesMs.length > 20) {
299
+ stats.samplesMs.shift();
300
+ }
301
+ }
302
+ };
303
+
304
+ // src/internal/store/ModuleCache.ts
305
+ var RUNTIME_CACHE = /* @__PURE__ */ new WeakMap();
306
+ var DEFAULT_GC_DELAY_MS = 500;
307
+ var ERROR_GC_DELAY_MS = 500;
308
+ var DEFAULT_RENDER_SYNC_BLOCKING_WARN_THRESHOLD_MS = 5;
309
+ var toErrorString2 = (error) => {
310
+ if (error instanceof Error) {
311
+ return error.stack ?? error.message;
312
+ }
313
+ return String(error);
314
+ };
315
+ var seenBestEffortFailures = /* @__PURE__ */ new Set();
316
+ var debugBestEffortFailure = (label, error) => {
317
+ if (!isDevEnv()) return;
318
+ const message = toErrorString2(error);
319
+ if (message.includes("ManagedRuntime disposed")) {
320
+ return;
321
+ }
322
+ const key = `${label}
323
+ ${message}`;
324
+ if (seenBestEffortFailures.has(key)) return;
325
+ seenBestEffortFailures.add(key);
326
+ console.debug(label, message);
327
+ };
328
+ var causeToUnknown = (cause) => {
329
+ const failure = Option.getOrUndefined(Cause2.failureOption(cause));
330
+ if (failure !== void 0) return failure;
331
+ const defect = Option.getOrUndefined(Cause2.dieOption(cause));
332
+ if (defect !== void 0) return defect;
333
+ return cause;
334
+ };
335
+ var yieldEffect = (strategy) => {
336
+ switch (strategy) {
337
+ case "none":
338
+ return Effect3.void;
339
+ case "microtask":
340
+ return Effect3.yieldNow();
341
+ case "macrotask":
342
+ return Effect3.promise(
343
+ () => new Promise((resolve) => {
344
+ setTimeout(resolve, 0);
345
+ })
346
+ );
347
+ }
348
+ };
349
+ var decideYieldStrategy = (runtime, workloadKey, policy) => {
350
+ const baseStrategy = policy?.strategy ?? "microtask";
351
+ if (baseStrategy === "none") {
352
+ return { strategy: "none", reason: "disabled" };
353
+ }
354
+ const budgetMs = policy?.onlyWhenOverBudgetMs;
355
+ if (budgetMs === void 0) {
356
+ return { strategy: baseStrategy };
357
+ }
358
+ const decision = YieldBudgetMemory.shouldYield({ runtime, workloadKey, policy });
359
+ return decision.shouldYield ? { strategy: baseStrategy, reason: decision.reason, p95Ms: decision.p95Ms } : { strategy: "none", reason: decision.reason, p95Ms: decision.p95Ms };
360
+ };
361
+ var warnedSyncBlockingKeys = /* @__PURE__ */ new Set();
362
+ var ModuleCache = class {
363
+ constructor(runtime, gcDelayMs = DEFAULT_GC_DELAY_MS) {
364
+ this.runtime = runtime;
365
+ this.gcDelayMs = gcDelayMs;
366
+ this.entries = /* @__PURE__ */ new Map();
367
+ }
368
+ scheduleGC(key, entry) {
369
+ if (entry.gcTimeout) {
370
+ return;
371
+ }
372
+ const delay = entry.gcTime;
373
+ if (!Number.isFinite(delay)) {
374
+ return;
375
+ }
376
+ const timeoutMs = delay <= 0 ? 0 : delay;
377
+ entry.gcTimeout = setTimeout(() => {
378
+ const current = this.entries.get(key);
379
+ if (!current || current !== entry) {
380
+ return;
381
+ }
382
+ current.gcTimeout = void 0;
383
+ if (current.refCount > 0 || current.preloadRefCount > 0) {
384
+ return;
385
+ }
386
+ if (current.status === "pending") {
387
+ this.scheduleGC(key, current);
388
+ return;
389
+ }
390
+ void this.runtime.runPromise(Scope2.close(current.scope, Exit2.void)).catch((error) => {
391
+ debugBestEffortFailure("[ModuleCache] Scope.close failed", error);
392
+ });
393
+ void this.runtime.runPromise(
394
+ Logix3.Debug.record({
395
+ type: "trace:react.module-instance",
396
+ moduleId: current.ownerId,
397
+ instanceId: current.value?.instanceId,
398
+ data: {
399
+ event: "gc",
400
+ key
401
+ }
402
+ })
403
+ ).catch((error) => {
404
+ debugBestEffortFailure("[ModuleCache] Debug.record failed", error);
405
+ });
406
+ this.entries.delete(key);
407
+ }, timeoutMs);
408
+ }
409
+ read(key, factory, gcTime, ownerId, options) {
410
+ const existing = this.entries.get(key);
411
+ if (existing) {
412
+ if (isDevEnv() && existing.ownerId !== void 0 && ownerId !== void 0 && existing.ownerId !== ownerId) {
413
+ console.error(
414
+ "[ModuleCache.read] resource key ownership mismatch:",
415
+ `key="${key}" previously owned by "${existing.ownerId}",`,
416
+ `but now requested by "${ownerId}".`
417
+ );
418
+ throw new Error(
419
+ `[ModuleCache.read] resource key "${key}" has already been claimed by module "${existing.ownerId}", but is now requested by module "${ownerId}". Within the same ManagedRuntime, a given key must not be shared across different ModuleImpl definitions. Please ensure each ModuleImpl uses a distinct key when sharing ModuleRuntime instances.`
420
+ );
421
+ }
422
+ if (existing.status === "pending") {
423
+ throw existing.promise;
424
+ }
425
+ if (existing.status === "error") {
426
+ throw existing.error;
427
+ }
428
+ return existing.value;
429
+ }
430
+ const scope = Effect3.runSync(Scope2.make());
431
+ const workloadKey = `${options?.entrypoint ?? "unknown"}::${ownerId ?? "unknown"}`;
432
+ const yieldDecision = decideYieldStrategy(this.runtime, workloadKey, options?.yield);
433
+ const entry = {
434
+ scope,
435
+ status: "pending",
436
+ // Placeholder; will be replaced immediately with the real Promise.
437
+ promise: Promise.resolve(null),
438
+ refCount: 0,
439
+ preloadRefCount: 0,
440
+ gcTime: gcTime ?? this.gcDelayMs,
441
+ ownerId,
442
+ createdBy: "read",
443
+ workloadKey,
444
+ yieldStrategy: yieldDecision.strategy
445
+ };
446
+ this.scheduleGC(key, entry);
447
+ const startedAt = performance.now();
448
+ const buildEffect = yieldEffect(yieldDecision.strategy).pipe(Effect3.zipRight(factory(scope)));
449
+ const fiber = this.runtime.runFork(buildEffect);
450
+ entry.fiber = fiber;
451
+ const promise = this.runtime.runPromise(Fiber.await(fiber)).then((exit) => {
452
+ if (Exit2.isSuccess(exit)) return exit.value;
453
+ throw causeToUnknown(exit.cause);
454
+ });
455
+ promise.then((value) => {
456
+ entry.status = "success";
457
+ entry.value = value;
458
+ const durationMs = performance.now() - startedAt;
459
+ YieldBudgetMemory.record({ runtime: this.runtime, workloadKey, durationMs });
460
+ if (isDevEnv() || Logix3.Debug.isDevtoolsEnabled()) {
461
+ void this.runtime.runPromise(
462
+ Logix3.Debug.record({
463
+ type: "trace:react.module.init",
464
+ moduleId: ownerId,
465
+ instanceId: value.instanceId,
466
+ data: {
467
+ mode: "suspend",
468
+ key,
469
+ durationMs: Math.round(durationMs * 100) / 100,
470
+ yieldStrategy: yieldDecision.strategy,
471
+ yieldReason: yieldDecision.reason,
472
+ yieldP95Ms: yieldDecision.p95Ms
473
+ }
474
+ })
475
+ ).catch((error) => {
476
+ debugBestEffortFailure("[ModuleCache] Debug.record failed", error);
477
+ });
478
+ void this.runtime.runPromise(
479
+ Logix3.Debug.record({
480
+ type: "trace:react.module-instance",
481
+ moduleId: ownerId,
482
+ instanceId: value.instanceId,
483
+ data: {
484
+ event: "attach",
485
+ key,
486
+ mode: "suspend",
487
+ gcTime: entry.gcTime
488
+ }
489
+ })
490
+ ).catch((error) => {
491
+ debugBestEffortFailure("[ModuleCache] Debug.record failed", error);
492
+ });
493
+ }
494
+ return value;
495
+ }).catch((error) => {
496
+ entry.status = "error";
497
+ entry.error = error;
498
+ entry.gcTime = ERROR_GC_DELAY_MS;
499
+ if (entry.gcTimeout) {
500
+ clearTimeout(entry.gcTimeout);
501
+ entry.gcTimeout = void 0;
502
+ }
503
+ this.scheduleGC(key, entry);
504
+ YieldBudgetMemory.record({
505
+ runtime: this.runtime,
506
+ workloadKey,
507
+ durationMs: performance.now() - startedAt
508
+ });
509
+ void this.runtime.runPromise(Scope2.close(scope, Exit2.fail(error))).catch((closeError) => {
510
+ debugBestEffortFailure("[ModuleCache] Scope.close failed", closeError);
511
+ });
512
+ throw error;
513
+ });
514
+ this.entries.set(key, entry);
515
+ entry.promise = promise;
516
+ throw promise;
517
+ }
518
+ readSync(key, factory, gcTime, ownerId, options) {
519
+ const existing = this.entries.get(key);
520
+ if (existing) {
521
+ if (isDevEnv() && existing.ownerId !== void 0 && ownerId !== void 0 && existing.ownerId !== ownerId) {
522
+ console.error(
523
+ "[ModuleCache.readSync] resource key ownership mismatch:",
524
+ `key="${key}" previously owned by "${existing.ownerId}",`,
525
+ `but now requested by "${ownerId}".`
526
+ );
527
+ throw new Error(
528
+ `[ModuleCache.readSync] resource key "${key}" has already been claimed by module "${existing.ownerId}", but is now requested by module "${ownerId}". Within the same ManagedRuntime, a given key must not be shared across different ModuleImpl definitions. Please ensure each ModuleImpl uses a distinct key when sharing ModuleRuntime instances.`
529
+ );
530
+ }
531
+ if (existing.status === "error") {
532
+ throw existing.error;
533
+ }
534
+ if (existing.status === "pending") {
535
+ throw new Error(
536
+ `[ModuleCache.readSync] encountered pending entry for key="${key}". This usually indicates that the same resource key is being used by both suspend:true (async) and sync consumers. Please either give different keys to async/sync callers, or stick to a single access mode for this resource.`
537
+ );
538
+ }
539
+ return existing.value;
540
+ }
541
+ const scope = this.runtime.runSync(Scope2.make());
542
+ const startedAt = performance.now();
543
+ try {
544
+ const value = this.runtime.runSync(factory(scope));
545
+ const durationMs = performance.now() - startedAt;
546
+ const entry = {
547
+ scope,
548
+ status: "success",
549
+ promise: Promise.resolve(value),
550
+ value,
551
+ refCount: 0,
552
+ preloadRefCount: 0,
553
+ gcTime: gcTime ?? this.gcDelayMs,
554
+ ownerId,
555
+ createdBy: "read",
556
+ workloadKey: `${options?.entrypoint ?? "unknown"}::${ownerId ?? "unknown"}`,
557
+ yieldStrategy: "none"
558
+ };
559
+ this.scheduleGC(key, entry);
560
+ this.entries.set(key, entry);
561
+ if (isDevEnv()) {
562
+ const threshold = options?.warnSyncBlockingThresholdMs ?? DEFAULT_RENDER_SYNC_BLOCKING_WARN_THRESHOLD_MS;
563
+ if (threshold > 0 && durationMs > threshold) {
564
+ const dedupeKey = `${options?.entrypoint ?? "unknown"}::${ownerId ?? "unknown"}::${key}`;
565
+ if (!warnedSyncBlockingKeys.has(dedupeKey)) {
566
+ warnedSyncBlockingKeys.add(dedupeKey);
567
+ const hint = options?.policyMode === "defer" ? 'Fix: \u5F53\u524D policy.mode="defer"\uFF1A\u8BF7\u68C0\u67E5\u8BE5\u6A21\u5757\u662F\u5426\u5728 policy.preload \u5217\u8868\u4E2D\uFF1B\u672A\u9884\u52A0\u8F7D\u7684\u6A21\u5757\u4ECD\u53EF\u80FD\u89E6\u53D1\u4E8C\u6B21 fallback \u6216\u540C\u6B65\u91CD\u6D3B\u3002' : 'Fix: \u5EFA\u8BAE\u5207\u6362\u5230 policy.mode="suspend"\uFF08\u9ED8\u8BA4\uFF09\uFF0C\u5E76\u63D0\u4F9B RuntimeProvider.fallback\uFF1B\u5FC5\u8981\u65F6\u542F\u7528 yield \u7B56\u7565\u3002';
568
+ const example = options?.policyMode === "defer" ? 'Example: <RuntimeProvider policy={{ mode: "defer", preload: [MyImpl] }} fallback={<Loading />}>\u2026</RuntimeProvider>' : 'Example: <RuntimeProvider policy={{ mode: "suspend" }} fallback={<Loading />}>\u2026</RuntimeProvider>';
569
+ const docs = "Docs: apps/docs/content/docs/guide/essentials/react-integration.md";
570
+ console.warn(
571
+ "[Logix][React] Render-phase sync blocking detected",
572
+ `(${Math.round(durationMs * 100) / 100}ms > ${threshold}ms)`,
573
+ "\n",
574
+ `entrypoint=${options?.entrypoint ?? "unknown"}`,
575
+ "\n",
576
+ `ownerId=${ownerId ?? "unknown"}`,
577
+ "\n",
578
+ `key=${key}`,
579
+ "\n",
580
+ hint,
581
+ "\n",
582
+ example,
583
+ "\n",
584
+ docs
585
+ );
586
+ }
587
+ }
588
+ }
589
+ if (isDevEnv() || Logix3.Debug.isDevtoolsEnabled()) {
590
+ void this.runtime.runPromise(
591
+ Logix3.Debug.record({
592
+ type: "trace:react.module.init",
593
+ moduleId: ownerId,
594
+ instanceId: value.instanceId,
595
+ data: {
596
+ mode: "sync",
597
+ key,
598
+ durationMs: Math.round(durationMs * 100) / 100,
599
+ yieldStrategy: "none"
600
+ }
601
+ })
602
+ ).catch((error) => {
603
+ debugBestEffortFailure("[ModuleCache] Debug.record failed", error);
604
+ });
605
+ void this.runtime.runPromise(
606
+ Logix3.Debug.record({
607
+ type: "trace:react.module-instance",
608
+ moduleId: ownerId,
609
+ instanceId: value.instanceId,
610
+ data: {
611
+ event: "attach",
612
+ key,
613
+ mode: "sync",
614
+ gcTime: entry.gcTime
615
+ }
616
+ })
617
+ ).catch((error) => {
618
+ debugBestEffortFailure("[ModuleCache] Debug.record failed", error);
619
+ });
620
+ }
621
+ return value;
622
+ } catch (error) {
623
+ void this.runtime.runPromise(Scope2.close(scope, Exit2.fail(error))).catch((closeError) => {
624
+ debugBestEffortFailure("[ModuleCache] Scope.close failed", closeError);
625
+ });
626
+ const entry = {
627
+ scope,
628
+ status: "error",
629
+ promise: Promise.reject(error),
630
+ error,
631
+ refCount: 0,
632
+ preloadRefCount: 0,
633
+ gcTime: ERROR_GC_DELAY_MS,
634
+ ownerId,
635
+ createdBy: "read",
636
+ workloadKey: `${options?.entrypoint ?? "unknown"}::${ownerId ?? "unknown"}`,
637
+ yieldStrategy: "none"
638
+ };
639
+ this.scheduleGC(key, entry);
640
+ this.entries.set(key, entry);
641
+ throw error;
642
+ }
643
+ }
644
+ preload(key, factory, options) {
645
+ const existing = this.entries.get(key);
646
+ if (existing) {
647
+ if (existing.status === "success") {
648
+ existing.preloadRefCount += 1;
649
+ return {
650
+ promise: Promise.resolve(existing.value),
651
+ cancel: () => {
652
+ this.cancelPreload(key, existing);
653
+ }
654
+ };
655
+ }
656
+ if (existing.status === "error") {
657
+ return { promise: Promise.reject(existing.error), cancel: () => {
658
+ } };
659
+ }
660
+ existing.preloadRefCount += 1;
661
+ return {
662
+ promise: existing.promise,
663
+ cancel: () => {
664
+ this.cancelPreload(key, existing);
665
+ }
666
+ };
667
+ }
668
+ const scope = Effect3.runSync(Scope2.make());
669
+ const ownerId = options?.ownerId;
670
+ const gcTime = options?.gcTime ?? this.gcDelayMs;
671
+ const workloadKey = `${options?.entrypoint ?? "unknown"}::${ownerId ?? "unknown"}`;
672
+ const yieldDecision = decideYieldStrategy(this.runtime, workloadKey, options?.yield);
673
+ const entry = {
674
+ scope,
675
+ status: "pending",
676
+ promise: Promise.resolve(null),
677
+ refCount: 0,
678
+ preloadRefCount: 1,
679
+ gcTime,
680
+ ownerId,
681
+ createdBy: "preload",
682
+ workloadKey,
683
+ yieldStrategy: yieldDecision.strategy
684
+ };
685
+ this.scheduleGC(key, entry);
686
+ this.entries.set(key, entry);
687
+ const startedAt = performance.now();
688
+ const buildEffect = yieldEffect(yieldDecision.strategy).pipe(Effect3.zipRight(factory(scope)));
689
+ const fiber = this.runtime.runFork(buildEffect);
690
+ entry.fiber = fiber;
691
+ const promise = this.runtime.runPromise(Fiber.await(fiber)).then((exit) => {
692
+ if (Exit2.isSuccess(exit)) return exit.value;
693
+ throw causeToUnknown(exit.cause);
694
+ });
695
+ entry.promise = promise;
696
+ void promise.then((value) => {
697
+ entry.status = "success";
698
+ entry.value = value;
699
+ YieldBudgetMemory.record({
700
+ runtime: this.runtime,
701
+ workloadKey,
702
+ durationMs: performance.now() - startedAt
703
+ });
704
+ }).catch((error) => {
705
+ entry.status = "error";
706
+ entry.error = error;
707
+ entry.gcTime = ERROR_GC_DELAY_MS;
708
+ if (entry.gcTimeout) {
709
+ clearTimeout(entry.gcTimeout);
710
+ entry.gcTimeout = void 0;
711
+ }
712
+ this.scheduleGC(key, entry);
713
+ YieldBudgetMemory.record({
714
+ runtime: this.runtime,
715
+ workloadKey,
716
+ durationMs: performance.now() - startedAt
717
+ });
718
+ void this.runtime.runPromise(Scope2.close(scope, Exit2.fail(error))).catch((closeError) => {
719
+ debugBestEffortFailure("[ModuleCache] Scope.close failed", closeError);
720
+ });
721
+ });
722
+ return {
723
+ promise,
724
+ cancel: () => {
725
+ this.cancelPreload(key, entry);
726
+ }
727
+ };
728
+ }
729
+ cancelPreload(key, entry) {
730
+ const current = this.entries.get(key);
731
+ if (!current || current !== entry) {
732
+ return;
733
+ }
734
+ entry.preloadRefCount = Math.max(0, entry.preloadRefCount - 1);
735
+ if (entry.preloadRefCount > 0) {
736
+ return;
737
+ }
738
+ if (entry.refCount > 0) {
739
+ return;
740
+ }
741
+ if (entry.status === "pending") {
742
+ if (entry.createdBy !== "preload") {
743
+ this.scheduleGC(key, entry);
744
+ return;
745
+ }
746
+ const running = entry.fiber;
747
+ entry.fiber = void 0;
748
+ this.entries.delete(key);
749
+ if (running) {
750
+ this.runtime.runFork(Fiber.interrupt(running));
751
+ }
752
+ void this.runtime.runPromise(Scope2.close(entry.scope, Exit2.void)).catch((closeError) => {
753
+ debugBestEffortFailure("[ModuleCache] Scope.close failed", closeError);
754
+ });
755
+ return;
756
+ }
757
+ if (entry.gcTimeout) {
758
+ clearTimeout(entry.gcTimeout);
759
+ entry.gcTimeout = void 0;
760
+ }
761
+ this.scheduleGC(key, entry);
762
+ }
763
+ /**
764
+ * Declares ownership of a resource after component commit.
765
+ * The returned function releases the reference during cleanup.
766
+ */
767
+ retain(key) {
768
+ const entry = this.entries.get(key);
769
+ if (!entry) {
770
+ return () => {
771
+ };
772
+ }
773
+ entry.refCount += 1;
774
+ if (entry.gcTimeout) {
775
+ clearTimeout(entry.gcTimeout);
776
+ entry.gcTimeout = void 0;
777
+ }
778
+ return () => {
779
+ this.release(key);
780
+ };
781
+ }
782
+ release(key) {
783
+ const entry = this.entries.get(key);
784
+ if (!entry) {
785
+ return;
786
+ }
787
+ entry.refCount -= 1;
788
+ if (entry.refCount > 0) {
789
+ return;
790
+ }
791
+ this.scheduleGC(key, entry);
792
+ }
793
+ dispose() {
794
+ for (const [key, entry] of this.entries) {
795
+ if (entry.gcTimeout) {
796
+ clearTimeout(entry.gcTimeout);
797
+ }
798
+ void this.runtime.runPromise(Scope2.close(entry.scope, Exit2.void)).catch((error) => {
799
+ debugBestEffortFailure("[ModuleCache] Scope.close failed", error);
800
+ });
801
+ this.entries.delete(key);
802
+ }
803
+ }
804
+ };
805
+ var getModuleCache = (runtime, config, version) => {
806
+ const cached = RUNTIME_CACHE.get(runtime);
807
+ if (cached && cached.version === version) {
808
+ return cached.cache;
809
+ }
810
+ if (cached) {
811
+ cached.cache.dispose();
812
+ }
813
+ const cache = new ModuleCache(runtime, config.gcTime);
814
+ RUNTIME_CACHE.set(runtime, { version, cache });
815
+ return cache;
816
+ };
817
+ var hashOf = (value) => {
818
+ if (value === null) {
819
+ return "null";
820
+ }
821
+ const type = typeof value;
822
+ if (type === "string") {
823
+ return `s:${value}`;
824
+ }
825
+ if (type === "number") {
826
+ return `n:${value}`;
827
+ }
828
+ if (type === "boolean") {
829
+ return `b:${value}`;
830
+ }
831
+ return `${type}:${Object.prototype.toString.call(value)}`;
832
+ };
833
+ var stableHash = (deps) => {
834
+ if (isDevEnv()) {
835
+ const hasNonPrimitive = deps.some((value) => {
836
+ if (value === null) return false;
837
+ const type = typeof value;
838
+ return type === "object" || type === "function";
839
+ });
840
+ if (hasNonPrimitive) {
841
+ console.warn(
842
+ "[ModuleCache] deps contains non-primitive values. stableHash() only distinguishes primitives (string/number/boolean/null/undefined); object/function entries may not trigger expected cache updates. Consider passing explicit primitive deps or controlling invalidation via `key`."
843
+ );
844
+ }
845
+ }
846
+ return deps.map(hashOf).join("|");
847
+ };
848
+
849
+ export {
850
+ RuntimeContext,
851
+ isDevEnv,
852
+ useLayerBinding,
853
+ createRuntimeAdapter,
854
+ getModuleCache,
855
+ stableHash
856
+ };