@agoric/swingset-vat 0.32.3-dev-2b7c650.0 → 0.32.3-dev-31ed402.0
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 +12 -12
- package/src/controller/controller.js +28 -3
- package/src/kernel/kernelQueue.js +1 -1
- package/src/lib/kmarshal.js +49 -31
- package/tools/bootstrap-relay.js +19 -44
- package/tools/passableEncoding.js +0 -81
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swingset-vat",
|
|
3
|
-
"version": "0.32.3-dev-
|
|
3
|
+
"version": "0.32.3-dev-31ed402.0+31ed402",
|
|
4
4
|
"description": "Vat/Container Launcher",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
"@types/yargs-parser": "^21.0.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@agoric/assert": "0.6.1-dev-
|
|
31
|
-
"@agoric/internal": "0.3.3-dev-
|
|
32
|
-
"@agoric/store": "0.9.3-dev-
|
|
33
|
-
"@agoric/swing-store": "0.9.2-dev-
|
|
34
|
-
"@agoric/swingset-liveslots": "0.10.3-dev-
|
|
35
|
-
"@agoric/swingset-xsnap-supervisor": "0.10.3-dev-
|
|
36
|
-
"@agoric/time": "0.3.3-dev-
|
|
37
|
-
"@agoric/vat-data": "0.5.3-dev-
|
|
38
|
-
"@agoric/xsnap": "0.14.3-dev-
|
|
39
|
-
"@agoric/xsnap-lockdown": "0.14.1-dev-
|
|
30
|
+
"@agoric/assert": "0.6.1-dev-31ed402.0+31ed402",
|
|
31
|
+
"@agoric/internal": "0.3.3-dev-31ed402.0+31ed402",
|
|
32
|
+
"@agoric/store": "0.9.3-dev-31ed402.0+31ed402",
|
|
33
|
+
"@agoric/swing-store": "0.9.2-dev-31ed402.0+31ed402",
|
|
34
|
+
"@agoric/swingset-liveslots": "0.10.3-dev-31ed402.0+31ed402",
|
|
35
|
+
"@agoric/swingset-xsnap-supervisor": "0.10.3-dev-31ed402.0+31ed402",
|
|
36
|
+
"@agoric/time": "0.3.3-dev-31ed402.0+31ed402",
|
|
37
|
+
"@agoric/vat-data": "0.5.3-dev-31ed402.0+31ed402",
|
|
38
|
+
"@agoric/xsnap": "0.14.3-dev-31ed402.0+31ed402",
|
|
39
|
+
"@agoric/xsnap-lockdown": "0.14.1-dev-31ed402.0+31ed402",
|
|
40
40
|
"@endo/base64": "^0.2.34",
|
|
41
41
|
"@endo/bundle-source": "^2.7.0",
|
|
42
42
|
"@endo/captp": "^3.1.4",
|
|
@@ -97,5 +97,5 @@
|
|
|
97
97
|
"publishConfig": {
|
|
98
98
|
"access": "public"
|
|
99
99
|
},
|
|
100
|
-
"gitHead": "
|
|
100
|
+
"gitHead": "31ed40207648bb76e2b37cb82dd068fb1837fdd8"
|
|
101
101
|
}
|
|
@@ -20,7 +20,7 @@ import engineGC from '@agoric/internal/src/lib-nodejs/engine-gc.js';
|
|
|
20
20
|
import { startSubprocessWorker } from '@agoric/internal/src/lib-nodejs/spawnSubprocessWorker.js';
|
|
21
21
|
import { waitUntilQuiescent } from '@agoric/internal/src/lib-nodejs/waitUntilQuiescent.js';
|
|
22
22
|
import { makeGcAndFinalize } from '@agoric/internal/src/lib-nodejs/gc-and-finalize.js';
|
|
23
|
-
import { kslot } from '../lib/kmarshal.js';
|
|
23
|
+
import { kslot, krefOf } from '../lib/kmarshal.js';
|
|
24
24
|
import { insistStorageAPI } from '../lib/storageAPI.js';
|
|
25
25
|
import {
|
|
26
26
|
buildKernelBundle,
|
|
@@ -360,6 +360,7 @@ export async function makeSwingsetController(
|
|
|
360
360
|
kernelStorage.emitCrankHashes();
|
|
361
361
|
return result;
|
|
362
362
|
},
|
|
363
|
+
|
|
363
364
|
vatNameToID(vatName) {
|
|
364
365
|
return kernel.vatNameToID(vatName);
|
|
365
366
|
},
|
|
@@ -371,13 +372,15 @@ export async function makeSwingsetController(
|
|
|
371
372
|
* Queue a method call into the named vat
|
|
372
373
|
*
|
|
373
374
|
* @param {string} vatName
|
|
374
|
-
* @param {string} method
|
|
375
|
+
* @param {string|symbol} method
|
|
375
376
|
* @param {unknown[]} args
|
|
376
377
|
* @param {ResolutionPolicy} resultPolicy
|
|
377
378
|
*/
|
|
378
379
|
queueToVatRoot(vatName, method, args = [], resultPolicy = 'ignore') {
|
|
379
380
|
const vatID = kernel.vatNameToID(vatName);
|
|
380
|
-
|
|
381
|
+
if (typeof method !== 'symbol') {
|
|
382
|
+
assert.typeof(method, 'string');
|
|
383
|
+
}
|
|
381
384
|
const kref = kernel.getRootObject(vatID);
|
|
382
385
|
const kpid = kernel.queueToKref(kref, method, args, resultPolicy);
|
|
383
386
|
if (kpid) {
|
|
@@ -387,6 +390,28 @@ export async function makeSwingsetController(
|
|
|
387
390
|
return kpid;
|
|
388
391
|
},
|
|
389
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Queue a method call to an object represented by a kmarshal token
|
|
395
|
+
*
|
|
396
|
+
* @param {any} target
|
|
397
|
+
* @param {string|symbol} method
|
|
398
|
+
* @param {unknown[]} args
|
|
399
|
+
* @param {ResolutionPolicy} resultPolicy
|
|
400
|
+
*/
|
|
401
|
+
queueToVatObject(target, method, args = [], resultPolicy = 'ignore') {
|
|
402
|
+
const targetKref = krefOf(target);
|
|
403
|
+
assert.typeof(targetKref, 'string');
|
|
404
|
+
if (typeof method !== 'symbol') {
|
|
405
|
+
assert.typeof(method, 'string');
|
|
406
|
+
}
|
|
407
|
+
const kpid = kernel.queueToKref(targetKref, method, args, resultPolicy);
|
|
408
|
+
if (kpid) {
|
|
409
|
+
kernel.kpRegisterInterest(kpid);
|
|
410
|
+
}
|
|
411
|
+
kernelStorage.emitCrankHashes();
|
|
412
|
+
return kpid;
|
|
413
|
+
},
|
|
414
|
+
|
|
390
415
|
upgradeStaticVat(vatName, shouldPauseFirst, bundleID, options = {}) {
|
|
391
416
|
const vatID = kernel.vatNameToID(vatName);
|
|
392
417
|
let pauseTarget = null;
|
|
@@ -84,7 +84,7 @@ export function makeKernelQueueHandler(tools) {
|
|
|
84
84
|
* by some other vat. This requires a kref as a target.
|
|
85
85
|
*
|
|
86
86
|
* @param {string} kref Target of the message
|
|
87
|
-
* @param {string} method The method name
|
|
87
|
+
* @param {string|symbol} method The method name
|
|
88
88
|
* @param {any[]} args The arguments array
|
|
89
89
|
* @param {ResolutionPolicy} [policy] How the kernel should handle an eventual
|
|
90
90
|
* resolution or rejection of the message's result promise. Should be
|
package/src/lib/kmarshal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Far, passStyleOf } from '@endo/far';
|
|
2
2
|
import { makeMarshal } from '@endo/marshal';
|
|
3
|
-
import { assert } from '@agoric/assert';
|
|
3
|
+
import { assert, Fail } from '@agoric/assert';
|
|
4
4
|
|
|
5
5
|
// Simple wrapper for serializing and unserializing marshalled values inside the
|
|
6
6
|
// kernel, where we don't actually want to use clists nor actually allocate real
|
|
@@ -9,10 +9,10 @@ import { assert } from '@agoric/assert';
|
|
|
9
9
|
// agnostic about the internal details of the serialization encoding.
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* @typedef {{getKref: () => string, iface: () => string}} KCap
|
|
12
|
+
* @typedef {{getKref: () => string, iface: () => string} | Promise} KCap
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const { toStringTag } = Symbol;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @param {string} kref
|
|
@@ -32,21 +32,26 @@ export const kslot = (kref, iface = 'undefined') => {
|
|
|
32
32
|
kref.startsWith('lp') ||
|
|
33
33
|
kref.startsWith('rp')
|
|
34
34
|
) {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
const p =
|
|
49
|
-
|
|
35
|
+
// Bizarro World hack for attaching a string property to a Promise, courtesy
|
|
36
|
+
// of MarkM. Even though the @@toStringTag property nominally *is* a
|
|
37
|
+
// string, some unfortunate stuff in our hardened JS safety checks blows up
|
|
38
|
+
// if it actually is. Eventually that will be fixed and we'll be able to
|
|
39
|
+
// use the @@toStringTag property directly, but for now we need to use this
|
|
40
|
+
// weird construct employing a sneaky getter function. Note that this is
|
|
41
|
+
// only necessary in the first place because smallcaps encodes promise
|
|
42
|
+
// references differently from remotable object references, and the current
|
|
43
|
+
// version of the smallcaps decoder annoyingly insists that if the encoded
|
|
44
|
+
// body string says a slot is a promise, then convertSlotToVal had better by
|
|
45
|
+
// damn return an actual Promise, even if, as in the intended use case here,
|
|
46
|
+
// we neither want nor need a promise nor are capable of making any use of
|
|
47
|
+
// the fact that it is one.
|
|
48
|
+
const p = Promise.resolve(`${kref} stand in`);
|
|
49
|
+
// Bizarro World makes eslint hallucinate
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
51
|
+
Object.defineProperty(p, toStringTag, {
|
|
52
|
+
get: reveal => (reveal ? kref : NaN),
|
|
53
|
+
enumerable: false,
|
|
54
|
+
});
|
|
50
55
|
return harden(p);
|
|
51
56
|
} else {
|
|
52
57
|
const o = Far(iface, {
|
|
@@ -62,20 +67,33 @@ export const kslot = (kref, iface = 'undefined') => {
|
|
|
62
67
|
* @returns {string}
|
|
63
68
|
*/
|
|
64
69
|
export const krefOf = obj => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
switch (passStyleOf(obj)) {
|
|
71
|
+
case 'promise': {
|
|
72
|
+
// Other half of Bizarro World hack for handling promises. Note the
|
|
73
|
+
// peculiar way the @@toStringTag getter is used.
|
|
74
|
+
const desc = Object.getOwnPropertyDescriptor(obj, toStringTag);
|
|
75
|
+
assert(desc !== undefined, 'promise lacks toStringTag getter');
|
|
76
|
+
const getter = desc.get;
|
|
77
|
+
assert.typeof(getter, 'function', 'toStringTag getter is not a function');
|
|
78
|
+
// @ts-expect-error violates the norm that getters have zero parameters
|
|
79
|
+
const kref = getter(true);
|
|
80
|
+
assert.typeof(kref, 'string');
|
|
81
|
+
return kref;
|
|
82
|
+
}
|
|
83
|
+
case 'remotable': {
|
|
84
|
+
const getKref = obj.getKref;
|
|
85
|
+
assert.typeof(getKref, 'function', 'object lacks getKref function');
|
|
86
|
+
return getKref();
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
// When krefOf() is called as part of kmarshal.serialize, marshal
|
|
90
|
+
// will only give it things that are 'remotable' (Promises and the
|
|
91
|
+
// Far objects created by kslot()). When krefOf() is called by
|
|
92
|
+
// kernel code (as part of extractSingleSlot() or the vat-comms
|
|
93
|
+
// equivalent), it ought to throw if 'obj' is not one of the
|
|
94
|
+
// objects created by our kslot().
|
|
95
|
+
return Fail`krefOf requires a promise or remotable`;
|
|
68
96
|
}
|
|
69
|
-
// When krefOf() is called as part of kmarshal.serialize, marshal
|
|
70
|
-
// will only give it things that are 'remotable' (Promises and the
|
|
71
|
-
// Far objects created by kslot()). When krefOf() is called by
|
|
72
|
-
// kernel code (as part of extractSingleSlot() or the vat-comms
|
|
73
|
-
// equivalent), it ought to throw if 'obj' is not one of the Far
|
|
74
|
-
// objects created by our kslot().
|
|
75
|
-
assert.equal(passStyleOf(obj), 'remotable', obj);
|
|
76
|
-
const getKref = obj.getKref;
|
|
77
|
-
assert.typeof(getKref, 'function', 'object lacking getKref function');
|
|
78
|
-
return getKref();
|
|
79
97
|
};
|
|
80
98
|
|
|
81
99
|
const kmarshal = makeMarshal(krefOf, kslot, {
|
package/tools/bootstrap-relay.js
CHANGED
|
@@ -2,7 +2,6 @@ import { assert } from '@agoric/assert';
|
|
|
2
2
|
import { objectMap } from '@agoric/internal';
|
|
3
3
|
import { Far, E } from '@endo/far';
|
|
4
4
|
import { buildManualTimer } from './manual-timer.js';
|
|
5
|
-
import { makePassableEncoding } from './passableEncoding.js';
|
|
6
5
|
|
|
7
6
|
const { Fail, quote: q } = assert;
|
|
8
7
|
|
|
@@ -13,8 +12,6 @@ export const buildRootObject = () => {
|
|
|
13
12
|
const devicesByName = new Map();
|
|
14
13
|
const callLogsByRemotable = new Map();
|
|
15
14
|
|
|
16
|
-
const { encodePassable, decodePassable } = makePassableEncoding();
|
|
17
|
-
|
|
18
15
|
return Far('root', {
|
|
19
16
|
bootstrap: async (vats, devices) => {
|
|
20
17
|
vatAdmin = await E(vats.vatAdmin).createVatAdminService(devices.vatAdmin);
|
|
@@ -28,17 +25,16 @@ export const buildRootObject = () => {
|
|
|
28
25
|
}
|
|
29
26
|
},
|
|
30
27
|
|
|
31
|
-
getDevice: async deviceName =>
|
|
32
|
-
encodePassable(devicesByName.get(deviceName)),
|
|
28
|
+
getDevice: async deviceName => devicesByName.get(deviceName),
|
|
33
29
|
|
|
34
|
-
getVatAdmin: async () =>
|
|
30
|
+
getVatAdmin: async () => vatAdmin,
|
|
35
31
|
|
|
36
|
-
getTimer: async () =>
|
|
32
|
+
getTimer: async () => timer,
|
|
37
33
|
|
|
38
|
-
getVatRoot: async
|
|
39
|
-
const vat = vatData.get(
|
|
34
|
+
getVatRoot: async vatName => {
|
|
35
|
+
const vat = vatData.get(vatName) || Fail`unknown vat name: ${q(vatName)}`;
|
|
40
36
|
const { root } = vat;
|
|
41
|
-
return
|
|
37
|
+
return root;
|
|
42
38
|
},
|
|
43
39
|
|
|
44
40
|
createVat: async ({ name, bundleCapName, vatParameters = {} }) => {
|
|
@@ -46,7 +42,7 @@ export const buildRootObject = () => {
|
|
|
46
42
|
const options = { vatParameters };
|
|
47
43
|
const { adminNode, root } = await E(vatAdmin).createVat(bcap, options);
|
|
48
44
|
vatData.set(name, { adminNode, root });
|
|
49
|
-
return
|
|
45
|
+
return root;
|
|
50
46
|
},
|
|
51
47
|
|
|
52
48
|
upgradeVat: async ({ name, bundleCapName, vatParameters = {} }) => {
|
|
@@ -60,14 +56,11 @@ export const buildRootObject = () => {
|
|
|
60
56
|
|
|
61
57
|
/**
|
|
62
58
|
* Derives a remotable from an object by mapping each object property into a method that returns the value.
|
|
63
|
-
* This function is intended to be called from testing code outside of the vat environment,
|
|
64
|
-
* but methods of the returned remotable are intended to be called from *inside* that environment
|
|
65
|
-
* and therefore do not perform argument/response translation.
|
|
66
59
|
*
|
|
67
60
|
* @param {string} label
|
|
68
|
-
* @param {Record<string, any>}
|
|
61
|
+
* @param {Record<string, any>} returnValues
|
|
69
62
|
*/
|
|
70
|
-
makeRemotable: (label,
|
|
63
|
+
makeRemotable: (label, returnValues) => {
|
|
71
64
|
const callLogs = [];
|
|
72
65
|
const makeGetterFunction = (value, name) => {
|
|
73
66
|
const getValue = (...args) => {
|
|
@@ -76,51 +69,33 @@ export const buildRootObject = () => {
|
|
|
76
69
|
};
|
|
77
70
|
return getValue;
|
|
78
71
|
};
|
|
79
|
-
const methodReturnValues = decodePassable(encodedMethodReturnValues);
|
|
80
72
|
// `objectMap` hardens its result, but...
|
|
81
|
-
const methods = objectMap(
|
|
73
|
+
const methods = objectMap(returnValues, makeGetterFunction);
|
|
82
74
|
// ... `Far` requires its methods argument not to be hardened.
|
|
83
|
-
const
|
|
84
|
-
const remotable = Far(label, unhardenedMethods);
|
|
75
|
+
const remotable = Far(label, { ...methods });
|
|
85
76
|
callLogsByRemotable.set(remotable, callLogs);
|
|
86
|
-
return
|
|
77
|
+
return remotable;
|
|
87
78
|
},
|
|
88
79
|
|
|
89
80
|
/**
|
|
90
81
|
* Returns a copy of a remotable's logs.
|
|
91
82
|
*
|
|
92
|
-
* @param {object}
|
|
83
|
+
* @param {object} remotable
|
|
93
84
|
*/
|
|
94
|
-
getLogForRemotable:
|
|
95
|
-
const
|
|
96
|
-
const decodedLogs =
|
|
85
|
+
getLogForRemotable: remotable => {
|
|
86
|
+
const logs =
|
|
97
87
|
callLogsByRemotable.get(remotable) ||
|
|
98
88
|
Fail`logs not found for ${q(remotable)}`;
|
|
99
89
|
// Return a copy so that the original remains mutable.
|
|
100
|
-
return
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
messageVat: async ({ name, methodName, args = [] }) => {
|
|
104
|
-
const vat = vatData.get(name) || Fail`unknown vat name: ${q(name)}`;
|
|
105
|
-
const { root } = vat;
|
|
106
|
-
const decodedArgs = args.map(decodePassable);
|
|
107
|
-
const result = await E(root)[methodName](...decodedArgs);
|
|
108
|
-
return encodePassable(result);
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
messageVatObject: async ({ presence, methodName, args = [] }) => {
|
|
112
|
-
const object = decodePassable(presence);
|
|
113
|
-
const decodedArgs = args.map(decodePassable);
|
|
114
|
-
const result = await E(object)[methodName](...decodedArgs);
|
|
115
|
-
return encodePassable(result);
|
|
90
|
+
return harden([...logs]);
|
|
116
91
|
},
|
|
117
92
|
|
|
118
|
-
awaitVatObject: async (
|
|
119
|
-
let value = await
|
|
93
|
+
awaitVatObject: async (presence, path = []) => {
|
|
94
|
+
let value = await presence;
|
|
120
95
|
for (const key of path) {
|
|
121
96
|
value = await value[key];
|
|
122
97
|
}
|
|
123
|
-
return
|
|
98
|
+
return value;
|
|
124
99
|
},
|
|
125
100
|
});
|
|
126
101
|
};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { assert } from '@agoric/assert';
|
|
2
|
-
import { E } from '@endo/far';
|
|
3
|
-
import { isObject, makeMarshal } from '@endo/marshal';
|
|
4
|
-
|
|
5
|
-
const { Fail, quote: q } = assert;
|
|
6
|
-
|
|
7
|
-
const noop = () => {};
|
|
8
|
-
|
|
9
|
-
// TODO define these somewhere more accessible. https://github.com/endojs/endo/issues/1488
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {import('@endo/eventual-send').FarRef<unknown>} PassByRef
|
|
12
|
-
* Gets transformed by a marshaller encoding.
|
|
13
|
-
* As opposed to pass-by-copy
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
export const makePassableEncoding = () => {
|
|
17
|
-
// Represent all data as passable by replacing non-passable values
|
|
18
|
-
// with special-prefix registered symbols.
|
|
19
|
-
/** @type {Map<symbol, PassByRef>} */
|
|
20
|
-
const replaced = new Map();
|
|
21
|
-
/** @type {Map<PassByRef, symbol>} */
|
|
22
|
-
const replacements = new Map(); // inverse of 'replaced'
|
|
23
|
-
|
|
24
|
-
// This is testing code, so we don't enforce absence of this prefix
|
|
25
|
-
// from manually created symbols.
|
|
26
|
-
const replacementPrefix = 'replaced:';
|
|
27
|
-
const provideReplacement = value => {
|
|
28
|
-
const extant = replacements.get(value);
|
|
29
|
-
if (extant) {
|
|
30
|
-
return extant;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const replacement = Symbol.for(`${replacementPrefix}${replaced.size}`);
|
|
34
|
-
replacements.set(value, replacement);
|
|
35
|
-
replaced.set(replacement, value);
|
|
36
|
-
|
|
37
|
-
// Suppress unhandled promise rejection warnings.
|
|
38
|
-
void E.when(value, noop, noop);
|
|
39
|
-
|
|
40
|
-
return replacement;
|
|
41
|
-
};
|
|
42
|
-
const { serialize: encodeReplacements } = makeMarshal(
|
|
43
|
-
provideReplacement,
|
|
44
|
-
undefined,
|
|
45
|
-
{
|
|
46
|
-
marshalSaveError: () => {},
|
|
47
|
-
serializeBodyFormat: 'capdata',
|
|
48
|
-
},
|
|
49
|
-
);
|
|
50
|
-
const { unserialize: decodeReplacements } = makeMarshal(
|
|
51
|
-
undefined,
|
|
52
|
-
undefined,
|
|
53
|
-
{
|
|
54
|
-
serializeBodyFormat: 'capdata',
|
|
55
|
-
},
|
|
56
|
-
);
|
|
57
|
-
const encodePassable = value => decodeReplacements(encodeReplacements(value));
|
|
58
|
-
const decodePassable = arg => {
|
|
59
|
-
// Recursively replace our symbols with their non-passable source data.
|
|
60
|
-
if (Array.isArray(arg)) {
|
|
61
|
-
return arg.map(decodePassable);
|
|
62
|
-
} else if (isObject(arg)) {
|
|
63
|
-
const entries = Object.entries(arg);
|
|
64
|
-
const decodedEntries = entries.map(([key, value]) => [
|
|
65
|
-
key,
|
|
66
|
-
decodePassable(value),
|
|
67
|
-
]);
|
|
68
|
-
return Object.fromEntries(decodedEntries);
|
|
69
|
-
} else if (
|
|
70
|
-
typeof arg !== 'symbol' ||
|
|
71
|
-
!Symbol.keyFor(arg) ||
|
|
72
|
-
!arg.description?.startsWith(replacementPrefix)
|
|
73
|
-
) {
|
|
74
|
-
return arg;
|
|
75
|
-
}
|
|
76
|
-
const value =
|
|
77
|
-
replaced.get(arg) || Fail`no value for replacement: ${q(arg)}`;
|
|
78
|
-
return value;
|
|
79
|
-
};
|
|
80
|
-
return { encodePassable, decodePassable };
|
|
81
|
-
};
|