@stream44.studio/encapsulate 0.4.0-rc.23 → 0.4.0-rc.25
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/README.md +14 -5
- package/docs/SourceToGraphIsomorphism.drawio +100 -0
- package/docs/SourceToGraphIsomorphism.svg +1 -0
- package/package.json +1 -1
- package/src/capsule-projectors/CapsuleModuleProjector.v0.ts +15 -1
- package/src/encapsulate.ts +58 -5
- package/src/spine-contracts/CapsuleSpineContract.v0/Membrane.v0.ts +99 -74
- package/src/spine-contracts/CapsuleSpineContract.v0/Static.v0.ts +46 -4
- package/src/spine-factories/CapsuleSpineFactory.v0.ts +127 -1
- package/src/static-analyzer.v0.ts +105 -9
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<td><a href="https://Stream44.Studio"><img src=".o/stream44.studio/assets/Icon-v1.svg" width="42" height="42"></a></td>
|
|
4
4
|
<td><strong><a href="https://Stream44.Studio">Stream44 Studio</a></strong><br/>Open Development Project</td>
|
|
5
5
|
<td>Preview release for community feedback.<br/>Get in touch on <a href="https://discord.gg/9eBcQXEJAN">discord</a>.</td>
|
|
6
|
+
<td>Hand Designed<br/><b>AI Coded Alpha</a></td>
|
|
6
7
|
</tr>
|
|
7
8
|
</table>
|
|
8
9
|
|
|
@@ -16,6 +17,7 @@ An *experimental* implementation of the [PrivateData.Space](https://privatedata.
|
|
|
16
17
|
***NOTE:** Not intended for direct use until it matures in light of the projects below.*
|
|
17
18
|
|
|
18
19
|
It is being used to underpin:
|
|
20
|
+
- [Framespace Genesis](https://github.com/Stream44/FramespaceGenesis) - Modeling engine with realtime interactive visualization
|
|
19
21
|
- [t44](https://github.com/Stream44/t44) - A web3 + AI ready workspace
|
|
20
22
|
- [Stream44.Studio](https://stream44.studio) - A **full-stack IDE** for building **embodied distributed systems**
|
|
21
23
|
|
|
@@ -25,13 +27,23 @@ It is being used to underpin:
|
|
|
25
27
|
<br/><br/>
|
|
26
28
|
</p>
|
|
27
29
|
|
|
30
|
+
### Why
|
|
31
|
+
|
|
32
|
+
When encoding a software system as a body (complete model), the body is structured through the declarative semantic schema and animated by functional processing in nodes.
|
|
33
|
+
|
|
34
|
+
We need a minimal abstraction to conveniently author graph processing nodes.
|
|
35
|
+
|
|
36
|
+
`encapsulate` provides such a primitive building block.
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
|
|
28
41
|
The CAPSULE Spine Contract
|
|
29
42
|
---
|
|
30
43
|
|
|
31
44
|
The `encapsulate` library wraps TypeScript objects and binds reference trees for constructing executable component graphs.
|
|
32
45
|
|
|
33
|
-
The binding rules are defined by **Spine Contracts**. The first *experimental* spine contract is the **Capsule Spine Contract**. It builds a model
|
|
34
|
-
around **Capsules** which have certain properties.
|
|
46
|
+
The binding rules are defined by **Spine Contracts**. The first *experimental* spine contract is the **Capsule Spine Contract**. It builds a model around **Capsules** which have certain properties.
|
|
35
47
|
|
|
36
48
|
The capsule spine contract is implemented here: [src/spine-contracts/CapsuleSpineContract.v0/](src/spine-contracts/CapsuleSpineContract.v0/)
|
|
37
49
|
|
|
@@ -42,9 +54,6 @@ The capsule spine contract is implemented here: [src/spine-contracts/CapsuleSpin
|
|
|
42
54
|
- [ ] Capsule Projectors
|
|
43
55
|
- [ ] Load capsules from packs
|
|
44
56
|
|
|
45
|
-

|
|
46
|
-
|
|
47
|
-
|
|
48
57
|
Provenance
|
|
49
58
|
===
|
|
50
59
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<mxfile host="65bd71144e">
|
|
2
|
+
<diagram name="Page-1" id="QeIE7gKKPfThrfCM8ygm">
|
|
3
|
+
<mxGraphModel dx="1145" dy="1728" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" math="0" shadow="0">
|
|
4
|
+
<root>
|
|
5
|
+
<mxCell id="0"/>
|
|
6
|
+
<mxCell id="1" parent="0"/>
|
|
7
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-29" value="" style="rounded=0;whiteSpace=wrap;html=1;movable=1;resizable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" parent="1" vertex="1">
|
|
8
|
+
<mxGeometry x="10" y="-840" width="1070" height="480" as="geometry"/>
|
|
9
|
+
</mxCell>
|
|
10
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-33" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;strokeWidth=2;" parent="1" source="N3hJ6-WijfH2HfO5JL5S-28" target="N3hJ6-WijfH2HfO5JL5S-25" edge="1">
|
|
11
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
12
|
+
</mxCell>
|
|
13
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-28" value="Source Code Module" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;dashed=1;" parent="1" vertex="1">
|
|
14
|
+
<mxGeometry x="30" y="-800" width="570" height="420" as="geometry"/>
|
|
15
|
+
</mxCell>
|
|
16
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-25" value="Semantic Graph Module" style="rounded=0;whiteSpace=wrap;html=1;dashed=1;verticalAlign=top;" parent="1" vertex="1">
|
|
17
|
+
<mxGeometry x="660" y="-800" width="390" height="420" as="geometry"/>
|
|
18
|
+
</mxCell>
|
|
19
|
+
<mxCell id="o5PIcx3FIGXJDux8oxUI-1" value="const <font style="color: rgb(0, 102, 204);">capsule</font> = await Capsule({<div>&nbsp; '<font style="color: rgb(204, 102, 0);">#@&lt;org&gt;/&lt;project&gt;/structs/StructA</font>': {</div><div>&nbsp; &nbsp; as:&nbsp;<span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span><font style="color: rgb(153, 51, 255);"><span style="background-color: transparent;">$#</span><span style="background-color: transparent;">@&lt;org&gt;/&lt;project&gt;/AspectX</span></font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span></div><div>&nbsp; }</div><div>&nbsp; '#'{&nbsp;</div><div>&nbsp; &nbsp; <font style="color: rgb(0, 153, 0);">property1</font>: 'val1',</div><div>&nbsp; &nbsp; <font style="color: rgb(204, 0, 102);">mapping1</font>: '<span style="background-color: transparent;"><font style="color: rgb(204, 0, 102);">@&lt;org&gt;/&lt;project&gt;/FeatureY</font></span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">',</span></div><div>&nbsp; &nbsp; <font style="color: rgb(0, 153, 0);">method1</font>: () =&gt; {</div><div><div>&nbsp; &nbsp; &nbsp; this.<span style="background-color: transparent;"><font style="color: light-dark(rgb(204, 0, 102), rgb(255, 255, 255));">mapping1</font></span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">.featureMethod()</span></div></div><div>&nbsp; &nbsp; &nbsp; this['<font style="color: rgb(153, 51, 255);"><span style="background-color: transparent;">$#</span><span style="background-color: transparent;">@&lt;org&gt;/&lt;project&gt;/AspectX</span></font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">]</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">.instanceMethod2()</span></div><div>&nbsp; &nbsp; }</div><div>&nbsp; }<br>}, {<div>&nbsp; capsuleName: '<font style="color: rgb(0, 102, 204);"><span style="background-color: transparent;">@&lt;org&gt;/&lt;project&gt;/</span><span style="background-color: transparent;">MyCapsule</span></font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span></div><div><div>})</div></div></div><div><br></div><div><font style="color: rgb(0, 102, 204);">capsule</font>.<span style="background-color: transparent;"><font style="color: light-dark(rgb(0, 153, 0), rgb(255, 255, 255));">property1</font></span></div><div><font style="color: rgb(0, 102, 204);">capsule</font>.<span style="background-color: transparent;"><font style="color: light-dark(rgb(0, 153, 0), rgb(255, 255, 255));">method1</font></span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">()</span></div><div><div><font style="color: rgb(0, 102, 204);">capsule</font>.<span style="color: rgb(204, 0, 102); background-color: transparent;">mapping1</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">.featureMethod</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">()</span></div></div><div><div><div><font style="color: rgb(0, 102, 204);">capsule</font>['<font style="color: rgb(204, 102, 0);">#@&lt;org&gt;/&lt;project&gt;/structs/StructA</font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">].protoMethod1</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">()</span></div></div><div><font style="color: rgb(0, 102, 204);">capsule</font>['<font style="color: rgb(153, 51, 255);"><span style="background-color: transparent;">$#</span><span style="background-color: transparent;">@&lt;org&gt;/&lt;project&gt;/AspectX</span></font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">].</span>instanceMethod2<span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">()</span></div></div>" style="rounded=0;whiteSpace=wrap;html=1;align=left;fontFamily=Courier New;fontStyle=1;fontSize=14;spacingLeft=10;fillColor=#FFFCF4;" parent="1" vertex="1">
|
|
20
|
+
<mxGeometry x="55" y="-765" width="520" height="370" as="geometry"/>
|
|
21
|
+
</mxCell>
|
|
22
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-26" value="" style="group;fillColor=default;" parent="1" connectable="0" vertex="1">
|
|
23
|
+
<mxGeometry x="685" y="-750" width="340" height="340" as="geometry"/>
|
|
24
|
+
</mxCell>
|
|
25
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-20" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#FFFCF4;" parent="N3hJ6-WijfH2HfO5JL5S-26" vertex="1">
|
|
26
|
+
<mxGeometry width="340" height="340" as="geometry"/>
|
|
27
|
+
</mxCell>
|
|
28
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-9" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-1" target="N3hJ6-WijfH2HfO5JL5S-5" edge="1">
|
|
29
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
30
|
+
</mxCell>
|
|
31
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-1" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="N3hJ6-WijfH2HfO5JL5S-26" vertex="1">
|
|
32
|
+
<mxGeometry x="120" y="50" width="20" height="20" as="geometry"/>
|
|
33
|
+
</mxCell>
|
|
34
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-17" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-4" target="N3hJ6-WijfH2HfO5JL5S-16" edge="1">
|
|
35
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
36
|
+
</mxCell>
|
|
37
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-4" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="N3hJ6-WijfH2HfO5JL5S-26" vertex="1">
|
|
38
|
+
<mxGeometry x="50" y="180" width="20" height="20" as="geometry"/>
|
|
39
|
+
</mxCell>
|
|
40
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-10" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-5" target="N3hJ6-WijfH2HfO5JL5S-7" edge="1">
|
|
41
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
42
|
+
</mxCell>
|
|
43
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-11" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-5" target="N3hJ6-WijfH2HfO5JL5S-6" edge="1">
|
|
44
|
+
<mxGeometry relative="1" as="geometry">
|
|
45
|
+
<mxPoint x="220" y="250" as="targetPoint"/>
|
|
46
|
+
</mxGeometry>
|
|
47
|
+
</mxCell>
|
|
48
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-12" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-5" target="N3hJ6-WijfH2HfO5JL5S-8" edge="1">
|
|
49
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
50
|
+
</mxCell>
|
|
51
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-13" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-5" target="N3hJ6-WijfH2HfO5JL5S-4" edge="1">
|
|
52
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
53
|
+
</mxCell>
|
|
54
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-5" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="N3hJ6-WijfH2HfO5JL5S-26" vertex="1">
|
|
55
|
+
<mxGeometry x="170" y="140" width="20" height="20" as="geometry"/>
|
|
56
|
+
</mxCell>
|
|
57
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-6" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="N3hJ6-WijfH2HfO5JL5S-26" vertex="1">
|
|
58
|
+
<mxGeometry x="230" y="250" width="20" height="20" as="geometry"/>
|
|
59
|
+
</mxCell>
|
|
60
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-7" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="N3hJ6-WijfH2HfO5JL5S-26" vertex="1">
|
|
61
|
+
<mxGeometry x="280" y="140" width="20" height="20" as="geometry"/>
|
|
62
|
+
</mxCell>
|
|
63
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-8" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="N3hJ6-WijfH2HfO5JL5S-26" vertex="1">
|
|
64
|
+
<mxGeometry x="90" y="260" width="20" height="20" as="geometry"/>
|
|
65
|
+
</mxCell>
|
|
66
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-14" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-8" target="N3hJ6-WijfH2HfO5JL5S-6" edge="1">
|
|
67
|
+
<mxGeometry relative="1" as="geometry">
|
|
68
|
+
<mxPoint x="210" y="250" as="targetPoint"/>
|
|
69
|
+
</mxGeometry>
|
|
70
|
+
</mxCell>
|
|
71
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-18" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=none;endFill=0;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-15" target="N3hJ6-WijfH2HfO5JL5S-7" edge="1">
|
|
72
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
73
|
+
</mxCell>
|
|
74
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-15" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="N3hJ6-WijfH2HfO5JL5S-26" vertex="1">
|
|
75
|
+
<mxGeometry x="220" y="80" width="20" height="20" as="geometry"/>
|
|
76
|
+
</mxCell>
|
|
77
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-16" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="N3hJ6-WijfH2HfO5JL5S-26" vertex="1">
|
|
78
|
+
<mxGeometry x="70" y="110" width="20" height="20" as="geometry"/>
|
|
79
|
+
</mxCell>
|
|
80
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-21" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.265;entryY=0.059;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=none;endFill=0;dashed=1;strokeColor=#3399FF;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-1" target="N3hJ6-WijfH2HfO5JL5S-20" edge="1">
|
|
81
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
82
|
+
</mxCell>
|
|
83
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-22" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.997;entryY=0.585;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=none;endFill=0;dashed=1;strokeColor=#3399FF;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-6" target="N3hJ6-WijfH2HfO5JL5S-20" edge="1">
|
|
84
|
+
<mxGeometry relative="1" as="geometry"/>
|
|
85
|
+
</mxCell>
|
|
86
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-23" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.015;entryY=0.603;entryDx=0;entryDy=0;entryPerimeter=0;endArrow=none;endFill=0;dashed=1;strokeColor=#3399FF;" parent="N3hJ6-WijfH2HfO5JL5S-26" source="N3hJ6-WijfH2HfO5JL5S-4" target="N3hJ6-WijfH2HfO5JL5S-20" edge="1">
|
|
87
|
+
<mxGeometry relative="1" as="geometry">
|
|
88
|
+
<Array as="points"/>
|
|
89
|
+
</mxGeometry>
|
|
90
|
+
</mxCell>
|
|
91
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-31" value="(c) 2026 <a href="https://christoph.diy">Christoph.diy</a>&nbsp;CC BY" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=right;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
92
|
+
<mxGeometry x="880" y="-840" width="185" height="30" as="geometry"/>
|
|
93
|
+
</mxCell>
|
|
94
|
+
<mxCell id="N3hJ6-WijfH2HfO5JL5S-32" value="Encapsulate: Source Code / Semantic Graph Isomorphism - v0.1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
95
|
+
<mxGeometry x="360" y="-840" width="420" height="30" as="geometry"/>
|
|
96
|
+
</mxCell>
|
|
97
|
+
</root>
|
|
98
|
+
</mxGraphModel>
|
|
99
|
+
</diagram>
|
|
100
|
+
</mxfile>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1071px" height="481px" viewBox="-0.5 -0.5 1071 481" content="<mxfile><diagram name="Page-1" id="QeIE7gKKPfThrfCM8ygm">7Vxbc9o6EP41TJKHML5yeQwQ2nSans5kzrR9VGyB1RjLRxYJ9NcfyZKMb8FADNhppp2JtbrvfrtaiZU65nix+kRA6N1jF/odQ3NXHXPSMQxdt3T2h1PWktI3LUGZE+RK2obwgP5ASdQkdYlcGGUKUox9isIs0cFBAB2aoQFC8Eu22Az72V5DMIcFwoMD/CL1B3KppyamaZuMzxDNPdn1wJYZC6AKS0LkARe/pEjmbcccE4yp+FqsxtDn3FN8EfWmr+QmAyMwoLtU+GZ6X3rXP9Dv2Wfj8+wf+8tX++HaGIpmnoG/lDOWo6VrxQKCl4ELeStaxxy9eIjChxA4PPeFSZ3RPLrwWUpnnwv8DB7jmjxFYIT+pNOYAppKM7jAdBq6KJ30sfOUdCwlnMouckAy5RkSClcpkuTIJ4gXkJI1K7LKwkzC83pgScJLSthaXxK9lKCtgSQCibB50vhGCOxDymEPmZhmQQTQZaCUSUyoh+c4AP7thjrKCmlT5ivGoWTXb0jpWmoYWFKcFRzjIVn/lPXjxC+e6NoqOVmlMydrmYooIPSGaxojOD6IIuQo8hT5qvmIEvyUqJCxTXoRXhIHbgPtQBoCQOaQbitoi4KcfVvRQKAPKHrO6nztglXjTinbg5iroY2Z6WR/mAVdMnm+SQU5+hGzYDc+mgeMRjkCRi6IvLiFmjTHzGuOVtQcu1RxjGMpjhJ3mr9wAQLGDUaNV6iaWJxhZjm/a2Bxr7cDj83hsXiM7e93zsqc3n36+WWyXA3w6t+7a73AYmaXI9rhHfZ81v1ohgOaYW3vvyVf45gB9zHpmDdc3+aPl6wCG4Kma4b4MDTrKuabKs++5nE9EEaxxET7bNiiC5HLe+a9aOAFIN7xWBS/7PRHqoqLnjfFe2DB5RM8RkJM2sW+I2cjzY1dKx95xzA7fEGRXcadYDJPCLLQNFsiJPg3d2XypZgJXTo0Yl8P8dfNKxy5ECNNzZ8VyLJgB66UkDiYbop00VoUgqCUeY/AeZrH2nWd4iMlIIiUdoxiBy7J8zmQr11Ani5TQNESVsd/Y0HYtsRO6uOqXBYXKWaIsWa5savwddsUXdn6pstXpF8LZ4rNWhxX22dTd5/1ofgmChn5Z8X4C0r+PkB2kCL2J2+pzoZkXghbUKq3NdmGQ62/0qZXLChDUcjWy7X+GjDiBi/YcqRfxA2ddVLJwrBZ28qntQBhiIJ5xayOZkCONKsarcQUArok8FeFPTiO9TuFmRhXTKHZasl8SQ+72+F7eaVcs6xs93VMDp54nkQ9FHUboFM5SJWq10GwqrIq7Vaa7kzYhPsYexxdByhQfWpVhq6OPdp7M/HhT374kwf7k00ff8eetHwGXRREFASONDvGgXZnfwvzNq8/Vf2RJOU4cZxdgbc3I49evoEFzPmktZ/yNN/SnNZE3q/HhXOvv8iyHeQZ9idX2+sf7hTkVWnfqic+FW2gn1uytTgIOZVHAjUB60NidUmsYrfYbgehFoegNRjc9WhIOwysJTvYNmIiu1Vt+WSOuNNuriYcspNv+M+CbcHbO9jzdpmsKL4vLHttnM6x1b8Fiv1xRPc2C/VxRNfg8XNz9coUckdfbZlRTRbrLfFhQIaC+XCmDM0ULJDPA7nGeEkQJKz1b/BFZqrYTl2lRZSmbgl2O8wn/ho3NdF57zPk+2PBRTY0czqdjqdWMug3xZzZdjbkrN+zi2F9KrosHXJmqli/+sP6etUxyhx2YY4zLpyBpU+38SUbU7yJ1d4jRm+QZ5gKAE/H6FllDLOOxjCtmmGsGRRGcAcox8abJWZoxeG/B/i2S7Oaxefj4LBC/Q+Kr3ZVlHSAAygoMkBa2599lUHS6rJEVYx0s0Kki+GlNSK3foSqXKVx0giUmIAyk3m0QGi93378WjviV+81CsDWCU2vC+BgJi5b8MsVqZyeM4CPs2NC3s4iXh+cG/Jagc+tg7y9I+T7jUK8rp+e869cVupsv6p0LnnVbqFk1e8YxQcYUiWN3CqU6KhqQgxU1spJPRnGG4BQdXXwHangoFkqWLwx+G45bzWK88UbZ+9zudf7ufW+5J7sSdf7Hc4EGrhRyN+aPPtOod9ONg4ahsbixd73aQaGOfj2zu31W+1f+Ha9zH4qL1I/sxc5aL9I9Zbu5P4Wbya/Uzr74UU7vZm8T6ifmY3GUU4i1ElD1+glb6GIp1E0e1hx4hCnvkOC2AQhSYjbrVHmcYmCdpjmcDidnvEHAyW+hhitElWpU+jDYT8rdHtQ9SJOm4Tea6nQj3LgkQhd03Oa3tPMdyT0XX9aqV/ocVXGErBOFQi5Kxkdw5c0iz8nXjr8WjL7x5iV3I4GfIUicBYXUeEkHqWhePBkyv47HkERxaHXddG6GHMyzmWriBKQijLJXK4a8xiW0a8CjNnSS7O4LFvus1iR2Eo7WJKk4lCIWH0LTxUtkOuWPB9WBNzecSSD3Ba9/GU1FTyR+VX/WO6BaRTQcBuIaEFAoQhIyr/HFQtfK3lF6i7CC0xCD0ULlrrmzWrdov9xHHHKsKLTSdMsvERVIk2rzNk7QJosuXkVUZiCzeOS5u3/</diagram></mxfile>"><defs/><g><rect x="0" y="0" width="1070" height="480" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 598.24 250 L 641.76 250" fill="none" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 592.24 250 L 600.24 246 L 598.24 250 L 600.24 254 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 647.76 250 L 639.76 254 L 641.76 250 L 639.76 246 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><rect x="20" y="40" width="570" height="420" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 568px; height: 1px; padding-top: 47px; margin-left: 21px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Source Code Module</div></div></div></foreignObject><text x="305" y="59" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Source Code Module</text></switch></g><rect x="650" y="40" width="390" height="420" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-dasharray="3 3" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 388px; height: 1px; padding-top: 47px; margin-left: 651px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Semantic Graph Module</div></div></div></foreignObject><text x="845" y="59" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">Semantic Graph Module</text></switch></g><rect x="45" y="75" width="520" height="370" fill="#fffcf4" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 508px; height: 1px; padding-top: 260px; margin-left: 57px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"><div style="display: inline-block; font-size: 14px; font-family: "Courier New"; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">const <font style="color: rgb(0, 102, 204);">capsule</font> = await Capsule({<div> '<font style="color: rgb(204, 102, 0);">#@<org>/<project>/structs/StructA</font>': {</div><div> as: <span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span><font style="color: rgb(153, 51, 255);"><span style="background-color: transparent;">$#</span><span style="background-color: transparent;">@<org>/<project>/AspectX</span></font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span></div><div> }</div><div> '#'{ </div><div> <font style="color: rgb(0, 153, 0);">property1</font>: 'val1',</div><div> <font style="color: rgb(204, 0, 102);">mapping1</font>: '<span style="background-color: transparent;"><font style="color: rgb(204, 0, 102);">@<org>/<project>/FeatureY</font></span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">',</span></div><div> <font style="color: rgb(0, 153, 0);">method1</font>: () => {</div><div><div> this.<span style="background-color: transparent;"><font style="color: light-dark(rgb(204, 0, 102), rgb(255, 255, 255));">mapping1</font></span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">.featureMethod()</span></div></div><div> this['<font style="color: rgb(153, 51, 255);"><span style="background-color: transparent;">$#</span><span style="background-color: transparent;">@<org>/<project>/AspectX</span></font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">]</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">.instanceMethod2()</span></div><div> }</div><div> }<br />}, {<div> capsuleName: '<font style="color: rgb(0, 102, 204);"><span style="background-color: transparent;">@<org>/<project>/</span><span style="background-color: transparent;">MyCapsule</span></font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span></div><div><div>})</div></div></div><div><br /></div><div><font style="color: rgb(0, 102, 204);">capsule</font>.<span style="background-color: transparent;"><font style="color: light-dark(rgb(0, 153, 0), rgb(255, 255, 255));">property1</font></span></div><div><font style="color: rgb(0, 102, 204);">capsule</font>.<span style="background-color: transparent;"><font style="color: light-dark(rgb(0, 153, 0), rgb(255, 255, 255));">method1</font></span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">()</span></div><div><div><font style="color: rgb(0, 102, 204);">capsule</font>.<span style="color: rgb(204, 0, 102); background-color: transparent;">mapping1</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">.featureMethod</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">()</span></div></div><div><div><div><font style="color: rgb(0, 102, 204);">capsule</font>['<font style="color: rgb(204, 102, 0);">#@<org>/<project>/structs/StructA</font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">].protoMethod1</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">()</span></div></div><div><font style="color: rgb(0, 102, 204);">capsule</font>['<font style="color: rgb(153, 51, 255);"><span style="background-color: transparent;">$#</span><span style="background-color: transparent;">@<org>/<project>/AspectX</span></font><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">'</span><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">].</span>instanceMethod2<span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">()</span></div></div></div></div></div></foreignObject><text x="57" y="264" fill="rgb(0, 0, 0)" font-family="Courier New" font-size="14px" font-weight="bold">const capsule = await Capsule({...</text></switch></g><rect x="675" y="90" width="340" height="340" fill="rgb(255, 255, 255)" stroke="none" pointer-events="all"/><ellipse cx="845" cy="260" rx="170" ry="170" fill="#fffcf4" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 809.86 158.74 L 850.14 231.26" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><ellipse cx="805" cy="150" rx="10" ry="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 737.73 270.38 L 752.25 219.62" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><ellipse cx="735" cy="280" rx="10" ry="10" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><path d="M 865 240 L 955 240" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 859.54 248.91 L 907.93 342.93" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 849.45 248.32 L 780.55 351.68" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 845.5 243.11 L 744.49 276.84" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><ellipse cx="855" cy="240" rx="10" ry="10" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><ellipse cx="915" cy="350" rx="10" ry="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><ellipse cx="965" cy="240" rx="10" ry="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><ellipse cx="775" cy="360" rx="10" ry="10" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><path d="M 784.98 359.31 L 905.03 350.71" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 912.07 187.07 L 957.93 232.93" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><ellipse cx="905" cy="180" rx="10" ry="10" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><ellipse cx="755" cy="210" rx="10" ry="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 797.93 142.93 L 765.1 110.06" fill="none" stroke="#3399ff" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 923.49 344.72 L 1013.98 288.9" fill="none" stroke="#3399ff" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><path d="M 725.36 282.68 L 680.1 295.02" fill="none" stroke="#3399ff" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/><rect x="870" y="0" width="185" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-end; width: 183px; height: 1px; padding-top: 15px; margin-left: 870px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: right;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">(c) 2026 <a href="https://christoph.diy">Christoph.diy</a> CC BY</div></div></div></foreignObject><text x="1053" y="19" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="end">(c) 2026 Christoph.diy CC BY</text></switch></g><rect x="350" y="0" width="420" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 418px; height: 1px; padding-top: 15px; margin-left: 352px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Encapsulate: Source Code / Semantic Graph Isomorphism - v0.1</div></div></div></foreignObject><text x="352" y="19" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px">Encapsulate: Source Code / Semantic Graph Isomorphism - v0.1</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg>
|
package/package.json
CHANGED
|
@@ -103,6 +103,7 @@ export function CapsuleModuleProjector({
|
|
|
103
103
|
projectionStore,
|
|
104
104
|
projectionCacheStore,
|
|
105
105
|
spineFilesystemRoot,
|
|
106
|
+
capsuleModuleProjectionRoot,
|
|
106
107
|
capsuleModuleProjectionPackage,
|
|
107
108
|
timing
|
|
108
109
|
}: {
|
|
@@ -120,6 +121,7 @@ export function CapsuleModuleProjector({
|
|
|
120
121
|
getStats?: (filepath: string) => Promise<{ mtime: Date } | null>
|
|
121
122
|
},
|
|
122
123
|
spineFilesystemRoot: string,
|
|
124
|
+
capsuleModuleProjectionRoot?: string,
|
|
123
125
|
capsuleModuleProjectionPackage?: string,
|
|
124
126
|
timing?: { record: (step: string) => void, chalk?: any }
|
|
125
127
|
}) {
|
|
@@ -739,6 +741,18 @@ export function CapsuleModuleProjector({
|
|
|
739
741
|
delete ambientReferences['makeImportStack']
|
|
740
742
|
}
|
|
741
743
|
|
|
744
|
+
// Compute the relative path from the projected caps file directory
|
|
745
|
+
// back to the original source file directory so that relative imports
|
|
746
|
+
// in the caps file resolve to the original source location.
|
|
747
|
+
const absSourceDir = dirname(join(spineFilesystemRoot, capsule.cst.source.moduleFilepath))
|
|
748
|
+
const projectionRoot = capsuleModuleProjectionRoot || spineFilesystemRoot
|
|
749
|
+
const absCapsDir = dirname(join(projectionRoot, filepath))
|
|
750
|
+
const capsToSourcePrefix = relative(absCapsDir, absSourceDir)
|
|
751
|
+
function rewriteRelativeModuleUri(moduleUri: string): string {
|
|
752
|
+
if (!moduleUri.startsWith('./') && !moduleUri.startsWith('../')) return moduleUri
|
|
753
|
+
return capsToSourcePrefix + '/' + moduleUri.replace(/^\.\//, '')
|
|
754
|
+
}
|
|
755
|
+
|
|
742
756
|
const importStatements = Object.entries(ambientReferences)
|
|
743
757
|
.map(([name, ref]: [string, any]) => {
|
|
744
758
|
if (ref.type === 'import') {
|
|
@@ -748,7 +762,7 @@ export function CapsuleModuleProjector({
|
|
|
748
762
|
const cssPath = cssImportMapping[ref.moduleUri] || ref.moduleUri
|
|
749
763
|
return `import '${cssPath}'`
|
|
750
764
|
}
|
|
751
|
-
return `import ${ref.importSpecifier} from '${ref.moduleUri}'`
|
|
765
|
+
return `import ${ref.importSpecifier} from '${rewriteRelativeModuleUri(ref.moduleUri)}'`
|
|
752
766
|
}
|
|
753
767
|
if (ref.type === 'assigned') {
|
|
754
768
|
// If the assignment comes from a spine-factory module, import from encapsulate.ts instead
|
package/src/encapsulate.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
// CACHE_BUST_VERSION: Increment this whenever CST cache must be invalidated due to structural changes
|
|
3
3
|
// This ensures projected capsules are regenerated when the CST format changes
|
|
4
|
-
const CACHE_BUST_VERSION =
|
|
4
|
+
const CACHE_BUST_VERSION = 21
|
|
5
5
|
|
|
6
6
|
type TSpineOptions = {
|
|
7
7
|
spineFilesystemRoot?: string,
|
|
@@ -53,7 +53,9 @@ type TCapsuleMakeInstanceOptions = {
|
|
|
53
53
|
capsuleName: string,
|
|
54
54
|
capsuleSourceLineRef: string,
|
|
55
55
|
moduleFilepath: string
|
|
56
|
-
}
|
|
56
|
+
},
|
|
57
|
+
parentCapsuleSourceUriLineRefInstanceId?: string,
|
|
58
|
+
sit?: { capsuleInstances: Record<string, { capsuleName: string, capsuleSourceUriLineRef: string, parentCapsuleSourceUriLineRefInstanceId: string }> }
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
type TCapsule = {
|
|
@@ -552,7 +554,7 @@ async function encapsulate(definition: TCapsuleDefinition, options: TCapsuleOpti
|
|
|
552
554
|
encapsulateOptions,
|
|
553
555
|
cst,
|
|
554
556
|
crt: crts?.[capsuleSourceLineRef],
|
|
555
|
-
makeInstance: async ({ overrides = {}, options = {}, runtimeSpineContracts, sharedSelf, rootCapsule }: TCapsuleMakeInstanceOptions = {}) => {
|
|
557
|
+
makeInstance: async ({ overrides = {}, options = {}, runtimeSpineContracts, sharedSelf, rootCapsule, parentCapsuleSourceUriLineRefInstanceId, sit }: TCapsuleMakeInstanceOptions = {}) => {
|
|
556
558
|
|
|
557
559
|
// Create cache key based on parameters
|
|
558
560
|
// When sharedSelf is provided, we must NOT cache because each extending capsule
|
|
@@ -811,8 +813,27 @@ async function encapsulate(definition: TCapsuleDefinition, options: TCapsuleOpti
|
|
|
811
813
|
capsuleName: encapsulateOptions.capsuleName!,
|
|
812
814
|
capsuleSourceLineRef: absoluteCapsuleSourceLineRef,
|
|
813
815
|
moduleFilepath: absoluteModuleFilepath
|
|
814
|
-
}
|
|
816
|
+
},
|
|
817
|
+
parentCapsuleSourceUriLineRefInstanceId: parentCapsuleSourceUriLineRefInstanceId
|
|
818
|
+
? sha256(parentCapsuleSourceUriLineRefInstanceId + ':' + (cst?.capsuleSourceUriLineRef || encapsulateOptions.capsuleSourceLineRef))
|
|
819
|
+
: sha256(cst?.capsuleSourceUriLineRef || encapsulateOptions.capsuleSourceLineRef),
|
|
820
|
+
sit
|
|
815
821
|
})
|
|
822
|
+
|
|
823
|
+
// Propagate this (child) capsule's encapsulatedApi to all parent spine contract
|
|
824
|
+
// instances up the extends chain, so parent functions can resolve child Function
|
|
825
|
+
// properties via this (e.g. Engine.mergeNode calling this._mergeNode from QueryAPI).
|
|
826
|
+
// Each ancestor accumulates child APIs so the proxy can check all levels.
|
|
827
|
+
let ancestor = extendedCapsuleInstance
|
|
828
|
+
while (ancestor) {
|
|
829
|
+
for (const sci of Object.values(ancestor.spineContractCapsuleInstances || {})) {
|
|
830
|
+
if (!(sci as any).childEncapsulatedApis) {
|
|
831
|
+
(sci as any).childEncapsulatedApis = []
|
|
832
|
+
}
|
|
833
|
+
; (sci as any).childEncapsulatedApis.push(encapsulatedApi)
|
|
834
|
+
}
|
|
835
|
+
ancestor = ancestor.extendedCapsuleInstance
|
|
836
|
+
}
|
|
816
837
|
}
|
|
817
838
|
|
|
818
839
|
// Resolve the root capsule for this instance:
|
|
@@ -827,6 +848,23 @@ async function encapsulate(definition: TCapsuleDefinition, options: TCapsuleOpti
|
|
|
827
848
|
capsuleMetadataStruct.rootCapsule.capsuleSourceLineRef = resolvedRootCapsule.capsuleSourceLineRef
|
|
828
849
|
capsuleMetadataStruct.rootCapsule.moduleFilepath = resolvedRootCapsule.moduleFilepath
|
|
829
850
|
|
|
851
|
+
// Compute deterministic instance ID:
|
|
852
|
+
// root: sha256(capsuleSourceUriLineRef)
|
|
853
|
+
// child: sha256(parentCapsuleSourceUriLineRefInstanceId + ":" + capsuleSourceUriLineRef)
|
|
854
|
+
const capsuleSourceUriLineRef = cst?.capsuleSourceUriLineRef || encapsulateOptions.capsuleSourceLineRef
|
|
855
|
+
const capsuleSourceUriLineRefInstanceId = parentCapsuleSourceUriLineRefInstanceId
|
|
856
|
+
? sha256(parentCapsuleSourceUriLineRefInstanceId + ':' + capsuleSourceUriLineRef)
|
|
857
|
+
: sha256(capsuleSourceUriLineRef)
|
|
858
|
+
|
|
859
|
+
// Register this instance in the sit structure if provided
|
|
860
|
+
if (sit) {
|
|
861
|
+
sit.capsuleInstances[capsuleSourceUriLineRefInstanceId] = {
|
|
862
|
+
capsuleName: encapsulateOptions.capsuleName || '',
|
|
863
|
+
capsuleSourceUriLineRef,
|
|
864
|
+
parentCapsuleSourceUriLineRefInstanceId: parentCapsuleSourceUriLineRefInstanceId || ''
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
830
868
|
const capsuleInstance: any = {
|
|
831
869
|
api: encapsulatedApi,
|
|
832
870
|
spineContractCapsuleInstances,
|
|
@@ -836,7 +874,11 @@ async function encapsulate(definition: TCapsuleDefinition, options: TCapsuleOpti
|
|
|
836
874
|
initFunctions: [] as Array<() => any>,
|
|
837
875
|
disposeFunctions: [] as Array<() => any>,
|
|
838
876
|
mappedCapsuleInstances: [] as Array<any>,
|
|
839
|
-
rootCapsule: resolvedRootCapsule
|
|
877
|
+
rootCapsule: resolvedRootCapsule,
|
|
878
|
+
capsuleSourceUriLineRefInstanceId,
|
|
879
|
+
capsuleName: encapsulateOptions.capsuleName,
|
|
880
|
+
capsuleSourceUriLineRef,
|
|
881
|
+
sit
|
|
840
882
|
}
|
|
841
883
|
|
|
842
884
|
// Set capsule metadata struct on self early so it's available in options() callbacks during mapping
|
|
@@ -1062,6 +1104,17 @@ function relative(from: string, to: string): string {
|
|
|
1062
1104
|
return result || '.'
|
|
1063
1105
|
}
|
|
1064
1106
|
|
|
1107
|
+
function sha256(input: string): string {
|
|
1108
|
+
// Use Bun's native hasher for speed; falls back to Node crypto
|
|
1109
|
+
if (typeof globalThis.Bun !== 'undefined') {
|
|
1110
|
+
const hasher = new globalThis.Bun.CryptoHasher('sha256')
|
|
1111
|
+
hasher.update(input)
|
|
1112
|
+
return hasher.digest('hex') as string
|
|
1113
|
+
}
|
|
1114
|
+
const { createHash } = require('crypto')
|
|
1115
|
+
return createHash('sha256').update(input).digest('hex')
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1065
1118
|
function isObject(item: any): boolean {
|
|
1066
1119
|
if (!item || typeof item !== 'object' || Array.isArray(item)) return false
|
|
1067
1120
|
// Only deep-merge plain objects — preserve instances like Map, Set, Date, etc.
|
|
@@ -6,6 +6,7 @@ type CallerContext = {
|
|
|
6
6
|
capsuleSourceNameRef?: string
|
|
7
7
|
spineContractCapsuleInstanceId: string
|
|
8
8
|
capsuleSourceNameRefHash?: string
|
|
9
|
+
capsuleSourceUriLineRefInstanceId?: string
|
|
9
10
|
prop?: string
|
|
10
11
|
filepath?: string
|
|
11
12
|
line?: number
|
|
@@ -53,7 +54,8 @@ function CapsuleMembrane(target: Record<string, any>, hooks?: {
|
|
|
53
54
|
class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFactory {
|
|
54
55
|
private getEventIndex: () => number
|
|
55
56
|
private incrementEventIndex: () => number
|
|
56
|
-
private
|
|
57
|
+
private getCurrentCallerContext: () => CallerContext | undefined
|
|
58
|
+
private setCurrentCallerContext: (ctx: CallerContext | undefined) => void
|
|
57
59
|
private onMembraneEvent?: (event: any) => void
|
|
58
60
|
private enableCallerStackInference: boolean
|
|
59
61
|
private encapsulateOptions: any
|
|
@@ -77,7 +79,8 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
77
79
|
encapsulateOptions,
|
|
78
80
|
getEventIndex,
|
|
79
81
|
incrementEventIndex,
|
|
80
|
-
|
|
82
|
+
getCurrentCallerContext,
|
|
83
|
+
setCurrentCallerContext,
|
|
81
84
|
runtimeSpineContracts,
|
|
82
85
|
instanceRegistry,
|
|
83
86
|
extendedCapsuleInstance,
|
|
@@ -97,7 +100,8 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
97
100
|
encapsulateOptions: any
|
|
98
101
|
getEventIndex: () => number
|
|
99
102
|
incrementEventIndex: () => number
|
|
100
|
-
|
|
103
|
+
getCurrentCallerContext: () => CallerContext | undefined
|
|
104
|
+
setCurrentCallerContext: (ctx: CallerContext | undefined) => void
|
|
101
105
|
runtimeSpineContracts?: Record<string, any>
|
|
102
106
|
instanceRegistry?: CapsuleInstanceRegistry
|
|
103
107
|
extendedCapsuleInstance?: any
|
|
@@ -106,7 +110,8 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
106
110
|
super({ spineContractUri, capsule, self, ownSelf, encapsulatedApi, resolve, importCapsule, spineFilesystemRoot, freezeCapsule, instanceRegistry, extendedCapsuleInstance, capsuleInstance })
|
|
107
111
|
this.getEventIndex = getEventIndex
|
|
108
112
|
this.incrementEventIndex = incrementEventIndex
|
|
109
|
-
this.
|
|
113
|
+
this.getCurrentCallerContext = getCurrentCallerContext
|
|
114
|
+
this.setCurrentCallerContext = setCurrentCallerContext
|
|
110
115
|
this.onMembraneEvent = onMembraneEvent
|
|
111
116
|
this.enableCallerStackInference = enableCallerStackInference
|
|
112
117
|
this.encapsulateOptions = encapsulateOptions
|
|
@@ -116,8 +121,16 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
116
121
|
this.id = `$${encapsulateOptions.capsuleSourceLineRef}`
|
|
117
122
|
}
|
|
118
123
|
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
private buildCallerContext(prop?: string): CallerContext {
|
|
125
|
+
const ctx: CallerContext = {
|
|
126
|
+
capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
|
|
127
|
+
spineContractCapsuleInstanceId: this.id,
|
|
128
|
+
}
|
|
129
|
+
if (prop) ctx.prop = prop
|
|
130
|
+
if (this.capsuleSourceNameRef) ctx.capsuleSourceNameRef = this.capsuleSourceNameRef
|
|
131
|
+
if (this.capsuleSourceNameRefHash) ctx.capsuleSourceNameRefHash = this.capsuleSourceNameRefHash
|
|
132
|
+
if (this.capsuleInstance?.capsuleSourceUriLineRefInstanceId) ctx.capsuleSourceUriLineRefInstanceId = this.capsuleInstance.capsuleSourceUriLineRefInstanceId
|
|
133
|
+
return ctx
|
|
121
134
|
}
|
|
122
135
|
|
|
123
136
|
protected async mapMappingProperty({ overrides, options, property }: { overrides: any, options: any, property: any }) {
|
|
@@ -155,25 +168,23 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
155
168
|
throw new Error(`Capsule instance not yet resolved: ${capsuleName}`)
|
|
156
169
|
}
|
|
157
170
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
this.currentCallerContext.filepath = callerInfo.filepath
|
|
173
|
-
this.currentCallerContext.line = callerInfo.line
|
|
174
|
-
this.currentCallerContext.stack = stackFrames
|
|
171
|
+
// Only update caller context if not already set by a function/getter execution
|
|
172
|
+
if (!this.getCurrentCallerContext()) {
|
|
173
|
+
const callerCtx = this.buildCallerContext(undefined)
|
|
174
|
+
|
|
175
|
+
if (this.enableCallerStackInference) {
|
|
176
|
+
const stackStr = new Error('[MAPPED_CAPSULE]').stack
|
|
177
|
+
if (stackStr) {
|
|
178
|
+
const stackFrames = parseCallerFromStack(stackStr, this.spineFilesystemRoot)
|
|
179
|
+
if (stackFrames.length > 0) {
|
|
180
|
+
const callerInfo = extractCallerInfo(stackFrames, 3)
|
|
181
|
+
callerCtx.filepath = callerInfo.filepath
|
|
182
|
+
callerCtx.line = callerInfo.line
|
|
183
|
+
callerCtx.stack = stackFrames
|
|
184
|
+
}
|
|
175
185
|
}
|
|
176
186
|
}
|
|
187
|
+
this.setCurrentCallerContext(callerCtx)
|
|
177
188
|
}
|
|
178
189
|
|
|
179
190
|
// Access through .api if it exists (for capsule instances with getters)
|
|
@@ -280,25 +291,23 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
280
291
|
get: (apiTarget: any, apiProp: string | symbol) => {
|
|
281
292
|
if (typeof apiProp === 'symbol') return apiTarget[apiProp]
|
|
282
293
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
this.currentCallerContext.filepath = callerInfo.filepath
|
|
298
|
-
this.currentCallerContext.line = callerInfo.line
|
|
299
|
-
this.currentCallerContext.stack = stackFrames
|
|
294
|
+
// Only update caller context if not already set by a function/getter execution
|
|
295
|
+
if (!this.getCurrentCallerContext()) {
|
|
296
|
+
const callerCtx = this.buildCallerContext(undefined)
|
|
297
|
+
|
|
298
|
+
if (this.enableCallerStackInference) {
|
|
299
|
+
const stackStr = new Error('[MAPPED_CAPSULE]').stack
|
|
300
|
+
if (stackStr) {
|
|
301
|
+
const stackFrames = parseCallerFromStack(stackStr, this.spineFilesystemRoot)
|
|
302
|
+
if (stackFrames.length > 0) {
|
|
303
|
+
const callerInfo = extractCallerInfo(stackFrames, 3)
|
|
304
|
+
callerCtx.filepath = callerInfo.filepath
|
|
305
|
+
callerCtx.line = callerInfo.line
|
|
306
|
+
callerCtx.stack = stackFrames
|
|
307
|
+
}
|
|
300
308
|
}
|
|
301
309
|
}
|
|
310
|
+
this.setCurrentCallerContext(callerCtx)
|
|
302
311
|
}
|
|
303
312
|
|
|
304
313
|
// Access through .api if it exists (for capsule instances with getters)
|
|
@@ -345,25 +354,23 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
345
354
|
// Wrap the property access in a proxy to track membrane events
|
|
346
355
|
Object.defineProperty(delegateTarget, key, {
|
|
347
356
|
get: () => {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
this.currentCallerContext.filepath = callerInfo.filepath
|
|
363
|
-
this.currentCallerContext.line = callerInfo.line
|
|
364
|
-
this.currentCallerContext.stack = stackFrames
|
|
357
|
+
// Only update caller context if not already set by a function/getter execution
|
|
358
|
+
if (!this.getCurrentCallerContext()) {
|
|
359
|
+
const callerCtx = this.buildCallerContext(undefined)
|
|
360
|
+
|
|
361
|
+
if (this.enableCallerStackInference) {
|
|
362
|
+
const stackStr = new Error('[PROPERTY_CONTRACT_DELEGATE]').stack
|
|
363
|
+
if (stackStr) {
|
|
364
|
+
const stackFrames = parseCallerFromStack(stackStr, this.spineFilesystemRoot)
|
|
365
|
+
if (stackFrames.length > 0) {
|
|
366
|
+
const callerInfo = extractCallerInfo(stackFrames, 3)
|
|
367
|
+
callerCtx.filepath = callerInfo.filepath
|
|
368
|
+
callerCtx.line = callerInfo.line
|
|
369
|
+
callerCtx.stack = stackFrames
|
|
370
|
+
}
|
|
365
371
|
}
|
|
366
372
|
}
|
|
373
|
+
this.setCurrentCallerContext(callerCtx)
|
|
367
374
|
}
|
|
368
375
|
|
|
369
376
|
// Access the actual value from the instance's api
|
|
@@ -513,6 +520,9 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
513
520
|
if (this.capsuleSourceNameRefHash) {
|
|
514
521
|
callEvent.target.capsuleSourceNameRefHash = this.capsuleSourceNameRefHash
|
|
515
522
|
}
|
|
523
|
+
if (this.capsuleInstance?.capsuleSourceUriLineRefInstanceId) {
|
|
524
|
+
callEvent.target.capsuleSourceUriLineRefInstanceId = this.capsuleInstance.capsuleSourceUriLineRefInstanceId
|
|
525
|
+
}
|
|
516
526
|
|
|
517
527
|
this.addCallerContextToEvent(callEvent)
|
|
518
528
|
this.onMembraneEvent?.(callEvent)
|
|
@@ -550,11 +560,18 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
550
560
|
if (this.capsuleSourceNameRefHash) {
|
|
551
561
|
callEvent.target.capsuleSourceNameRefHash = this.capsuleSourceNameRefHash
|
|
552
562
|
}
|
|
563
|
+
if (this.capsuleInstance?.capsuleSourceUriLineRefInstanceId) {
|
|
564
|
+
callEvent.target.capsuleSourceUriLineRefInstanceId = this.capsuleInstance.capsuleSourceUriLineRefInstanceId
|
|
565
|
+
}
|
|
553
566
|
|
|
554
567
|
this.addCallerContextToEvent(callEvent)
|
|
555
568
|
this.onMembraneEvent?.(callEvent)
|
|
556
569
|
|
|
570
|
+
// Set this capsule as caller for any inner membrane events triggered by the function body
|
|
571
|
+
const previousCallerContext = this.getCurrentCallerContext()
|
|
572
|
+
this.setCurrentCallerContext(this.buildCallerContext(property.name))
|
|
557
573
|
const result = boundFunction(...args)
|
|
574
|
+
this.setCurrentCallerContext(previousCallerContext)
|
|
558
575
|
|
|
559
576
|
// Store in memoize cache if memoize is enabled
|
|
560
577
|
if (shouldMemoize) {
|
|
@@ -635,8 +652,11 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
635
652
|
return cachedResult
|
|
636
653
|
}
|
|
637
654
|
|
|
638
|
-
//
|
|
655
|
+
// Set this capsule as caller for any inner membrane events triggered by the getter body
|
|
656
|
+
const previousCallerContext = this.getCurrentCallerContext()
|
|
657
|
+
this.setCurrentCallerContext(this.buildCallerContext(property.name))
|
|
639
658
|
const result = getterFn.call(selfProxy)
|
|
659
|
+
this.setCurrentCallerContext(previousCallerContext)
|
|
640
660
|
|
|
641
661
|
// Store in memoize cache if memoize is enabled
|
|
642
662
|
if (shouldMemoize) {
|
|
@@ -693,28 +713,32 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
|
|
|
693
713
|
}
|
|
694
714
|
|
|
695
715
|
private addCallerContextToEvent(event: any): void {
|
|
696
|
-
|
|
716
|
+
const callerCtx = this.getCurrentCallerContext()
|
|
717
|
+
if (callerCtx) {
|
|
697
718
|
event.caller = {
|
|
698
|
-
capsuleSourceLineRef:
|
|
699
|
-
spineContractCapsuleInstanceId:
|
|
719
|
+
capsuleSourceLineRef: callerCtx.capsuleSourceLineRef,
|
|
720
|
+
spineContractCapsuleInstanceId: callerCtx.spineContractCapsuleInstanceId,
|
|
721
|
+
}
|
|
722
|
+
if (callerCtx.capsuleSourceNameRef) {
|
|
723
|
+
event.caller.capsuleSourceNameRef = callerCtx.capsuleSourceNameRef
|
|
700
724
|
}
|
|
701
|
-
if (
|
|
702
|
-
event.caller.
|
|
725
|
+
if (callerCtx.capsuleSourceNameRefHash) {
|
|
726
|
+
event.caller.capsuleSourceNameRefHash = callerCtx.capsuleSourceNameRefHash
|
|
703
727
|
}
|
|
704
|
-
if (
|
|
705
|
-
event.caller.
|
|
728
|
+
if (callerCtx.capsuleSourceUriLineRefInstanceId) {
|
|
729
|
+
event.caller.capsuleSourceUriLineRefInstanceId = callerCtx.capsuleSourceUriLineRefInstanceId
|
|
706
730
|
}
|
|
707
|
-
if (
|
|
708
|
-
event.caller.prop =
|
|
731
|
+
if (callerCtx.prop) {
|
|
732
|
+
event.caller.prop = callerCtx.prop
|
|
709
733
|
}
|
|
710
|
-
if (
|
|
711
|
-
event.caller.filepath =
|
|
734
|
+
if (callerCtx.filepath) {
|
|
735
|
+
event.caller.filepath = callerCtx.filepath
|
|
712
736
|
}
|
|
713
|
-
if (
|
|
714
|
-
event.caller.line =
|
|
737
|
+
if (callerCtx.line) {
|
|
738
|
+
event.caller.line = callerCtx.line
|
|
715
739
|
}
|
|
716
|
-
if (
|
|
717
|
-
event.caller.stack =
|
|
740
|
+
if (callerCtx.stack) {
|
|
741
|
+
event.caller.stack = callerCtx.stack
|
|
718
742
|
}
|
|
719
743
|
} else if (this.enableCallerStackInference) {
|
|
720
744
|
const stackStr = new Error('[MEMBRANE_EVENT]').stack
|
|
@@ -771,7 +795,8 @@ export function CapsuleSpineContract({
|
|
|
771
795
|
encapsulateOptions,
|
|
772
796
|
getEventIndex: () => eventIndex,
|
|
773
797
|
incrementEventIndex: () => eventIndex++,
|
|
774
|
-
currentCallerContext,
|
|
798
|
+
getCurrentCallerContext: () => currentCallerContext,
|
|
799
|
+
setCurrentCallerContext: (ctx: CallerContext | undefined) => { currentCallerContext = ctx },
|
|
775
800
|
runtimeSpineContracts,
|
|
776
801
|
instanceRegistry,
|
|
777
802
|
extendedCapsuleInstance,
|
|
@@ -16,6 +16,7 @@ export class ContractCapsuleInstanceFactory {
|
|
|
16
16
|
protected instanceRegistry?: CapsuleInstanceRegistry
|
|
17
17
|
protected extendedCapsuleInstance?: any
|
|
18
18
|
protected ownSelf?: any
|
|
19
|
+
public childEncapsulatedApis?: Record<string, any>[]
|
|
19
20
|
protected runtimeSpineContracts?: Record<string, any>
|
|
20
21
|
protected capsuleInstance?: any
|
|
21
22
|
public structInitFunctions: Array<() => any> = []
|
|
@@ -61,7 +62,9 @@ export class ContractCapsuleInstanceFactory {
|
|
|
61
62
|
overrides: overrides || {},
|
|
62
63
|
options: options,
|
|
63
64
|
runtimeSpineContracts: this.runtimeSpineContracts,
|
|
64
|
-
rootCapsule: this.capsuleInstance?.rootCapsule
|
|
65
|
+
rootCapsule: this.capsuleInstance?.rootCapsule,
|
|
66
|
+
parentCapsuleSourceUriLineRefInstanceId: this.capsuleInstance?.capsuleSourceUriLineRefInstanceId,
|
|
67
|
+
sit: this.capsuleInstance?.sit
|
|
65
68
|
})
|
|
66
69
|
|
|
67
70
|
// Run init functions on the imported capsule instance
|
|
@@ -310,7 +313,9 @@ export class ContractCapsuleInstanceFactory {
|
|
|
310
313
|
overrides: mappedOverrides,
|
|
311
314
|
options: ownMappingOptions,
|
|
312
315
|
runtimeSpineContracts: this.runtimeSpineContracts,
|
|
313
|
-
rootCapsule: this.capsuleInstance?.rootCapsule
|
|
316
|
+
rootCapsule: this.capsuleInstance?.rootCapsule,
|
|
317
|
+
parentCapsuleSourceUriLineRefInstanceId: this.capsuleInstance?.capsuleSourceUriLineRefInstanceId,
|
|
318
|
+
sit: this.capsuleInstance?.sit
|
|
314
319
|
})
|
|
315
320
|
|
|
316
321
|
// Register the instance (replaces null pre-registration marker)
|
|
@@ -369,6 +374,7 @@ export class ContractCapsuleInstanceFactory {
|
|
|
369
374
|
protected createSelfProxy() {
|
|
370
375
|
const extendedApi = this.extendedCapsuleInstance?.api
|
|
371
376
|
const ownSelf = this.ownSelf
|
|
377
|
+
const factory = this
|
|
372
378
|
return new Proxy(this.self, {
|
|
373
379
|
get: (target: any, prop: string | symbol) => {
|
|
374
380
|
if (typeof prop === 'symbol') return target[prop]
|
|
@@ -384,8 +390,15 @@ export class ContractCapsuleInstanceFactory {
|
|
|
384
390
|
}
|
|
385
391
|
|
|
386
392
|
// Fall back to encapsulatedApi
|
|
387
|
-
if (prop in
|
|
388
|
-
return
|
|
393
|
+
if (prop in factory.encapsulatedApi) {
|
|
394
|
+
return factory.encapsulatedApi[prop]
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Fall back to child capsule APIs (for parent→child function delegation)
|
|
398
|
+
if (factory.childEncapsulatedApis) {
|
|
399
|
+
for (const childApi of factory.childEncapsulatedApis) {
|
|
400
|
+
if (prop in childApi) return childApi[prop]
|
|
401
|
+
}
|
|
389
402
|
}
|
|
390
403
|
|
|
391
404
|
// Fall back to extended capsule's API
|
|
@@ -393,6 +406,35 @@ export class ContractCapsuleInstanceFactory {
|
|
|
393
406
|
return extendedApi[prop]
|
|
394
407
|
}
|
|
395
408
|
|
|
409
|
+
return undefined
|
|
410
|
+
},
|
|
411
|
+
ownKeys: (target: any) => {
|
|
412
|
+
const keys = new Set<string>(Object.keys(target))
|
|
413
|
+
for (const k of Object.keys(factory.encapsulatedApi)) keys.add(k)
|
|
414
|
+
if (factory.childEncapsulatedApis) {
|
|
415
|
+
for (const childApi of factory.childEncapsulatedApis) {
|
|
416
|
+
for (const k of Object.keys(childApi)) keys.add(k)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (extendedApi) {
|
|
420
|
+
for (const k of Object.keys(extendedApi)) keys.add(k)
|
|
421
|
+
}
|
|
422
|
+
return [...keys]
|
|
423
|
+
},
|
|
424
|
+
set: (target: any, prop: string | symbol, value: any) => {
|
|
425
|
+
target[prop] = value
|
|
426
|
+
return true
|
|
427
|
+
},
|
|
428
|
+
getOwnPropertyDescriptor: (target: any, prop: string | symbol) => {
|
|
429
|
+
if (typeof prop === 'symbol') return Object.getOwnPropertyDescriptor(target, prop)
|
|
430
|
+
if (prop in target) return Object.getOwnPropertyDescriptor(target, prop)
|
|
431
|
+
if (prop in factory.encapsulatedApi) return { configurable: true, enumerable: true, writable: true, value: factory.encapsulatedApi[prop as string] }
|
|
432
|
+
if (factory.childEncapsulatedApis) {
|
|
433
|
+
for (const childApi of factory.childEncapsulatedApis) {
|
|
434
|
+
if (prop in childApi) return { configurable: true, enumerable: true, writable: true, value: childApi[prop as string] }
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (extendedApi && prop in extendedApi) return { configurable: true, enumerable: true, writable: true, value: extendedApi[prop as string] }
|
|
396
438
|
return undefined
|
|
397
439
|
}
|
|
398
440
|
})
|
|
@@ -473,6 +473,7 @@ export async function CapsuleSpineFactory({
|
|
|
473
473
|
},
|
|
474
474
|
},
|
|
475
475
|
spineFilesystemRoot,
|
|
476
|
+
capsuleModuleProjectionRoot,
|
|
476
477
|
capsuleModuleProjectionPackage,
|
|
477
478
|
timing
|
|
478
479
|
}) : undefined
|
|
@@ -570,13 +571,138 @@ export async function CapsuleSpineFactory({
|
|
|
570
571
|
return capsule
|
|
571
572
|
}
|
|
572
573
|
|
|
574
|
+
// Wrap freeze to also write spine instance (.sit.json) files
|
|
575
|
+
const wrappedFreeze = async function () {
|
|
576
|
+
const snapshot = await freeze()
|
|
577
|
+
|
|
578
|
+
// Write spine instance files if capsuleModuleProjectionRoot is available
|
|
579
|
+
if (capsuleModuleProjectionRoot) {
|
|
580
|
+
try {
|
|
581
|
+
// Deduplicate capsules: the capsules dict has entries keyed by both
|
|
582
|
+
// capsuleSourceLineRef and capsuleName — only process capsuleSourceLineRef keys
|
|
583
|
+
const uniqueCapsules: Record<string, any> = {}
|
|
584
|
+
for (const [key, capsule] of Object.entries(capsules)) {
|
|
585
|
+
if (key.includes(':') && /:\d+$/.test(key)) {
|
|
586
|
+
uniqueCapsules[key] = capsule
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Find root capsules — capsules that are NOT referenced as mapped dependencies
|
|
591
|
+
const mappedCapsuleNames = new Set<string>()
|
|
592
|
+
for (const [, capsule] of Object.entries(uniqueCapsules)) {
|
|
593
|
+
const cst = capsule.cst
|
|
594
|
+
if (cst?.spineContracts) {
|
|
595
|
+
for (const [, spineContract] of Object.entries(cst.spineContracts) as any) {
|
|
596
|
+
if (spineContract.propertyContracts) {
|
|
597
|
+
for (const [, propContract] of Object.entries(spineContract.propertyContracts) as any) {
|
|
598
|
+
if (propContract.properties) {
|
|
599
|
+
for (const [, propDef] of Object.entries(propContract.properties) as any) {
|
|
600
|
+
if (propDef.type === 'CapsulePropertyTypes.Mapping' && propDef.mappedModuleUri) {
|
|
601
|
+
mappedCapsuleNames.add(propDef.mappedModuleUri)
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (cst?.source?.extendsCapsuleUri) {
|
|
610
|
+
mappedCapsuleNames.add(cst.source.extendsCapsuleUri)
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
for (const [, capsule] of Object.entries(uniqueCapsules)) {
|
|
615
|
+
const cst = capsule.cst
|
|
616
|
+
const rootCapsuleName = cst?.source?.capsuleName
|
|
617
|
+
if (!rootCapsuleName) continue
|
|
618
|
+
|
|
619
|
+
const moduleUri = cst?.source?.moduleUri
|
|
620
|
+
if (mappedCapsuleNames.has(rootCapsuleName) || (moduleUri && mappedCapsuleNames.has(moduleUri))) {
|
|
621
|
+
continue
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// This is a root capsule — write its .sit.json
|
|
625
|
+
const dirName = rootCapsuleName.replace(/\//g, '~')
|
|
626
|
+
const sitDir = join(capsuleModuleProjectionRoot, '.~o/encapsulate.dev/spine-instances', dirName)
|
|
627
|
+
const sitFilePath = join(sitDir, `root-capsule.sit.json`)
|
|
628
|
+
|
|
629
|
+
// Build the capsules map
|
|
630
|
+
const capsuleEntries: Record<string, { capsuleSourceUriLineRef: string }> = {}
|
|
631
|
+
for (const [, cap] of Object.entries(uniqueCapsules)) {
|
|
632
|
+
const capCst = cap.cst
|
|
633
|
+
if (capCst?.source?.capsuleName) {
|
|
634
|
+
capsuleEntries[capCst.source.capsuleName] = {
|
|
635
|
+
capsuleSourceUriLineRef: capCst.capsuleSourceUriLineRef
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Collect capsuleInstances from the cached root instance using an
|
|
641
|
+
// iterative stack — each instance stores its ID and parent ID from init
|
|
642
|
+
const rootInstance = await capsule.makeInstance()
|
|
643
|
+
const capsuleInstances: Record<string, { capsuleName: string, capsuleSourceUriLineRef: string, parentCapsuleSourceUriLineRefInstanceId: string }> = {}
|
|
644
|
+
|
|
645
|
+
// If the root instance has a sit with pre-populated capsuleInstances, use it directly
|
|
646
|
+
if (rootInstance.sit?.capsuleInstances && Object.keys(rootInstance.sit.capsuleInstances).length > 0) {
|
|
647
|
+
Object.assign(capsuleInstances, rootInstance.sit.capsuleInstances)
|
|
648
|
+
} else {
|
|
649
|
+
// Iterative stack-based collection from instance tree
|
|
650
|
+
const stack: Array<{ instance: any, parentId: string }> = [{ instance: rootInstance, parentId: '' }]
|
|
651
|
+
const visited = new Set<string>()
|
|
652
|
+
while (stack.length > 0) {
|
|
653
|
+
const { instance, parentId } = stack.pop()!
|
|
654
|
+
if (!instance?.capsuleSourceUriLineRefInstanceId) continue
|
|
655
|
+
const id = instance.capsuleSourceUriLineRefInstanceId
|
|
656
|
+
if (visited.has(id)) continue
|
|
657
|
+
visited.add(id)
|
|
658
|
+
|
|
659
|
+
capsuleInstances[id] = {
|
|
660
|
+
capsuleName: instance.capsuleName || '',
|
|
661
|
+
capsuleSourceUriLineRef: instance.capsuleSourceUriLineRef || '',
|
|
662
|
+
parentCapsuleSourceUriLineRefInstanceId: parentId
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (instance.extendedCapsuleInstance) {
|
|
666
|
+
stack.push({ instance: instance.extendedCapsuleInstance, parentId: id })
|
|
667
|
+
}
|
|
668
|
+
if (instance.mappedCapsuleInstances) {
|
|
669
|
+
for (const mapped of instance.mappedCapsuleInstances) {
|
|
670
|
+
stack.push({ instance: mapped, parentId: id })
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const rootInstanceId = rootInstance.capsuleSourceUriLineRefInstanceId || ''
|
|
677
|
+
|
|
678
|
+
const sitData = {
|
|
679
|
+
rootCapsule: {
|
|
680
|
+
capsuleSourceUriLineRef: cst.capsuleSourceUriLineRef,
|
|
681
|
+
capsuleSourceUriLineRefInstanceId: rootInstanceId
|
|
682
|
+
},
|
|
683
|
+
capsules: capsuleEntries,
|
|
684
|
+
capsuleInstances
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
await mkdir(sitDir, { recursive: true })
|
|
688
|
+
await writeFile(sitFilePath, JSON.stringify(sitData, null, 2), 'utf-8')
|
|
689
|
+
}
|
|
690
|
+
} catch (error) {
|
|
691
|
+
// Spine instance file writing is best-effort
|
|
692
|
+
console.warn('Warning: Failed to write spine instance files:', error)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return snapshot
|
|
697
|
+
}
|
|
698
|
+
|
|
573
699
|
return {
|
|
574
700
|
commonSpineContractOpts,
|
|
575
701
|
CapsulePropertyTypes,
|
|
576
702
|
makeImportStack,
|
|
577
703
|
encapsulate,
|
|
578
704
|
run,
|
|
579
|
-
freeze,
|
|
705
|
+
freeze: wrappedFreeze,
|
|
580
706
|
loadCapsule,
|
|
581
707
|
spineContractInstances, // Expose for testing
|
|
582
708
|
hoistSnapshot: async ({ snapshot }: { snapshot: any }) => {
|
|
@@ -202,6 +202,11 @@ const MODULE_GLOBAL_BUILTINS = new Set([
|
|
|
202
202
|
'Intl',
|
|
203
203
|
'WebAssembly',
|
|
204
204
|
|
|
205
|
+
// Global scope references
|
|
206
|
+
'globalThis',
|
|
207
|
+
'window',
|
|
208
|
+
'global',
|
|
209
|
+
|
|
205
210
|
// Global functions
|
|
206
211
|
'isNaN',
|
|
207
212
|
'isFinite',
|
|
@@ -366,7 +371,7 @@ export function StaticAnalyzer({
|
|
|
366
371
|
|
|
367
372
|
const capsuleSourceLineRef = `${encapsulateOptions.moduleFilepath}:${encapsulateOptions.importStackLine}`
|
|
368
373
|
const capsuleSourceNameRef = encapsulateOptions.capsuleName && `${encapsulateOptions.moduleFilepath}:${encapsulateOptions.capsuleName}`
|
|
369
|
-
const capsuleSourceNameRefHash = capsuleSourceNameRef && createHash('
|
|
374
|
+
const capsuleSourceNameRefHash = capsuleSourceNameRef && createHash('sha256').update(capsuleSourceNameRef).digest('hex')
|
|
370
375
|
|
|
371
376
|
// Construct npm URI for the module - try for all modules
|
|
372
377
|
let moduleUri: string | null = await constructNpmUri(moduleFilepath, spineOptions.spineFilesystemRoot)
|
|
@@ -528,20 +533,39 @@ export function StaticAnalyzer({
|
|
|
528
533
|
if (propName.startsWith('#')) {
|
|
529
534
|
const propertyContractUri = propName.substring(1) // Remove the '#' prefix
|
|
530
535
|
|
|
536
|
+
// Resolve relative property contract URIs to full npm URIs
|
|
537
|
+
let resolvedPropertyContractUri = propertyContractUri
|
|
538
|
+
let resolvedPropName = propName
|
|
539
|
+
if (propertyContractUri.startsWith('./') || propertyContractUri.startsWith('../')) {
|
|
540
|
+
const resolvedPath = resolve(dirname(moduleFilepath), propertyContractUri)
|
|
541
|
+
const npmUri = await constructNpmUri(resolvedPath + '.ts', spineOptions.spineFilesystemRoot)
|
|
542
|
+
if (npmUri) {
|
|
543
|
+
resolvedPropertyContractUri = npmUri.replace(/\.(ts|tsx|js|jsx)$/, '')
|
|
544
|
+
resolvedPropName = '#' + resolvedPropertyContractUri
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
531
548
|
if (ts.isObjectLiteralExpression(propValue)) {
|
|
532
549
|
// Create property contract entry
|
|
533
|
-
if (!spineContractDef.propertyContracts[
|
|
534
|
-
spineContractDef.propertyContracts[
|
|
535
|
-
propertyContractUri,
|
|
550
|
+
if (!spineContractDef.propertyContracts[resolvedPropName]) {
|
|
551
|
+
spineContractDef.propertyContracts[resolvedPropName] = {
|
|
552
|
+
propertyContractUri: resolvedPropertyContractUri,
|
|
536
553
|
properties: {}
|
|
537
554
|
}
|
|
538
555
|
}
|
|
539
556
|
|
|
540
|
-
// Check for 'as'
|
|
557
|
+
// Check for 'as' and 'options' properties at the property contract level
|
|
541
558
|
for (const contractProp of propValue.properties) {
|
|
542
|
-
if (ts.isPropertyAssignment(contractProp) && ts.isIdentifier(contractProp.name)
|
|
543
|
-
if (
|
|
544
|
-
|
|
559
|
+
if (ts.isPropertyAssignment(contractProp) && ts.isIdentifier(contractProp.name)) {
|
|
560
|
+
if (contractProp.name.text === 'as') {
|
|
561
|
+
if (ts.isStringLiteral(contractProp.initializer)) {
|
|
562
|
+
spineContractDef.propertyContracts[resolvedPropName].as = contractProp.initializer.text
|
|
563
|
+
}
|
|
564
|
+
} else if (contractProp.name.text === 'options') {
|
|
565
|
+
// Store literal options object on the property contract for graph queries
|
|
566
|
+
if (ts.isObjectLiteralExpression(contractProp.initializer)) {
|
|
567
|
+
spineContractDef.propertyContracts[resolvedPropName].options = await extractLiteralObject(contractProp.initializer, sourceFile, moduleFilepath, spineOptions.spineFilesystemRoot)
|
|
568
|
+
}
|
|
545
569
|
}
|
|
546
570
|
}
|
|
547
571
|
}
|
|
@@ -632,6 +656,9 @@ export function StaticAnalyzer({
|
|
|
632
656
|
if (selfRefs.size > 0) {
|
|
633
657
|
propDef.depends = Array.from(selfRefs)
|
|
634
658
|
}
|
|
659
|
+
} else if (ts.isObjectLiteralExpression(fieldValue)) {
|
|
660
|
+
// Store literal options object in the CST for graph queries
|
|
661
|
+
propDef.options = await extractLiteralObject(fieldValue, sourceFile, moduleFilepath, spineOptions.spineFilesystemRoot)
|
|
635
662
|
}
|
|
636
663
|
} else if (fieldName === 'kind') {
|
|
637
664
|
propDef.kind = fieldValue.getText(sourceFile)
|
|
@@ -643,7 +670,7 @@ export function StaticAnalyzer({
|
|
|
643
670
|
}
|
|
644
671
|
}
|
|
645
672
|
|
|
646
|
-
spineContractDef.propertyContracts[
|
|
673
|
+
spineContractDef.propertyContracts[resolvedPropName].properties[contractPropName] = propDef
|
|
647
674
|
}
|
|
648
675
|
}
|
|
649
676
|
}
|
|
@@ -2188,6 +2215,75 @@ function extractAndValidateAmbientReferences(
|
|
|
2188
2215
|
return ambientRefs
|
|
2189
2216
|
}
|
|
2190
2217
|
|
|
2218
|
+
// Extract a literal object from a TypeScript AST ObjectLiteralExpression.
|
|
2219
|
+
// Recursively walks the object tree and extracts string, number, boolean,
|
|
2220
|
+
// null values, arrays, and nested objects. String values that look like
|
|
2221
|
+
// relative paths (starting with ./ or ../) are resolved to npm URIs.
|
|
2222
|
+
async function extractLiteralObject(
|
|
2223
|
+
node: ts.ObjectLiteralExpression,
|
|
2224
|
+
sourceFile: ts.SourceFile,
|
|
2225
|
+
moduleFilepath: string,
|
|
2226
|
+
spineFilesystemRoot: string
|
|
2227
|
+
): Promise<Record<string, any>> {
|
|
2228
|
+
const result: Record<string, any> = {}
|
|
2229
|
+
|
|
2230
|
+
for (const prop of node.properties) {
|
|
2231
|
+
if (!ts.isPropertyAssignment(prop)) continue
|
|
2232
|
+
let key: string | null = null
|
|
2233
|
+
if (ts.isIdentifier(prop.name)) key = prop.name.text
|
|
2234
|
+
else if (ts.isStringLiteral(prop.name)) key = prop.name.text
|
|
2235
|
+
if (!key) continue
|
|
2236
|
+
|
|
2237
|
+
result[key] = await extractLiteralValue(prop.initializer, sourceFile, moduleFilepath, spineFilesystemRoot)
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
return result
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
// Extract a single literal value from a TS AST node.
|
|
2244
|
+
// Handles strings (with relative path resolution), numbers, booleans,
|
|
2245
|
+
// null, undefined, arrays, and nested objects.
|
|
2246
|
+
async function extractLiteralValue(
|
|
2247
|
+
node: ts.Expression,
|
|
2248
|
+
sourceFile: ts.SourceFile,
|
|
2249
|
+
moduleFilepath: string,
|
|
2250
|
+
spineFilesystemRoot: string
|
|
2251
|
+
): Promise<any> {
|
|
2252
|
+
// String literals — resolve relative paths to npm URIs
|
|
2253
|
+
if (ts.isStringLiteral(node)) {
|
|
2254
|
+
const text = node.text
|
|
2255
|
+
if (text.startsWith('./') || text.startsWith('../')) {
|
|
2256
|
+
const resolvedPath = resolve(dirname(moduleFilepath), text)
|
|
2257
|
+
const npmUri = await constructNpmUri(resolvedPath + '.ts', spineFilesystemRoot)
|
|
2258
|
+
if (npmUri) return npmUri.replace(/\.(ts|tsx|js|jsx)$/, '')
|
|
2259
|
+
}
|
|
2260
|
+
return text
|
|
2261
|
+
}
|
|
2262
|
+
// Numeric literals
|
|
2263
|
+
if (ts.isNumericLiteral(node)) return Number(node.text)
|
|
2264
|
+
// Boolean literals
|
|
2265
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true
|
|
2266
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false
|
|
2267
|
+
// Null
|
|
2268
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null
|
|
2269
|
+
// Undefined
|
|
2270
|
+
if (node.kind === ts.SyntaxKind.UndefinedKeyword) return undefined
|
|
2271
|
+
// Nested objects
|
|
2272
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
2273
|
+
return await extractLiteralObject(node, sourceFile, moduleFilepath, spineFilesystemRoot)
|
|
2274
|
+
}
|
|
2275
|
+
// Arrays
|
|
2276
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
2277
|
+
const arr: any[] = []
|
|
2278
|
+
for (const elem of node.elements) {
|
|
2279
|
+
arr.push(await extractLiteralValue(elem, sourceFile, moduleFilepath, spineFilesystemRoot))
|
|
2280
|
+
}
|
|
2281
|
+
return arr
|
|
2282
|
+
}
|
|
2283
|
+
// Fallback: store the raw expression text
|
|
2284
|
+
return node.getText(sourceFile)
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2191
2287
|
// Check if a value is a literal type
|
|
2192
2288
|
function isLiteralType(value: any): boolean {
|
|
2193
2289
|
const type = typeof value
|