-
Notifications
You must be signed in to change notification settings - Fork 6
feat(kernel): Differentiate cluster, kernel and vat command types. #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
1d5fd26 to
0859c7b
Compare
|
|
||
| import { kernelTestCommandKit } from './kernel-test-command.js'; | ||
|
|
||
| export const messageKit = kernelTestCommandKit; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, the kernel commands are purely test commands. In a future PR under #123, I will add an InitKernel command here.
As discussed here, I don't believe that this is overzealous. The resulting "unions" only end up erasing the more specific types, which can be misleading. Given: type A = string;
type B = number;
// Elsewhere
type X = unknown;
// In a third location:
type MyUnion = A | B | X;
Edit: Another reason to keep the rule: re-enabling it on this PR doesn't seem to cause any lint errors. |
rekmarks
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although we could probably live with it if we had to due to tree-shaking, we should avoid entraining lodash if possible, and for the uses in this PR, it is definitely possible. We can discuss whether to upstream any desired utilities to @metamask/utils (or our own utils package), but IMO typeof x === 'string' and .toUpperCase() are good enough.
packages/kernel/src/types.ts
Outdated
| import type { StreamEnvelopeReply, StreamEnvelope } from './stream-envelope.js'; | ||
|
|
||
| export type MessageId = string; | ||
| export type VatId = `v${Capitalize<string>}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than relying on capitalizing the first letter after the v, we could just add a "special" character to form e.g. v:, v_, v# etc. We shouldn't use -, because some UUID schemes we may use that character.
However, that being said, I just realized that we discussed only using incrementing IDs anyway, in which case it would be completely feasible to do something like:
| export type VatId = `v${Capitalize<string>}`; | |
| export type VatId = `v${number}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like incremental.
type VatId = `v${number}`;
type VatMessageId = `${VatId}:${number}`;6af86d7 to
a0bd613
Compare
I understand your concern that in this case the union renders pointless the types for But besides that, the rule incorrectly asserts that the resulting types are Observed Lint Error
Explanation (
|
| readonly #messageCounter: () => number; | ||
|
|
||
| readonly unresolvedMessages: UnresolvedMessages = new Map(); | ||
| readonly unresolvedMessages: Map<VatMessageId, PromiseCallbacks> = new Map(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is made to avoid a cyclic dependency between types.ts and messages/vat-message.ts.
a0bd613 to
420a602
Compare
@grypez I completely agree with this statement. My point is not about the behavior of TypeScript in this case, which I have no qualms with, but the legibility of our code. Specifically: when a union causes type specificity to be lost, we should just replace the union with the resulting type. This is especially true in cases where the result is For your specific example, we can get around the problem via the type KernelWorkerCommand = Extract<
KernelCommand,
{ method: typeof KernelCommandMethod.KVSet | typeof KernelCommandMethod.KVGet }
>;As you will see, Does this make sense? |
That looks how I want it to look. I'll prefer these utilities going forward. |
821c572 to
cca8901
Compare
cca8901 to
d146904
Compare
sirtimid
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm approving but please wait for @rekmarks approval as well.
Also some types like in ./src/packages/kernel/src/messages/kernel.ts rely on a "test" temporary file (is it temporary?). Should/do we have an issue to fix this?
This may be handled differently under #120; these |
packages/extension/src/offscreen.ts
Outdated
| // @ts-expect-error TODO: resolve method types. | ||
| await replyToBackground(method, result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm investigating this locally, and it appears that KernelCommandReplyFunction<Promise<void>> evaluates to:
(alias) type KernelCommandReplyFunction<Return> = ((method: "evaluate", params: string) => Return) & ((method: "ping", params: "pong") => Return) & ((method: "capTpCall", params: string) => Return) & ((method: "kVSet", params: string) => Return) & ((method: "kVGet", params: string) => Return)Note the intersection (&) joiners between the specific function types. It seems impossible for any value to simultaneously satisfy an intersection between distinct string literals. I'm worried that we have to throw in this @ts-expect-error here seems to signal a fundamental error with the message kit types.
Now, if we remove the usage of UnionToIntersection in order to replace the intersection type with a union (|) type, we are still in trouble because the method param of replyToBackground() becomes never instead. This is surprising to me and I can't immediately explain why this is happening.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's the string literal type "pong" in the result for ping. If we refactor to an object union type, it all works without casts in this file:
type KernelCommandReply =
| { method: "evaluate"; params: string }
| { method: "ping"; params: string }
| { method: "capTpCall"; params: string }
| { method: "kVSet"; params: string }
| { method: "kVGet"; params: string };
const replyToBackground = async (reply: KernelCommandReply) => {
await backgroundStreams.writer.next({
method: reply.method,
params: reply.params,
});
};There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot get the original KernelCommandReplyFunction, ultimately derived from MessageFunction, to work, however. Not even if I remove UnionToIntersection in message-kit.ts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per our out of band chat, we should probably just use a typed message object as the argument to the reply function. It is the simplest implementation that definitely works.
rekmarks
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I approve of this, with the caveat that I'm uncertain whether we are getting our money's worth from the message kit utility. For example:
- most of the
*CommandFunctionand*CommandReplyFunctiontypes are not used - it's possible that we could achieve the same degree of type safety with less complex types (less generics, less dependencies between types)
We should examine whether we can make any such simplifications before proceeding #131.

Changes
@ocap/extensionVatIdfrom@ocap/kernelinstead of redefining it inshared.ts.@ocap/kernelcommand.tsintomessages/<actor>.tsandmessages/<actor>-test.tsfiles for cluster, kernel and vat actors.message-kit.tsfile to denoise the command schema declarations in the above.messages/captp.tsmessages.tsto promote legibility ofindex.ts.@ocap/utilsGuardTypetype is relocated from@ocap/extensionand renamedExtractGuardType.Note to reviewers
The
message-kit.tsfile and the precise interface it introduces are likely to be replaced by@metamask/superstructin the near future. Since the behavior is compile-time checked, reviewers are encouraged to consider the resulting declarations ofmessages/kernel-test.ts,messages/vat.ts, etc. as a preview of the intended effect and may be forgiven for reviewing the type transformations inmessage-kit.tsandutils.tswith reserved interest.Footnotes
The exact pattern is not important to me. I found that when I made this change it revealed some discrepancies in the types and their usage that had snuck through due to these types being merely aliases of string. ↩