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

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.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- encapsulate: Object encapsulation & mapping system with runtime kernel for TypeScript
3
+ encapsulate: Object encapsulation & mapping library with runtime kernel for TypeScript
4
4
 
5
5
  Copyright 2026 Christoph Dorn - https://Christoph.diy
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream44.studio/encapsulate",
3
- "version": "0.4.0-rc.25",
3
+ "version": "0.4.0-rc.26",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -55,7 +55,8 @@ type TCapsuleMakeInstanceOptions = {
55
55
  moduleFilepath: string
56
56
  },
57
57
  parentCapsuleSourceUriLineRefInstanceId?: string,
58
- sit?: { capsuleInstances: Record<string, { capsuleName: string, capsuleSourceUriLineRef: string, parentCapsuleSourceUriLineRefInstanceId: string }> }
58
+ sit?: { capsuleInstances: Record<string, { capsuleName: string, capsuleSourceUriLineRef: string, parentCapsuleSourceUriLineRefInstanceId: string }> },
59
+ skipCache?: boolean
59
60
  }
60
61
 
61
62
  type TCapsule = {
@@ -554,13 +555,15 @@ async function encapsulate(definition: TCapsuleDefinition, options: TCapsuleOpti
554
555
  encapsulateOptions,
555
556
  cst,
556
557
  crt: crts?.[capsuleSourceLineRef],
557
- makeInstance: async ({ overrides = {}, options = {}, runtimeSpineContracts, sharedSelf, rootCapsule, parentCapsuleSourceUriLineRefInstanceId, sit }: TCapsuleMakeInstanceOptions = {}) => {
558
+ makeInstance: async ({ overrides = {}, options = {}, runtimeSpineContracts, sharedSelf, rootCapsule, parentCapsuleSourceUriLineRefInstanceId, sit, skipCache }: TCapsuleMakeInstanceOptions = {}) => {
558
559
 
559
560
  // Create cache key based on parameters
560
561
  // When sharedSelf is provided, we must NOT cache because each extending capsule
561
562
  // needs its own instance with its own 'this' context (sharedSelf).
562
563
  // This is critical for the pattern where multiple structs extend the same parent.
563
- const cacheKey = sharedSelf ? null : JSON.stringify({
564
+ // When skipCache is true (property contract delegates like structs/Capsule),
565
+ // each parent capsule must get its own unique instance.
566
+ const cacheKey = (sharedSelf || skipCache) ? null : JSON.stringify({
564
567
  overrides,
565
568
  options,
566
569
  hasRuntimeContracts: !!runtimeSpineContracts
@@ -149,9 +149,11 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
149
149
 
150
150
  // Check for existing instance in registry - reuse if available (regardless of options)
151
151
  // Pre-registration with null allows parent capsules to "claim" a slot before child capsules process
152
+ // Property contract delegates (structs) always get a fresh instance per parent capsule
152
153
  const capsuleName = mappedCapsule.encapsulateOptions?.capsuleName
154
+ const isCapsuleStruct = property.definition.propertyContractDelegate === '#@stream44.studio/encapsulate/structs/Capsule'
153
155
 
154
- if (capsuleName && this.instanceRegistry) {
156
+ if (capsuleName && this.instanceRegistry && !isCapsuleStruct) {
155
157
  if (this.instanceRegistry.has(capsuleName)) {
156
158
  const existingEntry = this.instanceRegistry.get(capsuleName)
157
159
 
@@ -275,12 +277,16 @@ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFact
275
277
  overrides: mappedOverrides,
276
278
  options: ownMappingOptions,
277
279
  runtimeSpineContracts: this.runtimeSpineContracts,
278
- rootCapsule: this.capsuleInstance?.rootCapsule
280
+ rootCapsule: this.capsuleInstance?.rootCapsule,
281
+ parentCapsuleSourceUriLineRefInstanceId: this.capsuleInstance?.capsuleSourceUriLineRefInstanceId,
282
+ sit: this.capsuleInstance?.sit,
283
+ skipCache: isCapsuleStruct
279
284
  })
280
285
 
281
286
  // Register the instance (replaces null pre-registration marker)
282
287
  // Always register to make instance available for child capsules with deferred proxies
283
- if (capsuleName && this.instanceRegistry) {
288
+ // Property contract delegates skip registry (each parent gets its own instance)
289
+ if (capsuleName && this.instanceRegistry && !isCapsuleStruct) {
284
290
  this.instanceRegistry.set(capsuleName, mappedCapsuleInstance)
285
291
  }
286
292
 
@@ -206,9 +206,11 @@ export class ContractCapsuleInstanceFactory {
206
206
 
207
207
  // Check for existing instance in registry - reuse if available when no options
208
208
  // Pre-registration with null allows parent capsules to "claim" a slot before child capsules process
209
+ // Property contract delegates (structs) always get a fresh instance per parent capsule
209
210
  const capsuleName = mappedCapsule.encapsulateOptions?.capsuleName
211
+ const isCapsuleStruct = property.definition.propertyContractDelegate === '#@stream44.studio/encapsulate/structs/Capsule'
210
212
 
211
- if (capsuleName && this.instanceRegistry) {
213
+ if (capsuleName && this.instanceRegistry && !isCapsuleStruct) {
212
214
  if (this.instanceRegistry.has(capsuleName)) {
213
215
  const existingEntry = this.instanceRegistry.get(capsuleName)
214
216
 
@@ -315,12 +317,14 @@ export class ContractCapsuleInstanceFactory {
315
317
  runtimeSpineContracts: this.runtimeSpineContracts,
316
318
  rootCapsule: this.capsuleInstance?.rootCapsule,
317
319
  parentCapsuleSourceUriLineRefInstanceId: this.capsuleInstance?.capsuleSourceUriLineRefInstanceId,
318
- sit: this.capsuleInstance?.sit
320
+ sit: this.capsuleInstance?.sit,
321
+ skipCache: isCapsuleStruct
319
322
  })
320
323
 
321
324
  // Register the instance (replaces null pre-registration marker)
322
325
  // Always register to make instance available for child capsules with deferred proxies
323
- if (capsuleName && this.instanceRegistry) {
326
+ // Property contract delegates skip registry (each parent gets its own instance)
327
+ if (capsuleName && this.instanceRegistry && !isCapsuleStruct) {
324
328
  this.instanceRegistry.set(capsuleName, mappedInstance)
325
329
  }
326
330
 
@@ -642,33 +642,28 @@ export async function CapsuleSpineFactory({
642
642
  const rootInstance = await capsule.makeInstance()
643
643
  const capsuleInstances: Record<string, { capsuleName: string, capsuleSourceUriLineRef: string, parentCapsuleSourceUriLineRefInstanceId: string }> = {}
644
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
- }
645
+ // Iterative stack-based collection from instance tree
646
+ const stack: Array<{ instance: any, parentId: string }> = [{ instance: rootInstance, parentId: '' }]
647
+ const visited = new Set<string>()
648
+ while (stack.length > 0) {
649
+ const { instance, parentId } = stack.pop()!
650
+ if (!instance?.capsuleSourceUriLineRefInstanceId) continue
651
+ const id = instance.capsuleSourceUriLineRefInstanceId
652
+ if (visited.has(id)) continue
653
+ visited.add(id)
654
+
655
+ capsuleInstances[id] = {
656
+ capsuleName: instance.capsuleName || '',
657
+ capsuleSourceUriLineRef: instance.capsuleSourceUriLineRef || '',
658
+ parentCapsuleSourceUriLineRefInstanceId: parentId
659
+ }
664
660
 
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
- }
661
+ if (instance.extendedCapsuleInstance) {
662
+ stack.push({ instance: instance.extendedCapsuleInstance, parentId: id })
663
+ }
664
+ if (instance.mappedCapsuleInstances) {
665
+ for (const mapped of instance.mappedCapsuleInstances) {
666
+ stack.push({ instance: mapped, parentId: id })
672
667
  }
673
668
  }
674
669
  }