@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.
@@ -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
- properties: {}
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
- const propName = ts.isIdentifier(prop.name) ? prop.name.text : (prop.name as ts.StringLiteral).text
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.properties[propName]) {
453
- spineContractDef.properties[propName] = {
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.properties[propName].as = contractProp.initializer.text
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.properties[propName].properties[contractPropName] = propDef
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.properties) {
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.properties)) {
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.properties['#']) {
567
- spineContract.properties['#'] = {
656
+ if (!spineContract.propertyContracts['#']) {
657
+ spineContract.propertyContracts['#'] = {
568
658
  propertyContractUri: '',
569
659
  properties: {}
570
660
  }
571
661
  }
572
- if (!spineContract.properties['#'].properties) {
573
- spineContract.properties['#'].properties = {}
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.properties[propContractUri]?.as
669
+ const aliasName = spineContract.propertyContracts[propContractUri]?.as
580
670
  const contractKey = aliasName || ('#' + propContractUri.substring(1))
581
- spineContract.properties['#'].properties[contractKey] = {
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: `"${propContractUri.substring(1)}"`,
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 function expressions
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