@stream44.studio/encapsulate 0.4.0-rc.34 → 0.4.0-rc.39
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 +2 -2
- package/package.json +6 -5
- package/src/capsule-projectors/{CapsuleModuleProjector.v0.ts → CapsuleModuleProjector.ts} +52 -447
- package/src/encapsulate.ts +126 -20
- package/src/spine-contracts/CapsuleSpineContract.v0/{Membrane.v0.ts → Membrane.ts} +124 -77
- package/src/spine-contracts/CapsuleSpineContract.v0/README.md +29 -3
- package/src/spine-contracts/CapsuleSpineContract.v0/{Static.v0.ts → Static.ts} +144 -22
- package/src/spine-factories/{CapsuleSpineFactory.v0.ts → CapsuleSpineFactory.ts} +117 -6
- package/src/{static-analyzer.v0.ts → static-analyzer.ts} +15 -0
- package/structs/CapsuleProjectionContext.ts +53 -0
- package/structs/StructFactory.ts +90 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<table>
|
|
2
2
|
<tr>
|
|
3
|
-
<td><a href="https://Stream44.
|
|
4
|
-
<td><strong><a href="https://Stream44.
|
|
3
|
+
<td><a href="https://Stream44.Systems"><img src=".o/stream44.studio/assets/Icon-v1.svg" width="42" height="42"></a></td>
|
|
4
|
+
<td><strong><a href="https://Stream44.Systems">Stream44 Systems</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
6
|
<td>Designed by Hand<br/><b>AI assisted Code</a></td>
|
|
7
7
|
</tr>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream44.studio/encapsulate",
|
|
3
|
-
"version": "0.4.0-rc.
|
|
3
|
+
"version": "0.4.0-rc.39",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
"type": "module",
|
|
11
11
|
"exports": {
|
|
12
12
|
"./encapsulate": "./src/encapsulate.ts",
|
|
13
|
-
"./spine-contracts/CapsuleSpineContract.v0/Static
|
|
14
|
-
"./spine-contracts/CapsuleSpineContract.v0/Membrane
|
|
15
|
-
"./spine-factories/CapsuleSpineFactory
|
|
13
|
+
"./spine-contracts/CapsuleSpineContract.v0/Static": "./src/spine-contracts/CapsuleSpineContract.v0/Static.ts",
|
|
14
|
+
"./spine-contracts/CapsuleSpineContract.v0/Membrane": "./src/spine-contracts/CapsuleSpineContract.v0/Membrane.ts",
|
|
15
|
+
"./spine-factories/CapsuleSpineFactory": "./src/spine-factories/CapsuleSpineFactory.ts",
|
|
16
16
|
"./spine-factories/TimingObserver": "./src/spine-factories/TimingObserver.ts",
|
|
17
|
-
"./structs/Capsule": "./structs/Capsule.ts"
|
|
17
|
+
"./structs/Capsule": "./structs/Capsule.ts",
|
|
18
|
+
"./structs/CapsuleProjectionContext": "./structs/CapsuleProjectionContext.ts"
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
21
|
"test": "bun test",
|
|
@@ -50,7 +50,7 @@ async function constructCacheFilePath(moduleFilepath: string, importStackLine: n
|
|
|
50
50
|
* Finds the nearest package.json and constructs an npm URI for cache files.
|
|
51
51
|
* First checks if the path contains /node_modules/ and if so, extracts the portion
|
|
52
52
|
* after the last /node_modules/ occurrence for consistent paths in dev and installed mode.
|
|
53
|
-
* This matches the logic from static-analyzer.
|
|
53
|
+
* This matches the logic from static-analyzer.ts
|
|
54
54
|
*/
|
|
55
55
|
async function constructNpmUriForCache(absoluteFilepath: string, spineRoot: string): Promise<string | null> {
|
|
56
56
|
// Check for /node_modules/ in the path — use the last occurrence to handle nested node_modules
|
|
@@ -199,6 +199,9 @@ export function CapsuleModuleProjector({
|
|
|
199
199
|
for (const nestedPropName in propContract.properties) {
|
|
200
200
|
if (nestedPropName.startsWith('/')) {
|
|
201
201
|
const nestedProp = propContract.properties[nestedPropName]
|
|
202
|
+
// Skip if this mapping has a propertyContractDelegate — the delegate
|
|
203
|
+
// handles its own projection via OnFreeze
|
|
204
|
+
if (nestedProp.propertyContractDelegate) continue
|
|
202
205
|
// Check if this is a Mapping type property
|
|
203
206
|
if (nestedProp.type === 'CapsulePropertyTypes.Mapping') {
|
|
204
207
|
// First try to find the mapped capsule in ambient references
|
|
@@ -287,23 +290,6 @@ export function CapsuleModuleProjector({
|
|
|
287
290
|
return uris
|
|
288
291
|
}
|
|
289
292
|
|
|
290
|
-
// Helper: Check if capsule has solidjs.com/standalone property
|
|
291
|
-
function hasSolidJsProperty(capsule: any, spineContractUri: string): boolean {
|
|
292
|
-
const spineContract = capsule.cst.spineContracts[spineContractUri]
|
|
293
|
-
// Check both top-level and nested under '#' property contract
|
|
294
|
-
const topLevelProps = spineContract?.propertyContracts || {}
|
|
295
|
-
const nestedProps = spineContract?.propertyContracts?.['#']?.properties || {}
|
|
296
|
-
|
|
297
|
-
// Check for solidjs.com/standalone specifically
|
|
298
|
-
for (const key of Object.keys(topLevelProps)) {
|
|
299
|
-
if (key === 'solidjs.com/standalone') return true
|
|
300
|
-
}
|
|
301
|
-
for (const key of Object.keys(nestedProps)) {
|
|
302
|
-
if (key === 'solidjs.com/standalone') return true
|
|
303
|
-
}
|
|
304
|
-
return false
|
|
305
|
-
}
|
|
306
|
-
|
|
307
293
|
// Helper: Check if capsule has encapsulate.dev/standalone property (with optional suffix)
|
|
308
294
|
function hasStandaloneProperty(capsule: any, spineContractUri: string): boolean {
|
|
309
295
|
const spineContract = capsule.cst.spineContracts[spineContractUri]
|
|
@@ -321,66 +307,6 @@ export function CapsuleModuleProjector({
|
|
|
321
307
|
return false
|
|
322
308
|
}
|
|
323
309
|
|
|
324
|
-
// Helper: Extract SolidJS component function from capsule definition
|
|
325
|
-
function extractSolidJsComponent(capsule: any, spineContractUri: string): string | null {
|
|
326
|
-
const spineContract = capsule.cst.spineContracts[spineContractUri]
|
|
327
|
-
|
|
328
|
-
// Check nested under '#' property contract first, looking for solidjs.com/standalone
|
|
329
|
-
const nestedProps = spineContract?.propertyContracts?.['#']?.properties || {}
|
|
330
|
-
const topLevelProps = spineContract?.propertyContracts || {}
|
|
331
|
-
|
|
332
|
-
let solidjsProp = null
|
|
333
|
-
for (const key of Object.keys(nestedProps)) {
|
|
334
|
-
if (key === 'solidjs.com/standalone') {
|
|
335
|
-
solidjsProp = nestedProps[key]
|
|
336
|
-
break
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
if (!solidjsProp) {
|
|
340
|
-
for (const key of Object.keys(topLevelProps)) {
|
|
341
|
-
if (key === 'solidjs.com/standalone') {
|
|
342
|
-
solidjsProp = topLevelProps[key]
|
|
343
|
-
break
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (!solidjsProp || solidjsProp.type !== 'CapsulePropertyTypes.Function') {
|
|
349
|
-
return null
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Extract the value expression which contains the component function
|
|
353
|
-
const valueExpression = solidjsProp.valueExpression
|
|
354
|
-
if (!valueExpression) return null
|
|
355
|
-
|
|
356
|
-
// The value expression is: "function (this: any): Function {\n return function ComponentName() { ... }\n}"
|
|
357
|
-
// We need to extract the inner function after "return "
|
|
358
|
-
// Use a more flexible regex that handles multiline and varying whitespace
|
|
359
|
-
const match = valueExpression.match(/return\s+(function\s+\w*\s*\([^)]*\)\s*\{[\s\S]*)\s*\}\s*$/m)
|
|
360
|
-
if (match) {
|
|
361
|
-
// Clean up the extracted function - remove extra indentation
|
|
362
|
-
let extracted = match[1].trim()
|
|
363
|
-
// Add back the closing brace if it was removed
|
|
364
|
-
if (!extracted.endsWith('}')) {
|
|
365
|
-
extracted += '\n}'
|
|
366
|
-
}
|
|
367
|
-
// Remove leading indentation from each line
|
|
368
|
-
const lines = extracted.split('\n')
|
|
369
|
-
const minIndent = lines
|
|
370
|
-
.filter(line => line.trim().length > 0)
|
|
371
|
-
.map(line => line.match(/^(\s*)/)?.[1].length || 0)
|
|
372
|
-
.reduce((min, indent) => Math.min(min, indent), Infinity)
|
|
373
|
-
|
|
374
|
-
if (minIndent > 0 && minIndent !== Infinity) {
|
|
375
|
-
extracted = lines.map(line => line.substring(minIndent)).join('\n')
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return extracted
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return null
|
|
382
|
-
}
|
|
383
|
-
|
|
384
310
|
// Helper: Extract standalone function from capsule definition
|
|
385
311
|
function extractStandaloneFunction(capsule: any, spineContractUri: string): string | null {
|
|
386
312
|
const spineContract = capsule.cst.spineContracts[spineContractUri]
|
|
@@ -812,196 +738,15 @@ export function CapsuleModuleProjector({
|
|
|
812
738
|
|
|
813
739
|
const allStatements = [importStatements, literalReferences, moduleLocalFunctions].filter(Boolean).join('\n')
|
|
814
740
|
|
|
815
|
-
// Check if this capsule has
|
|
816
|
-
const hasSolidJs = hasSolidJsProperty(capsule, spineContractUri)
|
|
741
|
+
// Check if this capsule has an encapsulate.dev/standalone property
|
|
817
742
|
const hasStandalone = hasStandaloneProperty(capsule, spineContractUri)
|
|
818
|
-
const needsRuntime = hasSolidJs || hasStandalone
|
|
819
|
-
|
|
820
|
-
// Determine which solid-js imports are needed (avoid duplicates with ambient references)
|
|
821
|
-
const existingSolidJsImports = new Set<string>()
|
|
822
|
-
for (const [name, ref] of Object.entries(ambientReferences)) {
|
|
823
|
-
const refTyped = ref as any
|
|
824
|
-
if (refTyped.type === 'import' && refTyped.moduleUri === 'solid-js') {
|
|
825
|
-
// Parse existing imports from solid-js
|
|
826
|
-
const match = refTyped.importSpecifier?.match(/\{([^}]+)\}/)
|
|
827
|
-
if (match) {
|
|
828
|
-
match[1].split(',').forEach((imp: string) => existingSolidJsImports.add(imp.trim()))
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
const neededSolidJsImports = ['createSignal', 'onMount', 'Show'].filter(imp => !existingSolidJsImports.has(imp))
|
|
834
|
-
const solidJsImport = hasSolidJs && neededSolidJsImports.length > 0 ? `import { ${neededSolidJsImports.join(', ')} } from 'solid-js'\n` : ''
|
|
835
743
|
|
|
836
|
-
// Add runtime imports for
|
|
837
|
-
const runtimeImport =
|
|
744
|
+
// Add runtime imports for standalone functions
|
|
745
|
+
const runtimeImport = hasStandalone ? `"use client"\nimport { Spine, SpineRuntime } from '@stream44.studio/encapsulate/encapsulate'\nimport { CapsuleSpineContract } from '@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0/Membrane'\n` : ''
|
|
838
746
|
|
|
839
747
|
// Generate default export based on capsule type
|
|
840
748
|
let defaultExport = ''
|
|
841
|
-
if (
|
|
842
|
-
// Generate a wrapper that sets up runtime and exports the SolidJS component
|
|
843
|
-
const capsuleSourceLineRef = capsule.cst.capsuleSourceLineRef
|
|
844
|
-
const solidjsComponent = extractSolidJsComponent(capsule, spineContractUri)
|
|
845
|
-
if (solidjsComponent) {
|
|
846
|
-
// Collect all capsule URIs from CST (mappings and property contracts)
|
|
847
|
-
const allCapsuleUris = collectAllCapsuleUris(capsule, spineContractUri)
|
|
848
|
-
|
|
849
|
-
// Also collect from ambient references and build import paths from snapshots
|
|
850
|
-
const capsuleDeps: Array<{ uri: string, importName: string, importPath: string }> = []
|
|
851
|
-
for (const [name, ref] of Object.entries(ambientReferences)) {
|
|
852
|
-
const refTyped = ref as any
|
|
853
|
-
if (refTyped.type === 'capsule') {
|
|
854
|
-
const snapshot = await buildCapsuleSnapshotForReference(refTyped, capsules, spineContractUri)
|
|
855
|
-
|
|
856
|
-
// Use dynamic spineContractUri instead of hardcoded URI
|
|
857
|
-
const contractData = snapshot.spineContracts?.[spineContractUri]
|
|
858
|
-
const structKey = Object.keys(contractData || {}).find(k => k.includes('/structs/Capsule'))
|
|
859
|
-
const capsuleName = structKey ? contractData[structKey]?.capsuleName : undefined
|
|
860
|
-
const projectedFilepath = structKey ? contractData[structKey]?.projectedCapsuleFilepath : undefined
|
|
861
|
-
|
|
862
|
-
if (capsuleName && projectedFilepath) {
|
|
863
|
-
allCapsuleUris.add(capsuleName)
|
|
864
|
-
|
|
865
|
-
// Build import path from projected filepath
|
|
866
|
-
const importName = `_capsule_${capsuleName.replace(/[^a-zA-Z0-9]/g, '_')}`
|
|
867
|
-
// Remove .~o/encapsulate.dev/caps/ prefix and strip extension
|
|
868
|
-
const importPath = projectedFilepath.replace(/^\.~o\/encapsulate\.dev\/caps\//, '').replace(/\.(ts|tsx)$/, '')
|
|
869
|
-
|
|
870
|
-
capsuleDeps.push({ uri: capsuleName, importName, importPath })
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// Generate static imports for all capsule dependencies
|
|
876
|
-
// Compute relative path from projected file to caps directory
|
|
877
|
-
let importPrefix: string
|
|
878
|
-
if (capsuleModuleProjectionPackage) {
|
|
879
|
-
importPrefix = capsuleModuleProjectionPackage
|
|
880
|
-
} else {
|
|
881
|
-
const projectedFileDir = dirname(filepath)
|
|
882
|
-
const capsDir = '.~o/encapsulate.dev/caps'
|
|
883
|
-
const relativePathToCaps = relative(projectedFileDir, capsDir)
|
|
884
|
-
importPrefix = relativePathToCaps.startsWith('.') ? relativePathToCaps : './' + relativePathToCaps
|
|
885
|
-
}
|
|
886
|
-
const capsuleImports = capsuleDeps.map(dep =>
|
|
887
|
-
`import * as ${dep.importName} from '${importPrefix}/${dep.importPath}'`
|
|
888
|
-
).join('\n')
|
|
889
|
-
|
|
890
|
-
// Generate capsules map
|
|
891
|
-
const capsulesMapEntries = capsuleDeps.map(dep =>
|
|
892
|
-
` '${dep.uri}': ${dep.importName}`
|
|
893
|
-
).join(',\n')
|
|
894
|
-
|
|
895
|
-
defaultExport = `
|
|
896
|
-
${capsuleImports}
|
|
897
|
-
|
|
898
|
-
// Set up runtime for browser execution
|
|
899
|
-
const sourceSpine: { encapsulate?: any } = {}
|
|
900
|
-
|
|
901
|
-
// Map of statically imported capsules
|
|
902
|
-
const capsulesMap: Record<string, any> = {
|
|
903
|
-
${capsulesMapEntries}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
// Helper to import and instantiate a capsule from the capsules map
|
|
907
|
-
const importCapsule = async (uri: string) => {
|
|
908
|
-
const capsuleModule = capsulesMap[uri]
|
|
909
|
-
if (!capsuleModule) {
|
|
910
|
-
throw new Error(\`Capsule not found in static imports: \${uri}\`)
|
|
911
|
-
}
|
|
912
|
-
const capsule = await capsuleModule.capsule({
|
|
913
|
-
encapsulate: sourceSpine.encapsulate,
|
|
914
|
-
loadCapsule
|
|
915
|
-
})
|
|
916
|
-
return capsule
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
const loadCapsule = async ({ capsuleSourceLineRef, capsuleName }: any) => {
|
|
920
|
-
// Return the capsule function from this projected file
|
|
921
|
-
if (capsuleSourceLineRef === '${capsuleSourceLineRef}') {
|
|
922
|
-
return capsule
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
// Use capsuleName directly if provided
|
|
926
|
-
if (capsuleName) {
|
|
927
|
-
return await importCapsule(capsuleName)
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
throw new Error(\`Cannot load capsule: \${capsuleSourceLineRef}\`)
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
const spineContractOpts = {
|
|
934
|
-
spineFilesystemRoot: '.',
|
|
935
|
-
resolve: async (uri: string) => uri,
|
|
936
|
-
importCapsule
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
const runtimeSpineContracts = {
|
|
940
|
-
['#' + CapsuleSpineContract['#']]: CapsuleSpineContract(spineContractOpts)
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
const snapshot = {
|
|
944
|
-
capsules: {
|
|
945
|
-
['${capsuleSourceLineRef}']: {
|
|
946
|
-
spineContracts: {}
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// Export wrapper function that initializes runtime and returns component
|
|
952
|
-
export default function({ onMembraneEvent }: { onMembraneEvent?: (event: any) => void } = {}) {
|
|
953
|
-
const [component, setComponent] = createSignal(null)
|
|
954
|
-
|
|
955
|
-
onMount(async () => {
|
|
956
|
-
// Add onMembraneEvent to spine contract opts - use provided or default logger
|
|
957
|
-
const defaultMembraneLogger = (event: any) => {
|
|
958
|
-
console.log('[Membrane Event]', event)
|
|
959
|
-
}
|
|
960
|
-
const opts = {
|
|
961
|
-
...spineContractOpts,
|
|
962
|
-
onMembraneEvent: onMembraneEvent || defaultMembraneLogger
|
|
963
|
-
}
|
|
964
|
-
const contracts = {
|
|
965
|
-
['#' + CapsuleSpineContract['#']]: CapsuleSpineContract(opts)
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
const { encapsulate, capsules } = await Spine({
|
|
969
|
-
spineFilesystemRoot: '.',
|
|
970
|
-
spineContracts: contracts
|
|
971
|
-
})
|
|
972
|
-
|
|
973
|
-
sourceSpine.encapsulate = encapsulate
|
|
974
|
-
|
|
975
|
-
const capsuleInstance = await capsule({ encapsulate, loadCapsule })
|
|
976
|
-
|
|
977
|
-
const { run } = await SpineRuntime({
|
|
978
|
-
spineFilesystemRoot: '.',
|
|
979
|
-
spineContracts: contracts,
|
|
980
|
-
snapshot,
|
|
981
|
-
loadCapsule
|
|
982
|
-
})
|
|
983
|
-
|
|
984
|
-
const Component = await run({}, async ({ apis }) => {
|
|
985
|
-
const capsuleApi = apis['${capsuleSourceLineRef}']
|
|
986
|
-
const solidjsKey = Object.keys(capsuleApi).find(k => k === 'solidjs.com/standalone')
|
|
987
|
-
if (!solidjsKey) throw new Error('solidjs.com/standalone property not found')
|
|
988
|
-
return capsuleApi[solidjsKey]()
|
|
989
|
-
})
|
|
990
|
-
|
|
991
|
-
setComponent(() => Component)
|
|
992
|
-
})
|
|
993
|
-
|
|
994
|
-
// Return the wrapper function itself, not call it
|
|
995
|
-
const WrapperComponent = () => {
|
|
996
|
-
const Component = component()
|
|
997
|
-
return Show({ when: Component, children: (Component) => Component() })
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
return WrapperComponent
|
|
1001
|
-
}
|
|
1002
|
-
`
|
|
1003
|
-
}
|
|
1004
|
-
} else if (hasStandalone) {
|
|
749
|
+
if (hasStandalone) {
|
|
1005
750
|
// Generate a wrapper function that directly invokes the standalone function
|
|
1006
751
|
const capsuleSourceLineRef = capsule.cst.capsuleSourceLineRef
|
|
1007
752
|
|
|
@@ -1095,6 +840,7 @@ ${capsulesMapEntries}
|
|
|
1095
840
|
}
|
|
1096
841
|
const capsule = await capsuleModule.capsule({
|
|
1097
842
|
encapsulate: sourceSpine.encapsulate,
|
|
843
|
+
CapsulePropertyTypes,
|
|
1098
844
|
loadCapsule
|
|
1099
845
|
})
|
|
1100
846
|
return capsule
|
|
@@ -1255,8 +1001,12 @@ ${defaultExport}
|
|
|
1255
1001
|
mappedCapsule.cst.source.moduleFilepath
|
|
1256
1002
|
)
|
|
1257
1003
|
|
|
1258
|
-
// Get ambient references for the mapped capsule
|
|
1259
|
-
|
|
1004
|
+
// Get ambient references for the mapped capsule, excluding makeImportStack
|
|
1005
|
+
let mappedAmbientRefs = mappedCapsule.cst.source?.ambientReferences || {}
|
|
1006
|
+
if (mappedAmbientRefs['makeImportStack']) {
|
|
1007
|
+
mappedAmbientRefs = { ...mappedAmbientRefs }
|
|
1008
|
+
delete mappedAmbientRefs['makeImportStack']
|
|
1009
|
+
}
|
|
1260
1010
|
|
|
1261
1011
|
// Generate import statements with projection CSS paths
|
|
1262
1012
|
const mappedImportStatements = Object.entries(mappedAmbientRefs)
|
|
@@ -1315,191 +1065,17 @@ ${defaultExport}
|
|
|
1315
1065
|
|
|
1316
1066
|
const mappedAllStatements = [mappedImportStatements, mappedLiteralReferences, mappedModuleLocalFunctions].filter(Boolean).join('\n')
|
|
1317
1067
|
|
|
1318
|
-
// Check if mapped capsule has
|
|
1319
|
-
const mappedHasSolidJs = hasSolidJsProperty(mappedCapsule, spineContractUri)
|
|
1068
|
+
// Check if mapped capsule has encapsulate.dev/standalone property
|
|
1320
1069
|
const mappedHasStandalone = hasStandaloneProperty(mappedCapsule, spineContractUri)
|
|
1321
|
-
const mappedNeedsRuntime = mappedHasSolidJs || mappedHasStandalone
|
|
1322
|
-
|
|
1323
|
-
// Determine which solid-js imports are needed for mapped capsule
|
|
1324
|
-
const mappedExistingSolidJsImports = new Set<string>()
|
|
1325
|
-
for (const [name, ref] of Object.entries(mappedAmbientRefs)) {
|
|
1326
|
-
const refTyped = ref as any
|
|
1327
|
-
if (refTyped.type === 'import' && refTyped.moduleUri === 'solid-js') {
|
|
1328
|
-
const match = refTyped.importSpecifier?.match(/\{([^}]+)\}/)
|
|
1329
|
-
if (match) {
|
|
1330
|
-
match[1].split(',').forEach((imp: string) => mappedExistingSolidJsImports.add(imp.trim()))
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
const mappedNeededSolidJsImports = ['createSignal', 'onMount', 'Show'].filter(imp => !mappedExistingSolidJsImports.has(imp))
|
|
1336
|
-
const mappedSolidJsImport = mappedHasSolidJs && mappedNeededSolidJsImports.length > 0 ? `import { ${mappedNeededSolidJsImports.join(', ')} } from 'solid-js'\n` : ''
|
|
1337
1070
|
|
|
1338
1071
|
// Rewrite the mapped capsule expression to include CST (reuse the same function)
|
|
1339
1072
|
const mappedCapsuleExpression = rewriteCapsuleExpressionWithCST(mappedCapsule)
|
|
1340
1073
|
|
|
1341
|
-
// Add runtime imports for
|
|
1342
|
-
const mappedRuntimeImport =
|
|
1074
|
+
// Add runtime imports for standalone functions
|
|
1075
|
+
const mappedRuntimeImport = mappedHasStandalone ? `"use client"\nimport { Spine, SpineRuntime } from '@stream44.studio/encapsulate/encapsulate'\nimport { CapsuleSpineContract } from '@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0/Membrane'\n` : ''
|
|
1343
1076
|
|
|
1344
1077
|
let mappedDefaultExport = ''
|
|
1345
|
-
if (
|
|
1346
|
-
// Generate a wrapper that sets up runtime and exports the SolidJS component
|
|
1347
|
-
const mappedCapsuleSourceLineRef = mappedCapsule.cst.capsuleSourceLineRef
|
|
1348
|
-
const solidjsComponent = extractSolidJsComponent(mappedCapsule, spineContractUri)
|
|
1349
|
-
if (solidjsComponent) {
|
|
1350
|
-
// Collect all capsule URIs from CST (mappings and property contracts)
|
|
1351
|
-
const allMappedCapsuleUris = collectAllCapsuleUris(mappedCapsule, spineContractUri)
|
|
1352
|
-
|
|
1353
|
-
// Also collect from ambient references
|
|
1354
|
-
for (const [name, ref] of Object.entries(mappedAmbientRefs)) {
|
|
1355
|
-
const refTyped = ref as any
|
|
1356
|
-
if (refTyped.type === 'capsule') {
|
|
1357
|
-
const snapshot = await buildCapsuleSnapshotForReference(refTyped, capsules, spineContractUri)
|
|
1358
|
-
const contractData = snapshot.spineContracts?.[spineContractUri]
|
|
1359
|
-
const structKey = Object.keys(contractData || {}).find(k => k.includes('/structs/Capsule'))
|
|
1360
|
-
const capsuleName = structKey ? contractData[structKey]?.capsuleName : undefined
|
|
1361
|
-
if (capsuleName) {
|
|
1362
|
-
allMappedCapsuleUris.add(capsuleName)
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
// Build capsule dependencies array
|
|
1368
|
-
const mappedCapsuleDeps: Array<{ uri: string, importName: string, importPath: string }> = []
|
|
1369
|
-
for (const uri of allMappedCapsuleUris) {
|
|
1370
|
-
const importName = `_capsule_${uri.replace(/[^a-zA-Z0-9]/g, '_')}`
|
|
1371
|
-
// Strip leading @ to match caps filesystem paths
|
|
1372
|
-
const importPath = uri.startsWith('@') ? uri.substring(1) : uri
|
|
1373
|
-
mappedCapsuleDeps.push({ uri, importName, importPath })
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
// Generate static imports for all capsule dependencies
|
|
1377
|
-
// Compute relative path from projected file to caps directory
|
|
1378
|
-
let importPrefix: string
|
|
1379
|
-
if (capsuleModuleProjectionPackage) {
|
|
1380
|
-
importPrefix = capsuleModuleProjectionPackage
|
|
1381
|
-
} else {
|
|
1382
|
-
const projectedFileDir = dirname(mapped.projectionPath)
|
|
1383
|
-
const capsDir = '.~o/encapsulate.dev/caps'
|
|
1384
|
-
const relativePathToCaps = relative(projectedFileDir, capsDir)
|
|
1385
|
-
importPrefix = relativePathToCaps.startsWith('.') ? relativePathToCaps : './' + relativePathToCaps
|
|
1386
|
-
}
|
|
1387
|
-
const mappedCapsuleImports = mappedCapsuleDeps.map(dep =>
|
|
1388
|
-
`import * as ${dep.importName} from '${importPrefix}/${dep.importPath}'`
|
|
1389
|
-
).join('\n')
|
|
1390
|
-
|
|
1391
|
-
// Generate capsules map
|
|
1392
|
-
const mappedCapsulesMapEntries = mappedCapsuleDeps.map(dep =>
|
|
1393
|
-
` '${dep.uri}': ${dep.importName}`
|
|
1394
|
-
).join(',\n')
|
|
1395
|
-
|
|
1396
|
-
mappedDefaultExport = `
|
|
1397
|
-
${mappedCapsuleImports}
|
|
1398
|
-
|
|
1399
|
-
// Set up runtime for browser execution
|
|
1400
|
-
const sourceSpine: { encapsulate?: any } = {}
|
|
1401
|
-
|
|
1402
|
-
// Map of statically imported capsules
|
|
1403
|
-
const capsulesMap: Record<string, any> = {
|
|
1404
|
-
${mappedCapsulesMapEntries}
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
// Helper to import and instantiate a capsule from the capsules map
|
|
1408
|
-
const importCapsule = async (uri: string) => {
|
|
1409
|
-
const capsuleModule = capsulesMap[uri]
|
|
1410
|
-
if (!capsuleModule) {
|
|
1411
|
-
throw new Error(\`Capsule not found in static imports: \${uri}\`)
|
|
1412
|
-
}
|
|
1413
|
-
const capsule = await capsuleModule.capsule({
|
|
1414
|
-
encapsulate: sourceSpine.encapsulate,
|
|
1415
|
-
loadCapsule
|
|
1416
|
-
})
|
|
1417
|
-
return capsule
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
const loadCapsule = async ({ capsuleSourceLineRef, capsuleName }: any) => {
|
|
1421
|
-
// Return the capsule function from this projected file
|
|
1422
|
-
if (capsuleSourceLineRef === '${mappedCapsuleSourceLineRef}') {
|
|
1423
|
-
return capsule
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
// Use capsuleName directly if provided
|
|
1427
|
-
if (capsuleName) {
|
|
1428
|
-
return await importCapsule(capsuleName)
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
throw new Error(\`Cannot load capsule: \${capsuleSourceLineRef}\`)
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
const spineContractOpts = {
|
|
1435
|
-
spineFilesystemRoot: '.',
|
|
1436
|
-
resolve: async (uri: string) => uri,
|
|
1437
|
-
importCapsule
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
const runtimeSpineContracts = {
|
|
1441
|
-
['#' + CapsuleSpineContract['#']]: CapsuleSpineContract(spineContractOpts)
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
const snapshot = {
|
|
1445
|
-
capsules: {
|
|
1446
|
-
['${mappedCapsuleSourceLineRef}']: {
|
|
1447
|
-
spineContracts: {}
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
// Export wrapper function that initializes runtime and returns component
|
|
1453
|
-
export default function({ onMembraneEvent }: { onMembraneEvent?: (event: any) => void } = {}) {
|
|
1454
|
-
const [component, setComponent] = createSignal(null)
|
|
1455
|
-
|
|
1456
|
-
onMount(async () => {
|
|
1457
|
-
// Add onMembraneEvent to spine contract opts - use provided or default logger
|
|
1458
|
-
const defaultMembraneLogger = (event: any) => {
|
|
1459
|
-
console.log('[Membrane Event]', event.type, event)
|
|
1460
|
-
}
|
|
1461
|
-
const opts = {
|
|
1462
|
-
...spineContractOpts,
|
|
1463
|
-
onMembraneEvent: onMembraneEvent || defaultMembraneLogger
|
|
1464
|
-
}
|
|
1465
|
-
const contracts = {
|
|
1466
|
-
['#' + CapsuleSpineContract['#']]: CapsuleSpineContract(opts)
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
const { encapsulate, capsules } = await Spine({
|
|
1470
|
-
spineFilesystemRoot: '.',
|
|
1471
|
-
spineContracts: contracts
|
|
1472
|
-
})
|
|
1473
|
-
|
|
1474
|
-
sourceSpine.encapsulate = encapsulate
|
|
1475
|
-
|
|
1476
|
-
const capsuleInstance = await capsule({ encapsulate, loadCapsule })
|
|
1477
|
-
|
|
1478
|
-
const { run } = await SpineRuntime({
|
|
1479
|
-
spineFilesystemRoot: '.',
|
|
1480
|
-
spineContracts: contracts,
|
|
1481
|
-
snapshot,
|
|
1482
|
-
loadCapsule
|
|
1483
|
-
})
|
|
1484
|
-
|
|
1485
|
-
const Component = await run({}, async ({ apis }) => {
|
|
1486
|
-
const capsuleApi = apis['${mappedCapsuleSourceLineRef}']
|
|
1487
|
-
const solidjsKey = Object.keys(capsuleApi).find(k => k === 'solidjs.com/standalone')
|
|
1488
|
-
if (!solidjsKey) throw new Error('solidjs.com/standalone property not found')
|
|
1489
|
-
return capsuleApi[solidjsKey]()
|
|
1490
|
-
})
|
|
1491
|
-
|
|
1492
|
-
setComponent(() => Component)
|
|
1493
|
-
})
|
|
1494
|
-
|
|
1495
|
-
return () => {
|
|
1496
|
-
const Component = component()
|
|
1497
|
-
return Show({ when: Component, children: (Component) => Component() })
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
`
|
|
1501
|
-
}
|
|
1502
|
-
} else if (mappedHasStandalone) {
|
|
1078
|
+
if (mappedHasStandalone) {
|
|
1503
1079
|
// Generate a wrapper function that directly invokes the standalone function
|
|
1504
1080
|
const mappedCapsuleSourceLineRef = mappedCapsule.cst.capsuleSourceLineRef
|
|
1505
1081
|
|
|
@@ -1570,6 +1146,7 @@ ${mappedCapsulesMapEntries}
|
|
|
1570
1146
|
}
|
|
1571
1147
|
const capsule = await capsuleModule.capsule({
|
|
1572
1148
|
encapsulate: sourceSpine.encapsulate,
|
|
1149
|
+
CapsulePropertyTypes,
|
|
1573
1150
|
loadCapsule
|
|
1574
1151
|
})
|
|
1575
1152
|
return capsule
|
|
@@ -1726,10 +1303,38 @@ ${mappedDefaultExport}
|
|
|
1726
1303
|
.filter(Boolean)
|
|
1727
1304
|
.join('\n ')
|
|
1728
1305
|
|
|
1729
|
-
//
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1306
|
+
// Filter out makeImportStack from ambient references (same as main capsule path)
|
|
1307
|
+
let filteredCapsuleAmbientRefs = capsuleAmbientRefs
|
|
1308
|
+
if (filteredCapsuleAmbientRefs['makeImportStack']) {
|
|
1309
|
+
filteredCapsuleAmbientRefs = { ...filteredCapsuleAmbientRefs }
|
|
1310
|
+
delete filteredCapsuleAmbientRefs['makeImportStack']
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Generate imports from filtered ambient references
|
|
1314
|
+
const capsuleImportStatements = Object.entries(filteredCapsuleAmbientRefs)
|
|
1315
|
+
.map(([name, ref]: [string, any]) => {
|
|
1316
|
+
if (ref.type === 'import') {
|
|
1317
|
+
if (ref.moduleUri.endsWith('.css')) {
|
|
1318
|
+
return `import '${ref.moduleUri}'`
|
|
1319
|
+
}
|
|
1320
|
+
return `import ${ref.importSpecifier} from '${ref.moduleUri}'`
|
|
1321
|
+
}
|
|
1322
|
+
if (ref.type === 'assigned') {
|
|
1323
|
+
if (ref.moduleUri.includes('/spine-factories/')) {
|
|
1324
|
+
return `import { ${name} } from '@stream44.studio/encapsulate/encapsulate'`
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
if (ref.type === 'invocation-argument') {
|
|
1328
|
+
if (ref.isEncapsulateExport) {
|
|
1329
|
+
return `import { ${name} } from '@stream44.studio/encapsulate/encapsulate'`
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
return ''
|
|
1333
|
+
})
|
|
1334
|
+
.filter(Boolean)
|
|
1335
|
+
.join('\n')
|
|
1336
|
+
|
|
1337
|
+
const imports = capsuleImportStatements ? capsuleImportStatements + '\n' : ''
|
|
1733
1338
|
|
|
1734
1339
|
// Get the capsule name for the assignment
|
|
1735
1340
|
const capsuleName = registryCapsule.cst.source.capsuleName || ''
|