Skip to content
File

Blob: types/test/types/rpc.ts

typescript797 lines
1// Copyright (c) 2023 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5import {
6 cache,
7 DurableObject,
8 RpcStub,
9 RpcTarget,
10 WorkerEntrypoint,
11} from 'cloudflare:workers';
12import { expectTypeOf } from 'expect-type';
13 
14// Check `cache` export from `cloudflare:workers` has the expected type.
15expectTypeOf(cache).toEqualTypeOf<CacheContext>();
16expectTypeOf(cache.purge).toEqualTypeOf<
17 (options: CachePurgeOptions) => Promise<CachePurgeResult>
18>();
19 
20type TestType = {
21 fieldString: string;
22 fieldCallback: (p: string) => number;
23 fieldBasicMap: Map<string, number>;
24 fieldComplexMap: Map<
25 string,
26 {
27 fieldString: string;
28 fieldCallback: (p: string) => number;
29 }
30 >;
31 fieldSet: Set<string>;
32 fieldSubLevel: {
33 fieldString: string;
34 fieldCallback: (p: string) => number;
35 };
36};
37 
38interface ABasicInterface {
39 fieldString: string;
40 fieldCallback: (p: string) => number;
41}
42 
43interface TestInterface extends ABasicInterface {
44 fieldBasicMap: Map<string, number>;
45 fieldComplexMap: Map<string, ABasicInterface>;
46 fieldSet: Set<string>;
47 fieldSubLevelInline: {
48 fieldString: string;
49 fieldCallback: (p: string) => number;
50 };
51 fieldSubLevelInterface: ABasicInterface;
52}
53 
54interface NonSerializableInterface {
55 field: ReadableStream<string>;
56}
57 
58class TestCounter extends RpcTarget {
59 constructor(private val = 0) {
60 super();
61 }
62 get value() {
63 return this.val;
64 }
65 increment(by = 1) {
66 return (this.val += by);
67 }
68 copy() {
69 return new TestCounter(this.val);
70 }
71 
72 [Symbol.dispose]() {
73 console.log('Disposing');
74 }
75 
76 // Check can't use custom `dup()` method
77 dup(x: number) {
78 return x;
79 }
80}
81 
82const symbolMethod = Symbol('symbolMethod');
83 
84type Props = {myProp: number};
85 
86class TestEntrypoint extends WorkerEntrypoint<Env, Props> {
87 constructor(ctx: ExecutionContext<Props>, env: Env) {
88 super(ctx, env);
89 }
90 
91 fetch(request: Request) {
92 return new Response(request.url);
93 }
94 async tail(_events: TraceItem[]) {}
95 trace(_events: TraceItem[]) {}
96 async scheduled(_controller: ScheduledController) {}
97 queue(_batch: MessageBatch<number>) {}
98 async test(_controller: TestController) {}
99 
100 private privateInstanceProperty = 0;
101 private get privateProperty() {
102 expectTypeOf(this.ctx).toEqualTypeOf<ExecutionContext<Props>>();
103 expectTypeOf(this.env).toEqualTypeOf<Env>();
104 
105 return 1;
106 }
107 
108 instanceProperty = '2';
109 get property(): number {
110 return 3;
111 }
112 get asyncProperty() {
113 return Promise.resolve(true as const);
114 }
115 
116 private privateMethod() {
117 return 4;
118 }
119 [symbolMethod]() {
120 return 5;
121 }
122 method() {
123 return null;
124 }
125 async asyncMethod() {
126 return true;
127 }
128 voidMethod(_x?: number) {}
129 async asyncVoidMethod() {}
130 
131 functionMethod() {
132 return (x: number) => x;
133 }
134 async asyncFunctionMethod() {
135 return (x: number) => x;
136 }
137 async asyncAsyncFunctionMethod() {
138 return async (x: number) => x;
139 }
140 
141 functionWithExtrasMethod() {
142 const fn = (x: number) => x;
143 fn.y = 'z';
144 return fn;
145 }
146 
147 async stubMethod(
148 callback: RpcStub<(x: number) => number>,
149 counter: RpcStub<TestCounter>,
150 _map: Map<RpcStub<TestCounter>, RpcStub<TestCounter>>,
151 _set: Set<RpcStub<TestCounter>>,
152 _array: Array<RpcStub<TestCounter>>,
153 _readonlyArray: ReadonlyArray<RpcStub<TestCounter>>,
154 _object: { a: { b: RpcStub<TestCounter> } }
155 ) {
156 expectTypeOf(callback(1)).toEqualTypeOf<Promise<number>>(); // (always async)
157 expectTypeOf(counter.value).toEqualTypeOf<Promise<number>>(); // (always async)
158 const result = await callback(2);
159 return result * 3;
160 }
161 
162 get objectProperty() {
163 return {
164 w: 1,
165 get x() {
166 return 2;
167 },
168 y() {
169 return 3;
170 },
171 async z(_a: boolean) {
172 return 4;
173 },
174 };
175 }
176 
177 get everySerializable() {
178 return {
179 undefined: undefined,
180 null: null,
181 boolean: false,
182 number: 42,
183 bigint: 1_000_000n,
184 string: 'hello',
185 Int8Array: new Int8Array(),
186 Uint8Array: new Uint8Array(),
187 Uint8ClampedArray: new Uint8ClampedArray(),
188 Int16Array: new Int16Array(),
189 Uint16Array: new Uint16Array(),
190 Int32Array: new Int32Array(),
191 Uint32Array: new Uint32Array(),
192 Float32Array: new Float32Array(),
193 Float64Array: new Float64Array(),
194 BigInt64Array: new BigInt64Array(),
195 BigUint64Array: new BigUint64Array(),
196 ArrayBuffer: new ArrayBuffer(4),
197 DataView: new DataView(new ArrayBuffer(4)),
198 Date: new Date(),
199 Error: new Error(),
200 EvalError: new EvalError(),
201 RangeError: new RangeError(),
202 ReferenceError: new ReferenceError(),
203 SyntaxError: new SyntaxError(),
204 TypeError: new TypeError(),
205 URIError: new URIError(),
206 RegExp: /abc/,
207 Map: new Map([['a', 1]]),
208 Set: new Set(['a']),
209 Array: [1, 2, 3],
210 ReadonlyArray: [4, 5, 6] as const,
211 Object: { a: { b: 1 } },
212 ReadableStream: new ReadableStream<Uint8Array>(),
213 WritableStream: new WritableStream<Uint8Array>(),
214 Request: new Request('https://example.com'),
215 Response: new Response(),
216 Headers: new Headers(),
217 Stub: new RpcStub(() => {}),
218 };
219 }
220 get everyCompositeSerializable() {
221 return {
222 Map: new Map([[new TestCounter(), new TestCounter()]]),
223 Set: new Set([new TestCounter()]),
224 Array: [new TestCounter()],
225 ReadonlyArray: [new TestCounter()] as const,
226 Object: { a: { b: new TestCounter() } },
227 };
228 }
229 
230 methodReturnsTypeObject(): TestType {
231 return {
232 fieldString: 'a',
233 fieldCallback: (p: string) => 1,
234 fieldBasicMap: new Map([['b', 2]]),
235 fieldComplexMap: new Map([
236 [
237 'c',
238 {
239 fieldString: 'd',
240 fieldCallback: (p: string) => 3,
241 },
242 ],
243 ]),
244 fieldSet: new Set(['e']),
245 fieldSubLevel: {
246 fieldString: 'f',
247 fieldCallback: (p: string) => 4,
248 },
249 };
250 }
251 methodReturnsInterfaceObject(): TestInterface {
252 return {
253 fieldString: 'a',
254 fieldCallback: (p: string) => 1,
255 fieldBasicMap: new Map([['b', 2]]),
256 fieldComplexMap: new Map([
257 [
258 'c',
259 {
260 fieldString: 'd',
261 fieldCallback: (p: string) => 3,
262 },
263 ],
264 ]),
265 fieldSet: new Set(['e']),
266 fieldSubLevelInline: {
267 fieldString: 'f',
268 fieldCallback: (p: string) => 4,
269 },
270 fieldSubLevelInterface: {
271 fieldString: 'e',
272 fieldCallback: (p: string) => 5,
273 },
274 };
275 }
276 
277 nonSerializable1() {
278 return new ReadableStream<string>();
279 }
280 nonSerializable2() {
281 return { field: new ReadableStream<string>() };
282 }
283 nonSerializable3(): NonSerializableInterface {
284 return { field: new ReadableStream<string>() };
285 }
286 
287 [Symbol.dispose]() {
288 console.log('Disposing');
289 }
290}
291 
292class TestObject extends DurableObject {
293 async fetch(request: Request) {
294 return new Response(request.url);
295 }
296 async alarm() {}
297 
298 complexTypes() {
299 return {
300 undefined: undefined,
301 void: void 0,
302 null: null,
303 boolean: true,
304 number: 1,
305 bigint: BigInt(4),
306 string: 'string',
307 ArrayBuffer: new ArrayBuffer(0),
308 DataView: new DataView(new ArrayBuffer(0)),
309 Date: new Date(),
310 Error: new Error(),
311 RegExp: new RegExp(''),
312 ReadableStream: new ReadableStream(),
313 WritableStream: new WritableStream(),
314 Request: new Request('https://example.com'),
315 Response: new Response(),
316 Headers: new Headers(),
317 nested: {
318 undefined: undefined,
319 void: void 0,
320 null: null,
321 boolean: true,
322 number: 1,
323 bigint: BigInt(4),
324 string: 'string',
325 ArrayBuffer: new ArrayBuffer(0),
326 DataView: new DataView(new ArrayBuffer(0)),
327 Date: new Date(),
328 Error: new Error(),
329 RegExp: new RegExp(''),
330 ReadableStream: new ReadableStream(),
331 WritableStream: new WritableStream(),
332 Request: new Request('https://example.com'),
333 Response: new Response(),
334 Headers: new Headers(),
335 },
336 };
337 }
338 
339 webSocketMessage(_ws: WebSocket, _message: string | ArrayBuffer) {}
340 async webSocketClose(
341 _ws: WebSocket,
342 _code: number,
343 _reason: string,
344 _wasClean: boolean
345 ) {}
346 webSocketError(_ws: WebSocket, _error: unknown) {}
347 
348 targetMethod() {
349 return new TestCounter();
350 }
351 async asyncTargetMethod() {
352 return new TestCounter();
353 }
354 
355 [Symbol.dispose]() {
356 console.log('Disposing');
357 }
358}
359 
360class TestAlarmObject extends DurableObject {
361 // Can declare alarm method consuming optional alarmInfo parameter
362 async alarm(alarmInfo?: AlarmInvocationInfo) {
363 if (alarmInfo !== undefined) {
364 const _isRetry: boolean = alarmInfo.isRetry;
365 const _retryCount: number = alarmInfo.retryCount;
366 const _scheduledTime: number = alarmInfo.scheduledTime;
367 }
368 }
369 
370 // User code can invoke alarm() directly, if desired.
371 async runAlarmVoid(): Promise<void> {
372 return await this.alarm();
373 }
374 async runAlarmInfo(): Promise<void> {
375 return await this.alarm({
376 isRetry: true,
377 retryCount: 1,
378 scheduledTime: 1000,
379 });
380 }
381}
382 
383class TestNaughtyEntrypoint extends WorkerEntrypoint {
384 // Check incorrectly typed methods
385 // @ts-expect-error
386 fetch(_request: Request) {
387 return 'body';
388 }
389 // @ts-expect-error
390 async tail(_animal: '๐Ÿถ') {}
391 // @ts-expect-error
392 trace(_draw: boolean) {}
393 // @ts-expect-error
394 async scheduled(_at: Date) {}
395 // @ts-expect-error
396 queue(_message: string) {}
397 // @ts-expect-error
398 async test(_x: number, _y: number) {}
399}
400 
401class TestNaughtyObject extends DurableObject {
402 // Check incorrectly typed methods
403 // @ts-expect-error
404 async fetch(url: string) {
405 return new Response(url);
406 }
407 // @ts-expect-error
408 async alarm(_x: boolean) {}
409 // @ts-expect-error
410 webSocketMessage(_x: boolean) {}
411 // @ts-expect-error
412 async webSocketClose(_x: boolean) {}
413 // @ts-expect-error
414 webSocketError(_x: boolean) {}
415}
416 
417interface Env {
418 REGULAR_SERVICE: Service;
419 RPC_SERVICE: Service<TestEntrypoint>;
420 TYPEOF_RPC_SERVICE: Service<typeof TestEntrypoint>;
421 NAUGHTY_SERVICE: Service<TestNaughtyEntrypoint>;
422 // @ts-expect-error `BoringClass` isn't an RPC capable type
423 __INVALID_RPC_SERVICE_1: Service<BoringClass>;
424 
425 REGULAR_OBJECT: DurableObjectNamespace;
426 RPC_OBJECT: DurableObjectNamespace<TestObject>;
427 ALARM_OBJECT: DurableObjectNamespace<TestAlarmObject>;
428 NAUGHTY_OBJECT: DurableObjectNamespace<TestNaughtyObject>;
429 // @ts-expect-error `BoringClass` isn't an RPC capable type
430 __INVALID_OBJECT_1: DurableObjectNamespace<BoringClass>;
431 // @ts-expect-error `TestEntrypoint` is a `WorkerEntrypoint`, not a `DurableObject`
432 __INVALID_OBJECT_2: DurableObjectNamespace<TestEntrypoint>;
433}
434 
435export default <ExportedHandler<Env>>{
436 async fetch(_request, env, _ctx) {
437 // Check non-RPC services and namespaces work as usual
438 {
439 const response = await env.REGULAR_SERVICE.fetch('https://example.com', {
440 method: 'POST',
441 });
442 expectTypeOf(response).toEqualTypeOf<Response>();
443 
444 const uniqueId = env.REGULAR_OBJECT.newUniqueId();
445 expectTypeOf(uniqueId).toEqualTypeOf<DurableObjectId>();
446 const nameId = env.REGULAR_OBJECT.newUniqueId();
447 expectTypeOf(nameId).toEqualTypeOf<DurableObjectId>();
448 const stringId = env.REGULAR_OBJECT.newUniqueId();
449 expectTypeOf(stringId).toEqualTypeOf<DurableObjectId>();
450 
451 const stub = env.REGULAR_OBJECT.get(uniqueId);
452 const objectResponse = await stub.fetch('https://example.com', {
453 method: 'POST',
454 });
455 expectTypeOf(objectResponse).toEqualTypeOf<Response>();
456 expectTypeOf(stub.id).toEqualTypeOf<DurableObjectId>();
457 expectTypeOf(stub.name).toEqualTypeOf<string | undefined>();
458 }
459 
460 // Check RPC services and namespaces support standard methods (without overloads,
461 // `toEqualTypeOf(...)` will fail if the function signature doesn't match *exactly*)
462 {
463 expectTypeOf(env.RPC_SERVICE.fetch).toEqualTypeOf<
464 (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
465 >();
466 expectTypeOf(env.RPC_SERVICE.connect).toEqualTypeOf<
467 (address: SocketAddress | string, options?: SocketOptions) => Socket
468 >();
469 expectTypeOf(env.RPC_SERVICE.queue).toEqualTypeOf<
470 (
471 queueName: string,
472 messages: ServiceBindingQueueMessage[],
473 metadata?: MessageBatchMetadata
474 ) => Promise<FetcherQueueResult>
475 >();
476 expectTypeOf(env.RPC_SERVICE.scheduled).toEqualTypeOf<
477 (options?: FetcherScheduledOptions) => Promise<FetcherScheduledResult>
478 >();
479 
480 const stub = env.RPC_OBJECT.get(env.RPC_OBJECT.newUniqueId());
481 expectTypeOf(stub.fetch).toEqualTypeOf<
482 (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
483 >();
484 expectTypeOf(stub.connect).toEqualTypeOf<
485 (address: SocketAddress | string, options?: SocketOptions) => Socket
486 >();
487 }
488 
489 // Check cannot access `env` and `ctx` over RPC
490 {
491 // @ts-expect-error protected properties are not accessible
492 env.RPC_SERVICE.env;
493 // @ts-expect-error protected properties are not accessible
494 env.RPC_SERVICE.ctx;
495 
496 const stub = env.RPC_OBJECT.get(env.RPC_OBJECT.newUniqueId());
497 // @ts-expect-error protected properties are not accessible
498 stub.env;
499 // @ts-expect-error protected properties are not accessible
500 stub.ctx;
501 }
502 
503 // Check accessing properties (including using)
504 {
505 const s = env.RPC_SERVICE;
506 
507 // @ts-expect-error private properties are not accessible
508 s.privateInstanceProperty;
509 // @ts-expect-error private properties are not accessible
510 s.privateProperty;
511 
512 // Unfortunately, TypeScript's structural typing makes it tricky to
513 // differentiate between instance and prototype properties. This line
514 // would fail at runtime. We should encourage people to use `private`
515 // access modifiers when using TypeScript.
516 // TODO(someday): try make this fail type checking
517 expectTypeOf(s.instanceProperty).toEqualTypeOf<Promise<string>>();
518 
519 expectTypeOf(s.property).toEqualTypeOf<Promise<number>>(); // (always async)
520 expectTypeOf(s.asyncProperty).toEqualTypeOf<Promise<true>>();
521 }
522 
523 // Check calling methods (including using)
524 {
525 const s = env.RPC_SERVICE;
526 
527 // @ts-expect-error private methods are not accessible
528 s.privateMethod;
529 // @ts-expect-error symbol methods are not accessible
530 s[symbolMethod];
531 
532 expectTypeOf(s.method()).toEqualTypeOf<Promise<null>>(); // (always async)
533 expectTypeOf(await s.asyncMethod()).toEqualTypeOf<boolean>();
534 
535 expectTypeOf(s.voidMethod).toEqualTypeOf<(x?: number) => Promise<void>>(); // (always async)
536 expectTypeOf(s.asyncVoidMethod).toEqualTypeOf<() => Promise<void>>();
537 }
538 
539 // Check methods returning functions (including pipelining/ERM)
540 {
541 const s = env.RPC_SERVICE;
542 
543 expectTypeOf(s.functionMethod()).toMatchTypeOf<Promise<unknown>>();
544 using f1 = await s.functionMethod();
545 expectTypeOf(f1).toEqualTypeOf<RpcStub<(x: number) => number>>();
546 expectTypeOf(f1.dup()).toEqualTypeOf(f1);
547 expectTypeOf(f1(1)).toEqualTypeOf<Promise<number>>(); // (always async)
548 expectTypeOf(s.functionMethod()(1)).toEqualTypeOf<Promise<number>>(); // (pipelining)
549 
550 expectTypeOf(s.asyncFunctionMethod()).toMatchTypeOf<Promise<unknown>>();
551 using f2 = await s.asyncFunctionMethod();
552 expectTypeOf(f2).toEqualTypeOf<RpcStub<(x: number) => number>>();
553 expectTypeOf(s.asyncFunctionMethod()(1)).toEqualTypeOf<Promise<number>>(); // (pipelining)
554 
555 expectTypeOf(s.asyncAsyncFunctionMethod()).toMatchTypeOf<
556 Promise<unknown>
557 >();
558 using f3 = await s.asyncAsyncFunctionMethod();
559 expectTypeOf(f3).toEqualTypeOf<RpcStub<(x: number) => Promise<number>>>();
560 expectTypeOf(f3(1)).toEqualTypeOf<Promise<number>>();
561 expectTypeOf(s.asyncAsyncFunctionMethod()(1)).toEqualTypeOf<
562 Promise<number>
563 >(); // (pipelining)
564 
565 expectTypeOf(s.functionWithExtrasMethod()).toMatchTypeOf<
566 Promise<unknown>
567 >();
568 using f4 = await s.functionWithExtrasMethod();
569 type F4 = { (x: number): number; y: string };
570 expectTypeOf(f4).toEqualTypeOf<RpcStub<F4>>();
571 expectTypeOf(f4(1)).toEqualTypeOf<Promise<number>>(); // (always async)
572 expectTypeOf(f4.y).toEqualTypeOf<Promise<string>>(); // (always async)
573 expectTypeOf(s.functionWithExtrasMethod()(1)).toEqualTypeOf<
574 Promise<number>
575 >(); // (pipelining)
576 expectTypeOf(s.functionWithExtrasMethod().y).toEqualTypeOf<
577 Promise<string>
578 >(); // (pipelining)
579 }
580 
581 // Check methods returning objects (including pipelining/ERM)
582 {
583 const s = env.RPC_SERVICE;
584 
585 expectTypeOf(s.objectProperty).toMatchTypeOf<Promise<unknown>>();
586 using o = await s.objectProperty;
587 expectTypeOf(o.w).toEqualTypeOf<number>();
588 expectTypeOf(o.x).toEqualTypeOf<number>();
589 expectTypeOf(o.y).toEqualTypeOf<RpcStub<() => number>>();
590 expectTypeOf(o.y()).toEqualTypeOf<Promise<number>>(); // (always async)
591 expectTypeOf(o.z).toEqualTypeOf<
592 RpcStub<(a: boolean) => Promise<number>>
593 >();
594 expectTypeOf(o.z(true)).toEqualTypeOf<Promise<number>>();
595 
596 expectTypeOf(s.objectProperty.w).toEqualTypeOf<Promise<number>>(); // (pipelining)
597 expectTypeOf(s.objectProperty.x).toEqualTypeOf<Promise<number>>(); // (pipelining)
598 expectTypeOf(s.objectProperty.y()).toEqualTypeOf<Promise<number>>(); // (pipelining)
599 expectTypeOf(s.objectProperty.z(false)).toEqualTypeOf<Promise<number>>(); // (pipelining)
600 
601 expectTypeOf(s.everySerializable).not.toBeNever();
602 
603 // Verify serializable composite objects defined with "type" keyword
604 const oType = await s.methodReturnsTypeObject();
605 expectTypeOf(oType).not.toBeNever();
606 expectTypeOf(oType.fieldString).toEqualTypeOf<string>();
607 expectTypeOf(oType.fieldCallback).toEqualTypeOf<
608 RpcStub<(p: string) => number>
609 >(); // stubified
610 expectTypeOf(oType.fieldBasicMap).toEqualTypeOf<Map<string, number>>();
611 expectTypeOf(oType.fieldComplexMap).toEqualTypeOf<
612 Map<
613 string,
614 {
615 fieldString: string;
616 fieldCallback: RpcStub<(p: string) => number>; // stubified
617 }
618 >
619 >();
620 expectTypeOf(oType.fieldSet).toEqualTypeOf<Set<string>>();
621 expectTypeOf(oType.fieldSubLevel.fieldString).toEqualTypeOf<string>();
622 expectTypeOf(oType.fieldSubLevel.fieldCallback).toEqualTypeOf<
623 RpcStub<(p: string) => number>
624 >(); // stubified
625 
626 // Verify serializable composite objects defined with "interface" keyword
627 const oInterface = await s.methodReturnsInterfaceObject();
628 expectTypeOf(oInterface).not.toBeNever();
629 expectTypeOf(oInterface.fieldString).toEqualTypeOf<string>();
630 expectTypeOf(oInterface.fieldCallback).toEqualTypeOf<
631 RpcStub<(p: string) => number>
632 >(); // stubified
633 expectTypeOf(oInterface.fieldBasicMap).toEqualTypeOf<
634 Map<string, number>
635 >();
636 expectTypeOf(oInterface.fieldComplexMap).toEqualTypeOf<
637 Map<
638 string,
639 {
640 fieldString: string;
641 fieldCallback: RpcStub<(p: string) => number>; // stubified
642 }
643 >
644 >();
645 expectTypeOf(oInterface.fieldSet).toEqualTypeOf<Set<string>>();
646 expectTypeOf(
647 oInterface.fieldSubLevelInline.fieldString
648 ).toEqualTypeOf<string>();
649 expectTypeOf(oInterface.fieldSubLevelInline.fieldCallback).toEqualTypeOf<
650 RpcStub<(p: string) => number>
651 >(); // stubified
652 expectTypeOf(
653 oInterface.fieldSubLevelInterface.fieldString
654 ).toEqualTypeOf<string>();
655 expectTypeOf(
656 oInterface.fieldSubLevelInterface.fieldCallback
657 ).toEqualTypeOf<RpcStub<(p: string) => number>>(); // stubified
658 
659 expectTypeOf(s.nonSerializable1).returns.toBeNever();
660 // Note: Since one of the object's members is non-serializable,
661 // the entire object is resolved as 'never'.
662 expectTypeOf(s.nonSerializable2).returns.toBeNever();
663 expectTypeOf(s.nonSerializable3).returns.toBeNever();
664 
665 // Verify serializable objects without any stubs are still disposable
666 (await s.everySerializable)[Symbol.dispose]();
667 
668 // Verify types passed by value can still be pipelined
669 expectTypeOf(s.everySerializable.Object.a.b).toEqualTypeOf<
670 Promise<number>
671 >;
672 // TODO(now): these next two don't actually work, should they?
673 expectTypeOf(s.everySerializable.Array[0]).toEqualTypeOf<Promise<number>>;
674 expectTypeOf(await s.everySerializable.Map.get('a')).toEqualTypeOf<
675 number | undefined
676 >;
677 
678 // Verify stubable types replaced with stubs
679 using ecs = await s.everyCompositeSerializable;
680 expectTypeOf(ecs.Map).toEqualTypeOf<
681 Map<RpcStub<TestCounter>, RpcStub<TestCounter>>
682 >();
683 expectTypeOf(ecs.Set).toEqualTypeOf<Set<RpcStub<TestCounter>>>();
684 expectTypeOf(ecs.Array).toEqualTypeOf<Array<RpcStub<TestCounter>>>();
685 expectTypeOf(ecs.ReadonlyArray).toEqualTypeOf<
686 ReadonlyArray<RpcStub<TestCounter>>
687 >();
688 expectTypeOf(ecs.Object).toEqualTypeOf<{
689 a: { b: RpcStub<TestCounter> };
690 }>();
691 }
692 
693 // Check methods returning targets (including pipelining/ERM)
694 {
695 const s = env.RPC_OBJECT.get(env.RPC_OBJECT.newUniqueId());
696 
697 expectTypeOf(s.targetMethod()).toMatchTypeOf<Promise<unknown>>();
698 using t1 = await s.targetMethod();
699 expectTypeOf(t1).toEqualTypeOf<RpcStub<TestCounter>>();
700 expectTypeOf(t1.dup()).toEqualTypeOf(t1);
701 expectTypeOf(t1.value).toEqualTypeOf<Promise<number>>(); // (always async)
702 expectTypeOf(t1.increment()).toEqualTypeOf<Promise<number>>(); // (always async)
703 expectTypeOf(t1.increment(1)).toEqualTypeOf<Promise<number>>(); // (always async)
704 expectTypeOf(t1.copy()).toMatchTypeOf<Promise<RpcStub<TestCounter>>>();
705 
706 expectTypeOf(s.targetMethod().copy().increment(1)).toEqualTypeOf<
707 Promise<number>
708 >(); // (pipelining)
709 
710 expectTypeOf(s.asyncTargetMethod()).toMatchTypeOf<Promise<unknown>>();
711 using t2 = await s.asyncTargetMethod();
712 expectTypeOf(t2).toEqualTypeOf<RpcStub<TestCounter>>();
713 }
714 
715 // Check methods accepting stubs
716 {
717 expectTypeOf(env.RPC_SERVICE.stubMethod).toEqualTypeOf<
718 (
719 callback: (x: number) => number,
720 counter: TestCounter,
721 map: Map<TestCounter, TestCounter>,
722 set: Set<TestCounter>,
723 array: Array<TestCounter>,
724 readonlyArray: ReadonlyArray<TestCounter>,
725 object: { a: { b: TestCounter } }
726 ) => Promise<number>
727 >();
728 }
729 
730 // Check loopback stubs support same operations as remote stubs
731 {
732 const stub = new RpcStub(new TestCounter(42));
733 expectTypeOf(stub).toEqualTypeOf<RpcStub<TestCounter>>();
734 expectTypeOf(stub.value).toEqualTypeOf<Promise<number>>(); // (always async)
735 expectTypeOf(stub.copy().increment(1)).toEqualTypeOf<Promise<number>>(); // (pipelining)
736 
737 // @ts-expect-error requires stubable type
738 new RpcStub(1);
739 }
740 
741 // Check can't override `dup()`
742 {
743 const stub = new RpcStub(new TestCounter(42));
744 expectTypeOf(stub.dup).toEqualTypeOf<() => RpcStub<TestCounter>>();
745 }
746 
747 // Check methods returning base types are not stubified
748 {
749 const s = env.RPC_OBJECT.get(env.RPC_OBJECT.newUniqueId());
750 
751 expectTypeOf(s.fetch(_request)).toMatchTypeOf<Promise<Response>>();
752 expectTypeOf(s.complexTypes()).toMatchTypeOf<
753 Promise<{
754 undefined: undefined;
755 void: void;
756 null: null;
757 boolean: boolean;
758 number: number;
759 bigint: bigint;
760 string: string;
761 ArrayBuffer: ArrayBuffer;
762 DataView: DataView;
763 Date: Date;
764 Error: Error;
765 RegExp: RegExp;
766 ReadableStream: ReadableStream;
767 WritableStream: WritableStream;
768 Request: Request;
769 Response: Response;
770 Headers: Headers;
771 nested: {
772 undefined: undefined;
773 void: void;
774 null: null;
775 boolean: boolean;
776 number: number;
777 bigint: bigint;
778 string: string;
779 ArrayBuffer: ArrayBuffer;
780 DataView: DataView;
781 Date: Date;
782 Error: Error;
783 RegExp: RegExp;
784 ReadableStream: ReadableStream;
785 WritableStream: WritableStream;
786 Request: Request;
787 Response: Response;
788 Headers: Headers;
789 };
790 }>
791 >;
792 }
793 
794 return new Response();
795 },
796};