@stream44.studio/encapsulate 0.2.0-rc.1
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 +21 -0
- package/package.json +21 -0
- package/src/capsule-projectors/CapsuleModuleProjector.v0.ts +1716 -0
- package/src/encapsulate.ts +662 -0
- package/src/spine-contracts/CapsuleSpineContract.v0/Membrane.v0.ts +624 -0
- package/src/spine-contracts/CapsuleSpineContract.v0/README.md +28 -0
- package/src/spine-contracts/CapsuleSpineContract.v0/Static.v0.ts +290 -0
- package/src/spine-factories/CapsuleSpineFactory.v0.ts +299 -0
- package/src/spine-factories/TimingObserver.ts +26 -0
- package/src/static-analyzer.v0.ts +1591 -0
- package/structs/Capsule.v0.ts +22 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { CapsulePropertyTypes, join } from "../../encapsulate"
|
|
2
|
+
|
|
3
|
+
export class ContractCapsuleInstanceFactory {
|
|
4
|
+
|
|
5
|
+
protected spineContractUri: string
|
|
6
|
+
protected capsule: any
|
|
7
|
+
protected self: any
|
|
8
|
+
protected encapsulatedApi: Record<string, any>
|
|
9
|
+
protected resolve?: (uri: string, parentFilepath: string) => Promise<string>
|
|
10
|
+
protected importCapsule?: (filepath: string) => Promise<any>
|
|
11
|
+
protected spineFilesystemRoot?: string
|
|
12
|
+
protected freezeCapsule?: (capsule: any) => Promise<any>
|
|
13
|
+
|
|
14
|
+
constructor({ spineContractUri, capsule, self, encapsulatedApi, resolve, importCapsule, spineFilesystemRoot, freezeCapsule }: { spineContractUri: string, capsule: any, self: any, encapsulatedApi: Record<string, any>, resolve?: (uri: string, parentFilepath: string) => Promise<string>, importCapsule?: (filepath: string) => Promise<any>, spineFilesystemRoot?: string, freezeCapsule?: (capsule: any) => Promise<any> }) {
|
|
15
|
+
this.spineContractUri = spineContractUri
|
|
16
|
+
this.capsule = capsule
|
|
17
|
+
this.self = self
|
|
18
|
+
this.encapsulatedApi = encapsulatedApi
|
|
19
|
+
this.resolve = resolve
|
|
20
|
+
this.importCapsule = importCapsule
|
|
21
|
+
this.spineFilesystemRoot = spineFilesystemRoot
|
|
22
|
+
this.freezeCapsule = freezeCapsule
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async mapProperty({ overrides, options, property }: { overrides: any, options: any, property: any }) {
|
|
26
|
+
if (property.definition.type === CapsulePropertyTypes.Mapping) {
|
|
27
|
+
await this.mapMappingProperty({ overrides, options, property })
|
|
28
|
+
} else if (
|
|
29
|
+
property.definition.type === CapsulePropertyTypes.String ||
|
|
30
|
+
property.definition.type === CapsulePropertyTypes.Literal
|
|
31
|
+
) {
|
|
32
|
+
this.mapLiteralProperty({ property })
|
|
33
|
+
} else if (property.definition.type === CapsulePropertyTypes.Function) {
|
|
34
|
+
this.mapFunctionProperty({ property })
|
|
35
|
+
} else if (property.definition.type === CapsulePropertyTypes.GetterFunction) {
|
|
36
|
+
this.mapGetterFunctionProperty({ property })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected getApiTarget({ property }: { property: any }) {
|
|
41
|
+
// Properties under '#' go directly on the API
|
|
42
|
+
// Properties under '#<uri>' go under api['#<uri>']
|
|
43
|
+
if (!property.propertyContractUri || property.propertyContractUri === '#') {
|
|
44
|
+
return this.encapsulatedApi
|
|
45
|
+
} else {
|
|
46
|
+
// Namespace under the property contract key
|
|
47
|
+
if (!this.encapsulatedApi[property.propertyContractUri]) {
|
|
48
|
+
this.encapsulatedApi[property.propertyContractUri] = {}
|
|
49
|
+
}
|
|
50
|
+
return this.encapsulatedApi[property.propertyContractUri]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected async resolveMappedCapsule({ property }: { property: any }) {
|
|
55
|
+
let mappedCapsule
|
|
56
|
+
|
|
57
|
+
if (typeof property.definition.value === 'string') {
|
|
58
|
+
if (!this.resolve) throw new Error(`'resolve' not set!`)
|
|
59
|
+
if (!this.spineFilesystemRoot) throw new Error(`'spineFilesystemRoot' not set!`)
|
|
60
|
+
if (!this.importCapsule) throw new Error(`'importCapsule' not set!`)
|
|
61
|
+
|
|
62
|
+
const parentPath = join(this.spineFilesystemRoot, this.capsule.cst.source.moduleFilepath)
|
|
63
|
+
const filepath = await this.resolve(property.definition.value, parentPath)
|
|
64
|
+
mappedCapsule = await this.importCapsule(filepath)
|
|
65
|
+
} else if (
|
|
66
|
+
typeof property.definition.value === 'object' &&
|
|
67
|
+
typeof property.definition.value.capsuleSourceLineRef === 'string'
|
|
68
|
+
) {
|
|
69
|
+
mappedCapsule = property.definition.value
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(`Unknown mapping value for property '${property.name}'!`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return mappedCapsule
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected async extractConstants({ mappedCapsule }: { mappedCapsule: any }) {
|
|
78
|
+
const constants: Record<string, any> = {}
|
|
79
|
+
|
|
80
|
+
const spineContractDef = mappedCapsule.definition[this.spineContractUri]
|
|
81
|
+
|
|
82
|
+
if (!spineContractDef) {
|
|
83
|
+
throw new Error(`Spine contract definition not found for URI: ${this.spineContractUri}. Available keys: ${Object.keys(mappedCapsule.definition).join(', ')}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Iterate through all keys in the spine contract definition
|
|
87
|
+
for (const [key, value] of Object.entries(spineContractDef)) {
|
|
88
|
+
if (key.startsWith('#')) {
|
|
89
|
+
// This is a property contract - iterate through its properties
|
|
90
|
+
for (const [prop, propDef] of Object.entries(value as Record<string, any>)) {
|
|
91
|
+
const { type, value: propValue } = propDef as any
|
|
92
|
+
|
|
93
|
+
if (typeof propValue === 'undefined') continue
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
type === CapsulePropertyTypes.String ||
|
|
97
|
+
type === CapsulePropertyTypes.Literal
|
|
98
|
+
) {
|
|
99
|
+
constants[prop] = propValue
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// Regular property (backwards compatibility)
|
|
104
|
+
const { type, value: propValue } = value as any
|
|
105
|
+
|
|
106
|
+
if (typeof propValue === 'undefined') continue
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
type === CapsulePropertyTypes.String ||
|
|
110
|
+
type === CapsulePropertyTypes.Literal
|
|
111
|
+
) {
|
|
112
|
+
constants[key] = propValue
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return constants
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
protected async mapMappingProperty({ overrides, options, property }: { overrides: any, options: any, property: any }) {
|
|
121
|
+
const mappedCapsule = await this.resolveMappedCapsule({ property })
|
|
122
|
+
const constants = await this.extractConstants({ mappedCapsule })
|
|
123
|
+
|
|
124
|
+
const mappingOptions = await property.definition.options?.({
|
|
125
|
+
constants
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Transform overrides if this mapping has a propertyContractDelegate
|
|
129
|
+
let mappedOverrides = overrides
|
|
130
|
+
if (property.definition.propertyContractDelegate) {
|
|
131
|
+
// Extract overrides for the delegate property contract and map them to '#'
|
|
132
|
+
// Try both capsuleSourceLineRef and capsuleName
|
|
133
|
+
const delegateOverrides =
|
|
134
|
+
overrides?.[this.capsule.encapsulateOptions.capsuleSourceLineRef]?.[property.definition.propertyContractDelegate] ||
|
|
135
|
+
(this.capsule.encapsulateOptions.capsuleName && overrides?.[this.capsule.encapsulateOptions.capsuleName]?.[property.definition.propertyContractDelegate])
|
|
136
|
+
|
|
137
|
+
if (delegateOverrides) {
|
|
138
|
+
mappedOverrides = {
|
|
139
|
+
...overrides,
|
|
140
|
+
[mappedCapsule.capsuleSourceLineRef]: {
|
|
141
|
+
'#': delegateOverrides
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (mappedCapsule.encapsulateOptions.capsuleName) {
|
|
145
|
+
mappedOverrides[mappedCapsule.encapsulateOptions.capsuleName] = {
|
|
146
|
+
'#': delegateOverrides
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const apiTarget = this.getApiTarget({ property })
|
|
153
|
+
const mappedInstance = await mappedCapsule.makeInstance({
|
|
154
|
+
overrides: mappedOverrides,
|
|
155
|
+
options: mappingOptions
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
apiTarget[property.name] = mappedInstance
|
|
159
|
+
// Use proxy to unwrap .api for this.self so internal references work
|
|
160
|
+
this.self[property.name] = mappedInstance.api ? new Proxy(mappedInstance.api, {
|
|
161
|
+
get: (target, prop) => {
|
|
162
|
+
const value = target[prop]
|
|
163
|
+
// Recursively unwrap nested .api objects
|
|
164
|
+
if (value && typeof value === 'object' && value.api) {
|
|
165
|
+
return value.api
|
|
166
|
+
}
|
|
167
|
+
return value
|
|
168
|
+
}
|
|
169
|
+
}) : mappedInstance
|
|
170
|
+
|
|
171
|
+
// If this mapping has a propertyContractDelegate, also mount the mapped capsule's API
|
|
172
|
+
// to the property contract namespace for direct access
|
|
173
|
+
if (property.definition.propertyContractDelegate) {
|
|
174
|
+
// Create the property contract namespace if it doesn't exist
|
|
175
|
+
if (!this.encapsulatedApi[property.definition.propertyContractDelegate]) {
|
|
176
|
+
this.encapsulatedApi[property.definition.propertyContractDelegate] = {}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Mount all properties from the mapped capsule's API to the property contract namespace
|
|
180
|
+
const delegateTarget = this.encapsulatedApi[property.definition.propertyContractDelegate]
|
|
181
|
+
for (const [key, value] of Object.entries(mappedInstance.api)) {
|
|
182
|
+
delegateTarget[key] = value
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
protected mapLiteralProperty({ property }: { property: any }) {
|
|
188
|
+
const apiTarget = this.getApiTarget({ property })
|
|
189
|
+
const value = typeof this.self[property.name] !== 'undefined'
|
|
190
|
+
? this.self[property.name]
|
|
191
|
+
: property.definition.value
|
|
192
|
+
|
|
193
|
+
// Assign to both apiTarget and self so getter functions can access via this
|
|
194
|
+
apiTarget[property.name] = value
|
|
195
|
+
this.self[property.name] = value
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
protected mapFunctionProperty({ property }: { property: any }) {
|
|
199
|
+
const apiTarget = this.getApiTarget({ property })
|
|
200
|
+
|
|
201
|
+
// Create a proxy for this.self that intercepts property access
|
|
202
|
+
// Prefer this.self (which has unwrapped APIs) over encapsulatedApi
|
|
203
|
+
const selfProxy = new Proxy(this.self, {
|
|
204
|
+
get: (target: any, prop: string | symbol) => {
|
|
205
|
+
if (typeof prop === 'symbol') return target[prop]
|
|
206
|
+
|
|
207
|
+
// First check if the property exists in target (this.self)
|
|
208
|
+
if (prop in target) {
|
|
209
|
+
return target[prop]
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Fall back to encapsulatedApi
|
|
213
|
+
if (prop in this.encapsulatedApi) {
|
|
214
|
+
return this.encapsulatedApi[prop]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return undefined
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
apiTarget[property.name] = property.definition.value.bind(selfProxy)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
protected mapGetterFunctionProperty({ property }: { property: any }) {
|
|
225
|
+
const apiTarget = this.getApiTarget({ property })
|
|
226
|
+
const getterFn = property.definition.value
|
|
227
|
+
|
|
228
|
+
// Create a proxy for this.self that intercepts property access
|
|
229
|
+
// Prefer this.self (which has unwrapped APIs) over encapsulatedApi
|
|
230
|
+
const selfProxy = new Proxy(this.self, {
|
|
231
|
+
get: (target: any, prop: string | symbol) => {
|
|
232
|
+
if (typeof prop === 'symbol') return target[prop]
|
|
233
|
+
|
|
234
|
+
// First check if the property exists in target (this.self)
|
|
235
|
+
if (prop in target) {
|
|
236
|
+
return target[prop]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Fall back to encapsulatedApi
|
|
240
|
+
if (prop in this.encapsulatedApi) {
|
|
241
|
+
return this.encapsulatedApi[prop]
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return undefined
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// Define a lazy getter that calls the function only when accessed with proper this context
|
|
249
|
+
Object.defineProperty(apiTarget, property.name, {
|
|
250
|
+
get: () => {
|
|
251
|
+
return getterFn.call(selfProxy)
|
|
252
|
+
},
|
|
253
|
+
enumerable: true,
|
|
254
|
+
configurable: true
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async freeze(options: any): Promise<any> {
|
|
259
|
+
return this.freezeCapsule?.(options) || {}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
export function CapsuleSpineContract({ freezeCapsule, resolve, importCapsule, spineFilesystemRoot }: { freezeCapsule?: (capsule: any) => Promise<any>, resolve?: (uri: string, parentFilepath: string) => Promise<string>, importCapsule?: (filepath: string) => Promise<any>, spineFilesystemRoot?: string } = {}) {
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
'#': CapsuleSpineContract['#'],
|
|
271
|
+
makeContractCapsuleInstance: ({ spineContractUri, capsule, self, encapsulatedApi }: { spineContractUri: string, capsule: any, self: any, encapsulatedApi: Record<string, any> }) => {
|
|
272
|
+
return new ContractCapsuleInstanceFactory({
|
|
273
|
+
spineContractUri,
|
|
274
|
+
capsule,
|
|
275
|
+
self,
|
|
276
|
+
encapsulatedApi,
|
|
277
|
+
resolve,
|
|
278
|
+
importCapsule,
|
|
279
|
+
spineFilesystemRoot,
|
|
280
|
+
freezeCapsule
|
|
281
|
+
})
|
|
282
|
+
},
|
|
283
|
+
hydrate: ({ capsuleSnapshot }: { capsuleSnapshot: any }): any => {
|
|
284
|
+
|
|
285
|
+
return capsuleSnapshot
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
CapsuleSpineContract['#'] = '@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0'
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { join, dirname } from 'path'
|
|
2
|
+
import { writeFile, mkdir, readFile, stat } from 'fs/promises'
|
|
3
|
+
import { Spine, SpineRuntime, CapsulePropertyTypes, makeImportStack, merge } from "../encapsulate"
|
|
4
|
+
import { StaticAnalyzer } from "../../src/static-analyzer.v0"
|
|
5
|
+
import { CapsuleModuleProjector } from "../../src/capsule-projectors/CapsuleModuleProjector.v0"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export { merge }
|
|
9
|
+
|
|
10
|
+
// TODO: Make portable so we can run on other runtimes
|
|
11
|
+
const resolve = Bun.resolve
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export async function CapsuleSpineFactory({
|
|
15
|
+
spineFilesystemRoot,
|
|
16
|
+
capsuleModuleProjectionRoot,
|
|
17
|
+
capsuleModuleProjectionPackage,
|
|
18
|
+
staticAnalysisEnabled = true,
|
|
19
|
+
onMembraneEvent,
|
|
20
|
+
enableCallerStackInference = false,
|
|
21
|
+
spineContracts,
|
|
22
|
+
timing: timingParam
|
|
23
|
+
}: {
|
|
24
|
+
spineFilesystemRoot: string,
|
|
25
|
+
capsuleModuleProjectionRoot?: string,
|
|
26
|
+
capsuleModuleProjectionPackage?: string,
|
|
27
|
+
staticAnalysisEnabled?: boolean,
|
|
28
|
+
onMembraneEvent?: (event: any) => void,
|
|
29
|
+
enableCallerStackInference?: boolean,
|
|
30
|
+
spineContracts: Record<string, any>,
|
|
31
|
+
timing?: { record: (step: string) => void, recordMajor: (step: string) => void, chalk?: any }
|
|
32
|
+
}) {
|
|
33
|
+
|
|
34
|
+
if (capsuleModuleProjectionRoot) capsuleModuleProjectionRoot = capsuleModuleProjectionRoot.replace(/^file:\/\//, '')
|
|
35
|
+
if (spineFilesystemRoot) spineFilesystemRoot = spineFilesystemRoot.replace(/^file:\/\//, '')
|
|
36
|
+
|
|
37
|
+
const timing = timingParam
|
|
38
|
+
|
|
39
|
+
timing?.recordMajor('CAPSULE SPINE FACTORY: INITIALIZATION')
|
|
40
|
+
|
|
41
|
+
const SingletonRegistry = () => {
|
|
42
|
+
const registry = new Map<string, Promise<any>>()
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
async ensure(id: string, createHandler: () => Promise<any>) {
|
|
46
|
+
if (!registry.has(id)) {
|
|
47
|
+
registry.set(id, createHandler())
|
|
48
|
+
}
|
|
49
|
+
return registry.get(id)!
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const registry = SingletonRegistry()
|
|
55
|
+
|
|
56
|
+
const spineContractInstances: {
|
|
57
|
+
encapsulation: Record<string, any>,
|
|
58
|
+
runtime: Record<string, any>
|
|
59
|
+
} = {
|
|
60
|
+
encapsulation: {},
|
|
61
|
+
runtime: {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const sourceSpine: { encapsulate?: any } = {}
|
|
65
|
+
const commonSpineContractOpts = {
|
|
66
|
+
spineFilesystemRoot,
|
|
67
|
+
resolve: async (uri: string, parentFilepath: string) => {
|
|
68
|
+
// For relative paths, join with parent directory first
|
|
69
|
+
if (/^\.\.?\//.test(uri)) {
|
|
70
|
+
return await resolve(join(parentFilepath, '..', uri), spineFilesystemRoot)
|
|
71
|
+
}
|
|
72
|
+
// For absolute/package paths, use Bun.resolve directly
|
|
73
|
+
return await resolve(uri, parentFilepath)
|
|
74
|
+
},
|
|
75
|
+
importCapsule: async (filepath: string) => {
|
|
76
|
+
const shortPath = filepath.replace(/^.*\/genesis\//, '')
|
|
77
|
+
timing?.record(`importCapsule: Called for ${shortPath}`)
|
|
78
|
+
const result = await registry.ensure(filepath, async () => {
|
|
79
|
+
timing?.recordMajor(`importCapsule: Starting import for ${shortPath}`)
|
|
80
|
+
const importStart = Date.now()
|
|
81
|
+
const exports = await import(filepath)
|
|
82
|
+
const importDuration = Date.now() - importStart
|
|
83
|
+
timing?.recordMajor(`importCapsule: import() took ${importDuration}ms for ${shortPath}`)
|
|
84
|
+
|
|
85
|
+
if (importDuration > 10) {
|
|
86
|
+
if (timing) {
|
|
87
|
+
console.log(timing.chalk.red(`\n⚠️ WARNING: Slow module load detected!`))
|
|
88
|
+
console.log(timing.chalk.red(` Module: ${filepath}`))
|
|
89
|
+
console.log(timing.chalk.red(` Load time: ${importDuration}ms`))
|
|
90
|
+
console.log(timing.chalk.red(` Consider using dynamic imports to load heavy dependencies only when needed.\n`))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof exports.capsule !== 'function') throw new Error(`Module at '${filepath}' does not export 'capsule'!`)
|
|
95
|
+
|
|
96
|
+
const capsuleStart = Date.now()
|
|
97
|
+
const capsule = await exports.capsule({
|
|
98
|
+
encapsulate: sourceSpine.encapsulate,
|
|
99
|
+
CapsulePropertyTypes,
|
|
100
|
+
makeImportStack
|
|
101
|
+
})
|
|
102
|
+
const capsuleDuration = Date.now() - capsuleStart
|
|
103
|
+
timing?.recordMajor(`importCapsule: exports.capsule() took ${capsuleDuration}ms for ${shortPath}`)
|
|
104
|
+
|
|
105
|
+
timing?.record(`importCapsule: Returning result for ${shortPath}`)
|
|
106
|
+
|
|
107
|
+
return capsule
|
|
108
|
+
})
|
|
109
|
+
return result
|
|
110
|
+
},
|
|
111
|
+
encapsulateOpts: {
|
|
112
|
+
CapsulePropertyTypes
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
timing?.recordMajor('SPINE CONTRACTS: INITIALIZATION')
|
|
117
|
+
|
|
118
|
+
for (const spineContractUri in spineContracts) {
|
|
119
|
+
spineContractInstances.encapsulation[spineContractUri] = spineContracts[spineContractUri]({
|
|
120
|
+
...commonSpineContractOpts,
|
|
121
|
+
freezeCapsule: async ({ spineContractUri, capsule }: { spineContractUri: string, capsule: any }): Promise<any> => {
|
|
122
|
+
|
|
123
|
+
if (!projector) {
|
|
124
|
+
throw new Error('capsuleModuleProjectionRoot must be provided to enable freezing')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let snapshotValues = {}
|
|
128
|
+
|
|
129
|
+
const projected = await projector.projectCapsule({
|
|
130
|
+
capsule,
|
|
131
|
+
capsules,
|
|
132
|
+
snapshotValues,
|
|
133
|
+
spineContractUri
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
return snapshotValues
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
spineContractInstances.runtime[spineContractUri] = spineContracts[spineContractUri]({
|
|
140
|
+
...commonSpineContractOpts,
|
|
141
|
+
enableCallerStackInference,
|
|
142
|
+
onMembraneEvent,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
timing?.recordMajor('CAPSULE MODULE PROJECTOR: INITIALIZATION')
|
|
147
|
+
|
|
148
|
+
const projector = capsuleModuleProjectionRoot ? CapsuleModuleProjector({
|
|
149
|
+
spineStore: {
|
|
150
|
+
writeFile: async (filepath: string, content: string) => {
|
|
151
|
+
filepath = join(spineFilesystemRoot, filepath)
|
|
152
|
+
await mkdir(dirname(filepath), { recursive: true })
|
|
153
|
+
await writeFile(filepath, content, 'utf-8')
|
|
154
|
+
},
|
|
155
|
+
getStats: async (filepath: string) => {
|
|
156
|
+
filepath = join(spineFilesystemRoot, filepath)
|
|
157
|
+
try {
|
|
158
|
+
const stats = await stat(filepath)
|
|
159
|
+
return { mtime: stats.mtime }
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
projectionStore: {
|
|
166
|
+
writeFile: async (filepath: string, content: string) => {
|
|
167
|
+
filepath = join(capsuleModuleProjectionRoot, filepath)
|
|
168
|
+
await mkdir(dirname(filepath), { recursive: true })
|
|
169
|
+
await writeFile(filepath, content, 'utf-8')
|
|
170
|
+
},
|
|
171
|
+
getStats: async (filepath: string) => {
|
|
172
|
+
filepath = join(capsuleModuleProjectionRoot, filepath)
|
|
173
|
+
try {
|
|
174
|
+
const stats = await stat(filepath)
|
|
175
|
+
return { mtime: stats.mtime }
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
projectionCacheStore: {
|
|
182
|
+
writeFile: async (filepath: string, content: string) => {
|
|
183
|
+
filepath = join(spineFilesystemRoot, '.~o/encapsulate.dev/projection-cache', filepath)
|
|
184
|
+
await mkdir(dirname(filepath), { recursive: true })
|
|
185
|
+
await writeFile(filepath, content, 'utf-8')
|
|
186
|
+
},
|
|
187
|
+
readFile: async (filepath: string) => {
|
|
188
|
+
filepath = join(spineFilesystemRoot, '.~o/encapsulate.dev/projection-cache', filepath)
|
|
189
|
+
return readFile(filepath, 'utf-8')
|
|
190
|
+
},
|
|
191
|
+
getStats: async (filepath: string) => {
|
|
192
|
+
filepath = join(spineFilesystemRoot, '.~o/encapsulate.dev/projection-cache', filepath)
|
|
193
|
+
try {
|
|
194
|
+
const stats = await stat(filepath)
|
|
195
|
+
return { mtime: stats.mtime }
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return null
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
spineFilesystemRoot,
|
|
202
|
+
capsuleModuleProjectionPackage,
|
|
203
|
+
timing
|
|
204
|
+
}) : undefined
|
|
205
|
+
|
|
206
|
+
timing?.recordMajor('SPINE: INITIALIZATION')
|
|
207
|
+
|
|
208
|
+
let { encapsulate, freeze, capsules } = await Spine({
|
|
209
|
+
spineFilesystemRoot,
|
|
210
|
+
timing,
|
|
211
|
+
staticAnalyzer: staticAnalysisEnabled ? StaticAnalyzer({
|
|
212
|
+
timing,
|
|
213
|
+
cacheStore: {
|
|
214
|
+
writeFile: async (filepath: string, content: string) => {
|
|
215
|
+
filepath = join(spineFilesystemRoot, '.~o/encapsulate.dev/static-analysis', filepath)
|
|
216
|
+
await mkdir(dirname(filepath), { recursive: true })
|
|
217
|
+
await writeFile(filepath, content, 'utf-8')
|
|
218
|
+
},
|
|
219
|
+
readFile: async (filepath: string) => {
|
|
220
|
+
filepath = join(spineFilesystemRoot, '.~o/encapsulate.dev/static-analysis', filepath)
|
|
221
|
+
return readFile(filepath, 'utf-8')
|
|
222
|
+
},
|
|
223
|
+
getStats: async (filepath: string) => {
|
|
224
|
+
filepath = join(spineFilesystemRoot, '.~o/encapsulate.dev/static-analysis', filepath)
|
|
225
|
+
try {
|
|
226
|
+
const stats = await stat(filepath)
|
|
227
|
+
return { mtime: stats.mtime }
|
|
228
|
+
} catch (error) {
|
|
229
|
+
// File doesn't exist
|
|
230
|
+
return null
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
spineStore: {
|
|
235
|
+
getStats: async (filepath: string) => {
|
|
236
|
+
filepath = join(spineFilesystemRoot, filepath)
|
|
237
|
+
try {
|
|
238
|
+
const stats = await stat(filepath)
|
|
239
|
+
return { mtime: stats.mtime }
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// File doesn't exist
|
|
242
|
+
return null
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
}) : undefined,
|
|
247
|
+
spineContracts: spineContractInstances.encapsulation
|
|
248
|
+
})
|
|
249
|
+
sourceSpine.encapsulate = encapsulate
|
|
250
|
+
|
|
251
|
+
timing?.recordMajor('SPINE RUNTIME: INITIALIZATION')
|
|
252
|
+
|
|
253
|
+
let { run } = await SpineRuntime({
|
|
254
|
+
spineFilesystemRoot,
|
|
255
|
+
spineContracts: spineContractInstances.runtime,
|
|
256
|
+
capsules
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
timing?.recordMajor('CAPSULE SPINE FACTORY: READY')
|
|
260
|
+
|
|
261
|
+
const loadCapsule = async ({ capsuleSnapshot }: { capsuleSourceLineRef: string, capsuleSnapshot: any }) => {
|
|
262
|
+
|
|
263
|
+
if (!capsuleModuleProjectionRoot) {
|
|
264
|
+
throw new Error('capsuleModuleProjectionRoot must be provided to enable dynamic loading of capsules')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let filepath = capsuleSnapshot.spineContracts?.['#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0']?.['#@stream44.studio/encapsulate/structs/Capsule.v0']?.projectedCapsuleFilepath
|
|
268
|
+
|
|
269
|
+
if (!filepath) throw new Error(`Cannot load capsule. No 'filepath' found at 'spineContracts["#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0"]["#@stream44.studio/encapsulate/structs/Capsule.v0"].projectedCapsuleFilepath'!`)
|
|
270
|
+
|
|
271
|
+
const { capsule } = await import(join(capsuleModuleProjectionRoot, filepath))
|
|
272
|
+
|
|
273
|
+
return capsule
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
commonSpineContractOpts,
|
|
278
|
+
CapsulePropertyTypes,
|
|
279
|
+
makeImportStack,
|
|
280
|
+
encapsulate,
|
|
281
|
+
run,
|
|
282
|
+
freeze,
|
|
283
|
+
loadCapsule,
|
|
284
|
+
hoistSnapshot: async ({ snapshot }: { snapshot: any }) => {
|
|
285
|
+
|
|
286
|
+
timing?.recordMajor('HOIST SNAPSHOT: START')
|
|
287
|
+
|
|
288
|
+
const result = await SpineRuntime({
|
|
289
|
+
snapshot,
|
|
290
|
+
spineContracts: spineContractInstances.runtime,
|
|
291
|
+
loadCapsule
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
timing?.recordMajor('HOIST SNAPSHOT: COMPLETE')
|
|
295
|
+
|
|
296
|
+
return result
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
export function TimingObserver({ startTime }: { startTime: number }) {
|
|
5
|
+
let lastTime = startTime
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
chalk,
|
|
9
|
+
record: (step: string) => {
|
|
10
|
+
const now = Date.now()
|
|
11
|
+
const diff = now - lastTime
|
|
12
|
+
lastTime = now
|
|
13
|
+
|
|
14
|
+
const line = `[+${diff}ms] ${step}`
|
|
15
|
+
console.log(diff > 10 ? chalk.red(line) : line)
|
|
16
|
+
},
|
|
17
|
+
recordMajor: (step: string) => {
|
|
18
|
+
const now = Date.now()
|
|
19
|
+
const diff = now - lastTime
|
|
20
|
+
lastTime = now
|
|
21
|
+
|
|
22
|
+
const line = `[+${diff}ms] ${step}`
|
|
23
|
+
console.log(diff > 10 ? chalk.red(line) : chalk.cyan(line))
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|