Skip to content

Commit 6405b36

Browse files
committed
feat(types): parameterizes CapTP slots
1 parent beac3d7 commit 6405b36

File tree

7 files changed

+49
-22
lines changed

7 files changed

+49
-22
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@endo/ocapn': minor
3+
---
4+
5+
Parameterize CapTP slot types and improve TypeScript 6 conformance across the OCapN client surface. Compile-time type changes only; no runtime behavior changes.

packages/ocapn/src/captp/pairwise.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import { makeRefCounter } from './refcount.js';
44

55
/**
66
* @typedef {import('@endo/eventual-send').Settler} Settler
7-
* @typedef {import('./types.js').Slot} Slot
87
* @typedef {import('./types.js').SlotType} SlotType
8+
*/
9+
/**
10+
* @template {SlotType} [T=SlotType]
11+
* @typedef {import('./types.js').Slot<T>} Slot
912
* @typedef {import('./finalize.js').FinalizingMap<Slot, object>} ExportTable
1013
* @typedef {import('./finalize.js').FinalizingMap<Slot, object>} ImportTable
1114
*
@@ -26,22 +29,23 @@ import { makeRefCounter } from './refcount.js';
2629
*/
2730

2831
/**
29-
* @param {SlotType} type
32+
* @template {SlotType} T
33+
* @param {T} type
3034
* @param {boolean} isLocal
3135
* @param {bigint} position
32-
* @returns {Slot}
36+
* @returns {Slot<T>}
3337
*/
3438
export const makeSlot = (type, isLocal, position) => {
35-
// @ts-expect-error - we're returning a branded type.
36-
return `${type}${isLocal ? '+' : '-'}${position}`;
39+
return /** @type {Slot<T>} */ (`${type}${isLocal ? '+' : '-'}${position}`);
3740
};
3841

