@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.
@@ -0,0 +1,624 @@
1
+ import { CapsulePropertyTypes } from "../../encapsulate"
2
+ import { ContractCapsuleInstanceFactory } from "./Static.v0"
3
+
4
+ function parseCallerFromStack(stack: string, spineFilesystemRoot?: string): Array<{ function?: string, filepath?: string, line?: number, column?: number }> {
5
+ const lines = stack.split('\n')
6
+ const result: Array<{ function?: string, filepath?: string, line?: number, column?: number }> = []
7
+
8
+ // Skip first line (Error message), then collect ALL frames
9
+ for (let i = 1; i < lines.length; i++) {
10
+ const line = lines[i].trim()
11
+
12
+ // Match various stack trace formats:
13
+ // "at functionName (/path/to/file:line:column)"
14
+ // "at /path/to/file:line:column"
15
+ // "at functionName (file:line:column)"
16
+ const match = line.match(/at\s+(.+)/)
17
+ if (match) {
18
+ const frame: { function?: string, filepath?: string, line?: number, column?: number } = {}
19
+ const content = match[1]
20
+
21
+ // Try to extract function name and location
22
+ const funcMatch = content.match(/^(.+?)\s+\((.+)\)$/)
23
+ if (funcMatch) {
24
+ // Has function name: "functionName (/path/to/file:line:column)"
25
+ const funcName = funcMatch[1].trim()
26
+ // Only include function name if not anonymous
27
+ if (funcName !== '<anonymous>' && funcName !== 'async <anonymous>') {
28
+ frame.function = funcName
29
+ }
30
+ const location = funcMatch[2]
31
+ const locMatch = location.match(/^(.+):(\d+):(\d+)$/)
32
+ if (locMatch) {
33
+ frame.filepath = locMatch[1]
34
+ frame.line = parseInt(locMatch[2], 10)
35
+ frame.column = parseInt(locMatch[3], 10)
36
+ }
37
+ } else {
38
+ // No function name: "/path/to/file:line:column"
39
+ const locMatch = content.match(/^(.+):(\d+):(\d+)$/)
40
+ if (locMatch) {
41
+ frame.filepath = locMatch[1]
42
+ frame.line = parseInt(locMatch[2], 10)
43
+ frame.column = parseInt(locMatch[3], 10)
44
+ }
45
+ }
46
+
47
+ // Convert absolute paths to relative paths if spineFilesystemRoot is provided
48
+ if (frame.filepath && spineFilesystemRoot) {
49
+ if (frame.filepath.startsWith(spineFilesystemRoot)) {
50
+ frame.filepath = frame.filepath.slice(spineFilesystemRoot.length)
51
+ // Remove leading slash if present
52
+ if (frame.filepath.startsWith('/')) {
53
+ frame.filepath = frame.filepath.slice(1)
54
+ }
55
+ }
56
+ }
57
+
58
+ // Include all frames, even if incomplete
59
+ if (frame.filepath || frame.function) {
60
+ result.push(frame)
61
+ }
62
+ }
63
+ }
64
+ return result
65
+ }
66
+
67
+ function extractCallerInfo(stack: Array<{ function?: string, filepath?: string, line?: number, column?: number }>, offset: number = 0) {
68
+ // Use offset to skip frames in the stack
69
+ // offset 0 = first frame, offset 1 = second frame, etc.
70
+
71
+ if (offset < stack.length) {
72
+ const frame = stack[offset]
73
+ return {
74
+ filepath: frame.filepath,
75
+ line: frame.line
76
+ }
77
+ }
78
+
79
+ // Fallback to first frame if offset is out of bounds
80
+ if (stack.length > 0) {
81
+ return {
82
+ filepath: stack[0].filepath,
83
+ line: stack[0].line
84
+ }
85
+ }
86
+ return {}
87
+ }
88
+
89
+ type CallerContext = {
90
+ capsuleSourceLineRef: string
91
+ capsuleSourceNameRef?: string
92
+ spineContractCapsuleInstanceId: string
93
+ capsuleSourceNameRefHash?: string
94
+ prop?: string
95
+ filepath?: string
96
+ line?: number
97
+ stack?: Array<{ function?: string, filepath?: string, line?: number, column?: number }>
98
+ }
99
+
100
+ function CapsuleMembrane(target: Record<string, any>, hooks?: {
101
+ onGet?: (data: { prop: string, value: any }) => void
102
+ onSet?: (data: { prop: string, value: any }) => void
103
+ onBeforeCall?: (data: { prop: string, args: any[] }) => void
104
+ onAfterCall?: (data: { prop: string, result: any, args: any[] }) => void
105
+ }, callerContext?: CallerContext) {
106
+ return new Proxy(target, {
107
+ get(obj: any, prop: string | symbol) {
108
+ if (typeof prop === 'symbol') return obj[prop]
109
+
110
+ const value = obj[prop]
111
+ hooks?.onGet?.({ prop: prop as string, value })
112
+
113
+ if (typeof value === 'function') {
114
+ return function (this: any, ...args: any[]) {
115
+ hooks?.onBeforeCall?.({ prop: prop as string, args })
116
+ const result = value.apply(this, args)
117
+ hooks?.onAfterCall?.({ prop: prop as string, args, result })
118
+ return result
119
+ }
120
+ }
121
+
122
+ return value
123
+ },
124
+ set(obj: any, prop: string | symbol, value: any) {
125
+ if (typeof prop === 'symbol') {
126
+ obj[prop] = value
127
+ return true
128
+ }
129
+
130
+ hooks?.onSet?.({ prop: prop as string, value })
131
+ obj[prop] = value
132
+ return true
133
+ }
134
+ })
135
+ }
136
+
137
+
138
+ class MembraneContractCapsuleInstanceFactory extends ContractCapsuleInstanceFactory {
139
+ private getEventIndex: () => number
140
+ private incrementEventIndex: () => number
141
+ private currentCallerContext: CallerContext | undefined
142
+ private onMembraneEvent?: (event: any) => void
143
+ private enableCallerStackInference: boolean
144
+ private encapsulateOptions: any
145
+ private capsuleSourceNameRef?: string
146
+ private capsuleSourceNameRefHash?: string
147
+ private runtimeSpineContracts?: Record<string, any>
148
+ public id: string
149
+
150
+ constructor({
151
+ spineContractUri,
152
+ capsule,
153
+ self,
154
+ encapsulatedApi,
155
+ resolve,
156
+ importCapsule,
157
+ spineFilesystemRoot,
158
+ freezeCapsule,
159
+ onMembraneEvent,
160
+ enableCallerStackInference,
161
+ encapsulateOptions,
162
+ getEventIndex,
163
+ incrementEventIndex,
164
+ currentCallerContext,
165
+ runtimeSpineContracts
166
+ }: {
167
+ spineContractUri: string
168
+ capsule: any
169
+ self: any
170
+ encapsulatedApi: Record<string, any>
171
+ resolve?: (uri: string, parentFilepath: string) => Promise<string>
172
+ importCapsule?: (filepath: string) => Promise<any>
173
+ spineFilesystemRoot?: string
174
+ freezeCapsule?: (capsule: any) => Promise<any>
175
+ onMembraneEvent?: (event: any) => void
176
+ enableCallerStackInference: boolean
177
+ encapsulateOptions: any
178
+ getEventIndex: () => number
179
+ incrementEventIndex: () => number
180
+ currentCallerContext?: CallerContext
181
+ runtimeSpineContracts?: Record<string, any>
182
+ }) {
183
+ super({ spineContractUri, capsule, self, encapsulatedApi, resolve, importCapsule, spineFilesystemRoot, freezeCapsule })
184
+ this.getEventIndex = getEventIndex
185
+ this.incrementEventIndex = incrementEventIndex
186
+ this.currentCallerContext = currentCallerContext
187
+ this.onMembraneEvent = onMembraneEvent
188
+ this.enableCallerStackInference = enableCallerStackInference
189
+ this.encapsulateOptions = encapsulateOptions
190
+ this.capsuleSourceNameRef = capsule?.cst?.capsuleSourceNameRef
191
+ this.capsuleSourceNameRefHash = capsule?.cst?.capsuleSourceNameRefHash
192
+ this.runtimeSpineContracts = runtimeSpineContracts
193
+ this.id = `$${encapsulateOptions.capsuleSourceLineRef}`
194
+ }
195
+
196
+ setCurrentCallerContext(context: CallerContext | undefined): void {
197
+ this.currentCallerContext = context
198
+ }
199
+
200
+ protected async mapMappingProperty({ overrides, options, property }: { overrides: any, options: any, property: any }) {
201
+
202
+ const mappedCapsule = await this.resolveMappedCapsule({ property })
203
+ const constants = await this.extractConstants({ mappedCapsule })
204
+
205
+ const mappingOptions = await property.definition.options?.({
206
+ constants
207
+ })
208
+
209
+ // Transform overrides if this mapping has a propertyContractDelegate
210
+ let mappedOverrides = overrides
211
+ if (property.definition.propertyContractDelegate) {
212
+
213
+ // Extract overrides for the delegate property contract and map them to '#'
214
+ // Try both capsuleSourceLineRef and capsuleName
215
+ const delegateOverrides =
216
+ overrides?.[this.capsule.encapsulateOptions.capsuleSourceLineRef]?.[property.definition.propertyContractDelegate] ||
217
+ (this.capsule.encapsulateOptions.capsuleName && overrides?.[this.capsule.encapsulateOptions.capsuleName]?.[property.definition.propertyContractDelegate])
218
+
219
+ if (delegateOverrides) {
220
+ mappedOverrides = {
221
+ ...overrides,
222
+ [mappedCapsule.capsuleSourceLineRef]: {
223
+ '#': delegateOverrides
224
+ }
225
+ }
226
+ if (mappedCapsule.encapsulateOptions.capsuleName) {
227
+ mappedOverrides[mappedCapsule.encapsulateOptions.capsuleName] = {
228
+ '#': delegateOverrides
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ const mappedCapsuleInstance = await mappedCapsule.makeInstance({
235
+ overrides: mappedOverrides,
236
+ options: mappingOptions,
237
+ runtimeSpineContracts: this.runtimeSpineContracts
238
+ })
239
+
240
+ const apiTarget = this.getApiTarget({ property })
241
+ apiTarget[property.name] = new Proxy(mappedCapsuleInstance, {
242
+ get: (apiTarget: any, apiProp: string | symbol) => {
243
+ if (typeof apiProp === 'symbol') return apiTarget[apiProp]
244
+
245
+ this.currentCallerContext = {
246
+ capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
247
+ capsuleSourceNameRef: this.capsuleSourceNameRef,
248
+ spineContractCapsuleInstanceId: this.id,
249
+ capsuleSourceNameRefHash: this.capsuleSourceNameRefHash,
250
+ prop: apiProp as string
251
+ }
252
+
253
+ if (this.enableCallerStackInference) {
254
+ const stackStr = new Error('[MAPPED_CAPSULE]').stack
255
+ if (stackStr) {
256
+ const stackFrames = parseCallerFromStack(stackStr, this.spineFilesystemRoot)
257
+ if (stackFrames.length > 0) {
258
+ const callerInfo = extractCallerInfo(stackFrames, 3)
259
+ this.currentCallerContext.filepath = callerInfo.filepath
260
+ this.currentCallerContext.line = callerInfo.line
261
+ this.currentCallerContext.stack = stackFrames
262
+ }
263
+ }
264
+ }
265
+
266
+ return apiTarget[apiProp]
267
+ }
268
+ })
269
+
270
+ // Wrap unwrapped API in membrane proxy for this.self
271
+ this.self[property.name] = mappedCapsuleInstance.api ? new Proxy(mappedCapsuleInstance.api, {
272
+ get: (target, prop) => {
273
+ if (typeof prop === 'symbol') return target[prop]
274
+
275
+ const value = target[prop]
276
+ // Recursively unwrap nested .api objects
277
+ if (value && typeof value === 'object' && value.api) {
278
+ return value.api
279
+ }
280
+ return value
281
+ }
282
+ }) : mappedCapsuleInstance
283
+
284
+ // If this mapping has a propertyContractDelegate, also mount the mapped capsule's properties
285
+ // to the property contract namespace for direct access
286
+ if (property.definition.propertyContractDelegate) {
287
+ // Create the property contract namespace if it doesn't exist
288
+ if (!this.encapsulatedApi[property.definition.propertyContractDelegate]) {
289
+ this.encapsulatedApi[property.definition.propertyContractDelegate] = {}
290
+ }
291
+
292
+ // Get property definitions from the mapped capsule's CST instead of accessing .api
293
+ // This avoids triggering the proxy and firing unwanted membrane events
294
+ const delegateTarget = this.encapsulatedApi[property.definition.propertyContractDelegate]
295
+ const mappedCapsuleCst = mappedCapsule.cst
296
+ const spineContractProperties = mappedCapsuleCst?.spineContracts?.[this.spineContractUri]?.properties
297
+
298
+ if (spineContractProperties) {
299
+ for (const [key, propDef] of Object.entries(spineContractProperties)) {
300
+ // Skip internal properties that start with '#'
301
+ if (key.startsWith('#')) continue
302
+
303
+ // Wrap the property access in a proxy to track membrane events
304
+ Object.defineProperty(delegateTarget, key, {
305
+ get: () => {
306
+ this.currentCallerContext = {
307
+ capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
308
+ capsuleSourceNameRef: this.capsuleSourceNameRef,
309
+ spineContractCapsuleInstanceId: this.id,
310
+ capsuleSourceNameRefHash: this.capsuleSourceNameRefHash,
311
+ prop: key
312
+ }
313
+
314
+ if (this.enableCallerStackInference) {
315
+ const stackStr = new Error('[PROPERTY_CONTRACT_DELEGATE]').stack
316
+ if (stackStr) {
317
+ const stackFrames = parseCallerFromStack(stackStr, this.spineFilesystemRoot)
318
+ if (stackFrames.length > 0) {
319
+ const callerInfo = extractCallerInfo(stackFrames, 3)
320
+ this.currentCallerContext.filepath = callerInfo.filepath
321
+ this.currentCallerContext.line = callerInfo.line
322
+ this.currentCallerContext.stack = stackFrames
323
+ }
324
+ }
325
+ }
326
+
327
+ // Access the actual value from the instance's api
328
+ return mappedCapsuleInstance.api[key]
329
+ },
330
+ enumerable: true,
331
+ configurable: true
332
+ })
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ protected mapLiteralProperty({ property }: { property: any }) {
339
+
340
+ const value = typeof this.self[property.name] !== 'undefined'
341
+ ? this.self[property.name]
342
+ : property.definition.value
343
+
344
+ const valueKey = `__value_${property.name}`
345
+ Object.defineProperty(this.encapsulatedApi, valueKey, {
346
+ value: value,
347
+ writable: true,
348
+ enumerable: false,
349
+ configurable: true
350
+ })
351
+
352
+ Object.defineProperty(this.encapsulatedApi, property.name, {
353
+ get: () => {
354
+ const event: any = {
355
+ event: 'get',
356
+ eventIndex: this.incrementEventIndex(),
357
+ target: {
358
+ capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
359
+ spineContractCapsuleInstanceId: this.id,
360
+ prop: property.name,
361
+ },
362
+ value: this.encapsulatedApi[valueKey]
363
+ }
364
+
365
+ if (this.capsuleSourceNameRef) {
366
+ event.target.capsuleSourceNameRef = this.capsuleSourceNameRef
367
+ }
368
+ if (this.capsuleSourceNameRefHash) {
369
+ event.target.capsuleSourceNameRefHash = this.capsuleSourceNameRefHash
370
+ }
371
+
372
+ this.addCallerContextToEvent(event)
373
+ this.onMembraneEvent?.(event)
374
+ return this.encapsulatedApi[valueKey]
375
+ },
376
+ set: (newValue) => {
377
+ const event: any = {
378
+ event: 'set',
379
+ eventIndex: this.incrementEventIndex(),
380
+ target: {
381
+ capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
382
+ spineContractCapsuleInstanceId: this.id,
383
+ prop: property.name,
384
+ },
385
+ value: newValue
386
+ }
387
+
388
+ if (this.capsuleSourceNameRef) {
389
+ event.target.capsuleSourceNameRef = this.capsuleSourceNameRef
390
+ }
391
+ if (this.capsuleSourceNameRefHash) {
392
+ event.target.capsuleSourceNameRefHash = this.capsuleSourceNameRefHash
393
+ }
394
+
395
+ this.addCallerContextToEvent(event)
396
+ this.onMembraneEvent?.(event)
397
+ this.encapsulatedApi[valueKey] = newValue
398
+ },
399
+ enumerable: true,
400
+ configurable: true
401
+ })
402
+ }
403
+
404
+ protected mapFunctionProperty({ property }: { property: any }) {
405
+
406
+ // Create a proxy for this.self that intercepts property access
407
+ // Prefer this.self (which has unwrapped APIs) over encapsulatedApi
408
+ const selfProxy = new Proxy(this.self, {
409
+ get: (target: any, prop: string | symbol) => {
410
+ if (typeof prop === 'symbol') return target[prop]
411
+
412
+ // First check if the property exists in target (this.self)
413
+ if (prop in target) {
414
+ return target[prop]
415
+ }
416
+
417
+ // Fall back to encapsulatedApi
418
+ if (prop in this.encapsulatedApi) {
419
+ return this.encapsulatedApi[prop]
420
+ }
421
+
422
+ return undefined
423
+ }
424
+ })
425
+
426
+ const boundFunction = property.definition.value.bind(selfProxy)
427
+
428
+ const valueKey = `__value_${property.name}`
429
+ Object.defineProperty(this.encapsulatedApi, valueKey, {
430
+ value: boundFunction,
431
+ writable: true,
432
+ enumerable: false,
433
+ configurable: true
434
+ })
435
+
436
+ Object.defineProperty(this.encapsulatedApi, property.name, {
437
+ get: () => {
438
+ return (...args: any[]) => {
439
+ const callEvent: any = {
440
+ event: 'call',
441
+ eventIndex: this.incrementEventIndex(),
442
+ target: {
443
+ capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
444
+ spineContractCapsuleInstanceId: this.id,
445
+ prop: property.name,
446
+ },
447
+ args
448
+ }
449
+
450
+ if (this.capsuleSourceNameRef) {
451
+ callEvent.target.capsuleSourceNameRef = this.capsuleSourceNameRef
452
+ }
453
+ if (this.capsuleSourceNameRefHash) {
454
+ callEvent.target.capsuleSourceNameRefHash = this.capsuleSourceNameRefHash
455
+ }
456
+
457
+ this.addCallerContextToEvent(callEvent)
458
+ this.onMembraneEvent?.(callEvent)
459
+
460
+ const result = boundFunction(...args)
461
+
462
+ const resultEvent: any = {
463
+ event: 'call-result',
464
+ eventIndex: this.incrementEventIndex(),
465
+ callEventIndex: callEvent.eventIndex,
466
+ target: {
467
+ spineContractCapsuleInstanceId: this.id,
468
+ },
469
+ result
470
+ }
471
+
472
+ this.onMembraneEvent?.(resultEvent)
473
+
474
+ return result
475
+ }
476
+ },
477
+ enumerable: true,
478
+ configurable: true
479
+ })
480
+ }
481
+
482
+ protected mapGetterFunctionProperty({ property }: { property: any }) {
483
+ const getterFn = property.definition.value
484
+
485
+ // Create a proxy for this.self that intercepts property access
486
+ // Prefer this.self (which has unwrapped APIs) over encapsulatedApi
487
+ const selfProxy = new Proxy(this.self, {
488
+ get: (target: any, prop: string | symbol) => {
489
+ if (typeof prop === 'symbol') return target[prop]
490
+
491
+ // First check if the property exists in target (this.self)
492
+ if (prop in target) {
493
+ return target[prop]
494
+ }
495
+
496
+ // Fall back to encapsulatedApi
497
+ if (prop in this.encapsulatedApi) {
498
+ return this.encapsulatedApi[prop]
499
+ }
500
+
501
+ return undefined
502
+ }
503
+ })
504
+
505
+ Object.defineProperty(this.encapsulatedApi, property.name, {
506
+ get: () => {
507
+ // Call the getter function lazily when accessed with proper this context
508
+ const result = getterFn.call(selfProxy)
509
+
510
+ const event: any = {
511
+ event: 'get',
512
+ eventIndex: this.incrementEventIndex(),
513
+ target: {
514
+ capsuleSourceLineRef: this.encapsulateOptions.capsuleSourceLineRef,
515
+ spineContractCapsuleInstanceId: this.id,
516
+ prop: property.name,
517
+ },
518
+ value: result
519
+ }
520
+
521
+ if (this.capsuleSourceNameRef) {
522
+ event.target.capsuleSourceNameRef = this.capsuleSourceNameRef
523
+ }
524
+ if (this.capsuleSourceNameRefHash) {
525
+ event.target.capsuleSourceNameRefHash = this.capsuleSourceNameRefHash
526
+ }
527
+
528
+ this.addCallerContextToEvent(event)
529
+ this.onMembraneEvent?.(event)
530
+ return result
531
+ },
532
+ enumerable: true,
533
+ configurable: true
534
+ })
535
+ }
536
+
537
+ private addCallerContextToEvent(event: any): void {
538
+ if (this.currentCallerContext) {
539
+ event.caller = {
540
+ capsuleSourceLineRef: this.currentCallerContext.capsuleSourceLineRef,
541
+ spineContractCapsuleInstanceId: this.currentCallerContext.spineContractCapsuleInstanceId,
542
+ }
543
+ if (this.currentCallerContext.capsuleSourceNameRef) {
544
+ event.caller.capsuleSourceNameRef = this.currentCallerContext.capsuleSourceNameRef
545
+ }
546
+ if (this.currentCallerContext.capsuleSourceNameRefHash) {
547
+ event.caller.capsuleSourceNameRefHash = this.currentCallerContext.capsuleSourceNameRefHash
548
+ }
549
+ if (this.currentCallerContext.prop) {
550
+ event.caller.prop = this.currentCallerContext.prop
551
+ }
552
+ if (this.currentCallerContext.filepath) {
553
+ event.caller.filepath = this.currentCallerContext.filepath
554
+ }
555
+ if (this.currentCallerContext.line) {
556
+ event.caller.line = this.currentCallerContext.line
557
+ }
558
+ if (this.currentCallerContext.stack) {
559
+ event.caller.stack = this.currentCallerContext.stack
560
+ }
561
+ } else if (this.enableCallerStackInference) {
562
+ const stackStr = new Error('[MEMBRANE_EVENT]').stack
563
+ if (stackStr) {
564
+ const stackFrames = parseCallerFromStack(stackStr, this.spineFilesystemRoot)
565
+ if (stackFrames.length > 0) {
566
+ const callerInfo = extractCallerInfo(stackFrames, 3)
567
+ event.caller = {
568
+ ...callerInfo,
569
+ stack: stackFrames
570
+ }
571
+ }
572
+ }
573
+ }
574
+ }
575
+ }
576
+
577
+ export function CapsuleSpineContract({
578
+ onMembraneEvent,
579
+ freezeCapsule,
580
+ enableCallerStackInference = false,
581
+ spineFilesystemRoot,
582
+ resolve,
583
+ importCapsule
584
+ }: {
585
+ onMembraneEvent?: (event: any) => void
586
+ freezeCapsule?: (capsule: any) => Promise<any>
587
+ enableCallerStackInference?: boolean
588
+ spineFilesystemRoot?: string
589
+ resolve?: (uri: string, parentFilepath: string) => Promise<string>
590
+ importCapsule?: (filepath: string) => Promise<any>
591
+ } = {}) {
592
+
593
+ let eventIndex = 0
594
+ let currentCallerContext: CallerContext | undefined = undefined
595
+
596
+ return {
597
+ '#': CapsuleSpineContract['#'],
598
+ makeContractCapsuleInstance: ({ encapsulateOptions, spineContractUri, self, capsule, encapsulatedApi, runtimeSpineContracts }: { encapsulateOptions: any, spineContractUri: string, self: any, capsule?: any, encapsulatedApi: Record<string, any>, runtimeSpineContracts?: Record<string, any> }) => {
599
+ return new MembraneContractCapsuleInstanceFactory({
600
+ spineContractUri,
601
+ capsule,
602
+ self,
603
+ encapsulatedApi,
604
+ spineFilesystemRoot,
605
+ freezeCapsule,
606
+ resolve,
607
+ importCapsule,
608
+ onMembraneEvent,
609
+ enableCallerStackInference,
610
+ encapsulateOptions,
611
+ getEventIndex: () => eventIndex,
612
+ incrementEventIndex: () => eventIndex++,
613
+ currentCallerContext,
614
+ runtimeSpineContracts
615
+ })
616
+ },
617
+ hydrate: ({ capsuleSnapshot }: { capsuleSnapshot: any }): any => {
618
+ return capsuleSnapshot
619
+ }
620
+ }
621
+ }
622
+
623
+ CapsuleSpineContract['#'] = '@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0'
624
+
@@ -0,0 +1,28 @@
1
+
2
+ Capsule Spine Contract v0
3
+ ===
4
+
5
+ The default spine contract used to incubate spine functionality.
6
+
7
+ A spine contract is a standard and implementation that governs:
8
+
9
+ - how capsule properties are mapped to the encapsulated api and
10
+ - which features are available to bind additional logic controlled by definitions declared in the capsule source
11
+
12
+ Spine contracts define ecosystems as source code is written against these standards. They define the fundamental logic of how components and their internal APIs are bound.
13
+
14
+ In practice there should only ever be very few spine contracts but there can be a plethora of different partial or full implementations of the same standard.
15
+
16
+ This spine contract aims to realize a concrete implementation of the [PrivateData.Space](https://privatedata.space/) model for the purpose of building full-stack distributed applications & systems.
17
+
18
+
19
+ Example Capsule Source
20
+ ---
21
+
22
+
23
+
24
+
25
+ Reference
26
+ ---
27
+
28
+