Skip to content

Commit 45b5f58

Browse files
feat(core): relax extension() Local constraint from JSONObject to object
Interfaces have no implicit index signature (TS#15300), so capability shapes declared as `interface` were rejected. JSONObject did not actually enforce serializability; the cast moves to one place inside the SDK instead of every consumer.
1 parent 714c236 commit 45b5f58

4 files changed

Lines changed: 22 additions & 10 deletions

File tree

packages/client/src/client/client.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,21 +321,21 @@ export class Client extends Protocol<ClientContext> {
321321
* {@linkcode ExtensionHandle.getPeerSettings | handle.getPeerSettings()} returns the server's
322322
* `capabilities.extensions[id]` blob (validated against `peerSchema` if provided).
323323
*/
324-
public extension<L extends JSONObject>(id: string, settings: L): ExtensionHandle<L, JSONObject, ClientContext>;
325-
public extension<L extends JSONObject, P extends AnySchema>(
324+
public extension<L extends object>(id: string, settings: L): ExtensionHandle<L, JSONObject, ClientContext>;
325+
public extension<L extends object, P extends AnySchema>(
326326
id: string,
327327
settings: L,
328328
opts: ExtensionOptions<P>
329329
): ExtensionHandle<L, SchemaOutput<P>, ClientContext>;
330-
public extension<L extends JSONObject, P extends AnySchema>(
330+
public extension<L extends object, P extends AnySchema>(
331331
id: string,
332332
settings: L,
333333
opts?: ExtensionOptions<P>
334334
): ExtensionHandle<L, SchemaOutput<P> | JSONObject, ClientContext> {
335335
if (this.transport) {
336336
throw new SdkError(SdkErrorCode.AlreadyConnected, 'Cannot register extension after connecting to transport');
337337
}
338-
this._capabilities.extensions = { ...this._capabilities.extensions, [id]: settings };
338+
this._capabilities.extensions = { ...this._capabilities.extensions, [id]: settings as JSONObject };
339339
return new ExtensionHandle(
340340
this,
341341
id,

packages/core/src/shared/extensionHandle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export interface ExtensionOptions<P extends AnySchema> {
5656
* did not advertise the same extension ID; when lax (the default), sends proceed regardless and
5757
* {@linkcode getPeerSettings} returns `undefined`.
5858
*/
59-
export class ExtensionHandle<Local extends JSONObject, Peer = JSONObject, ContextT extends BaseContext = BaseContext> {
59+
export class ExtensionHandle<Local extends object, Peer = JSONObject, ContextT extends BaseContext = BaseContext> {
6060
private _peerSettingsCache?: { value: Peer | undefined };
6161

6262
/**

packages/core/test/shared/extensionHandle.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function makeHandle(opts: { peer?: JSONObject | undefined; strict?: boolean; pee
2727
host: MockHost;
2828
handle: ExtensionHandle<JSONObject, unknown, BaseContext>;
2929
} {
30-
const host = makeMockHost();
30+
const host = makeMockHost() as unknown as ExtensionHost<BaseContext>;
3131
const handle = new ExtensionHandle(
3232
host as unknown as ExtensionHost<BaseContext>,
3333
'io.example/ui',
@@ -146,3 +146,15 @@ describe('ExtensionHandle — id and settings', () => {
146146
expect(handle.settings).toEqual({ local: true });
147147
});
148148
});
149+
150+
describe('ExtensionHandle — accepts interface-typed settings', () => {
151+
test('compiles with an interface (no implicit index signature)', () => {
152+
interface MyCaps {
153+
featureA: boolean;
154+
}
155+
const caps: MyCaps = { featureA: true };
156+
const host = makeMockHost() as unknown as ExtensionHost<BaseContext>;
157+
const handle = new ExtensionHandle(host, 'test/iface', caps, () => undefined, false);
158+
expect(handle.settings.featureA).toBe(true);
159+
});
160+
});

packages/server/src/server/server.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,21 +235,21 @@ export class Server extends Protocol<ServerContext> {
235235
* {@linkcode ExtensionHandle.getPeerSettings | handle.getPeerSettings()} returns the client's
236236
* `capabilities.extensions[id]` blob (validated against `peerSchema` if provided).
237237
*/
238-
public extension<L extends JSONObject>(id: string, settings: L): ExtensionHandle<L, JSONObject, ServerContext>;
239-
public extension<L extends JSONObject, P extends AnySchema>(
238+
public extension<L extends object>(id: string, settings: L): ExtensionHandle<L, JSONObject, ServerContext>;
239+
public extension<L extends object, P extends AnySchema>(
240240
id: string,
241241
settings: L,
242242
opts: ExtensionOptions<P>
243243
): ExtensionHandle<L, SchemaOutput<P>, ServerContext>;
244-
public extension<L extends JSONObject, P extends AnySchema>(
244+
public extension<L extends object, P extends AnySchema>(
245245
id: string,
246246
settings: L,
247247
opts?: ExtensionOptions<P>
248248
): ExtensionHandle<L, SchemaOutput<P> | JSONObject, ServerContext> {
249249
if (this.transport) {
250250
throw new SdkError(SdkErrorCode.AlreadyConnected, 'Cannot register extension after connecting to transport');
251251
}
252-
this._capabilities.extensions = { ...this._capabilities.extensions, [id]: settings };
252+
this._capabilities.extensions = { ...this._capabilities.extensions, [id]: settings as JSONObject };
253253
return new ExtensionHandle(
254254
this,
255255
id,

0 commit comments

Comments
 (0)