3942
/**
40-
* @param {Slot} slot
41-
* @returns {{ type: SlotType, isLocal: boolean, position: bigint }}
43+
* @template {SlotType} T
44+
* @param {Slot<T>} slot
45+
* @returns {{ type: T, isLocal: boolean, position: bigint }}
4246
*/
4347
export const parseSlot = slot => {
44-
const type = slot[0];
48+
const type = /** @type {T} */ (slot[0]);
4549
if (type !== 'a' && type !== 'p' && type !== 'o') {
4650
throw new Error(`Invalid slot type: ${type}`);
4751
}

packages/ocapn/src/captp/types.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
export {};
22

33
/**
4-
* @typedef {string & { _brand: "captp-slot" }} Slot
54
* @typedef {'a' | 'p' | 'o'} SlotType
65
*/
6+
7+
/**
8+
* @template {SlotType} [T=SlotType]
9+
* @typedef {`${T}${'+' | '-'}${string}` & { _brand: "captp-slot" }} Slot
10+
* A slot is a branded string of the form (type)(+|-)(position),
11+
* where `+` means local (exported) and `-` means remote (imported).
12+
* The type parameter carries the slot-type letter so that callers which
13+
* build slots with a known literal (e.g. makeSlot('p', ...)) can recover
14+
* that information through parseSlot and table lookups.
15+
*/

packages/ocapn/src/client/ocapn.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ const makeBootstrapObject = (
670670
* @typedef {object} Ocapn
671671
* @property {((reason?: Error) => void)} abort
672672
* @property {((data: Uint8Array) => void)} dispatchMessageData
673-
* @property {() => object} getRemoteBootstrap
673+
* @property {() => any} getRemoteBootstrap
674674
* @property {ReferenceKit} referenceKit
675675
* @property {(message: any) => Uint8Array} writeOcapnMessage
676676
* @property {OcapnDebug} [_debug] - **EXPERIMENTAL**: Internal APIs for testing. Only present when `debugMode` is true.

packages/ocapn/test/captp/pairwise.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -916,10 +916,10 @@ test('makePairwiseTable - getSlotForValue returns undefined for primitives', t =
916916
const table = makeTestTable();
917917

918918
// Primitives can't be used as WeakMap keys, so should return undefined
919-
t.is(table.getSlotForValue(42), undefined);
920-
t.is(table.getSlotForValue('string'), undefined);
921-
t.is(table.getSlotForValue(null), undefined);
922-
t.is(table.getSlotForValue(undefined), undefined);
919+
t.is(table.getSlotForValue(/** @type {any} */ (42)), undefined);
920+
t.is(table.getSlotForValue(/** @type {any} */ ('string')), undefined);
921+
t.is(table.getSlotForValue(/** @type {any} */ (null)), undefined);
922+
t.is(table.getSlotForValue(/** @type {any} */ (undefined)), undefined);
923923
});
924924

925925
test('makePairwiseTable - refcount accumulates across multiple commit cycles', t => {

packages/ocapn/test/client.test.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,9 +1519,11 @@ test('local answer promise on B is not rejected on connection close', async t =>
15191519

15201520
// Debug: Check B's slots
15211521
const ocapnTableB = getOcapnDebug(ocapnB).ocapnTable;
1522-
const slotOnB = ocapnTableB.getSlotForValue(capturedLocalAnswerPromise);
1522+
const slotOnB = ocapnTableB.getSlotForValue(
1523+
/** @type {any} */ (capturedLocalAnswerPromise),
1524+
);
15231525
const answerPosition = ocapnTableB.getLocalAnswerToPosition(
1524-
capturedLocalAnswerPromise,
1526+
/** @type {any} */ (capturedLocalAnswerPromise),
15251527
);
15261528
console.log('B: slot for capturedLocalAnswerPromise:', slotOnB);
15271529
console.log('B: getLocalAnswerToPosition result:', answerPosition);
@@ -1668,7 +1670,7 @@ test('E() sends op:deliver with answer tracking', async t => {
16681670
const unsubscribe = getOcapnDebug(ocapnA).subscribeMessages(
16691671
(direction, message) => {
16701672
if (direction === 'send') {
1671-
sentMessages.push(message);
1673+
sentMessages.push(/** @type {{type: string}} */ (message));
16721674
}
16731675
},
16741676
);
@@ -1733,7 +1735,7 @@ test('E.sendOnly() sends op:deliver-only without answer tracking', async t => {
17331735
const unsubscribe = getOcapnDebug(ocapnA).subscribeMessages(
17341736
(direction, message) => {
17351737
if (direction === 'send') {
1736-
sentMessages.push(message);
1738+
sentMessages.push(/** @type {{type: string}} */ (message));
17371739
}
17381740
},
17391741
);
@@ -1804,7 +1806,7 @@ test('E.sendOnly() on function call sends op:deliver-only', async t => {
18041806
const unsubscribe = getOcapnDebug(ocapnA).subscribeMessages(
18051807
(direction, message) => {
18061808
if (direction === 'send') {
1807-
sentMessages.push(message);
1809+
sentMessages.push(/** @type {{type: string}} */ (message));
18081810
}
18091811
},
18101812
);
@@ -1878,7 +1880,7 @@ test('resolver callbacks use op:deliver-only', async t => {
18781880
const unsubscribe = getOcapnDebug(ocapnB).subscribeMessages(
18791881
(direction, message) => {
18801882
if (direction === 'send') {
1881-
messagesSentByB.push(message);
1883+
messagesSentByB.push(/** @type {{type: string}} */ (message));
18821884
}
18831885
},
18841886
);

packages/ocapn/test/codecs/passable.test.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,12 @@ const table = [
186186
name: 'tagged with reference (local object)',
187187
makeValue: testKit => makeTagged('myTag', testKit.makeLocalObject(100n)),
188188
makeExpectedValue: testKit =>
189-
makeTagged('myTag', testKit.referenceKit.provideRemoteObjectValue(100n)),
189+
makeTagged(
190+
'myTag',
191+
/** @type {any} */ (
192+
testKit.referenceKit.provideRemoteObjectValue(100n)
193+
),
194+
),
190195
},
191196
{
192197
name: 'tagged with reference (local promise)',
@@ -205,7 +210,9 @@ const table = [
205210
makeExpectedValue: testKit =>
206211
makeTagged(
207212
'listTag',
208-
harden([testKit.referenceKit.provideRemoteObjectValue(102n), 'hello']),
213+
/** @type {any} */ (
214+
harden([testKit.referenceKit.provideRemoteObjectValue(102n), 'hello'])
215+
),
209216
),
210217
},
211218
{

0 commit comments

Comments
 (0)