@stream44.studio/encapsulate 0.4.0-rc.19 → 0.4.0-rc.21
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/.o/stream44.studio/assets/Icon-v1.svg +1170 -0
- package/LICENSE.txt +1 -1
- package/README.md +23 -12
- package/package.json +1 -1
- package/src/capsule-projectors/CapsuleModuleProjector.v0.ts +19 -19
- package/src/encapsulate.ts +56 -25
- package/src/spine-contracts/CapsuleSpineContract.v0/Membrane.v0.ts +6 -4
- package/src/spine-contracts/CapsuleSpineContract.v0/Overview.drawio +261 -0
- package/src/spine-contracts/CapsuleSpineContract.v0/Overview.svg +1 -0
- package/src/spine-contracts/CapsuleSpineContract.v0/README.md +437 -1
- package/src/spine-contracts/CapsuleSpineContract.v0/Static.v0.ts +41 -3
- package/src/spine-factories/CapsuleSpineFactory.v0.ts +23 -5
- package/src/static-analyzer.v0.ts +129 -17
|
@@ -98,6 +98,32 @@ const MODULE_GLOBAL_BUILTINS = new Set([
|
|
|
98
98
|
'Request',
|
|
99
99
|
'Response',
|
|
100
100
|
'Headers',
|
|
101
|
+
'AbortController',
|
|
102
|
+
'AbortSignal',
|
|
103
|
+
'FormData',
|
|
104
|
+
'Blob',
|
|
105
|
+
'File',
|
|
106
|
+
|
|
107
|
+
// Streams API
|
|
108
|
+
'ReadableStream',
|
|
109
|
+
'WritableStream',
|
|
110
|
+
'TransformStream',
|
|
111
|
+
'ByteLengthQueuingStrategy',
|
|
112
|
+
'CountQueuingStrategy',
|
|
113
|
+
|
|
114
|
+
// Events
|
|
115
|
+
'Event',
|
|
116
|
+
'EventTarget',
|
|
117
|
+
'CustomEvent',
|
|
118
|
+
'MessageEvent',
|
|
119
|
+
'ErrorEvent',
|
|
120
|
+
'CloseEvent',
|
|
121
|
+
|
|
122
|
+
// Web APIs
|
|
123
|
+
'WebSocket',
|
|
124
|
+
'MessageChannel',
|
|
125
|
+
'MessagePort',
|
|
126
|
+
'BroadcastChannel',
|
|
101
127
|
|
|
102
128
|
// Crypto
|
|
103
129
|
'crypto',
|
|
@@ -396,6 +422,17 @@ export function StaticAnalyzer({
|
|
|
396
422
|
// Check if it's a string literal (relative path or npm URI)
|
|
397
423
|
if (ts.isStringLiteral(prop.initializer)) {
|
|
398
424
|
cst.source.extendsCapsule = prop.initializer.text
|
|
425
|
+
// Resolve to npm URI
|
|
426
|
+
const extendsValue = prop.initializer.text
|
|
427
|
+
if (extendsValue.startsWith('./') || extendsValue.startsWith('../')) {
|
|
428
|
+
const resolvedPath = resolve(dirname(moduleFilepath), extendsValue)
|
|
429
|
+
const extendsNpmUri = await constructNpmUri(resolvedPath + '.ts', spineOptions.spineFilesystemRoot)
|
|
430
|
+
if (extendsNpmUri) {
|
|
431
|
+
cst.source.extendsCapsuleUri = extendsNpmUri.replace(/\.(ts|tsx|js|jsx)$/, '')
|
|
432
|
+
}
|
|
433
|
+
} else if (extendsValue.startsWith('@')) {
|
|
434
|
+
cst.source.extendsCapsuleUri = extendsValue
|
|
435
|
+
}
|
|
399
436
|
}
|
|
400
437
|
// Check if it's an identifier (capsule variable reference)
|
|
401
438
|
else if (ts.isIdentifier(prop.initializer)) {
|
|
@@ -434,13 +471,36 @@ export function StaticAnalyzer({
|
|
|
434
471
|
|
|
435
472
|
if (ts.isObjectLiteralExpression(spineContractValue)) {
|
|
436
473
|
const spineContractDef: any = {
|
|
437
|
-
|
|
474
|
+
propertyContracts: {}
|
|
438
475
|
}
|
|
439
476
|
|
|
440
477
|
// Parse properties within the spineContract
|
|
441
478
|
for (const prop of spineContractValue.properties) {
|
|
479
|
+
let propName: string | null = null
|
|
442
480
|
if (ts.isPropertyAssignment(prop) && (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
|
|
443
|
-
|
|
481
|
+
propName = ts.isIdentifier(prop.name) ? prop.name.text : (prop.name as ts.StringLiteral).text
|
|
482
|
+
}
|
|
483
|
+
// Handle computed property names like ['#' + capsule.capsuleSourceLineRef]
|
|
484
|
+
else if (ts.isPropertyAssignment(prop) && ts.isComputedPropertyName(prop.name)) {
|
|
485
|
+
const computedText = prop.name.expression.getText(sourceFile)
|
|
486
|
+
// Pattern: '#' + <identifier>.capsuleSourceLineRef
|
|
487
|
+
const capsuleRefMatch = computedText.match(/['"]#['"] \+ (\w+)\.capsuleSourceLineRef/)
|
|
488
|
+
if (capsuleRefMatch) {
|
|
489
|
+
const refName = capsuleRefMatch[1]
|
|
490
|
+
const ref = ambientReferences[refName]
|
|
491
|
+
if (ref && ref.type === 'capsule' && ref.value) {
|
|
492
|
+
// ref.value is the capsuleSourceLineRef string
|
|
493
|
+
propName = '#' + ref.value
|
|
494
|
+
} else if (encapsulateOptions.ambientReferences && encapsulateOptions.ambientReferences[refName]) {
|
|
495
|
+
const runtimeRef = encapsulateOptions.ambientReferences[refName]
|
|
496
|
+
if (runtimeRef && typeof runtimeRef === 'object' && runtimeRef.capsuleSourceLineRef) {
|
|
497
|
+
propName = '#' + runtimeRef.capsuleSourceLineRef
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (propName && ts.isPropertyAssignment(prop)) {
|
|
444
504
|
const propValue = prop.initializer
|
|
445
505
|
|
|
446
506
|
// Check if this is a property contract key (starts with '#')
|
|
@@ -449,8 +509,8 @@ export function StaticAnalyzer({
|
|
|
449
509
|
|
|
450
510
|
if (ts.isObjectLiteralExpression(propValue)) {
|
|
451
511
|
// Create property contract entry
|
|
452
|
-
if (!spineContractDef.
|
|
453
|
-
spineContractDef.
|
|
512
|
+
if (!spineContractDef.propertyContracts[propName]) {
|
|
513
|
+
spineContractDef.propertyContracts[propName] = {
|
|
454
514
|
propertyContractUri,
|
|
455
515
|
properties: {}
|
|
456
516
|
}
|
|
@@ -460,7 +520,7 @@ export function StaticAnalyzer({
|
|
|
460
520
|
for (const contractProp of propValue.properties) {
|
|
461
521
|
if (ts.isPropertyAssignment(contractProp) && ts.isIdentifier(contractProp.name) && contractProp.name.text === 'as') {
|
|
462
522
|
if (ts.isStringLiteral(contractProp.initializer)) {
|
|
463
|
-
spineContractDef.
|
|
523
|
+
spineContractDef.propertyContracts[propName].as = contractProp.initializer.text
|
|
464
524
|
}
|
|
465
525
|
}
|
|
466
526
|
}
|
|
@@ -502,6 +562,22 @@ export function StaticAnalyzer({
|
|
|
502
562
|
// Store the value expression as text
|
|
503
563
|
propDef.valueExpression = fieldValue.getText(sourceFile)
|
|
504
564
|
|
|
565
|
+
// For Mapping properties with string values, resolve to npm URI
|
|
566
|
+
if (propDef.type === 'CapsulePropertyTypes.Mapping' && ts.isStringLiteral(fieldValue)) {
|
|
567
|
+
const mappingValue = fieldValue.text
|
|
568
|
+
if (mappingValue.startsWith('./') || mappingValue.startsWith('../')) {
|
|
569
|
+
// Resolve relative to the current module's directory
|
|
570
|
+
const resolvedPath = resolve(dirname(moduleFilepath), mappingValue)
|
|
571
|
+
const npmUri = await constructNpmUri(resolvedPath + '.ts', spineOptions.spineFilesystemRoot)
|
|
572
|
+
if (npmUri) {
|
|
573
|
+
propDef.mappedModuleUri = npmUri.replace(/\.(ts|tsx|js|jsx)$/, '')
|
|
574
|
+
}
|
|
575
|
+
} else if (mappingValue.startsWith('@')) {
|
|
576
|
+
// Already an npm URI
|
|
577
|
+
propDef.mappedModuleUri = mappingValue
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
505
581
|
// Extract ambient references if it's a function
|
|
506
582
|
if (ts.isFunctionExpression(fieldValue) || ts.isArrowFunction(fieldValue)) {
|
|
507
583
|
propDef.ambientReferences = extractAndValidateAmbientReferences(
|
|
@@ -522,6 +598,20 @@ export function StaticAnalyzer({
|
|
|
522
598
|
assignmentMap
|
|
523
599
|
)
|
|
524
600
|
}
|
|
601
|
+
} else if (fieldName === 'options') {
|
|
602
|
+
// Detect self.<name> references in options function to auto-inject depends
|
|
603
|
+
if (ts.isFunctionExpression(fieldValue) || ts.isArrowFunction(fieldValue)) {
|
|
604
|
+
const optionsText = fieldValue.getText(sourceFile)
|
|
605
|
+
const selfRefs = new Set<string>()
|
|
606
|
+
const selfRefPattern = /self\.(\w+)/g
|
|
607
|
+
let match
|
|
608
|
+
while ((match = selfRefPattern.exec(optionsText)) !== null) {
|
|
609
|
+
selfRefs.add(match[1])
|
|
610
|
+
}
|
|
611
|
+
if (selfRefs.size > 0) {
|
|
612
|
+
propDef.depends = Array.from(selfRefs)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
525
615
|
} else if (fieldName === 'kind') {
|
|
526
616
|
propDef.kind = fieldValue.getText(sourceFile)
|
|
527
617
|
} else if (fieldName === 'projections') {
|
|
@@ -532,7 +622,7 @@ export function StaticAnalyzer({
|
|
|
532
622
|
}
|
|
533
623
|
}
|
|
534
624
|
|
|
535
|
-
spineContractDef.
|
|
625
|
+
spineContractDef.propertyContracts[propName].properties[contractPropName] = propDef
|
|
536
626
|
}
|
|
537
627
|
}
|
|
538
628
|
}
|
|
@@ -551,10 +641,10 @@ export function StaticAnalyzer({
|
|
|
551
641
|
// For each non-default property contract, create a mapping in the '#' contract
|
|
552
642
|
for (const [spineContractName, spineContractDef] of Object.entries(cst.spineContracts)) {
|
|
553
643
|
const spineContract = spineContractDef as any
|
|
554
|
-
if (spineContract.
|
|
644
|
+
if (spineContract.propertyContracts) {
|
|
555
645
|
// Find all non-default property contracts
|
|
556
646
|
const nonDefaultContracts: string[] = []
|
|
557
|
-
for (const propName of Object.keys(spineContract.
|
|
647
|
+
for (const propName of Object.keys(spineContract.propertyContracts)) {
|
|
558
648
|
if (propName.startsWith('#') && propName !== '#') {
|
|
559
649
|
nonDefaultContracts.push(propName)
|
|
560
650
|
}
|
|
@@ -563,28 +653,30 @@ export function StaticAnalyzer({
|
|
|
563
653
|
// Add dynamic mappings to the '#' contract
|
|
564
654
|
if (nonDefaultContracts.length > 0) {
|
|
565
655
|
// Ensure '#' contract exists
|
|
566
|
-
if (!spineContract.
|
|
567
|
-
spineContract.
|
|
656
|
+
if (!spineContract.propertyContracts['#']) {
|
|
657
|
+
spineContract.propertyContracts['#'] = {
|
|
568
658
|
propertyContractUri: '',
|
|
569
659
|
properties: {}
|
|
570
660
|
}
|
|
571
661
|
}
|
|
572
|
-
if (!spineContract.
|
|
573
|
-
spineContract.
|
|
662
|
+
if (!spineContract.propertyContracts['#'].properties) {
|
|
663
|
+
spineContract.propertyContracts['#'].properties = {}
|
|
574
664
|
}
|
|
575
665
|
|
|
576
666
|
// Add a dynamic mapping for each non-default property contract
|
|
577
667
|
for (const propContractUri of nonDefaultContracts) {
|
|
578
668
|
// Check if 'as' alias is defined for this property contract
|
|
579
|
-
const aliasName = spineContract.
|
|
669
|
+
const aliasName = spineContract.propertyContracts[propContractUri]?.as
|
|
580
670
|
const contractKey = aliasName || ('#' + propContractUri.substring(1))
|
|
581
|
-
|
|
671
|
+
const contractNpmUri = propContractUri.substring(1)
|
|
672
|
+
spineContract.propertyContracts['#'].properties[contractKey] = {
|
|
582
673
|
declarationLine: -1,
|
|
583
674
|
definitionStartLine: -1,
|
|
584
675
|
definitionEndLine: -1,
|
|
585
676
|
type: 'CapsulePropertyTypes.Mapping',
|
|
586
677
|
valueType: 'string',
|
|
587
|
-
valueExpression: `"${
|
|
678
|
+
valueExpression: `"${contractNpmUri}"`,
|
|
679
|
+
mappedModuleUri: contractNpmUri,
|
|
588
680
|
propertyContractDelegate: propContractUri,
|
|
589
681
|
as: aliasName
|
|
590
682
|
}
|
|
@@ -1475,6 +1567,11 @@ function extractCapsuleAmbientReferences(
|
|
|
1475
1567
|
|
|
1476
1568
|
// Second pass: find identifiers used as values (not property names)
|
|
1477
1569
|
function visit(node: ts.Node) {
|
|
1570
|
+
// Skip interface and type alias declarations — pure type constructs erased at runtime
|
|
1571
|
+
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
1572
|
+
return
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1478
1575
|
// Check for identifiers that might be ambient references
|
|
1479
1576
|
if (ts.isIdentifier(node)) {
|
|
1480
1577
|
const identifierName = node.text
|
|
@@ -1510,6 +1607,11 @@ function extractCapsuleAmbientReferences(
|
|
|
1510
1607
|
return
|
|
1511
1608
|
}
|
|
1512
1609
|
|
|
1610
|
+
// Skip if it's a method name in an object literal (shorthand method declaration)
|
|
1611
|
+
if (parent && ts.isMethodDeclaration(parent) && parent.name === node) {
|
|
1612
|
+
return
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1513
1615
|
// Skip if it's a property name in a destructuring binding pattern
|
|
1514
1616
|
// e.g., const { encryptString: encryptFn } = await import('...')
|
|
1515
1617
|
// 'encryptString' is the propertyName, 'encryptFn' is the binding name
|
|
@@ -1779,6 +1881,11 @@ function extractAndValidateAmbientReferences(
|
|
|
1779
1881
|
return
|
|
1780
1882
|
}
|
|
1781
1883
|
|
|
1884
|
+
// Skip interface and type alias declarations — pure type constructs erased at runtime
|
|
1885
|
+
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
1886
|
+
return
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1782
1889
|
// Track variable declarations within the function
|
|
1783
1890
|
if (ts.isVariableDeclaration(node)) {
|
|
1784
1891
|
extractBindingIdentifiers(node.name)
|
|
@@ -1798,9 +1905,9 @@ function extractAndValidateAmbientReferences(
|
|
|
1798
1905
|
localIdentifiers.add(node.name.text)
|
|
1799
1906
|
}
|
|
1800
1907
|
|
|
1801
|
-
// Track parameters from nested arrow functions and
|
|
1908
|
+
// Track parameters from nested arrow functions, function expressions, and method declarations
|
|
1802
1909
|
// This prevents false positives where callback parameters are treated as ambient references
|
|
1803
|
-
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
1910
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node)) {
|
|
1804
1911
|
for (const param of node.parameters) {
|
|
1805
1912
|
extractBindingIdentifiers(param.name)
|
|
1806
1913
|
}
|
|
@@ -1852,6 +1959,11 @@ function extractAndValidateAmbientReferences(
|
|
|
1852
1959
|
return
|
|
1853
1960
|
}
|
|
1854
1961
|
|
|
1962
|
+
// Skip if it's a method name in an object literal (shorthand method declaration)
|
|
1963
|
+
if (parent && ts.isMethodDeclaration(parent) && parent.name === node) {
|
|
1964
|
+
return
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1855
1967
|
// Skip if it's a property name in a destructuring binding pattern
|
|
1856
1968
|
// e.g., const { encryptString: encryptFn } = await import('...')
|
|
1857
1969
|
// 'encryptString' is the propertyName, 'encryptFn' is the binding name
|