@xmachines/play-react 1.0.0-beta.3 → 1.0.0-beta.30
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.
- package/LICENSE +21 -0
- package/README.md +234 -266
- package/dist/PlayErrorBoundary.d.ts +55 -0
- package/dist/PlayErrorBoundary.d.ts.map +1 -0
- package/dist/PlayErrorBoundary.js +45 -0
- package/dist/PlayErrorBoundary.js.map +1 -0
- package/dist/PlayRenderer.d.ts +19 -28
- package/dist/PlayRenderer.d.ts.map +1 -1
- package/dist/PlayRenderer.js +61 -45
- package/dist/PlayRenderer.js.map +1 -1
- package/dist/errors.d.ts +23 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +50 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/useActor.d.ts +26 -0
- package/dist/useActor.d.ts.map +1 -0
- package/dist/useActor.js +29 -0
- package/dist/useActor.js.map +1 -0
- package/dist/useSignalEffect.d.ts +6 -0
- package/dist/useSignalEffect.d.ts.map +1 -1
- package/dist/useSignalEffect.js +36 -19
- package/dist/useSignalEffect.js.map +1 -1
- package/package.json +29 -12
|
@@ -51,6 +51,12 @@
|
|
|
51
51
|
* **Implementation note:** We wrap the callback in Signal.Computed because
|
|
52
52
|
* Signal.subtle.Watcher cannot automatically track arbitrary function calls.
|
|
53
53
|
* The Computed handles dependency tracking, and the Watcher monitors it.
|
|
54
|
+
*
|
|
55
|
+
* **Memory safety (Phase 29):**
|
|
56
|
+
* - `disposed` flag prevents post-cleanup callback execution: if cleanup is
|
|
57
|
+
* called before a pending microtask fires, the microtask returns early.
|
|
58
|
+
* - `needsEnqueue` guard dedups rapid synchronous signal changes: only one
|
|
59
|
+
* microtask is ever queued per batch of synchronous mutations.
|
|
54
60
|
*/
|
|
55
61
|
export declare const useSignalEffect: (callback: () => void | (() => void)) => void;
|
|
56
62
|
//# sourceMappingURL=useSignalEffect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSignalEffect.d.ts","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH
|
|
1
|
+
{"version":3,"file":"useSignalEffect.d.ts","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,KAAG,IA+DrE,CAAC"}
|
package/dist/useSignalEffect.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
-
import { useEffect, useReducer } from "react";
|
|
6
|
+
import { useEffect, useReducer, useRef } from "react";
|
|
7
7
|
import { Signal } from "@xmachines/play-signals";
|
|
8
8
|
/**
|
|
9
9
|
* Marker symbol returned by effect Computed to satisfy type requirements
|
|
@@ -58,39 +58,54 @@ const EFFECT_RUN_MARKER = Symbol("effect-run");
|
|
|
58
58
|
* **Implementation note:** We wrap the callback in Signal.Computed because
|
|
59
59
|
* Signal.subtle.Watcher cannot automatically track arbitrary function calls.
|
|
60
60
|
* The Computed handles dependency tracking, and the Watcher monitors it.
|
|
61
|
+
*
|
|
62
|
+
* **Memory safety (Phase 29):**
|
|
63
|
+
* - `disposed` flag prevents post-cleanup callback execution: if cleanup is
|
|
64
|
+
* called before a pending microtask fires, the microtask returns early.
|
|
65
|
+
* - `needsEnqueue` guard dedups rapid synchronous signal changes: only one
|
|
66
|
+
* microtask is ever queued per batch of synchronous mutations.
|
|
61
67
|
*/
|
|
62
68
|
export const useSignalEffect = (callback) => {
|
|
69
|
+
// Store callback in a ref so the effect closure always calls the latest version
|
|
70
|
+
// WITHOUT triggering watcher teardown/re-setup on each render (standard React ref pattern)
|
|
71
|
+
const callbackRef = useRef(callback);
|
|
72
|
+
// Assignment outside useEffect keeps ref current without triggering re-effect
|
|
73
|
+
callbackRef.current = callback;
|
|
63
74
|
// Force re-render when signal changes (React needs state change to re-render)
|
|
64
75
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
65
76
|
useEffect(() => {
|
|
66
77
|
let cleanup;
|
|
67
78
|
let needsEnqueue = true;
|
|
79
|
+
let disposed = false;
|
|
68
80
|
// Wrap callback in a Computed to automatically track dependencies
|
|
69
81
|
// The Computed will re-evaluate when any accessed signal changes
|
|
70
82
|
const effect = new Signal.Computed(() => {
|
|
71
83
|
// Run cleanup from previous effect
|
|
72
|
-
cleanup
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
if (typeof cleanup === "function")
|
|
85
|
+
cleanup();
|
|
86
|
+
// Run user callback via ref (always latest version, no dep churn)
|
|
87
|
+
cleanup = callbackRef.current();
|
|
75
88
|
// Return marker value (Computed requires a return value)
|
|
76
89
|
return EFFECT_RUN_MARKER;
|
|
77
90
|
});
|
|
78
91
|
// Create watcher to detect when Computed needs re-evaluation
|
|
79
92
|
const watcher = new Signal.subtle.Watcher(() => {
|
|
80
93
|
// Batching flag prevents multiple microtasks for rapid changes
|
|
81
|
-
if (needsEnqueue)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
if (disposed || !needsEnqueue)
|
|
95
|
+
return;
|
|
96
|
+
needsEnqueue = false;
|
|
97
|
+
// Schedule microtask to batch updates (prevent React thrashing)
|
|
98
|
+
queueMicrotask(() => {
|
|
99
|
+
if (disposed)
|
|
100
|
+
return;
|
|
101
|
+
needsEnqueue = true;
|
|
102
|
+
// Re-evaluate the Computed (runs callback with new signal values)
|
|
103
|
+
effect.get();
|
|
104
|
+
// Force React re-render
|
|
105
|
+
forceUpdate();
|
|
106
|
+
// Re-watch for next change
|
|
107
|
+
watcher.watch();
|
|
108
|
+
});
|
|
94
109
|
});
|
|
95
110
|
// Watch the Computed signal
|
|
96
111
|
watcher.watch(effect);
|
|
@@ -98,10 +113,12 @@ export const useSignalEffect = (callback) => {
|
|
|
98
113
|
effect.get();
|
|
99
114
|
// Cleanup on unmount
|
|
100
115
|
return () => {
|
|
101
|
-
|
|
116
|
+
disposed = true;
|
|
117
|
+
if (typeof cleanup === "function")
|
|
118
|
+
cleanup();
|
|
102
119
|
// Unwatch to stop receiving signal updates
|
|
103
120
|
watcher.unwatch(effect);
|
|
104
121
|
};
|
|
105
|
-
}, [
|
|
122
|
+
}, []); // ← [] dep array: watcher created ONCE per mount, not once per render
|
|
106
123
|
};
|
|
107
124
|
//# sourceMappingURL=useSignalEffect.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSignalEffect.js","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useSignalEffect.js","sourceRoot":"","sources":["../src/useSignalEffect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD;;;GAGG;AACH,MAAM,iBAAiB,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAmC,EAAQ,EAAE;IAC5E,gFAAgF;IAChF,2FAA2F;IAC3F,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrC,8EAA8E;IAC9E,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;IAE/B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,WAAW,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,OAA4B,CAAC;QACjC,IAAI,YAAY,GAAG,IAAI,CAAC;QACxB,IAAI,QAAQ,GAAG,KAAK,CAAC;QAErB,kEAAkE;QAClE,iEAAiE;QACjE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;YACvC,mCAAmC;YACnC,IAAI,OAAO,OAAO,KAAK,UAAU;gBAAE,OAAO,EAAE,CAAC;YAE7C,kEAAkE;YAClE,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC;YAEhC,yDAAyD;YACzD,OAAO,iBAAiB,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,6DAA6D;QAC7D,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9C,+DAA+D;YAC/D,IAAI,QAAQ,IAAI,CAAC,YAAY;gBAAE,OAAO;YACtC,YAAY,GAAG,KAAK,CAAC;YACrB,gEAAgE;YAChE,cAAc,CAAC,GAAG,EAAE;gBACnB,IAAI,QAAQ;oBAAE,OAAO;gBACrB,YAAY,GAAG,IAAI,CAAC;gBAEpB,kEAAkE;gBAClE,MAAM,CAAC,GAAG,EAAE,CAAC;gBAEb,wBAAwB;gBACxB,WAAW,EAAE,CAAC;gBAEd,2BAA2B;gBAC3B,OAAO,CAAC,KAAK,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,4BAA4B;QAC5B,OAAO,CAAC,KAAK,CAAC,MAAkC,CAAC,CAAC;QAElD,yCAAyC;QACzC,MAAM,CAAC,GAAG,EAAE,CAAC;QAEb,qBAAqB;QACrB,OAAO,GAAG,EAAE;YACX,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,OAAO,OAAO,KAAK,UAAU;gBAAE,OAAO,EAAE,CAAC;YAC7C,2CAA2C;YAC3C,OAAO,CAAC,OAAO,CAAC,MAAkC,CAAC,CAAC;QACrD,CAAC,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,sEAAsE;AAC/E,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-react",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.30",
|
|
4
4
|
"description": "React renderer for XMachines Play architecture with signal-driven rendering",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"actor",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"type": "module",
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
|
+
"source": "./src/index.ts",
|
|
29
30
|
"types": "./dist/index.d.ts",
|
|
30
31
|
"import": "./dist/index.js"
|
|
31
32
|
}
|
|
@@ -35,30 +36,46 @@
|
|
|
35
36
|
},
|
|
36
37
|
"scripts": {
|
|
37
38
|
"build": "tsc --build",
|
|
38
|
-
"clean": "rm -rf dist *.tsbuildinfo",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
39
|
+
"clean": "rm -rf dist *.tsbuildinfo coverage .vitest-attachments test/browser/__screenshots__ node_modules/.vite*",
|
|
40
|
+
"lint": "oxlint .",
|
|
41
|
+
"format": "oxfmt .",
|
|
42
|
+
"test": "vitest",
|
|
41
43
|
"test:vitest": "vitest run",
|
|
42
|
-
"test:browser": "vitest
|
|
44
|
+
"test:browser": "vitest --browser.enabled --browser.name=chromium test/browser",
|
|
43
45
|
"prepublishOnly": "npm run build"
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
46
|
-
"@xmachines/play
|
|
47
|
-
"@xmachines/play-
|
|
48
|
-
"@xmachines/play-signals": "1.0.0-beta.
|
|
48
|
+
"@xmachines/play": "1.0.0-beta.30",
|
|
49
|
+
"@xmachines/play-actor": "1.0.0-beta.30",
|
|
50
|
+
"@xmachines/play-signals": "1.0.0-beta.30"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
53
|
+
"@json-render/core": "^0.16.0",
|
|
54
|
+
"@json-render/react": "^0.16.0",
|
|
55
|
+
"@json-render/xstate": "^0.16.0",
|
|
56
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
51
57
|
"@testing-library/react": "^16.3.2",
|
|
52
58
|
"@types/node": "^25.5.0",
|
|
53
59
|
"@types/react": "^19.2.14",
|
|
54
60
|
"@types/react-dom": "^19.2.3",
|
|
55
|
-
"
|
|
61
|
+
"@xmachines/shared": "1.0.0-beta.30",
|
|
62
|
+
"@xstate/store": ">=3.17.0",
|
|
63
|
+
"jsdom": "^29.0.1",
|
|
64
|
+
"oxfmt": "^0.43.0",
|
|
65
|
+
"oxlint": "^1.57.0",
|
|
56
66
|
"react": "^19.2.4",
|
|
57
67
|
"react-dom": "^19.2.4",
|
|
58
|
-
"typescript": "^5.9.3"
|
|
68
|
+
"typescript": "^5.9.3 || ^6.0.2",
|
|
69
|
+
"vitest": "^4.1.2",
|
|
70
|
+
"xstate": "^5.30.0"
|
|
59
71
|
},
|
|
60
72
|
"peerDependencies": {
|
|
61
|
-
"
|
|
62
|
-
"react
|
|
73
|
+
"@json-render/core": "^0.16.0",
|
|
74
|
+
"@json-render/react": "^0.16.0",
|
|
75
|
+
"@json-render/xstate": "^0.16.0",
|
|
76
|
+
"@xstate/store": ">=3.17.0",
|
|
77
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
78
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
79
|
+
"xstate": "^5.30.0"
|
|
63
80
|
}
|
|
64
81
|
}
|