@metamask/ocap-kernel
Version:
OCap kernel core components
185 lines • 5.73 kB
JavaScript
import { define, is, object, string, array, record, union, tuple, literal, boolean, exactOptional, type } from "@metamask/superstruct";
import { UnsafeJsonStruct } from "@metamask/utils";
import { Fail } from "./utils/assert.mjs";
export const ROOT_OBJECT_VREF = 'o+0';
export const CapDataStruct = object({
body: string(),
slots: array(string()),
});
export const VatOneResolutionStruct = tuple([
string(),
boolean(),
CapDataStruct,
]);
export const MessageStruct = object({
methargs: CapDataStruct,
result: exactOptional(union([string(), literal(null)])),
});
/**
* Coerce a {@link SwingsetMessage} to our own JSON-RPC-compatible {@link Message}.
*
* @param message - The SwingsetMessage to coerce.
* @returns The coerced Message.
*/
export function coerceMessage(message) {
if (message.result === undefined) {
delete message.result;
}
return message;
}
/**
* Coerce a {@link VatSyscallObject} to a JSON-RPC-compatible {@link JsonVatSyscallObject}.
*
* @param vso - The VatSyscallObject to coerce.
* @returns The coerced VatSyscallObject.
*/
export function coerceVatSyscallObject(vso) {
if (vso[0] === 'send') {
return ['send', vso[1], coerceMessage(vso[2])];
}
return vso;
}
const RunQueueItemSendStruct = object({
type: literal('send'),
target: string(), // KRef
message: MessageStruct,
});
const RunQueueItemNotifyStruct = object({
type: literal('notify'),
vatId: string(),
kpid: string(),
});
const GCRunQueueTypeStruct = union([
literal('dropExports'),
literal('retireExports'),
literal('retireImports'),
]);
export const actionTypePriorities = [
'dropExport',
'retireExport',
'retireImport',
];
const RunQueueItemGCActionStruct = object({
type: GCRunQueueTypeStruct,
vatId: string(), // VatId
krefs: array(string()), // KRefs
});
const RunQueueItemBringOutYourDeadStruct = object({
type: literal('bringOutYourDead'),
vatId: string(),
});
export const RunQueueItemStruct = union([
RunQueueItemSendStruct,
RunQueueItemNotifyStruct,
RunQueueItemGCActionStruct,
RunQueueItemBringOutYourDeadStruct,
]);
/**
* Assert that a value is a valid message.
*
* @param value - The value to check.
* @throws if the value is not a valid message.
*/
export function insistMessage(value) {
is(value, MessageStruct) || Fail `not a valid message`;
}
export const isVatId = (value) => typeof value === 'string' &&
value.at(0) === 'v' &&
value.slice(1) === String(Number(value.slice(1)));
/**
* Assert that a value is a valid vat id.
*
* @param value - The value to check.
* @throws if the value is not a valid vat id.
*/
export function insistVatId(value) {
isVatId(value) || Fail `not a valid VatId`;
}
export const VatIdStruct = define('VatId', isVatId);
export const isSubclusterId = (value) => typeof value === 'string' &&
value.at(0) === 's' &&
value.slice(1) === String(Number(value.slice(1)));
export const SubclusterIdStruct = define('SubclusterId', isSubclusterId);
export const isVatMessageId = (value) => typeof value === 'string' &&
value.at(0) === 'm' &&
value.slice(1) === String(Number(value.slice(1)));
export const VatMessageIdStruct = define('VatMessageId', isVatMessageId);
const UserCodeSpecStruct = union([
object({
sourceSpec: string(),
}),
object({
bundleSpec: string(),
}),
object({
bundleName: string(),
}),
]);
export const VatConfigStruct = define('VatConfig', (value) => {
if (!value) {
return false;
}
const { creationOptions, parameters, ...specOnly } = value;
return (is(specOnly, UserCodeSpecStruct) &&
(!creationOptions || is(creationOptions, UnsafeJsonStruct)) &&
(!parameters || is(parameters, UnsafeJsonStruct)));
});
export const isVatConfig = (value) => is(value, VatConfigStruct);
export const ClusterConfigStruct = object({
bootstrap: string(),
forceReset: exactOptional(boolean()),
services: exactOptional(array(string())),
vats: record(string(), VatConfigStruct),
bundles: exactOptional(record(string(), VatConfigStruct)),
});
export const isClusterConfig = (value) => is(value, ClusterConfigStruct);
export const SubclusterStruct = object({
id: SubclusterIdStruct,
config: ClusterConfigStruct,
vats: array(VatIdStruct),
});
export const KernelStatusStruct = type({
subclusters: array(SubclusterStruct),
vats: array(object({
id: VatIdStruct,
config: VatConfigStruct,
subclusterId: SubclusterIdStruct,
})),
});
/**
* A mapping of GC action type to queue event type.
*/
export const queueTypeFromActionType = new Map([
// Note: From singular to plural
['dropExport', 'dropExports'],
['retireExport', 'retireExports'],
['retireImport', 'retireImports'],
]);
export const isGCActionType = (value) => actionTypePriorities.includes(value);
/**
* Assert that a value is a valid GC action type.
*
* @param value - The value to check.
* @throws if the value is not a valid GC action type.
*/
export function insistGCActionType(value) {
isGCActionType(value) || Fail `not a valid GCActionType ${value}`;
}
export const GCActionStruct = define('GCAction', (value) => {
if (typeof value !== 'string') {
return false;
}
const [vatId, actionType, kref] = value.split(' ');
if (!isVatId(vatId)) {
return false;
}
if (!isGCActionType(actionType)) {
return false;
}
if (typeof kref !== 'string' || !kref.startsWith('ko')) {
return false;
}
return true;
});
export const isGCAction = (value) => is(value, GCActionStruct);
//# sourceMappingURL=types.mjs.map