@stream44.studio/encapsulate 0.4.0-rc.26 → 0.4.0-rc.28

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 CHANGED
@@ -3,7 +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
+ <td>Designed by Hand<br/><b>AI assisted Code</a></td>
7
7
  </tr>
8
8
  </table>
9
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream44.studio/encapsulate",
3
- "version": "0.4.0-rc.26",
3
+ "version": "0.4.0-rc.28",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -417,6 +417,7 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
417
417
  const event: any = {
418
418
  event: 'get',
419
419
  eventIndex: this.incrementEventIndex(),
420
+ membrane: 'external',
420
421
  target: {
421
422
  capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
422
423
  spineContractCapsuleInstanceId: this.id,
@@ -445,6 +446,7 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
445
446
  const event: any = {
446
447
  event: 'set',
447
448
  eventIndex: this.incrementEventIndex(),
449
+ membrane: 'external',
448
450
  target: {
449
451
  capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
450
452
  spineContractCapsuleInstanceId: this.id,
@@ -511,6 +513,7 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
511
513
  const callEvent: any = {
512
514
  event: 'call',
513
515
  eventIndex: this.incrementEventIndex(),
516
+ membrane: 'external',
514
517
  target: {
515
518
  capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
516
519
  spineContractCapsuleInstanceId: this.id,
@@ -536,6 +539,7 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
536
539
  const resultEvent: any = {
537
540
  event: 'call-result',
538
541
  eventIndex: this.incrementEventIndex(),
542
+ membrane: 'external',
539
543
  callEventIndex: callEvent.eventIndex,
540
544
  target: {
541
545
  spineContractCapsuleInstanceId: this.id,
@@ -552,6 +556,7 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
552
556
  const callEvent: any = {
553
557
  event: 'call',
554
558
  eventIndex: this.incrementEventIndex(),
559
+ membrane: 'external',
555
560
  target: {
556
561
  capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
557
562
  spineContractCapsuleInstanceId: this.id,
@@ -588,6 +593,7 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
588
593
  const resultEvent: any = {
589
594
  event: 'call-result',
590
595
  eventIndex: this.incrementEventIndex(),
596
+ membrane: 'external',
591
597
  callEventIndex: callEvent.eventIndex,
592
598
  target: {
593
599
  spineContractCapsuleInstanceId: this.id,
@@ -637,6 +643,7 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
637
643
  const event: any = {
638
644
  event: 'get',
639
645
  eventIndex: this.incrementEventIndex(),
646
+ membrane: 'external',
640
647
  target: {
641
648
  capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
642
649
  spineContractCapsuleInstanceId: this.id,
@@ -673,6 +680,7 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
673
680
  const event: any = {
674
681
  event: 'get',
675
682
  eventIndex: this.incrementEventIndex(),
683
+ membrane: 'external',
676
684
  target: {
677
685
  capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
678
686
  spineContractCapsuleInstanceId: this.id,
@@ -718,6 +726,134 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
718
726
  }
719
727
  }
720
728
 
729
+ protected override createSelfProxy() {
730
+ const extendedApi = this.extendedCapsuleInstance?.api
731
+ const ownSelf = this.ownSelf
732
+ const factory = this
733
+ return new Proxy(this.self, {
734
+ get: (target: any, prop: string | symbol) => {
735
+ if (typeof prop === 'symbol') return target[prop]
736
+
737
+ // 'self' property returns ownSelf (only this capsule's own properties)
738
+ if (prop === 'self' && ownSelf) {
739
+ return ownSelf
740
+ }
741
+
742
+ // Determine the value source and get the value
743
+ let value: any
744
+ let source: 'self' | 'encapsulatedApi' | 'childApi' | 'extendedApi' | undefined
745
+
746
+ if (prop in target) {
747
+ value = target[prop]
748
+ source = 'self'
749
+ } else if (prop in factory.encapsulatedApi) {
750
+ value = factory.encapsulatedApi[prop]
751
+ source = 'encapsulatedApi'
752
+ } else if (factory.childEncapsulatedApis) {
753
+ for (const childApi of factory.childEncapsulatedApis) {
754
+ if (prop in childApi) {
755
+ value = childApi[prop]
756
+ source = 'childApi'
757
+ break
758
+ }
759
+ }
760
+ }
761
+
762
+ if (source === undefined && extendedApi && prop in extendedApi) {
763
+ value = extendedApi[prop]
764
+ source = 'extendedApi'
765
+ }
766
+
767
+ // Only emit internal events if we're inside a function/getter execution (caller context is set)
768
+ // and the property is not a function (we don't want to emit get events for function references)
769
+ if (source && typeof value !== 'function' && this.getCurrentCallerContext()) {
770
+ const event: any = {
771
+ event: 'get',
772
+ eventIndex: this.incrementEventIndex(),
773
+ membrane: 'internal',
774
+ target: {
775
+ capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
776
+ spineContractCapsuleInstanceId: this.id,
777
+ prop: prop as string,
778
+ },
779
+ value
780
+ }
781
+
782
+ if (this.capsuleSourceNameRef) {
783
+ event.target.capsuleSourceNameRef = this.capsuleSourceNameRef
784
+ }
785
+ if (this.capsuleSourceNameRefHash) {
786
+ event.target.capsuleSourceNameRefHash = this.capsuleSourceNameRefHash
787
+ }
788
+
789
+ this.addCallerContextToEvent(event)
790
+ this.onMembraneEvent?.(event)
791
+ }
792
+
793
+ return value
794
+ },
795
+ ownKeys: (target: any) => {
796
+ const keys = new Set<string>(Object.keys(target))
797
+ for (const k of Object.keys(factory.encapsulatedApi)) keys.add(k)
798
+ if (factory.childEncapsulatedApis) {
799
+ for (const childApi of factory.childEncapsulatedApis) {
800
+ for (const k of Object.keys(childApi)) keys.add(k)
801
+ }
802
+ }
803
+ if (extendedApi) {
804
+ for (const k of Object.keys(extendedApi)) keys.add(k)
805
+ }
806
+ return [...keys]
807
+ },
808
+ set: (target: any, prop: string | symbol, value: any) => {
809
+ if (typeof prop === 'symbol') {
810
+ target[prop] = value
811
+ return true
812
+ }
813
+
814
+ // Emit internal set event if we're inside a function/getter execution
815
+ if (this.getCurrentCallerContext()) {
816
+ const event: any = {
817
+ event: 'set',
818
+ eventIndex: this.incrementEventIndex(),
819
+ membrane: 'internal',
820
+ target: {
821
+ capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
822
+ spineContractCapsuleInstanceId: this.id,
823
+ prop: prop as string,
824
+ },
825
+ value
826
+ }
827
+
828
+ if (this.capsuleSourceNameRef) {
829
+ event.target.capsuleSourceNameRef = this.capsuleSourceNameRef
830
+ }
831
+ if (this.capsuleSourceNameRefHash) {
832
+ event.target.capsuleSourceNameRefHash = this.capsuleSourceNameRefHash
833
+ }
834
+
835
+ this.addCallerContextToEvent(event)
836
+ this.onMembraneEvent?.(event)
837
+ }
838
+
839
+ target[prop] = value
840
+ return true
841
+ },
842
+ getOwnPropertyDescriptor: (target: any, prop: string | symbol) => {
843
+ if (typeof prop === 'symbol') return Object.getOwnPropertyDescriptor(target, prop)
844
+ if (prop in target) return Object.getOwnPropertyDescriptor(target, prop)
845
+ if (prop in factory.encapsulatedApi) return { configurable: true, enumerable: true, writable: true, value: factory.encapsulatedApi[prop as string] }
846
+ if (factory.childEncapsulatedApis) {
847
+ for (const childApi of factory.childEncapsulatedApis) {
848
+ if (prop in childApi) return { configurable: true, enumerable: true, writable: true, value: childApi[prop as string] }
849
+ }
850
+ }
851
+ if (extendedApi && prop in extendedApi) return { configurable: true, enumerable: true, writable: true, value: extendedApi[prop as string] }
852
+ return undefined
853
+ }
854
+ })
855
+ }
856
+
721
857
  private addCallerContextToEvent(event: any): void {
722
858
  const callerCtx = this.getCurrentCallerContext()
723
859
  if (callerCtx) {
@@ -782,6 +918,20 @@ export function CapsuleSpineContract({
782
918
  let currentCallerContext: CallerContext | undefined = undefined
783
919
  const instanceRegistry: CapsuleInstanceRegistry = new Map()
784
920
 
921
+ // Re-entrancy guard: suppress event emission while inside an onMembraneEvent callback.
922
+ // This prevents consumers (e.g. JSON.stringify on event.value) from triggering proxy getters
923
+ // that would cause spurious recursive membrane events with wrong caller context and ordering.
924
+ let isEmittingEvent = false
925
+ const guardedOnMembraneEvent = onMembraneEvent ? (event: any) => {
926
+ if (isEmittingEvent) return
927
+ isEmittingEvent = true
928
+ try {
929
+ onMembraneEvent(event)
930
+ } finally {
931
+ isEmittingEvent = false
932
+ }
933
+ } : undefined
934
+
785
935
  return {
786
936
  '#': CapsuleSpineContract['#'],
787
937
  instanceRegistry,
@@ -796,7 +946,7 @@ export function CapsuleSpineContract({
796
946
  freezeCapsule,
797
947
  resolve,
798
948
  importCapsule,
799
- onMembraneEvent,
949
+ onMembraneEvent: guardedOnMembraneEvent,
800
950
  enableCallerStackInference,
801
951
  encapsulateOptions,
802
952
  getEventIndex: () => eventIndex,