# Connect v1 to v2 migration guide

Connect v2 provides new features and simplifies some common APIs. In addition,
it makes use of all the [enhancements of Protobuf-ES v2](https://buf.build/blog/protobuf-es-v2).
If you're currently using Connect v1, this document walks you through all you need
to know to migrate and start using it right away.

> [!IMPORTANT]
>
> - Node 16 is no longer supported. Connect v2 now supports Node versions **18.14.1** and up.
> - TypeScript 4.1 is no longer supported. Connect v2 now requires at least TypeScript **v4.9.5**.

## Running the migration tool

To help with the process of migrating, we provide a tool called
[@connectrpc/connect-migrate](https://www.npmjs.com/package/@connectrpc/connect-migrate)
which will take care of dependency and plugin updates as well as a few minor code
changes. As a first step, execute the following command:

```shellsession
npx @connectrpc/connect-migrate@latest
```

While the tool will do a lot of the dependency legwork for you, there are many use
cases in code that it does not cover. Please read on to understand what has
changed and how you can leverage new features your application.

> [!TIP]
>
> If you use generated SDKs, read the section [Upgrading generated SDKs](#upgrading-generated-sdks)
> before you run `connect-migrate`.

## Dependency updates in package.json

One important dependency change to be aware of is that the plugin
`protoc-gen-connect-es` has been removed in v2. Connect now relies on service
descriptors generated by the Protobuf-ES v2 plugin `protoc-gen-es` and no longer
generates code itself. Therefore, that dependency must be removed entirely
from `package.json`.

Your mileage may vary according to what `@bufbuild`
and `@connectrpc` packages you depend on, but a list of relevant, compatible
dependencies should look similar to the following:

```diff
  "dependencies": {
    "@bufbuild/protobuf": "^2.2.0",
    "@bufbuild/protoc-gen-es": "^2.2.0",
    "@connectrpc/connect": "^2.0.0",
-   "@connectrpc/protoc-gen-connect-es": "^1.0.0",
    "@connectrpc/connect-web": "^2.0.0",
    "@connectrpc/connect-node": "^2.0.0",
    "@connectrpc/connect-next": "^2.0.0",
    "@connectrpc/connect-fastify": "^2.0.0",
    "@connectrpc/connect-express": "^2.0.0",
    "@connectrpc/connect-query": "^2.0.0",
    "@connectrpc/protoc-gen-connect-query": "^2.0.0",
    "@connectrpc/connect-playwright": "^0.6.0"
  }
```

:white_check_mark: The `connect-migrate` tool will handle this.

## Update buf.gen.yaml

Remove any usage of the `protoc-gen-connect-es` plugin from `buf.gen.yaml`.

If you are using local plugins:

```diff
# buf.gen.yaml
version: v2
plugins:
  - local: protoc-gen-es
    out: src/gen
    include_imports: true
    opt: target=ts
- - local: protoc-gen-connect-es
-   out: src/gen
-   opt: target=ts
```

If you are using remote plugins:

```diff
# buf.gen.yaml
version: v2
plugins:
- - remote: buf.build/bufbuild/es:v1.10.0
+ - remote: buf.build/bufbuild/es:v2.2.0
    out: src/gen
    include_imports: true
    opt: target=ts
- - remote: buf.build/connectrpc/es
-   out: src/gen
-   opt: target=ts
```

:white_check_mark: The `connect-migrate` tool will handle this.

#### Remove unused files with `clean`

The `*_connect.ts` files generated by the old plugin are no longer needed. We recommend to use the `clean` option
provided by the Buf CLI introduced in v1.36.0:

```diff
# buf.gen.yaml
version: v2
+ clean: true
plugins:
  - local: protoc-gen-es
    out: src/gen
    include_imports: true
```

With this option, `buf generate` will delete the contents of `src/gen` before generating code.

#### Update plugin options

The plugin option `import_extension` has changed behavior: If you are using [Node16 module resolution](https://www.typescriptlang.org/tsconfig/#moduleResolution)
and need the `.js` extension on all import paths, add the plugin option `import_extension=js`:

```diff
# buf.gen.yaml
version: v2
plugins:
 - local: protoc-gen-es
   out: src/gen
   include_imports: true
   opt:
     - target=ts
+    - import_extension=js
```

If you don't want the `.js` extension added to import paths, you can remove the
plugin option `import_extension=none` - it's the default behavior now:

```diff
# buf.gen.yaml
version: v2
plugins:
  - local: protoc-gen-es
    out: src/gen
    include_imports: true
    opt:
      - target=ts
-     - import_extension=none
```

If you have been using the plugin option `ts_nocheck=false`, you can remove it
as well - it's the default behavior now.

## Re-generate code

Now that dependencies and `buf.gen.yaml` are updated, the next step is to re-generate code. The
migration tool does not handle code generation, so be sure to do so in whatever
way your project is configured. For example, `npx buf generate` or `npm run generate`.

> [!NOTE]
> Ensure that your `buf.gen.yaml` includes the following options to generate
> code for imports.
>
> - `include_imports: true`

See the [Gotchas](#the-new-plugins-generates-missing-imports) section for an explanation.

## Update your application code

Now that dependencies are updated and new code is generated, let's go through the
changes to your application code.

### Import paths

Once your code is generated and the vestigial `*_connect.ts` files are removed,
import paths will need to be updated. This is usually an update from `*_connect` to
`*_pb`:

```diff
- import { ElizaService } from "./gen/eliza_connect";
+ import { ElizaService } from "./gen/eliza_pb";
```

:white_check_mark: The `connect-migrate` tool will handle this.

### New APIs for Protobuf messages

In many applications, it's likely that you'll encounter the most significant change
in version 2.0 of Protobuf-ES: we no longer generate classes for Protobuf messages.
Instead, we generate a schema object and an associated TypeScript type definition
for each message.

Instead of the `new` keyword, you create a message with a function call:

```diff
- import { SayRequest } from "./gen/eliza_connect.js";
+ import { SayRequestSchema } from "./gen/eliza_pb.js";
+ import { create } from "@bufbuild/protobuf";

- const sayRequest = new SayRequest({
+ const sayRequest = create(SayRequestSchema, {
  sentence: "Hello",
});
```

:white_check_mark: The `connect-migrate` tool will handle this.

Messages are now plain TypeScript types, which greatly improves compatibility
with the ecosystem. For example, messages can be passed from a server-side
component in Next.js to a client-side component without losing any data or types.

#### Message class methods

Because messages no longer have attached class methods, a standalone function is
provided as a replacement. Here is an example for `toBinary`:

```diff
import { toBinary } from "@bufbuild/protobuf";
import { SayRequestSchema } from "./gen/eliza_pb";

- sayRequest.toBinary();
+ toBinary(SayRequestSchema, sayRequest);
```

The same applies to the methods `equals`, `clone`, `toJson`, and `toJsonString`,
and to the static class methods.

:white_check_mark: The `connect-migrate` tool will handle the static class methods
`fromBinary`, `fromJson`, and `fromJsonString`.

> [!WARNING]
>
> Note that messages no longer implement the magic `toJSON` method, which serializes
> a message with the Protobuf JSON format when it's passed to `JSON.stringify`. Make
> sure to always serializes to JSON with the `toJson` or `toJsonString` function.

#### Identifying messages

Messages no longer include a reference to their type, but they have a property
for their qualified Protobuf name:

```diff
- sayRequest.getType().typeName; // "connectrpc.eliza.v1.SayRequest"
+ sayRequest.$typeName; // "connectrpc.eliza.v1.SayRequest"
```

To identify an unknown message, the function `isMessage` is still supported:

```diff
- import { SayRequest } from "./gen/eliza_connect.js";
+ import { SayRequestSchema } from "./gen/eliza_pb.js";
import { isMessage } from "@bufbuild/protobuf";

- if (isMessage(x, SayRequest)) {
+ if (isMessage(x, SayRequestSchema)) {
  x.sentence;
}
```

:white_check_mark: The `connect-migrate` tool will handle `isMessage` calls.

#### PlainMessage removed

The `PlainMessage<T>` type was used to represent just the fields of a message,
without class methods. Now that messages are plain types, the type is no longer
necessary and has been removed, along with the function `toPlainMessage`.

In most cases, you can simply remove usage of `PlainMessage`:

```diff
- import type { PlainMessage } from "@bufbuild/protobuf";
import type { SayRequest } from "./gen/eliza_pb";

- const sayRequest: PlainMessage<SayReqest> = {
+ const sayRequest: SayReqest = {
  $typeName: "connectrpc.eliza.v1.SayRequest",
  sentence: "Hello",
};
```

The `$typeName` property is required to identify messages. If you have a use-case
where it's distracting, use built-in types to remove it:

```ts
const sayRequest: Omit<SayReqest, "$typeName"> = {
  sentence: "Hello",
};
```

> [!TIP]
>
> `proto3` messages are plain objects, but with `proto2` or Editions, messages
> may use the prototype chain to track field presence. See the section
> [Field presence and default values](https://github.com/bufbuild/protobuf-es/blob/v2.2.2/MANUAL.md#field-presence-and-default-values)
> in the Protobuf-ES documentation.
> If you need messages to be as simple as possible in all cases, see the section
> about [JSON types](https://github.com/bufbuild/protobuf-es/blob/v2.2.2/MANUAL.md#json-types).

#### PartialMessage removed

Similar to `PlainMessage`, `PartialMessage` has been removed. It was used for
partial initializer objects when creating new messages. For this use case, the
replacement is `MessageInitShape`. It retrieves the type from a descriptor for
forwards compatibility.

For other use cases, built-in types are preferred, now that messages are plain
types.

#### proto2 field changes

`proto2` fields with default values are no longer generated as optional properties:

```diff
  /**
   * @generated from field: required int32 num = 3;
   */
- num?: number;
+ num: number;
```

> [!TIP]
>
> In general, this makes working with `proto2` messages much more convenient, because
> you no longer have to handle `undefined` when accessing the property. You can rely
> on the default value `0` instead. If you need to distinguish between an absent
> value for a field and the default value, use the function `isFieldSet` from
> `@bufbuild/protobuf`. You can learn more in the section
> [Field presence and default values](https://github.com/bufbuild/protobuf-es/blob/v2.2.2/MANUAL.md#field-presence-and-default-values)
> in the Protobuf-ES documentation.

#### Struct field changes

The well-known type `google.protobuf.Struct` is now generated as a more-convenient
`JsonObject` when used as a message field:

```diff
  /**
   * @generated from field: google.protobuf.Struct struct = 1;
   */
- struct?: Struct;
+ struct?: JsonObject;
```

> [!TIP]
>
> This feature makes it very easy to work with `Struct` fields:
>
> ```ts
> myMessage.struct = {
>   text: "abc",
>   number: 123,
> };
> ```

#### Well-known types

All well-known types have been moved to the subpath export `@bufbuild/protobuf/wkt`.
For example, if you want to refer to `google.protobuf.Timestamp`:

```diff
- import type { Timestamp } from "@bufbuild/protobuf";
+ import type { Timestamp } from "@bufbuild/protobuf/wkt";
```

:white_check_mark: The `connect-migrate` tool will handle this.

Helpers that were previously part of the generated class are now standalone
functions, also exported from `@bufbuild/protobuf/wkt`:

```diff
- import type { Timestamp } from "@bufbuild/protobuf";
+ import type { Timestamp } from "@bufbuild/protobuf/wkt";
+ import { timestampDate } from "@bufbuild/protobuf/wkt";

const timestamp: Timestamp = ...
- const date: Date = timestamp.toDate();
+ const date: Date = timestampDate(timestamp);
```

#### Reflection changes

Reflection has received a major update in Protobuf-ES v2, and is much more
capable and flexible now. Instead of providing minimal field
information from generated code, full Protobuf descriptors are available.

Here is an example for finding the JSON name of a field:

```diff
- SayRequest.fields.list()
+ SayRequestSchema.fields
  .find((f) => f.localName === "sentence")
  ?.jsonName;
```

All common use cases continue to be supported, but types and properties have
changed to consolidate the API and provide new features:

| Old type      | Replacement                                                                                |
| ------------- | ------------------------------------------------------------------------------------------ |
| `MessageType` | `DescMessage`                                                                              |
| `EnumType`    | `DescEnum`                                                                                 |
| `FieldInfo`   | `DescField`                                                                                |
| `OneofInfo`   | `DescOneof`                                                                                |
| `ServiceType` | `DescService` - also see [Changes to service descriptors](#changes-to-service-descriptors) |
| `MethodInfo`  | `DescMethod`                                                                               |

> [!TIP]
>
> For more information, see the [Protobuf reflection documentation](https://github.com/bufbuild/protobuf-es/blob/v2.2.2/MANUAL.md#reflection).

#### Registries

Registries are commonly used for serializing the well-known type `google.protobuf.Any`.
To create a registry, you simply pass the schema objects instead of message classes:

```diff
import { createRegistry } from "@bufbuild/protobuf";
- import { SayRequest, SayResponse } from "./gen/eliza_pb";
+ import { SayRequestSchema, SayRequestSchema } from "./gen/eliza_pb";

const registry = createRegistry(
- SayRequest, SayResponse,
+ SayRequestSchema, SayResponseSchema,
);
```

> [!TIP]
> In the new version, registries also support files, and can be composed or mutated.
> See the [documentation on Protobuf registries](https://github.com/bufbuild/protobuf-es/blob/v2.2.2/MANUAL.md#registries) to learn more.

### `createPromiseClient` is removed

Promise clients are now the default and the previously-deprecated function `createPromiseClient`
has been removed. Update any call sites using `createPromiseClient` to use `createClient`.

```diff
- import { createPromiseClient } from "@connectrpc/connect-node";
+ import { createClient } from "@connectrpc/connect-node";

- createPromiseClient(ElizaService, transport);
+ createClient(ElizaService, transport);
```

:white_check_mark: The `connect-migrate` tool will handle this.

### gRPC transport requires HTTP/2

The gRPC Transport now requires HTTP/2. If you are using `createGrpcTransport`
and specifying an `httpVersion`, it will fail compilation. Remove the
`httpVersion` property to use the default of HTTP/2.

```diff
import { createGrpcTransport } from "@connectrpc/connect-node";

createGrpcTransport({
  baseUrl: "https://demo.connectrpc.com",
- httpVersion: "2",
});
```

Note that if you were relying on HTTP/1.1 as part of your gRPC strategy, this
may require bigger architectural changes, but the hope is that this is not a
common problem.

### Transport option `credentials` is removed

We have removed the `credentials` option from transports as well as the `init`
option in interceptors. These two options were used to customize `fetch` routines.

To set the fetch option `credentials`, provide a fetch override:

```diff
createConnectTransport({
  baseUrl: "/",
- credentials: "include",
+ fetch: (input, init) => fetch(input, { ...init, credentials: "include" }),
});
```

### JSON option `typeRegistry` renamed

JSON serialization options (passed to a transport or a server plugin) have
been updated in Protobuf-ES v2. The option `typeRegistry` has been renamed to
`registry`:

```diff
import { createRegistry } from "@bufbuild/protobuf";
import { createConnectTransport } from "@connectrpc/connect-web";
- import { SayRequest } from "./gen/eliza_pb";
+ import { SayRequestSchema } from "./gen/eliza_pb";

const transport = createConnectTransport({
  baseUrl: "https://demo.connectrpc.com",
  jsonOptions: {
-   typeRegistry: createRegistry(SayRequest),
+   registry: createRegistry(SayRequestSchema),
  },
});
```

> [!TIP]
> Registries have received a major update in Protobuf-ES v2 and are
> much more capable and flexible now. For more information, see the
> [Protobuf registry documentation](https://github.com/bufbuild/protobuf-es/blob/v2.2.2/MANUAL.md#registries).

### JSON option `emitDefaultValues` renamed

JSON serialization options (passed to a transport or a server plugin) have
been updated in Protobuf-ES v2. The option `emitDefaultValues` has been renamed to
`alwaysEmitImplicit`:

```diff
await server.register(
  fastifyConnectPlugin,
  {
    routes,
    jsonOptions: {
-     emitDefaultValues: true,
+     alwaysEmitImplicit: true,
    },
  },
);
```

> [!TIP]
>
> When this option is enabled, `proto3` default values such as `0`, `"""`, or `false`
> are serialized to JSON, but `proto2` default values are not.

### Changes to service descriptors

Connect now relies on service descriptors generated by the Protobuf-ES v2 plugin
`protoc-gen-es` and no longer generates code itself. The type for service
descriptors changes from `ServiceType` to `DescService` from `@bufbuild/protobuf`.
The descriptors still provide the same functionality - typed metadata for clients
and servers - but in a slightly different form. Types and properties have changed
to consolidate the reflection APIs and to provide new features.

> [!TIP]
>
> Service and method descriptors provide access to custom options now, which can
> be very useful in interceptors to control authorization and other details.
> See the documentation for [Protobuf custom options](https://github.com/bufbuild/protobuf-es/blob/v2.2.2/MANUAL.md#custom-options) to learn more.

#### Access a method by name

The `methods` property is renamed to `method`:

```diff
- const say = ElizaService.methods.say;
+ const say = ElizaService.method.say;
```

> [!TIP]
>
> `methods` provides an Array of the methods.

#### Method kind

To distinguish between unary and streaming RPCs, use the `methodKind` property:

```diff
- import { MethodKind } from "@bufbuild/protobuf";

- say.kind; // MethodKind.Unary
+ say.methodKind; // "unary"
```

#### Method idempotency

The enum `MethodIdempotency` has been replaced by the well-known type `MethodOptions_IdempotencyLevel`:

```diff
- import { MethodIdempotency } from "@bufbuild/protobuf";
+ import { MethodOptions_IdempotencyLevel } from "@bufbuild/protobuf/wkt";

- say.idempotency; // MethodIdempotency.NoSideEffects
+ say.idempotency; // MethodOptions_IdempotencyLevel.NoSideEffects
```

### Changes to interceptors

Interceptors for streaming RPCs now use appropriate stream types. In
v1, the server used `UnaryRequest` and `StreamResponse` for server-streaming RPCs,
while the client always uses streaming variants. This was unintended behavior
and has been fixed in v2. Now all streaming RPCs use the `StreamRequest` and
`StreamResponse` types on the server as well.

The `init` property with fetch options has been removed. As a replacement to
determine whether an incoming request is a Connect GET request in server-side
interceptors, the property `requestMethod: string` has been added to intercepted
requests. This property is symmetrical to `HandlerContext.requestMethod`.

### Service argument removed from `ConnectRouter.rpc`

Because method descriptors are now self-sufficient, it is no longer necessary to
pass the service descriptor to `ConnectRouter.rpc`, and the argument has been
removed from the method signature. Update your call-sites as follows:

```diff
const routes = ({rpc}: ConnectRouter) => {
-   rpc(ElizaService, ElizaService.say, impl);
+   rpc(ElizaService.say, impl);
}
```

> [!NOTE]
>
> The same change applies to the `Transport` interface. This is only relevant if
> you access `Transports` without a client, or if you have implemented your own
> custom transport.

### Constructing error details

If you raise a Connect error on a server, you can include arbitrary Protobuf
messages as error details. The syntax to provide them has changed slightly:
In Connect v1, error details were specified as message instances. In v2, error
details are now an object that specifies both a message schema and initialization
object. For example:

```diff
- import { LocalizedMessage } from "./gen/google/rpc/error_details_pb";
- const details = [
-   new LocalizedMessage({
-       locale: "fr-CH",
-       message: "Je n'ai plus de mots.",
-   }),
- ];
+ import { LocalizedMessageSchema } from "./gen/google/rpc/error_details_pb";
+ const details = [
+   {
+     desc: LocalizedMessageSchema,
+     value: {
+       locale: "fr-CH",
+       message: "Je n'ai plus de mots.",
+     }
+   },
+ ];

const metadata = new Headers({
  "words-left": "none"
});
throw new ConnectError(
  "I have no words anymore.",
  Code.ResourceExhausted,
  metadata,
  details,
);
```

## Upgrading generated SDKs

The `connect-migrate` tool does not upgrade generated SDKs at this point in time.
We recommend that you use the following steps to upgrade:

1. Uninstall old generated SDKs
2. Run the migration tool
3. Install new generated SDKs
4. Update your application code

#### 1. Uninstall old generated SDKs

Since the Connect plugin no longer exists in v2, any generated
SDK dependencies in your `package.json` that rely on this plugin (i.e. have
`connectrpc_es` as part of their name) must be updated to use the Protobuf-ES
v2 plugin instead.

The name of a generated SDK dependency is structured as follows:

```
@buf/{module_owner}_{module_name}.{plugin_owner}_{plugin_name}
```

For example, if you are using a generated SDK for the BSR module [buf.build/googleapis/googleapis](https://buf.build/googleapis/googleapis),
you have dependency on `@buf/googleapis_googleapis.connectrpc_es` in your
package.json file.

Run the following command to remove the dependency:

```shellsession
npm remove @buf/googleapis_googleapis.connectrpc_es
```

To do this for other modules, simply replace `googleapis/googleapis`
with your module owner and name.

#### 2. Run the migration tool

```shellsession
npx @connectrpc/connect-migrate@latest
```

This command will update your dependencies on `@connect` and `@bufbuild` packages,
so that you can install the new generated SDK.

#### 3. Install new generated SDKs

Now you need to replace the old generated SDK with a dependency on `@buf/googleapis_googleapis.bufbuild_es`
(same BSR module, but using the [Protobuf-ES v2 plugin](https://buf.build/bufbuild/es)):

```shellsession
npm install @buf/googleapis_googleapis.bufbuild_es@latest
```

Your `package.json` should now resemble the following:

```diff
  "dependencies": {
    ...
-   "@buf/googleapis_googleapis.connectrpc_es": "^1.6.1-20241107203341-553fd4b4b3a6.1",
+   "@buf/googleapis_googleapis.bufbuild_es": "^2.2.2-20241107203341-553fd4b4b3a6.1",
    "@connectrpc/connect-web": "^2.0.0",
    "@bufbuild/protobuf": "^2.2.0",
    ...
  }
```

> [!NOTE]
> Your versions will differ per module.

#### 4. Update your application code

Now your dependencies are updated, and you can follow the guide to
[Update your application code](#update-your-application-code).

Make sure to update your imports to the new package name and file names:

```diff
- import { ByteStream } from "@buf/googleapis_googleapis.connectrpc_es/google/bytestream_connect.js";
+ import { ByteStream } from "@buf/googleapis_googleapis.bufbuild_es/google/bytestream_pb.js";
```

## Gotchas

### The new plugins generates missing imports

Because Protobuf-ES supports custom options and other reflection-based features now, generated code includes more
information than in the previous version, and will generate additional imports in some situations.

For example, if you have a Protobuf message that uses validation rules from [buf.build/bufbuild/protovalidate](https://buf.build/bufbuild/protovalidate),
the Protobuf file has an import for the validation options:

```protobuf
import "buf/validate/validate.proto";
```

The old plugin ignored this import, but the new plugin will generate a corresponding
ECMAScript import:

```diff
+ import { file_buf_validate_validate } from "./buf/validate/validate_pb";
```

The imported file is not generated by default. To include imports, add the following
option to your buf.gen.yaml config:

```diff
# buf.gen.yaml
version: v2
plugins:
  - local: protoc-gen-es
    out: src/gen
+   include_imports: true
```

### Parcel fails to resolve imports

Connect-ES and Protobuf-ES use [package exports](https://nodejs.org/docs/latest-v12.x/api/packages.html#packages_exports).
If you see the following error with Parcel, make sure to [enable package exports](https://parceljs.org/features/dependency-resolution/#package-exports):

```
@parcel/core: Failed to resolve '@bufbuild/protobuf/codegenv1'
```

### Metro fails to resolve imports

Connect-ES and Protobuf-ES use [package exports](https://nodejs.org/docs/latest-v12.x/api/packages.html#packages_exports).
If you see the following error with Metro or Expo, make sure to [enable package exports](https://metrobundler.dev/docs/package-exports/):

```
Metro error: Unable to resolve module @bufbuild/protobuf/codegenv1
```

### Type ... is not assignable to type ...

Previously, Connect allowed request objects with matching shapes to be passed
to API calls interchangeably as long as the passed object was a superset of the
target type. For example, given the following proto definitions:

```protobuf
syntax = "proto3";
package example.v1;
message MessageA {
  string field_a = 1;
}
message MessageB {
  string field_a = 1;
  int64 field_b = 2;
}
service ExampleService {
  rpc RequestA(MessageA) returns (Empty) {}
  rpc RequestB(MessageB) returns (Empty) {}
}
```

The following would have passed TypeScript compilation:

```ts
client.requestA(new MessageA());
client.requestA(new MessageB());
```

This was an unintended bug and not a feature. In Connect v2, only the specified
target type will pass compilation.

```ts
client.requestA(create(MessageASchema));
client.requestA(create(MessageBSchema)); // Type Error: Argument of type MessageBSchema is not assignable to parameter of type MessageInit<MessageASchema>
```

If you intend to pass a message as a different message with the same fields,
you can use object destructuring to drop the `$typeName`, and copy the rest
of the properties:

```ts
const messageA: MessageA = ...;
const { $typeName: _, ...properties } = messageA;
const messageB = create(MessageBSchema, properties);
```