@stream44.studio/t44 0.4.0-rc.30 → 0.4.0-rc.32
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 +1 -1
- package/caps/HomeRegistry.ts +2 -2
- package/caps/ProjectDeployment.ts +1 -1
- package/caps/ProjectPublishing.ts +1 -1
- package/caps/ProjectPulling.ts +1 -1
- package/caps/ProjectRepository.ts +1 -1
- package/caps/WorkspaceConfig.yaml +1 -1
- package/caps/WorkspaceConfigFile.ts +2 -2
- package/caps/WorkspaceConnection.ts +1 -1
- package/caps/WorkspaceModel.ts +5 -5
- package/caps/WorkspaceShell.yaml +1 -1
- package/caps/patterns/semver.org/ProjectPublishing.ts +26 -0
- package/package.json +7 -7
- package/standalone-rt.test.ts +119 -0
- package/standalone-rt.ts +104 -27
- package/workspace-rt.ts +1 -1
- package/workspace.yaml +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<td><a href="https://Stream44.Studio"><img src=".o/stream44.studio/assets/Icon-v1.svg" width="42" height="42"></a></td>
|
|
4
4
|
<td><strong><a href="https://Stream44.Studio">Stream44 Studio</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
|
-
<td>Hand
|
|
6
|
+
<td>Designed by Hand<br/><b>AI assisted Code</a></td>
|
|
7
7
|
</tr>
|
|
8
8
|
</table>
|
|
9
9
|
|
package/caps/HomeRegistry.ts
CHANGED
|
@@ -198,7 +198,7 @@ export async function capsule({
|
|
|
198
198
|
const { readdir, readFile } = await import('fs/promises')
|
|
199
199
|
const { join } = await import('path')
|
|
200
200
|
const rootDir = await this.rootDir
|
|
201
|
-
const keyDir = join(rootDir, '@
|
|
201
|
+
const keyDir = join(rootDir, '@stream44.studio~t44~structs~WorkspaceKey')
|
|
202
202
|
try {
|
|
203
203
|
const files = await readdir(keyDir)
|
|
204
204
|
const keys: Array<{ name: string; did: string; createdAt?: string }> = []
|
|
@@ -250,7 +250,7 @@ export async function capsule({
|
|
|
250
250
|
const { readdir, readFile } = await import('fs/promises')
|
|
251
251
|
const { join } = await import('path')
|
|
252
252
|
const rootDir = await this.rootDir
|
|
253
|
-
const rackDir = join(rootDir, '@
|
|
253
|
+
const rackDir = join(rootDir, '@stream44.studio~t44~structs~ProjectRack')
|
|
254
254
|
try {
|
|
255
255
|
const files = await readdir(rackDir)
|
|
256
256
|
const racks: Array<{ name: string; did: string; createdAt?: string }> = []
|
|
@@ -65,7 +65,7 @@ export async function capsule({
|
|
|
65
65
|
const workspaceConfig = await this.$WorkspaceConfig.config
|
|
66
66
|
const getProjectionDir = (capsuleName: string) => join(
|
|
67
67
|
workspaceConfig.rootDir,
|
|
68
|
-
'.~o/workspace.foundation/@
|
|
68
|
+
'.~o/workspace.foundation/@stream44.studio~t44~caps~ProjectDeployment',
|
|
69
69
|
capsuleName.replace(/\//g, '~')
|
|
70
70
|
)
|
|
71
71
|
|
|
@@ -184,7 +184,7 @@ export async function capsule({
|
|
|
184
184
|
const publishingApi = {
|
|
185
185
|
getProjectionDir: (capsuleName: string) => join(
|
|
186
186
|
this.WorkspaceConfig.workspaceRootDir,
|
|
187
|
-
'.~o/workspace.foundation/@
|
|
187
|
+
'.~o/workspace.foundation/@stream44.studio~t44~caps~ProjectPublishing',
|
|
188
188
|
capsuleName.replace(/\//g, '~')
|
|
189
189
|
),
|
|
190
190
|
}
|
package/caps/ProjectPulling.ts
CHANGED
|
@@ -189,7 +189,7 @@ export async function capsule({
|
|
|
189
189
|
const pullingApi = {
|
|
190
190
|
getProjectionDir: (capsuleName: string) => join(
|
|
191
191
|
this.WorkspaceConfig.workspaceRootDir,
|
|
192
|
-
'.~o/workspace.foundation/@
|
|
192
|
+
'.~o/workspace.foundation/@stream44.studio~t44~caps~ProjectPulling',
|
|
193
193
|
capsuleName.replace(/\//g, '~')
|
|
194
194
|
),
|
|
195
195
|
}
|
|
@@ -30,7 +30,7 @@ export async function capsule({
|
|
|
30
30
|
const normalizedUri = repoUri.replace(/[\/]/g, '~')
|
|
31
31
|
return join(
|
|
32
32
|
this.WorkspaceConfig.workspaceRootDir,
|
|
33
|
-
'.~o/workspace.foundation/@
|
|
33
|
+
'.~o/workspace.foundation/@stream44.studio~t44~caps~ProjectRepository/stage',
|
|
34
34
|
normalizedUri
|
|
35
35
|
)
|
|
36
36
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
$schema: ../../../../.~o/workspace.foundation/@
|
|
1
|
+
$schema: ../../../../.~o/workspace.foundation/@stream44.studio~t44~caps~JsonSchemas/@stream44.studio~t44~structs~WorkspaceConfigFile.json
|
|
2
2
|
extends:
|
|
3
3
|
- ./WorkspaceShell.yaml
|
|
4
4
|
"#@stream44.studio/t44/structs/WorkspaceCliConfig":
|
|
@@ -696,7 +696,7 @@ async function writeConfigMetadataCache(
|
|
|
696
696
|
workspaceRootDir: string
|
|
697
697
|
): Promise<void> {
|
|
698
698
|
const { mkdir, writeFile } = await import('fs/promises')
|
|
699
|
-
const metaCacheDir = join(workspaceRootDir, '.~o', 'workspace.foundation', '@
|
|
699
|
+
const metaCacheDir = join(workspaceRootDir, '.~o', 'workspace.foundation', '@stream44.studio~t44~caps~WorkspaceEntityFact', '@stream44.studio~t44~structs~WorkspaceConfigFileMeta')
|
|
700
700
|
|
|
701
701
|
await mkdir(metaCacheDir, { recursive: true })
|
|
702
702
|
|
|
@@ -847,7 +847,7 @@ function resolveSchemaRef(dataFilePath: string, capsuleName: string, workspaceRo
|
|
|
847
847
|
workspaceRootDir,
|
|
848
848
|
'.~o',
|
|
849
849
|
'workspace.foundation',
|
|
850
|
-
'@
|
|
850
|
+
'@stream44.studio~t44~caps~JsonSchemas'
|
|
851
851
|
)
|
|
852
852
|
const schemaFilename = capsuleName.replace(/\//g, '~') + '.json'
|
|
853
853
|
const schemaFilePath = join(jsonSchemaDir, schemaFilename)
|
|
@@ -77,7 +77,7 @@ export async function capsule({
|
|
|
77
77
|
const workspaceConfigStruct = workspaceConfig?.['#@stream44.studio/t44/structs/WorkspaceConfig'] || {}
|
|
78
78
|
const workspaceName = workspaceConfigStruct.name
|
|
79
79
|
const connectionType = this.capsuleName.replace(/\//g, '~')
|
|
80
|
-
return join(registryDir, '@
|
|
80
|
+
return join(registryDir, '@stream44.studio~t44~caps~WorkspaceConnection', workspaceName, `${connectionType}.json`)
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
getRelativeFilepath: {
|
package/caps/WorkspaceModel.ts
CHANGED
|
@@ -47,14 +47,14 @@ export async function capsule({
|
|
|
47
47
|
// Build resolver context with all required paths
|
|
48
48
|
const registryDir = await this.Home.registryDir
|
|
49
49
|
const foundationDir = join(workspaceRootDir, '.~o', 'workspace.foundation')
|
|
50
|
-
const homeRegistryConnectionsDir = join(registryDir, '@
|
|
50
|
+
const homeRegistryConnectionsDir = join(registryDir, '@stream44.studio~t44~caps~WorkspaceConnection', workspaceName)
|
|
51
51
|
|
|
52
52
|
const resolver = Resolver({
|
|
53
53
|
workspaceRootDir,
|
|
54
54
|
workspaceName,
|
|
55
|
-
schemasDir: join(foundationDir, '@
|
|
56
|
-
factsDir: join(foundationDir, '@
|
|
57
|
-
metaCacheDir: join(foundationDir, '@
|
|
55
|
+
schemasDir: join(foundationDir, '@stream44.studio~t44~caps~JsonSchemas'),
|
|
56
|
+
factsDir: join(foundationDir, '@stream44.studio~t44~caps~WorkspaceEntityFact'),
|
|
57
|
+
metaCacheDir: join(foundationDir, '@stream44.studio~t44~caps~WorkspaceEntityFact', '@stream44.studio~t44~structs~WorkspaceConfigFileMeta'),
|
|
58
58
|
homeRegistryConnectionsDir
|
|
59
59
|
})
|
|
60
60
|
|
|
@@ -100,7 +100,7 @@ export async function capsule({
|
|
|
100
100
|
|
|
101
101
|
// Build schema lookup: entity name (without #) → schema file path
|
|
102
102
|
const schemaMap = new Map<string, string>()
|
|
103
|
-
const schemasDir = join(workspaceRootDir, '.~o', 'workspace.foundation', '@
|
|
103
|
+
const schemasDir = join(workspaceRootDir, '.~o', 'workspace.foundation', '@stream44.studio~t44~caps~JsonSchemas')
|
|
104
104
|
for (const [schemaId] of schemas) {
|
|
105
105
|
const entityName = schemaId.replace(/\.v\d+$/, '')
|
|
106
106
|
// Schema files don't include version in filename, only in $id
|
package/caps/WorkspaceShell.yaml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
$schema: ../../../../../../../.~o/workspace.foundation/@
|
|
1
|
+
$schema: ../../../../../../../.~o/workspace.foundation/@stream44.studio~t44~caps~JsonSchemas/@stream44.studio~t44~structs~WorkspaceConfigFile.json
|
|
2
2
|
"#@stream44.studio/t44/structs/WorkspaceShellConfig":
|
|
3
3
|
env:
|
|
4
4
|
default: null
|
|
@@ -68,6 +68,19 @@ export async function capsule({
|
|
|
68
68
|
modified = true
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// Also replace tilde-separated variant of scoped names
|
|
72
|
+
// (e.g. @stream44.studio~FramespaceGenesis → @stream44.studio~FramespaceGenesis)
|
|
73
|
+
const pubTilde = publicName.replace(/^(@[^/]+)\//, '$1~')
|
|
74
|
+
if (pubTilde !== publicName) {
|
|
75
|
+
const wsTilde = workspaceName.replace(/^(@[^/]+)\//, '$1~')
|
|
76
|
+
const tildeRegex = new RegExp(pubTilde.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')
|
|
77
|
+
const replacedTilde = content.replace(tildeRegex, wsTilde)
|
|
78
|
+
if (replacedTilde !== content) {
|
|
79
|
+
content = replacedTilde
|
|
80
|
+
modified = true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
71
84
|
// Also replace regex-escaped versions of the public name
|
|
72
85
|
// (e.g. @stream44\.studio\/dco in test patterns)
|
|
73
86
|
const pubEscaped = publicName.replace(/[.*+?^${}()|[\]/\\]/g, '\\$&')
|
|
@@ -126,6 +139,19 @@ export async function capsule({
|
|
|
126
139
|
modified = true
|
|
127
140
|
}
|
|
128
141
|
|
|
142
|
+
// Also replace tilde-separated variant of scoped names
|
|
143
|
+
// (e.g. @stream44.studio~FramespaceGenesis → @stream44.studio~FramespaceGenesis)
|
|
144
|
+
const wsTilde = workspaceName.replace(/^(@[^/]+)\//, '$1~')
|
|
145
|
+
if (wsTilde !== workspaceName) {
|
|
146
|
+
const pubTilde = publicName.replace(/^(@[^/]+)\//, '$1~')
|
|
147
|
+
const tildeRegex = new RegExp(wsTilde.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')
|
|
148
|
+
const replacedTilde = content.replace(tildeRegex, pubTilde)
|
|
149
|
+
if (replacedTilde !== content) {
|
|
150
|
+
content = replacedTilde
|
|
151
|
+
modified = true
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
129
155
|
// Also replace regex-escaped versions of the workspace name
|
|
130
156
|
// (e.g. @stream44\.studio\/encapsulate in test patterns)
|
|
131
157
|
const wsEscaped = workspaceName.replace(/[.*+?^${}()|[\]/\\]/g, '\\$&')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream44.studio/t44",
|
|
3
|
-
"version": "0.4.0-rc.
|
|
3
|
+
"version": "0.4.0-rc.32",
|
|
4
4
|
"license": "LGPL",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -71,15 +71,15 @@
|
|
|
71
71
|
"@ucanto/server": "^11.0.3",
|
|
72
72
|
"json-schema-ref-resolver": "^3.0.0",
|
|
73
73
|
"json-stable-stringify": "^1.3.0",
|
|
74
|
-
"@stream44.studio/encapsulate": "^0.4.0-rc.
|
|
74
|
+
"@stream44.studio/encapsulate": "^0.4.0-rc.28",
|
|
75
75
|
"turbo": "^2.7.5"
|
|
76
76
|
},
|
|
77
77
|
"optionalDependencies": {
|
|
78
|
-
"@stream44.studio/t44-bunny.net": "^0.1.0-rc.
|
|
79
|
-
"@stream44.studio/t44-vercel.com": "^0.1.0-rc.
|
|
80
|
-
"@stream44.studio/t44-github.com": "^0.1.0-rc.
|
|
81
|
-
"@stream44.studio/t44-dynadot.com": "^0.1.0-rc.
|
|
82
|
-
"@stream44.studio/t44-npmjs.com": "^0.1.0-rc.
|
|
78
|
+
"@stream44.studio/t44-bunny.net": "^0.1.0-rc.8",
|
|
79
|
+
"@stream44.studio/t44-vercel.com": "^0.1.0-rc.8",
|
|
80
|
+
"@stream44.studio/t44-github.com": "^0.1.0-rc.8",
|
|
81
|
+
"@stream44.studio/t44-dynadot.com": "^0.1.0-rc.8",
|
|
82
|
+
"@stream44.studio/t44-npmjs.com": "^0.1.0-rc.8"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@types/bun": "^1.3.4",
|
package/standalone-rt.test.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import * as bunTest from 'bun:test'
|
|
4
4
|
import { describe, it, expect } from 'bun:test'
|
|
5
5
|
import { run } from './standalone-rt'
|
|
6
|
+
import * as fs from 'fs/promises'
|
|
7
|
+
import { join } from 'path'
|
|
6
8
|
|
|
7
9
|
// Top-level run() — shared across all tests (e.g. for workbenchDir)
|
|
8
10
|
const { test: { workbenchDir } } = await run(async ({ encapsulate, CapsulePropertyTypes, makeImportStack }: any) => {
|
|
@@ -147,4 +149,121 @@ describe('standalone-rt multiple run() calls', () => {
|
|
|
147
149
|
expect(workbenchDir).toBeDefined();
|
|
148
150
|
expect(typeof workbenchDir).toBe('string');
|
|
149
151
|
});
|
|
152
|
+
|
|
153
|
+
it('should write .events.json when captureEvents is true', async () => {
|
|
154
|
+
// Clean up any existing spine-instances directory
|
|
155
|
+
const spineInstancesDir = join(import.meta.dir, '.~o/encapsulate.dev/spine-instances');
|
|
156
|
+
try {
|
|
157
|
+
await fs.rm(spineInstancesDir, { recursive: true });
|
|
158
|
+
} catch { }
|
|
159
|
+
|
|
160
|
+
const result = await run(async ({ encapsulate, CapsulePropertyTypes, makeImportStack }: any) => {
|
|
161
|
+
const spine = await encapsulate({
|
|
162
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
163
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
164
|
+
'#': {
|
|
165
|
+
counter: {
|
|
166
|
+
type: CapsulePropertyTypes.Literal,
|
|
167
|
+
value: 0,
|
|
168
|
+
},
|
|
169
|
+
increment: {
|
|
170
|
+
type: CapsulePropertyTypes.Function,
|
|
171
|
+
value: function (this: any) {
|
|
172
|
+
this.counter++;
|
|
173
|
+
return this.counter;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}, { importMeta: import.meta, importStack: makeImportStack(), capsuleName: '@stream44.studio/t44/standalone-rt.test.events' })
|
|
179
|
+
return { spine }
|
|
180
|
+
}, async ({ spine, apis }: any) => {
|
|
181
|
+
const api = apis[spine.capsuleSourceLineRef];
|
|
182
|
+
// Trigger some membrane events
|
|
183
|
+
api.increment();
|
|
184
|
+
api.increment();
|
|
185
|
+
const finalCount = api.counter;
|
|
186
|
+
return { finalCount, spineRef: spine.capsuleSourceLineRef };
|
|
187
|
+
}, { importMeta: import.meta, runFromSnapshot: true, captureEvents: true })
|
|
188
|
+
|
|
189
|
+
expect(result.finalCount).toBe(2);
|
|
190
|
+
|
|
191
|
+
// Verify .events.json was written
|
|
192
|
+
const dirs = await fs.readdir(spineInstancesDir);
|
|
193
|
+
expect(dirs.length).toBeGreaterThan(0);
|
|
194
|
+
|
|
195
|
+
const eventsFile = join(spineInstancesDir, dirs[0], 'root-capsule.events.json');
|
|
196
|
+
await fs.access(eventsFile); // Should not throw
|
|
197
|
+
|
|
198
|
+
const eventsContent = await fs.readFile(eventsFile, 'utf-8');
|
|
199
|
+
const events = JSON.parse(eventsContent);
|
|
200
|
+
|
|
201
|
+
expect(Array.isArray(events)).toBe(true);
|
|
202
|
+
expect(events.length).toBeGreaterThan(0);
|
|
203
|
+
|
|
204
|
+
// Verify event structure
|
|
205
|
+
const callEvents = events.filter((e: any) => e.event === 'call');
|
|
206
|
+
expect(callEvents.length).toBe(2); // Two increment() calls
|
|
207
|
+
|
|
208
|
+
const getEvents = events.filter((e: any) => e.event === 'get');
|
|
209
|
+
expect(getEvents.length).toBeGreaterThan(0); // counter property reads
|
|
210
|
+
|
|
211
|
+
// Verify membrane property is captured
|
|
212
|
+
for (const event of events) {
|
|
213
|
+
expect(event.membrane).toBeDefined();
|
|
214
|
+
expect(['external', 'internal']).toContain(event.membrane);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Verify we have both internal and external events
|
|
218
|
+
const externalEvents = events.filter((e: any) => e.membrane === 'external');
|
|
219
|
+
const internalEvents = events.filter((e: any) => e.membrane === 'internal');
|
|
220
|
+
expect(externalEvents.length).toBeGreaterThan(0);
|
|
221
|
+
expect(internalEvents.length).toBeGreaterThan(0);
|
|
222
|
+
|
|
223
|
+
// Clean up
|
|
224
|
+
await fs.rm(spineInstancesDir, { recursive: true });
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should NOT write .events.json when captureEvents is false/undefined', async () => {
|
|
228
|
+
// Clean up any existing spine-instances directory
|
|
229
|
+
const spineInstancesDir = join(import.meta.dir, '.~o/encapsulate.dev/spine-instances');
|
|
230
|
+
try {
|
|
231
|
+
await fs.rm(spineInstancesDir, { recursive: true });
|
|
232
|
+
} catch { }
|
|
233
|
+
|
|
234
|
+
await run(async ({ encapsulate, CapsulePropertyTypes, makeImportStack }: any) => {
|
|
235
|
+
const spine = await encapsulate({
|
|
236
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
237
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
238
|
+
'#': {
|
|
239
|
+
value: {
|
|
240
|
+
type: CapsulePropertyTypes.Literal,
|
|
241
|
+
value: 'test',
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}, { importMeta: import.meta, importStack: makeImportStack(), capsuleName: '@stream44.studio/t44/standalone-rt.test.no-events' })
|
|
246
|
+
return { spine }
|
|
247
|
+
}, async ({ spine, apis }: any) => {
|
|
248
|
+
return apis[spine.capsuleSourceLineRef].value;
|
|
249
|
+
}, { importMeta: import.meta, runFromSnapshot: false }) // No captureEvents
|
|
250
|
+
|
|
251
|
+
// Verify .events.json was NOT written (directory may or may not exist)
|
|
252
|
+
try {
|
|
253
|
+
const dirs = await fs.readdir(spineInstancesDir);
|
|
254
|
+
for (const dir of dirs) {
|
|
255
|
+
const eventsFile = join(spineInstancesDir, dir, 'root-capsule.events.json');
|
|
256
|
+
try {
|
|
257
|
+
await fs.access(eventsFile);
|
|
258
|
+
throw new Error('events.json should not exist');
|
|
259
|
+
} catch (e: any) {
|
|
260
|
+
if (e.message === 'events.json should not exist') throw e;
|
|
261
|
+
// Expected: file does not exist
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch (e: any) {
|
|
265
|
+
// Directory doesn't exist - that's fine
|
|
266
|
+
if (e.code !== 'ENOENT') throw e;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
150
269
|
});
|
package/standalone-rt.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const startTime = Date.now()
|
|
6
6
|
|
|
7
7
|
import { resolve, join } from 'path'
|
|
8
|
-
import { access } from 'fs/promises'
|
|
8
|
+
import { access, readdir, writeFile } from 'fs/promises'
|
|
9
9
|
import chalk from 'chalk'
|
|
10
10
|
import { CapsuleSpineFactory } from "@stream44.studio/encapsulate/spine-factories/CapsuleSpineFactory.v0"
|
|
11
11
|
import { CapsuleSpineContract } from "@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0/Membrane.v0"
|
|
@@ -24,13 +24,16 @@ async function findPackageRoot(startDir: string): Promise<string> {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export async function run(encapsulateHandler: any, runHandler: any, options?: { importMeta?: { dir: string }, runFromSnapshot?: boolean }) {
|
|
27
|
+
export async function run(encapsulateHandler: any, runHandler: any, options?: { importMeta?: { dir: string }, runFromSnapshot?: boolean, captureEvents?: boolean }) {
|
|
28
28
|
|
|
29
29
|
const timing = process.argv.includes('--trace') ? TimingObserver({ startTime }) : undefined
|
|
30
|
+
const shouldCaptureEvents = options?.captureEvents === true || process.argv.includes('--capture-events')
|
|
31
|
+
const saveMembraneEvents = shouldCaptureEvents
|
|
30
32
|
|
|
31
33
|
timing?.recordMajor('INIT SPINE')
|
|
32
34
|
|
|
33
35
|
const eventsByKey = new Map<string, any>()
|
|
36
|
+
const capturedEvents: any[] = []
|
|
34
37
|
|
|
35
38
|
const spineFilesystemRoot = options?.importMeta?.dir
|
|
36
39
|
? await findPackageRoot(options.importMeta.dir)
|
|
@@ -44,7 +47,7 @@ export async function run(encapsulateHandler: any, runHandler: any, options?: {
|
|
|
44
47
|
['#' + CapsuleSpineContract['#']]: CapsuleSpineContract
|
|
45
48
|
},
|
|
46
49
|
timing,
|
|
47
|
-
onMembraneEvent: timing ? (event: any) => {
|
|
50
|
+
onMembraneEvent: (timing || shouldCaptureEvents) ? (event: any) => {
|
|
48
51
|
const instanceId = event.target?.spineContractCapsuleInstanceId
|
|
49
52
|
const eventIndex = event.eventIndex
|
|
50
53
|
|
|
@@ -52,31 +55,81 @@ export async function run(encapsulateHandler: any, runHandler: any, options?: {
|
|
|
52
55
|
const key = `${eventIndex}`
|
|
53
56
|
eventsByKey.set(key, event)
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (callEvent) {
|
|
65
|
-
capsuleRef = callEvent.target?.capsuleSourceLineRef || capsuleRef
|
|
66
|
-
prop = callEvent.target?.prop || prop
|
|
67
|
-
if (callEvent.caller) {
|
|
68
|
-
callerLocation = `${callEvent.caller.filepath}:${callEvent.caller.line}`
|
|
58
|
+
// Collect event for .events.json persistence
|
|
59
|
+
if (shouldCaptureEvents) {
|
|
60
|
+
// Serialize value/result safely — they may contain capsule proxies with cyclic references
|
|
61
|
+
let safeValue: any = undefined
|
|
62
|
+
let safeResult: any = undefined
|
|
63
|
+
try {
|
|
64
|
+
if (event.value !== undefined) {
|
|
65
|
+
JSON.stringify(event.value)
|
|
66
|
+
safeValue = event.value
|
|
69
67
|
}
|
|
68
|
+
} catch {
|
|
69
|
+
safeValue = typeof event.value === 'object' ? `[${typeof event.value}]` : String(event.value)
|
|
70
70
|
}
|
|
71
|
+
try {
|
|
72
|
+
if (event.result !== undefined) {
|
|
73
|
+
JSON.stringify(event.result)
|
|
74
|
+
safeResult = event.result
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
safeResult = typeof event.result === 'object' ? `[${typeof event.result}]` : String(event.result)
|
|
78
|
+
}
|
|
79
|
+
// Serialize caller safely — strip stack array which may contain non-serializable objects
|
|
80
|
+
let safeCaller: any = undefined
|
|
81
|
+
if (event.caller) {
|
|
82
|
+
const { stack, ...callerRest } = event.caller
|
|
83
|
+
safeCaller = { ...callerRest }
|
|
84
|
+
if (stack) {
|
|
85
|
+
try {
|
|
86
|
+
JSON.stringify(stack)
|
|
87
|
+
safeCaller.stack = stack
|
|
88
|
+
} catch {
|
|
89
|
+
// stack frames may contain non-serializable objects
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
capturedEvents.push({
|
|
95
|
+
eventIndex: event.eventIndex,
|
|
96
|
+
event: event.event,
|
|
97
|
+
membrane: event.membrane,
|
|
98
|
+
target: event.target ? { ...event.target } : undefined,
|
|
99
|
+
value: safeValue,
|
|
100
|
+
result: safeResult,
|
|
101
|
+
caller: safeCaller,
|
|
102
|
+
callEventIndex: event.callEventIndex,
|
|
103
|
+
})
|
|
71
104
|
}
|
|
72
105
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
106
|
+
if (timing) {
|
|
107
|
+
let capsuleRef = event.target?.capsuleSourceLineRef
|
|
108
|
+
let prop = event.target?.prop
|
|
109
|
+
let callerLocation = event.caller ? `${event.caller.filepath}:${event.caller.line}` : 'unknown'
|
|
110
|
+
const eventType = event.event
|
|
111
|
+
|
|
112
|
+
// For call-result events, look up the original call event to get all info
|
|
113
|
+
if (eventType === 'call-result') {
|
|
114
|
+
const callKey = `${event.callEventIndex}`
|
|
115
|
+
const callEvent = eventsByKey.get(callKey)
|
|
116
|
+
if (callEvent) {
|
|
117
|
+
capsuleRef = callEvent.target?.capsuleSourceLineRef || capsuleRef
|
|
118
|
+
prop = callEvent.target?.prop || prop
|
|
119
|
+
if (callEvent.caller) {
|
|
120
|
+
callerLocation = `${callEvent.caller.filepath}:${callEvent.caller.line}`
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.error(
|
|
126
|
+
chalk.gray(`[${eventIndex}]`),
|
|
127
|
+
chalk.cyan(eventType.padEnd(12)),
|
|
128
|
+
chalk.yellow(capsuleRef),
|
|
129
|
+
chalk.magenta(`.${prop}`),
|
|
130
|
+
chalk.dim(`from ${callerLocation}`)
|
|
131
|
+
)
|
|
132
|
+
}
|
|
80
133
|
} : undefined
|
|
81
134
|
})
|
|
82
135
|
|
|
@@ -122,16 +175,40 @@ export async function run(encapsulateHandler: any, runHandler: any, options?: {
|
|
|
122
175
|
},
|
|
123
176
|
['@stream44.studio/t44-ipfs.tech/caps/IpfsWorkbench']: {
|
|
124
177
|
'#': {
|
|
125
|
-
cacheDir: join(spineFilesystemRoot, '.~o/workspace.foundation', '@
|
|
178
|
+
cacheDir: join(spineFilesystemRoot, '.~o/workspace.foundation', '@stream44.studio~t44-ipfs.tech~caps~IpfsWorkbench', 'daemons')
|
|
126
179
|
}
|
|
127
180
|
}
|
|
128
|
-
}
|
|
181
|
+
},
|
|
182
|
+
saveMembraneEvents,
|
|
129
183
|
}, async (opts) => {
|
|
130
|
-
|
|
184
|
+
const handlerResult = await runHandler({
|
|
131
185
|
...opts,
|
|
132
186
|
...(exportedApi || {}),
|
|
133
187
|
spineFilesystemRoot
|
|
134
188
|
})
|
|
189
|
+
|
|
190
|
+
// Write .events.json alongside .sit.json files when capture is enabled
|
|
191
|
+
if (saveMembraneEvents && capturedEvents.length > 0 && options?.importMeta?.dir) {
|
|
192
|
+
try {
|
|
193
|
+
const spineInstancesDir = join(options.importMeta.dir, '.~o/encapsulate.dev/spine-instances')
|
|
194
|
+
await access(spineInstancesDir)
|
|
195
|
+
const dirs = await readdir(spineInstancesDir)
|
|
196
|
+
for (const dir of dirs) {
|
|
197
|
+
const dirPath = join(spineInstancesDir, String(dir))
|
|
198
|
+
const eventsFile = join(dirPath, 'root-capsule.events.json')
|
|
199
|
+
try {
|
|
200
|
+
await writeFile(eventsFile, JSON.stringify(capturedEvents, null, 2), 'utf-8')
|
|
201
|
+
} catch (writeErr: any) {
|
|
202
|
+
if (process.env.DEBUG_EVENTS) console.error(`[standalone-rt] write failed for ${eventsFile}: ${writeErr.message}`)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (process.env.DEBUG_EVENTS) console.error(`[standalone-rt] Wrote events to ${dirs.length} dirs (${capturedEvents.length} events)`)
|
|
206
|
+
} catch (outerErr: any) {
|
|
207
|
+
if (process.env.DEBUG_EVENTS) console.error(`[standalone-rt] events write skipped: ${outerErr.message}`)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return handlerResult
|
|
135
212
|
})
|
|
136
213
|
|
|
137
214
|
timing?.recordMajor('DONE')
|
package/workspace-rt.ts
CHANGED
|
@@ -135,7 +135,7 @@ export async function run(encapsulateHandler: any, runHandler: any, options?: {
|
|
|
135
135
|
},
|
|
136
136
|
['@stream44.studio/t44-ipfs.tech/caps/IpfsWorkbench']: {
|
|
137
137
|
'#': {
|
|
138
|
-
cacheDir: join(workspaceRootDir, '.~o/workspace.foundation', '@
|
|
138
|
+
cacheDir: join(workspaceRootDir, '.~o/workspace.foundation', '@stream44.studio~t44-ipfs.tech~caps~IpfsWorkbench', 'daemons')
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
}
|
package/workspace.yaml
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
$schema: ../../../.~o/workspace.foundation/@
|
|
1
|
+
$schema: ../../../.~o/workspace.foundation/@stream44.studio~t44~caps~JsonSchemas/@stream44.studio~t44~structs~WorkspaceConfigFile.json
|
|
2
2
|
extends:
|
|
3
3
|
- ./caps/WorkspaceConfig.yaml
|