@agoric/swingset-liveslots 0.10.3-other-dev-3eb1a1d.0 → 0.10.3-other-dev-fbe72e7.0.fbe72e7
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/package.json +26 -26
- package/src/boyd-gc.d.ts +12 -0
- package/src/boyd-gc.d.ts.map +1 -0
- package/src/cache.d.ts +71 -0
- package/src/cache.d.ts.map +1 -0
- package/src/capdata.d.ts +16 -0
- package/src/capdata.d.ts.map +1 -0
- package/src/capdata.js +16 -8
- package/src/collectionManager.d.ts +47 -0
- package/src/collectionManager.d.ts.map +1 -0
- package/src/collectionManager.js +1 -0
- package/src/facetiousness.d.ts +25 -0
- package/src/facetiousness.d.ts.map +1 -0
- package/src/index.d.ts +4 -0
- package/src/index.d.ts.map +1 -0
- package/src/kdebug.d.ts +7 -0
- package/src/kdebug.d.ts.map +1 -0
- package/src/liveslots.d.ts +42 -0
- package/src/liveslots.d.ts.map +1 -0
- package/src/liveslots.js +6 -4
- package/src/message.d.ts +49 -0
- package/src/message.d.ts.map +1 -0
- package/src/message.js +7 -3
- package/src/parseVatSlots.d.ts +125 -0
- package/src/parseVatSlots.d.ts.map +1 -0
- package/src/types.d.ts +81 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +6 -5
- package/src/vatDataTypes.d.ts +170 -0
- package/src/vatDataTypes.d.ts.map +1 -0
- package/src/vatDataTypes.ts +272 -0
- package/src/vatstore-iterators.d.ts +4 -0
- package/src/vatstore-iterators.d.ts.map +1 -0
- package/src/vatstore-usage.md +198 -0
- package/src/virtualObjectManager.d.ts +44 -0
- package/src/virtualObjectManager.d.ts.map +1 -0
- package/src/virtualObjectManager.js +70 -14
- package/src/virtualReferences.d.ts +61 -0
- package/src/virtualReferences.d.ts.map +1 -0
- package/src/vpid-tracking.md +92 -0
- package/src/watchedPromises.d.ts +31 -0
- package/src/watchedPromises.d.ts.map +1 -0
- package/src/watchedPromises.js +19 -12
- package/test/collections.test.js +25 -4
- package/test/dummyMeterControl.d.ts +2 -0
- package/test/dummyMeterControl.d.ts.map +1 -0
- package/test/engine-gc.d.ts +3 -0
- package/test/engine-gc.d.ts.map +1 -0
- package/test/gc-and-finalize.d.ts +5 -0
- package/test/gc-and-finalize.d.ts.map +1 -0
- package/test/gc-helpers.js +2 -2
- package/test/handled-promises.test.js +529 -163
- package/test/initial-vrefs.test.js +12 -18
- package/test/liveslots-helpers.d.ts +64 -0
- package/test/liveslots-helpers.d.ts.map +1 -0
- package/test/liveslots-helpers.js +1 -0
- package/test/liveslots-real-gc.test.js +11 -9
- package/test/liveslots.test.js +3 -3
- package/test/storeGC/lifecycle.test.js +13 -12
- package/test/util.d.ts +25 -0
- package/test/util.d.ts.map +1 -0
- package/test/util.js +2 -2
- package/test/vat-util.d.ts +9 -0
- package/test/vat-util.d.ts.map +1 -0
- package/test/virtual-objects/state-shape.test.js +312 -221
- package/test/virtual-objects/virtualObjectGC.test.js +37 -36
- package/test/virtual-objects/virtualObjectManager.test.js +41 -63
- package/test/vo-test-harness.test.js +13 -9
- package/test/waitUntilQuiescent.d.ts +3 -0
- package/test/waitUntilQuiescent.d.ts.map +1 -0
- package/tools/fakeCollectionManager.d.ts +14 -0
- package/tools/fakeCollectionManager.d.ts.map +1 -0
- package/tools/fakeVirtualObjectManager.d.ts +32 -0
- package/tools/fakeVirtualObjectManager.d.ts.map +1 -0
- package/tools/fakeVirtualSupport.d.ts +278 -0
- package/tools/fakeVirtualSupport.d.ts.map +1 -0
- package/tools/prepare-strict-test-env.d.ts +37 -0
- package/tools/prepare-strict-test-env.d.ts.map +1 -0
- package/tools/prepare-test-env.d.ts +2 -0
- package/tools/prepare-test-env.d.ts.map +1 -0
- package/tools/setup-vat-data.d.ts +9 -0
- package/tools/setup-vat-data.d.ts.map +1 -0
- package/tools/setup-vat-data.js +0 -1
- package/tools/vo-test-harness.d.ts +33 -0
- package/tools/vo-test-harness.d.ts.map +1 -0
- package/tools/vo-test-harness.js +21 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Types for vat-data
|
|
3
|
+
*
|
|
4
|
+
* Facet is a single object with methods.
|
|
5
|
+
* Behavior is a description when defining a kind of what facets it will have.
|
|
6
|
+
* For the non-multi defineKind, there is just one facet so it doesn't have a key.
|
|
7
|
+
*/
|
|
8
|
+
import type {
|
|
9
|
+
MapStore,
|
|
10
|
+
SetStore,
|
|
11
|
+
StoreOptions,
|
|
12
|
+
WeakMapStore,
|
|
13
|
+
WeakSetStore,
|
|
14
|
+
} from '@agoric/store';
|
|
15
|
+
import type { Amplify, IsInstance, ReceivePower, StateShape } from '@endo/exo';
|
|
16
|
+
import type { RemotableObject } from '@endo/pass-style';
|
|
17
|
+
import type { RemotableBrand } from '@endo/eventual-send';
|
|
18
|
+
import type { InterfaceGuard, Pattern } from '@endo/patterns';
|
|
19
|
+
import type { makeWatchedPromiseManager } from './watchedPromises.js';
|
|
20
|
+
|
|
21
|
+
// TODO should be moved into @endo/patterns and eventually imported here
|
|
22
|
+
// instead of this local definition.
|
|
23
|
+
export type InterfaceGuardKit = Record<string, InterfaceGuard>;
|
|
24
|
+
export type { MapStore, Pattern };
|
|
25
|
+
|
|
26
|
+
// This needs `any` values. If they were `unknown`, code that uses Baggage
|
|
27
|
+
// would need explicit runtime checks or casts for every fetch, which is
|
|
28
|
+
// onerous.
|
|
29
|
+
export type Baggage = MapStore<string, any>;
|
|
30
|
+
|
|
31
|
+
type WatchedPromisesManager = ReturnType<typeof makeWatchedPromiseManager>;
|
|
32
|
+
|
|
33
|
+
// used to omit the 'context' parameter
|
|
34
|
+
type OmitFirstArg<F> = F extends (x: any, ...args: infer P) => infer R
|
|
35
|
+
? (...args: P) => R
|
|
36
|
+
: never;
|
|
37
|
+
|
|
38
|
+
// The type of a passable local object with methods.
|
|
39
|
+
// An internal helper to avoid having to repeat `O`.
|
|
40
|
+
type PrimaryRemotable<O> = O & RemotableObject & RemotableBrand<object, O>;
|
|
41
|
+
|
|
42
|
+
export type KindFacet<O> = PrimaryRemotable<{
|
|
43
|
+
[K in keyof O]: OmitFirstArg<O[K]>; // omit the 'context' parameter
|
|
44
|
+
}>;
|
|
45
|
+
|
|
46
|
+
export type KindFacets<B> = {
|
|
47
|
+
[FacetKey in keyof B]: KindFacet<B[FacetKey]>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type KindContext<S, F> = { state: S; self: KindFacet<F> };
|
|
51
|
+
export type MultiKindContext<S, B> = { state: S; facets: KindFacets<B> };
|
|
52
|
+
|
|
53
|
+
export type PlusContext<C, M extends (...args: any[]) => any> = (
|
|
54
|
+
c: C,
|
|
55
|
+
...args: Parameters<M>
|
|
56
|
+
) => ReturnType<M>;
|
|
57
|
+
|
|
58
|
+
export type FunctionsPlusContext<
|
|
59
|
+
C,
|
|
60
|
+
O extends Record<string, (...args: any[]) => any>,
|
|
61
|
+
> = {
|
|
62
|
+
[K in keyof O]: PlusContext<C, O[K]>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
declare class DurableKindHandleClass {
|
|
66
|
+
private descriptionTag: string;
|
|
67
|
+
}
|
|
68
|
+
export type DurableKindHandle = DurableKindHandleClass;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Grab bag of options that can be provided to `defineDurableKind` and its
|
|
72
|
+
* siblings. Not all options are meaningful in all contexts. See the
|
|
73
|
+
* doc-comments on each option.
|
|
74
|
+
*/
|
|
75
|
+
export type DefineKindOptions<C> = {
|
|
76
|
+
/**
|
|
77
|
+
* If provided, the `finish` function will be called after the instance is
|
|
78
|
+
* made and internally registered, but before it is returned. The finish
|
|
79
|
+
* function is to do any post-intantiation initialization that should be
|
|
80
|
+
* done before exposing the object to its clients.
|
|
81
|
+
*/
|
|
82
|
+
finish?: (context: C) => void;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* If provided, it describes the shape of all state records of instances
|
|
86
|
+
* of this kind.
|
|
87
|
+
*/
|
|
88
|
+
stateShape?: StateShape;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* If a `receiveAmplifier` function is provided to an exo class kit definition,
|
|
92
|
+
* it will be called with an `Amplify` function. If provided to the definition
|
|
93
|
+
* of a normal exo or exo class, the definition will throw, since only
|
|
94
|
+
* exo kits can be amplified.
|
|
95
|
+
* An `Amplify` function is a function that takes a facet instance of
|
|
96
|
+
* this class kit as an argument, in which case it will return the facets
|
|
97
|
+
* record, giving access to all the facet instances of the same cohort.
|
|
98
|
+
*/
|
|
99
|
+
receiveAmplifier?: ReceivePower<Amplify>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* If a `receiveInstanceTester` function is provided, it will be called
|
|
103
|
+
* during the definition of the exo class or exo class kit with an
|
|
104
|
+
* `IsInstance` function. The first argument of `IsInstance`
|
|
105
|
+
* is the value to be tested. When it may be a facet instance of an
|
|
106
|
+
* exo class kit, the optional second argument, if provided, is
|
|
107
|
+
* a `facetName`. In that case, the function tests only if the first
|
|
108
|
+
* argument is an instance of that facet of the associated exo class kit.
|
|
109
|
+
*/
|
|
110
|
+
receiveInstanceTester?: ReceivePower<IsInstance>;
|
|
111
|
+
|
|
112
|
+
// TODO properties above are identical to those in FarClassOptions.
|
|
113
|
+
// These are the only options that should be exposed by
|
|
114
|
+
// vat-data's public virtual/durable exo APIs. This DefineKindOptions
|
|
115
|
+
// should explicitly be a subtype, where the methods below are only for
|
|
116
|
+
// internal use, i.e., below the exo level.
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* As a kind option, intended for internal use only.
|
|
120
|
+
* Meaningful to `makeScalarBigMapStore` and its siblings. These maker
|
|
121
|
+
* fuctions will make either virtual or durable stores, depending on
|
|
122
|
+
* this flag. Defaults to off, making virtual but not durable collections.
|
|
123
|
+
*
|
|
124
|
+
* Generally, durable collections are provided with `provideDurableMapStore`
|
|
125
|
+
* and its sibling, which use this flag internally. If you do not make
|
|
126
|
+
* durable collections by other means, you can consider this as
|
|
127
|
+
* intended for internal use only.
|
|
128
|
+
*/
|
|
129
|
+
durable?: boolean;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Intended for internal use only.
|
|
133
|
+
* Should the raw methods receive their `context` argument as their first
|
|
134
|
+
* argument or as their `this` binding? For `defineDurableKind` and its
|
|
135
|
+
* siblings (including `prepareSingleton`), this defaults to off, meaning that
|
|
136
|
+
* their behavior methods receive `context` as their first argument.
|
|
137
|
+
* `prepareExoClass` and its siblings (including `prepareExo`) use
|
|
138
|
+
* this flag internally to indicate that their methods receive `context`
|
|
139
|
+
* as their `this` binding.
|
|
140
|
+
*/
|
|
141
|
+
thisfulMethods?: boolean;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Intended for internal use only.
|
|
145
|
+
* Only applicable if this is a class kind. A class kit kind should use
|
|
146
|
+
* `interfaceGuardKit` instead.
|
|
147
|
+
*
|
|
148
|
+
* If an `interfaceGuard` is provided, then the raw methods passed alongside
|
|
149
|
+
* it are wrapped by a function that first checks that this method's guard
|
|
150
|
+
* pattern is satisfied before calling the raw method.
|
|
151
|
+
*
|
|
152
|
+
* In `defineDurableKind` and its siblings, this defaults to `undefined`.
|
|
153
|
+
* Exo classes use this internally to protect their raw class methods
|
|
154
|
+
* using the provided interface.
|
|
155
|
+
* In absence, an exo is protected anyway, while a bare kind is
|
|
156
|
+
* not (detected by `!thisfulMethods`),
|
|
157
|
+
*/
|
|
158
|
+
interfaceGuard?: InterfaceGuard;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Intended for internal use only.
|
|
162
|
+
* Only applicable if this is a class kit kind. A class kind should use
|
|
163
|
+
* `interfaceGuard` instead.
|
|
164
|
+
*
|
|
165
|
+
* If an `interfaceGuardKit` is provided, then each member of the
|
|
166
|
+
* interfaceGuardKit is used to guard the corresponding facet of the
|
|
167
|
+
* class kit.
|
|
168
|
+
*
|
|
169
|
+
* In `defineDurableKindMulti` and its siblings, this defaults to `undefined`.
|
|
170
|
+
* Exo class kits use this internally to protect their facets.
|
|
171
|
+
* In absence, an exo is protected anyway, while a bare kind is
|
|
172
|
+
* not (detected by `!thisfulMethods`),
|
|
173
|
+
*/
|
|
174
|
+
interfaceGuardKit?: InterfaceGuardKit;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export type VatData = {
|
|
178
|
+
// virtual kinds
|
|
179
|
+
/** @deprecated Use defineVirtualExoClass instead */
|
|
180
|
+
defineKind: <P extends Array<any>, S, F>(
|
|
181
|
+
tag: string,
|
|
182
|
+
init: (...args: P) => S,
|
|
183
|
+
facet: F,
|
|
184
|
+
options?: DefineKindOptions<KindContext<S, F>>,
|
|
185
|
+
) => (...args: P) => KindFacet<F>;
|
|
186
|
+
|
|
187
|
+
/** @deprecated Use defineVirtualExoClassKit instead */
|
|
188
|
+
defineKindMulti: <P extends Array<any>, S, B>(
|
|
189
|
+
tag: string,
|
|
190
|
+
init: (...args: P) => S,
|
|
191
|
+
behavior: B,
|
|
192
|
+
options?: DefineKindOptions<MultiKindContext<S, B>>,
|
|
193
|
+
) => (...args: P) => KindFacets<B>;
|
|
194
|
+
|
|
195
|
+
// durable kinds
|
|
196
|
+
makeKindHandle: (descriptionTag: string) => DurableKindHandle;
|
|
197
|
+
|
|
198
|
+
/** @deprecated Use defineDurableExoClass instead */
|
|
199
|
+
defineDurableKind: <P extends Array<any>, S, F>(
|
|
200
|
+
kindHandle: DurableKindHandle,
|
|
201
|
+
init: (...args: P) => S,
|
|
202
|
+
facet: F,
|
|
203
|
+
options?: DefineKindOptions<KindContext<S, F>>,
|
|
204
|
+
) => (...args: P) => KindFacet<F>;
|
|
205
|
+
|
|
206
|
+
/** @deprecated Use defineDurableExoClassKit instead */
|
|
207
|
+
defineDurableKindMulti: <P extends Array<any>, S, B>(
|
|
208
|
+
kindHandle: DurableKindHandle,
|
|
209
|
+
init: (...args: P) => S,
|
|
210
|
+
behavior: B,
|
|
211
|
+
options?: DefineKindOptions<MultiKindContext<S, B>>,
|
|
212
|
+
) => (...args: P) => KindFacets<B>;
|
|
213
|
+
|
|
214
|
+
providePromiseWatcher: WatchedPromisesManager['providePromiseWatcher'];
|
|
215
|
+
watchPromise: WatchedPromisesManager['watchPromise'];
|
|
216
|
+
|
|
217
|
+
makeScalarBigMapStore: <K, V>(
|
|
218
|
+
label: string,
|
|
219
|
+
options?: StoreOptions,
|
|
220
|
+
) => MapStore<K, V>;
|
|
221
|
+
makeScalarBigWeakMapStore: <K, V>(
|
|
222
|
+
label: string,
|
|
223
|
+
options?: StoreOptions,
|
|
224
|
+
) => WeakMapStore<K, V>;
|
|
225
|
+
|
|
226
|
+
makeScalarBigSetStore: <K>(
|
|
227
|
+
label: string,
|
|
228
|
+
options?: StoreOptions,
|
|
229
|
+
) => SetStore<K>;
|
|
230
|
+
makeScalarBigWeakSetStore: <K>(
|
|
231
|
+
label: string,
|
|
232
|
+
options?: StoreOptions,
|
|
233
|
+
) => WeakSetStore<K>;
|
|
234
|
+
canBeDurable: (specimen: unknown) => boolean;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// The JSDoc is repeated here and at the function definition so it appears
|
|
238
|
+
// in IDEs where it's used, regardless of type resolution.
|
|
239
|
+
export interface PickFacet {
|
|
240
|
+
/**
|
|
241
|
+
* When making a multi-facet kind, it's common to pick one facet to
|
|
242
|
+
* expose. E.g.,
|
|
243
|
+
*
|
|
244
|
+
* const makeFoo = (a, b, c, d) => makeFooBase(a, b, c, d).self;
|
|
245
|
+
*
|
|
246
|
+
* This helper reduces the duplication:
|
|
247
|
+
*
|
|
248
|
+
* const makeFoo = pickFacet(makeFooBase, 'self');
|
|
249
|
+
*/
|
|
250
|
+
<M extends (...args: any[]) => any, F extends keyof ReturnType<M>>(
|
|
251
|
+
maker: M,
|
|
252
|
+
facetName: F,
|
|
253
|
+
): (...args: Parameters<M>) => ReturnType<M>[F];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** @deprecated Use prepareExoClass instead */
|
|
257
|
+
export type PrepareKind = <P extends Array<any>, S, F>(
|
|
258
|
+
baggage: Baggage,
|
|
259
|
+
tag: string,
|
|
260
|
+
init: (...args: P) => S,
|
|
261
|
+
facet: F,
|
|
262
|
+
options?: DefineKindOptions<KindContext<S, F>>,
|
|
263
|
+
) => (...args: P) => KindFacet<F>;
|
|
264
|
+
|
|
265
|
+
/** @deprecated Use prepareExoClassKit instead */
|
|
266
|
+
export type PrepareKindMulti = <P extends Array<any>, S, B>(
|
|
267
|
+
baggage: Baggage,
|
|
268
|
+
tag: string,
|
|
269
|
+
init: (...args: P) => S,
|
|
270
|
+
behavior: B,
|
|
271
|
+
options?: DefineKindOptions<MultiKindContext<S, B>>,
|
|
272
|
+
) => (...args: P) => KindFacets<B>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export function enumerateKeysStartEnd(syscall: any, start: any, end: any, checkF: any): Generator<any, void, unknown>;
|
|
2
|
+
export function enumerateKeysWithPrefix(syscall: any, prefix: any): Generator<any, void, unknown>;
|
|
3
|
+
export function prefixedKeysExist(syscall: any, prefix: any): boolean;
|
|
4
|
+
//# sourceMappingURL=vatstore-iterators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vatstore-iterators.d.ts","sourceRoot":"","sources":["vatstore-iterators.js"],"names":[],"mappings":"AAIA,sHAeC;AAMD,kGASC;AAGD,sEAGC"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# How Liveslots Uses the Vatstore
|
|
2
|
+
|
|
3
|
+
Each vat gets exclusive access to a portion of the kernel's `kvStore`, implemented with a simple prefix: when vat `v6` uses `syscall.vatstoreSet('key', 'value')`, the kvStore records the value in key `v6.vs.key`.
|
|
4
|
+
|
|
5
|
+
Userspace gets an attenuated object named `vatPowers.vatstore`. When userspace calls `vatPowers.vatstore.set('key', 'value')`, liveslots performs `syscall.vatstoreSet('vvs.key', value)`, which results in a kernel kvStore entry under key `v6.vs.vvs.key`.
|
|
6
|
+
|
|
7
|
+
The rest of the vat's keyspace is used by liveslots:
|
|
8
|
+
|
|
9
|
+
* virtual object Kind metainformation
|
|
10
|
+
* data for each virtual object
|
|
11
|
+
* virtual collection metainformation and entries
|
|
12
|
+
* reference counts for virtual objects (tracking references from other virtual objects)
|
|
13
|
+
* export status for virtual objects
|
|
14
|
+
* the id of the "baggage" object, delivered across vat upgrades
|
|
15
|
+
|
|
16
|
+
This file describes the layout of the vatstore keyspace.
|
|
17
|
+
|
|
18
|
+
# vref virtual/durable annotations
|
|
19
|
+
|
|
20
|
+
When vats export object references into the kernel, they create "vrefs" as reference identifiers. For non-virtual objects (ephemeral "Remotables", held in RAM rather than the DB), these take the form of `o+NN` (e.g. `o+0`, `o+12`, `o+937`). Virtual and durable exported objects get an additional `v` or `d` annotation, so their vrefs start with e.g. `o+v99` or `o+v132` for virtual objects, and `o+d3` or `o+d77` for durable objects. The kernel can use these annotations to assist in the cleanup process when a vat is upgraded, and non-durable objects are abandoned.
|
|
21
|
+
|
|
22
|
+
Kind handles are necessarily durable, as is the "baggage" object. Promises are necessarily ephemeral, so vat-allocated promise vrefs (aka "vpids") always use the `p+NN` form, and never have a `v` or `d` annotation.
|
|
23
|
+
|
|
24
|
+
# Counters
|
|
25
|
+
|
|
26
|
+
Liveslots maintains three durable counters to create the distinct vrefs that it transmits to the kernel. These counters are initialized when `startVat` is called to create the first incarnation of a vat (see `initialIDCounters` and `initializeIDCounters` in [liveslots.js](./liveslots.js)), and written to the vatstore at the end of each delivery as a JSON-serialized record at key `idCounters` (see `flushIDCounters`).
|
|
27
|
+
|
|
28
|
+
* `exportID`: an integer indicating the ID of the next exported Remotable or defined Kind and incremented upon each allocation of such a value. Technically starts at 1, but that is mostly irrelevant because the root object itself is associated with 0 and many initial values are claimed before any userspace vat code has a chance to run (described below).
|
|
29
|
+
* `collectionID`: an integer indicating the ID of the next collection and incremented upon each allocation of a new collection. Starts at 1, which is claimed by "[baggage](#baggage)".
|
|
30
|
+
* `promiseID`: an integer indicating the ID for the next exported promise (including a promise implicitly created for outbound-message results) and incremented upon each allocation. Starts at 5.
|
|
31
|
+
|
|
32
|
+
The current version of liveslots consumes the first ten exportIDs (covering `o+0` through `o+9`) during vat startup, leaving `10` as the first exportID available for userspace.
|
|
33
|
+
|
|
34
|
+
* `o+0`: root object
|
|
35
|
+
* `o+d1`: identifies the KindHandle Kind ID, each `o+d1/${kindHandleID}` instance of which is a KindHandle
|
|
36
|
+
* `o+v2` through `o+v5`: KindHandles for the built-in [virtual collections](#virtualdurable-collections-aka-stores)
|
|
37
|
+
* `o+d6` through `o+d9`: KindHandles for the built-in [durable collections](#virtualdurable-collections-aka-stores)
|
|
38
|
+
* `o+10` / `o+v10` / `o+d10`: first available for userspace Remotables or Kinds
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Virtual Object Kinds
|
|
42
|
+
|
|
43
|
+
Vats can use `VatData.defineKind()` to define categories (classes) of virtual data (see [Virtual and Durable Objects](../../SwingSet/docs/virtual-objects.md)). The effective schema for each "Kind" contains an interface name, a set of `state` property names, and a list of facet names (which may be empty, indicating a single-facet Kind).
|
|
44
|
+
|
|
45
|
+
The standard Kind is "single-facet", meaning that each instance of the virtual object yields exactly one "Representative". However many security patterns require a collection of "Facet" Representatives which share access to common state. These Facets are all created at the same time and returned in a single record called a "cohort".
|
|
46
|
+
|
|
47
|
+
`defineKind` also specifies the runtime behavior: an `init` function called to create the initial state of each instance, a `behavior` record to provide methods for each instance, and an optional `finish` function to perform post-initialization tasks like registering the new object in a collection. `defineKind` returns a "kind constructor", typically named e.g. `makeFoo()` to make instances of the "foo" kind.
|
|
48
|
+
|
|
49
|
+
Each time the kind constructor is called, a new "baseref" is allocated for the cohort, and the (one or multiple) facet Representatives are created. Each Representative/Facet gets a separate vref, all of which are extensions of the baseref.
|
|
50
|
+
Those vrefs are used when interacting with the kernel (in `syscall.send` etc.), and in virtualized data (inside the capdata `slots` that point to other objects), but the vatstore keys that track GC refcounts and export status use the "baseref" instead.
|
|
51
|
+
|
|
52
|
+
The baseref is built out of two pieces separated by a literal `/`:
|
|
53
|
+
|
|
54
|
+
1. Prefixed Kind ID, e.g. `o+v11` or `o+d11`. These are allocated using the same exportID [counter](#counters) as exported Remotables (JavaScript objects marked with `Far`), meaning exported objects and Kind IDs share a numberspace.
|
|
55
|
+
2. Instance ID, an integer. `1` for the first instance of each Kind and incremented for each subsequent instance.
|
|
56
|
+
|
|
57
|
+
The vref for a Representative of a single-facet virtual Kind is just the `o+v${kindID}/${instanceID}` baseref. The vref for a Representative of a facet of a multi-facet virtual Kind extends that to `o+v${kindID}/${instanceID}:${facetID}`, where the additional component is:
|
|
58
|
+
|
|
59
|
+
3. Facet ID, an integer. `0` for the first facet and incremented for each subsequent facet.
|
|
60
|
+
|
|
61
|
+
In a c-list or virtualized data, you may see vrefs like these:
|
|
62
|
+
|
|
63
|
+
* `o-3`: an imported Presence, pointing to some object in a different vat
|
|
64
|
+
* `o+0`: the root object, a plain Remotable
|
|
65
|
+
* `o+10`: another plain Remotable, exported from this vat or stored in virtualized data
|
|
66
|
+
* `o+d11/1`: a Representative for the first instance of single-facet virtual Kind `o+v11`
|
|
67
|
+
* `o+d11/2`: a Representative for the second instance of single-facet virtual Kind `o+v11`
|
|
68
|
+
* `o+d12/1:0`: the first facet of the first instance of a multi-facet durable Kind `o+d12`
|
|
69
|
+
* `o+d12/1:1`: the second facet of that same instance
|
|
70
|
+
* `o+d12/2:0`: the first facet of a different instance
|
|
71
|
+
* `o+d12/3:0`: the first facet of yet another different instance
|
|
72
|
+
|
|
73
|
+
Each instance of a virtual object stores state in a vatstore key indexed by the baseref. If `o+d12/1:0` and `o+d12/1:1` are the facet vrefs for a cohort whose baseref is `o+d12/1`, the cohort's shared state will be stored by the virtual object manager in `vom.o+d12/1` as a JSON-serialized record. The keys of this record are property names: if the Kind uses `state.prop1`, the record will have a key named `prop1`. For each property, the value is a `{ body: string, slots: any[] }` capdata record with string-valued slot items (and unlike the treatment of capdata in many other parts of the system, it is not serialized independently of the enclosing structure).
|
|
74
|
+
|
|
75
|
+
* `v6.vs.vom.o+d12/1` : `{"booleanProp":{"body":"true","slots":[]},"arrayProp":{"body":"[]","slots":[]}}`
|
|
76
|
+
|
|
77
|
+
In the refcounting portion of the vatstore (`vom.rc.${baseref}`), you will see baserefs:
|
|
78
|
+
|
|
79
|
+
* `v6.vs.vom.rc.o+10`: the count of virtualized references to plain Remotable `o+10` (held in RAM)
|
|
80
|
+
* `v6.vs.vom.rc.o+d12/1`: the count of references to any member of the cohort for the first instance of durable Kind `o+d12`
|
|
81
|
+
* This Kind might be single-facet or multi-facet.
|
|
82
|
+
* References to distinct facets of the same cohort are counted independently. For example, if one object references both the first and second facets (`o+d12/1:0` and `o+d12/1:1`), it accounts for two increments to this value.
|
|
83
|
+
|
|
84
|
+
In the export-status portion of the vatstore (`vom.es.${baseref}`), you will see baserefs, and any facets are tracked in the value, not the key:
|
|
85
|
+
|
|
86
|
+
* `v6.vs.vom.es.o+10` records the export status for plain Remotable `o+10`
|
|
87
|
+
* value `r`: the plain Remotable has been exported and is "reachable" by the kernel
|
|
88
|
+
* value `s`: the Remotable was exported, the kernel dropped it, and is still "recognizable" by the kernel ("s" for "see", refer to [Garbage Collection in SwingSet](../../SwingSet/docs/garbage-collection.md) for details)
|
|
89
|
+
* If the kernel can neither reach nor recognize the export, the vatstore key will be missing entirely.
|
|
90
|
+
* `v6.vs.vom.es.o+d12/1` records the export status for all facets of the durable object whose vref is `o+d12/1` (i.e., the first instance of durable Kind `o+d12`)
|
|
91
|
+
* If the Kind is single-facet, the value will be the same as for a plain Remotable: a single `r` or `s` character
|
|
92
|
+
* If the Kind is multi-facet, the value will be a string with one letter for each facet, in the same order as their Facet ID. `n` is used to indicate neither reachable nor recognizable. For example, a value of `rsnr` means there are four facets, the first (`o+d12/1:0`) and last (`o+d12/1:3`) are reachable, the second (`o+d12/1:1`) is recognizable, and the third (`o+d12/1:2`) is neither.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Durable Kinds
|
|
96
|
+
|
|
97
|
+
Virtual objects are held on disk, which makes them suitable for high-cardinality data that is likely too large for our vat workers' limited RAM budget: many objects, most of which are "idle" at any given time. However virtual objects do not survive a vat upgrade. For this, vats should define one or more "Durable Kinds" instead.
|
|
98
|
+
|
|
99
|
+
Durable Kinds are defined just like virtual Kinds, but they use a different constructor (`defineDurableKind` instead of `defineKind`), which requires a "handle" created by `makeKindHandle`. Durable virtual objects can only hold durable data in their `state`.
|
|
100
|
+
|
|
101
|
+
The KindHandle is a durable virtual object of a special internal Kind. This is the first Kind allocated, so usually it gets exportID 1 to use as a Kind ID, and the kind handles for virtual and durable Kinds get vrefs like `o+d1/${kindHandleID}`.
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Kind Metadata
|
|
105
|
+
|
|
106
|
+
For each virtual object kind that is defined, we store a metadata record for purposes of scanning directly through the defined kinds when a vat is stopped or upgraded. For durable kinds this record is stored in `vom.dkind.${kindID}.descriptor`; for non-durable kinds it is stored in `vom.vkind.${kindID}.descriptor`. Currently this metadata takes the form of a JSON-serialized record with the following properties:
|
|
107
|
+
* `kindID`, the kind ID as a numeric string (redundantly with the storage key)
|
|
108
|
+
* `tag`, the tag string as provided in the `defineKind` or `makeKindHandle` call
|
|
109
|
+
* `nextInstanceID`, an integer that is present only for durable kinds. It ensures that all versions share a single sequence of unique instance IDs. `nextInstanceID` is added to the metadata record upon allocation of the first instance of the kind and updated after each subsequent allocation.
|
|
110
|
+
* `unfaceted`, a property with boolean value `true` that is present only for single-facet durable kinds.
|
|
111
|
+
* `facets`, an array that is present only for multi-facet durable kinds. Its elements are the names of the kind's facets, in the same order as the assignment of facet indices within the cohort record.
|
|
112
|
+
* `stateShapeCapData`, a property with a `{ body, slots }` CapData record that decodes into `undefined`, or else into a CopyRecord [Pattern](https://www.npmjs.com/package/@endo/patterns) for constraining instance `state` records.
|
|
113
|
+
|
|
114
|
+
`unfaceted` and `facets` are required so that kind definitions in upgraded versions of the containing vat are maintained in a backwards compatible manner over time.
|
|
115
|
+
|
|
116
|
+
# Virtual/Durable Collections (aka Stores)
|
|
117
|
+
|
|
118
|
+
Liveslots provides vats with a handful of "virtual collection" types for storing high-cardinality data on disk rather than in RAM. Each instance of one of these types is described as a "[Store](../../store/docs/store-taxonomy.md)". They provide limited range queries and offer a single fixed sort index: numbers sort as usual, BigInts sort as usual but separate from numbers, strings sort lexicographically by UTF-8 encoding, and object references sort by insertion order.
|
|
119
|
+
|
|
120
|
+
Collections are created by functions on the `VatStore` global:
|
|
121
|
+
|
|
122
|
+
* `makeScalarBigMapStore`
|
|
123
|
+
* `makeScalarBigWeakMapStore`
|
|
124
|
+
* `makeScalarBigSetStore`
|
|
125
|
+
* `makeScalarBigWeakSetStore`
|
|
126
|
+
|
|
127
|
+
Each function accepts a boolean `durable` option, so there are currently 8 collection types.
|
|
128
|
+
|
|
129
|
+
Each collection type is assigned a Kind index, just like the user-defined Kinds. The 8 collection types are allocated before userspace gets a chance to call `defineKind` or `defineDurableKind`, so they claim earlier exportID numbers as described [above](#counters).
|
|
130
|
+
|
|
131
|
+
These index values are stored in `storeKindIDTable`, as a mapping from the collection type name ("scalarMapStore", "scalarDurableMapStore", "scalarWeakSetStore", etc.) to the integer of their ID. The current table assignments are:
|
|
132
|
+
|
|
133
|
+
* `v6.vs.storeKindIDTable` : `{"scalarMapStore":2,"scalarWeakMapStore":3,"scalarSetStore":4,"scalarWeakSetStore":5,"scalarDurableMapStore":6,"scalarDurableWeakMapStore":7,"scalarDurableSetStore":8,"scalarDurableWeakSetStore":9}`
|
|
134
|
+
|
|
135
|
+
which means `2` is the Kind ID for non-durable merely-virtual "scalarMapStore", instances of which will have vrefs like `o+v2/${collectionID}`.
|
|
136
|
+
|
|
137
|
+
Each new store, regardless of type, is allocated the next available collectionID [counter](#counters). This is an incrementing integer that starts at 1, and forms a numberspace independent of the exportID counter used by exported Remotables and Kind IDs. The same collectionID numberspace is shared by all collection types. So unlike virtual objects (where the instanceID in `o+v${kindID}/${instanceID}` is scoped to `o+v${kindID}`), for collections the collectionID in `o+v${collectionType}/${collectionID}` is global to the entire vat. No two stores will have the same collectionID, even if they are of different types.
|
|
138
|
+
|
|
139
|
+
The interpretation of a vref therefore varies based on whether the initial "type" portion before a slash (`o+v${exportID}` or `o+d${exportID}`) identifies a collection type or a virtual object kind:
|
|
140
|
+
|
|
141
|
+
* `o+d6/1`: `6` is a collection type in the storeKindIDTable ("scalarDurableMapStore"), so `o+d6/1` refers to the first collection (of any type) in the vat
|
|
142
|
+
* `o+d7/1`: `7` is also a collection type in the storeKindIDTable ("scalarDurableWeakMapStore"), so `o+d7/1` is guaranteed not to coexist with `o+d6/1`
|
|
143
|
+
* `o+d7/2`: refers to the second collection (of any type) in the vat, which has type `7` ("scalarDurableWeakMapStore")
|
|
144
|
+
* `o+v5/3`: refers to the third collection (of any type) in the vat, which has type `5` ("scalarWeakSetStore")
|
|
145
|
+
* `o+v5/4`: refers to the fourth collection (of any type) in the vat, also a scalarWeakSetStore
|
|
146
|
+
* `o+v11/1`: `11` is not a collection type in the storeKindIDTable, so is instead a kind and `o+v11/1` refers to the first instance of that kind
|
|
147
|
+
* `o+v11/2`: refers to the second instance of that kind
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Baggage
|
|
151
|
+
|
|
152
|
+
Most collections are created by userspace code, but to support vat upgrade, liveslots creates one special collection named "baggage". This is a `scalarDurableMapStore` that is passed into the third argument of `buildRootObject`.
|
|
153
|
+
|
|
154
|
+
This object needs to be pre-generated because the second (and subsequent) versions of the vat will use it to reach all other durable objects from their predecessors, so v2 can remember things that were stored by v1. The most significant values of "baggage" are the KindHandles for durable Kinds made by v1. V2 will need these to call `defineDurableKind` and re-attach behavior for each one. Each version must re-attach behavior for *all* durable Kinds created by its predecessors.
|
|
155
|
+
|
|
156
|
+
The above rules about kindID and collectionID allocation result in the baggage being associated with vref `o+d6/1`, indicating that it is a scalarDurableMapStore (`o+d6` is used for that collection type) and also that it is the first collection of any type allocated in the vat. This value is also stored under key `baggageID` (see `provideBaggage` in [collectionManager.js](./collectionManager.js)).
|
|
157
|
+
* `v6.vs.baggageID` : `o+d6/1`
|
|
158
|
+
|
|
159
|
+
If userspace version 1 starts `buildRootObject` by calling `makeScalarBigWeakSetStore()` and then three `makeScalarSetStore()`s, the collections are likely to be assigned `o+v5/2`, `o+v4/3`, `o+v4/4`, and `o+v4/5` respectively. Such collectionIDs start with `2` because `1` is claimed by baggage.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Collection Data Records
|
|
163
|
+
|
|
164
|
+
We examine a vat which performs the following at startup:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
const initFoo = arg => ({ prop1: arg });
|
|
168
|
+
const fooBehavior = {
|
|
169
|
+
getProp1: ({ state }) => state.prop1,
|
|
170
|
+
};
|
|
171
|
+
const makeFoo = VatData.defineKind('foo', initFoo, fooBehavior);
|
|
172
|
+
const foo = makeFoo(1);
|
|
173
|
+
const foo2 = makeFoo(2);
|
|
174
|
+
const c1 = VatData.makeScalarBigMapStore('mylabel');
|
|
175
|
+
c1.init('key1', foo);
|
|
176
|
+
c1.init('key2', foo);
|
|
177
|
+
c1.init('key3', foo);
|
|
178
|
+
c1.init('key4', foo2);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Each collection stores a number of metadata keys in the vatstore, all with a prefix of `vc.${collectionID}.|` (note that the collection *type* is not a part of the key, only the collection *index*). The currently defined metadata keys (copied from the record for the "mylabel" Kind stored in `c1`) are:
|
|
182
|
+
|
|
183
|
+
* `v6.vs.vc.2.|entryCount`: `4` (the size of the collection as a numeric string, incremented with each call to `c1.init` and decremented with each call to `c1.delete`)
|
|
184
|
+
* `v6.vs.vc.2.|nextOrdinal`: `1` (a numeric string counter used to allocate index values for Objects used as keys, see `generateOrdinal` in [collectionManager.js](./collectionManager.js))
|
|
185
|
+
* `v6.vs.vc.2.|schemata`: `{"label":"mylabel","body":"#[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}]","slots":[]}`
|
|
186
|
+
|
|
187
|
+
The `schemata` is a capdata serialization of the debugging label provided in the `make*Store` call that created the collection, plus the Matcher constraints recorded for the collection. These constraints can limit keys to be just strings, or numbers, etc. (see [Patterns](https://github.com/endojs/endo/tree/master/packages/patterns)). The schemata consists of an array in which the first element is a schema for the keys and the second is a separate schema for the values.
|
|
188
|
+
|
|
189
|
+
Each entry in the collection gets put into a single vatstore entry with a capdata-serialized value:
|
|
190
|
+
|
|
191
|
+
* `v6.vs.vc.2.skey1`: `{"body":"#\"$0.Alleged: foo\"","slots":["o+d9/1"]}`
|
|
192
|
+
* `v6.vs.vc.2.skey2`: `{"body":"#\"$0.Alleged: foo\"","slots":["o+d9/1"]}`
|
|
193
|
+
* `v6.vs.vc.2.skey3`: `{"body":"#\"$0.Alleged: foo\"","slots":["o+d9/1"]}`
|
|
194
|
+
* `v6.vs.vc.2.skey4`: `{"body":"#\"$0.Alleged: foo\"","slots":["o+d9/2"]}`
|
|
195
|
+
|
|
196
|
+
The key string for each entry (e.g. `skey1`) is formed by serializing the key object. Strings get a simple `s` prefix. Other objects use more complex encodings, designed to allow numbers (floats and BigInts, separately) to sort numerically despite the kvStore keys sorting lexicographically. See Endo [encodePassable.js](https://github.com/endojs/endo/blob/master/packages/marshal/src/encodePassable.js) for details. Object references involve an additional kvStore entry, to manage the mapping from Object to ordinal and back.
|
|
197
|
+
|
|
198
|
+
For weak stores, the collection manager also maintains inbound-reference database keys of the form `vom.ir.${vref}|${collectionID}`, where in this case `${vref}` is the vref of a virtual object, store, import, or remotable, and `${collectionID}` is the collectionID of a weak store in which the given vref is used as a key. This enables the collection manager to locate and remove collection entries whose keys are being garbage collected. Note that mere presence or absence of such a key in the database is significant but the value associated with it is not.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function makeVirtualObjectManager(syscall: any, vrm: import("./virtualReferences.js").VirtualReferenceManager, allocateExportID: () => number, getSlotForVal: (val: object) => string | undefined, requiredValForSlot: (slot: string) => object, registerValue: any, serialize: ToCapData<string>, unserialize: FromCapData<string>, assertAcceptableSyscallCapdataSize: any, liveSlotsOptions?: import("./types.js").LiveSlotsOptions, { WeakMap, WeakSet }?: {
|
|
2
|
+
WeakMap: typeof globalThis.WeakMap;
|
|
3
|
+
WeakSet: typeof globalThis.WeakSet;
|
|
4
|
+
}): {
|
|
5
|
+
initializeKindHandleKind: () => void;
|
|
6
|
+
defineKind: <P extends Array<any>, S, F>(tag: string, init: (...args: P) => S, facet: F, options?: DefineKindOptions<import("@agoric/swingset-liveslots").KindContext<S, F>>) => (...args: P) => import("@agoric/swingset-liveslots").KindFacet<F>;
|
|
7
|
+
defineKindMulti: <P extends Array<any>, S, B>(tag: string, init: (...args: P) => S, behavior: B, options?: DefineKindOptions<import("@agoric/swingset-liveslots").MultiKindContext<S, B>>) => (...args: P) => import("@agoric/swingset-liveslots").KindFacets<B>;
|
|
8
|
+
defineDurableKind: <P extends Array<any>, S, F>(kindHandle: DurableKindHandle, init: (...args: P) => S, facet: F, options?: DefineKindOptions<import("@agoric/swingset-liveslots").KindContext<S, F>>) => (...args: P) => import("@agoric/swingset-liveslots").KindFacet<F>;
|
|
9
|
+
defineDurableKindMulti: <P extends Array<any>, S, B>(kindHandle: DurableKindHandle, init: (...args: P) => S, behavior: B, options?: DefineKindOptions<import("@agoric/swingset-liveslots").MultiKindContext<S, B>>) => (...args: P) => import("@agoric/swingset-liveslots").KindFacets<B>;
|
|
10
|
+
makeKindHandle: (tag: string) => DurableKindHandle;
|
|
11
|
+
insistAllDurableKindsReconnected: () => void;
|
|
12
|
+
VirtualObjectAwareWeakMap: {
|
|
13
|
+
new (): {
|
|
14
|
+
has(key: any): any;
|
|
15
|
+
get(key: any): any;
|
|
16
|
+
set(key: any, value: any): /*elided*/ any;
|
|
17
|
+
delete(key: any): any;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
VirtualObjectAwareWeakSet: {
|
|
21
|
+
new (): {
|
|
22
|
+
has(value: any): any;
|
|
23
|
+
add(value: any): /*elided*/ any;
|
|
24
|
+
delete(value: any): any;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
flushStateCache: () => void;
|
|
28
|
+
getRetentionStats: () => {
|
|
29
|
+
definedDurableKinds: number;
|
|
30
|
+
nextInstanceIDs: number;
|
|
31
|
+
};
|
|
32
|
+
testHooks: {
|
|
33
|
+
countWeakKeysForCollection: (collection: any) => any;
|
|
34
|
+
definedDurableKinds: Set<any>;
|
|
35
|
+
nextInstanceIDs: Map<any, any>;
|
|
36
|
+
};
|
|
37
|
+
canBeDurable: (specimen: any) => boolean;
|
|
38
|
+
};
|
|
39
|
+
export type VirtualObjectManager = ReturnType<typeof makeVirtualObjectManager>;
|
|
40
|
+
import type { ToCapData } from '@endo/marshal';
|
|
41
|
+
import type { FromCapData } from '@endo/marshal';
|
|
42
|
+
import type { DefineKindOptions } from '@agoric/swingset-liveslots';
|
|
43
|
+
import type { DurableKindHandle } from '@agoric/swingset-liveslots';
|
|
44
|
+
//# sourceMappingURL=virtualObjectManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"virtualObjectManager.d.ts","sourceRoot":"","sources":["virtualObjectManager.js"],"names":[],"mappings":"AAiWO,kDApDI,GAAC,OACD,OAAO,wBAAwB,EAAE,uBAAuB,oBAExD,MAAM,MAAM,iBAEZ,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,sBAGnC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,iBACxB,GAAC,aAED,UAAU,MAAM,CAAC,eACjB,YAAY,MAAM,CAAC,sCACnB,GAAC,qBAED,OAAO,YAAY,EAAE,gBAAgB,yBACrC;IAAE,OAAO,EAAE,yBAAc,CAAC;IAAC,OAAO,EAAE,yBAAc,CAAA;CAAE;;iEA3KtB,GAAG,kHA+C5C,GA5CG;sEAIe,GAAE,0HAEb,GAAG;0FAK4C,GAAG,kHACU,GAAG;+FAKhC,GAAG,0HACgB,GAAG;0BA6lC/C,MAAM,KACJ,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqI/B;mCAEa,UAAU,CAAC,OAAO,wBAAwB,CAAC;+BAt3ChB,eAAe;iCAAf,eAAe;uCAFpB,4BAA4B;uCAD5B,4BAA4B"}
|