@azure/schema-registry-json 1.0.0-alpha.20230809.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/dist-esm/src/errors.js +23 -0
- package/dist-esm/src/errors.js.map +1 -0
- package/dist-esm/src/index.js +5 -0
- package/dist-esm/src/index.js.map +1 -0
- package/dist-esm/src/jsonSerializer.js +173 -0
- package/dist-esm/src/jsonSerializer.js.map +1 -0
- package/dist-esm/src/logger.js +8 -0
- package/dist-esm/src/logger.js.map +1 -0
- package/dist-esm/src/models.js +4 -0
- package/dist-esm/src/models.js.map +1 -0
- package/dist-esm/src/utility.js +7 -0
- package/dist-esm/src/utility.js.map +1 -0
- package/package.json +115 -0
- package/types/schema-registry-json.d.ts +101 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Microsoft
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Azure Schema Registry Json Serializer client library for JavaScript
|
|
2
|
+
|
|
3
|
+
Azure Schema Registry is a schema repository service hosted by Azure Event Hubs,
|
|
4
|
+
providing schema storage, versioning, and management. This package provides an
|
|
5
|
+
Json serializer capable of serializing and deserializing payloads containing
|
|
6
|
+
Json-serialized data.
|
|
7
|
+
|
|
8
|
+
Key links:
|
|
9
|
+
|
|
10
|
+
- [Source code](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/schemaregistry/schema-registry-json)
|
|
11
|
+
- [Package (npm)](https://www.npmjs.com/package/@azure/schema-registry-json)
|
|
12
|
+
- [API Reference Documentation](https://aka.ms/schemaregistryjson-js-api)
|
|
13
|
+
- [Samples](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/schemaregistry/schema-registry-json/samples)
|
|
14
|
+
|
|
15
|
+
## Getting started
|
|
16
|
+
|
|
17
|
+
- [LTS versions of Node.js](https://github.com/nodejs/release#release-schedule)
|
|
18
|
+
|
|
19
|
+
### Prerequisites
|
|
20
|
+
|
|
21
|
+
- An [Azure subscription][azure_sub]
|
|
22
|
+
- An existing [Schema Registry resource](https://aka.ms/schemaregistry)
|
|
23
|
+
|
|
24
|
+
### Install the `@azure/schema-registry-json` package
|
|
25
|
+
|
|
26
|
+
Install the Azure Text Analytics client library for JavaScript with `npm`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @azure/schema-registry-json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Key concepts
|
|
33
|
+
|
|
34
|
+
### JsonSerializer
|
|
35
|
+
|
|
36
|
+
Provides API to serialize to and deserialize from JSON wrapped in a message
|
|
37
|
+
with a content type field containing the schema ID. Uses
|
|
38
|
+
`SchemaRegistryClient` from the [@azure/schema-registry](https://www.npmjs.com/package/@azure/schema-registry) package
|
|
39
|
+
to get schema IDs from schema definition or vice versa. The provided API has internal cache to avoid calling the schema registry service when possible.
|
|
40
|
+
|
|
41
|
+
### Messages
|
|
42
|
+
|
|
43
|
+
By default, the serializer will create messages structured as follows:
|
|
44
|
+
|
|
45
|
+
- `data`: a byte array containing JSON data.
|
|
46
|
+
|
|
47
|
+
- `contentType`: a string of the following format `application/json+<Schema ID>` where
|
|
48
|
+
the `application/json` part signals that this message has a Json-serialized payload
|
|
49
|
+
and the `<Schema Id>` part is the Schema ID the Schema Registry service assigned
|
|
50
|
+
to the schema used to serialize this payload.
|
|
51
|
+
|
|
52
|
+
Not all messaging services are supporting the same message structure. To enable
|
|
53
|
+
integration with such services, the serializer can act on custom message structures
|
|
54
|
+
by setting the `messageAdapter` option in the constructor with a corresponding
|
|
55
|
+
message producer and consumer. Azure messaging client libraries export default
|
|
56
|
+
adapters for their message types.
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
### Serialize and deserialize an `@azure/event-hubs`'s `EventData`
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const { DefaultAzureCredential } = require("@azure/identity");
|
|
64
|
+
const { createEventDataAdapter } = require("@azure/event-hubs");
|
|
65
|
+
const { SchemaRegistryClient } = require("@azure/schema-registry");
|
|
66
|
+
const { JsonSerializer } = require("@azure/schema-registry-json");
|
|
67
|
+
|
|
68
|
+
const client = new SchemaRegistryClient(
|
|
69
|
+
"<fully qualified namespace>",
|
|
70
|
+
new DefaultAzureCredential()
|
|
71
|
+
);
|
|
72
|
+
const serializer = new JsonSerializer(client, {
|
|
73
|
+
groupName: "<group>",
|
|
74
|
+
messageAdapter: createEventDataAdapter(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Example Json schema
|
|
78
|
+
const schema = JSON.stringify({
|
|
79
|
+
$schema: "http://json-schema.org/draft-04/schema#",
|
|
80
|
+
$id: "person",
|
|
81
|
+
title: "Student",
|
|
82
|
+
description: "A student in the class",
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
name: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description: "The name of the student",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ["name"]
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Example value that matches the Json schema above
|
|
94
|
+
const value = { name: "Bob" };
|
|
95
|
+
|
|
96
|
+
// Serialize value to a message
|
|
97
|
+
const message = await serializer.serialize(value, schema);
|
|
98
|
+
|
|
99
|
+
// Deserialize a message to value
|
|
100
|
+
const deserializedValue = await serializer.deserialize(message);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The serializer doesn't check whether the deserialized value matches the schema
|
|
104
|
+
but provides an option to implement such validation. The application can pass a
|
|
105
|
+
validation callback function as one of the options to the deserialize method where schema validation can be implemented.
|
|
106
|
+
To see how the validation might be implemented, please checkout the [`schemaRegistryJsonWithValidation`](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/schemaregistry/schema-registry-json/samples-dev/schemaRegistryJsonWithValidation.ts)
|
|
107
|
+
sample.
|
|
108
|
+
|
|
109
|
+
## Troubleshooting
|
|
110
|
+
|
|
111
|
+
The Json serializer communicates with the [Schema Registry][schema_registry] service as needed to register or query schemas and those service calls could throw a [RestError][resterror]. Furthermore, errors of type `Error` will be thrown when serialization or deserialization fails. The `cause` property will contain the underlying error that was thrown from the JSON parser.
|
|
112
|
+
|
|
113
|
+
### Logging
|
|
114
|
+
|
|
115
|
+
Enabling logging may help uncover useful information about failures. In order to
|
|
116
|
+
see a log of HTTP requests and responses, set the `AZURE_LOG_LEVEL` environment
|
|
117
|
+
variable to `info`. Alternatively, logging can be enabled at runtime by calling
|
|
118
|
+
`setLogLevel` in the `@azure/logger`:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
const { setLogLevel } = require("@azure/logger");
|
|
122
|
+
|
|
123
|
+
setLogLevel("info");
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Next steps
|
|
127
|
+
|
|
128
|
+
Please take a look at the
|
|
129
|
+
[samples](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/schemaregistry/schema-registry-json/samples)
|
|
130
|
+
directory for detailed examples on how to use this library.
|
|
131
|
+
|
|
132
|
+
## Contributing
|
|
133
|
+
|
|
134
|
+
This project welcomes contributions and suggestions. Most contributions require
|
|
135
|
+
you to agree to a Contributor License Agreement (CLA) declaring that you have
|
|
136
|
+
the right to, and actually do, grant us the rights to use your contribution. For
|
|
137
|
+
details, visit https://cla.microsoft.com.
|
|
138
|
+
|
|
139
|
+
When you submit a pull request, a CLA-bot will automatically determine whether
|
|
140
|
+
you need to provide a CLA and decorate the PR appropriately (e.g., label,
|
|
141
|
+
comment). Simply follow the instructions provided by the bot. You will only need
|
|
142
|
+
to do this once across all repos using our CLA.
|
|
143
|
+
|
|
144
|
+
This project has adopted the [Microsoft Open Source Code of
|
|
145
|
+
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
|
146
|
+
see the [Code of Conduct
|
|
147
|
+
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
|
148
|
+
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
|
149
|
+
questions or comments.
|
|
150
|
+
|
|
151
|
+
If you'd like to contribute to this library, please read the [contributing
|
|
152
|
+
guide](https://github.com/Azure/azure-sdk-for-js/blob/main/CONTRIBUTING.md) to
|
|
153
|
+
learn more about how to build and test the code.
|
|
154
|
+
|
|
155
|
+
## Related projects
|
|
156
|
+
|
|
157
|
+
- [Microsoft Azure SDK for Javascript](https://github.com/Azure/azure-sdk-for-js)
|
|
158
|
+
|
|
159
|
+

|
|
160
|
+
|
|
161
|
+
[azure_cli]: https://docs.microsoft.com/cli/azure
|
|
162
|
+
[azure_sub]: https://azure.microsoft.com/free/
|
|
163
|
+
[azure_portal]: https://portal.azure.com
|
|
164
|
+
[azure_identity]: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity
|
|
165
|
+
[defaultazurecredential]: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity#defaultazurecredential
|
|
166
|
+
[resterror]: https://docs.microsoft.com/javascript/api/@azure/core-rest-pipeline/resterror?view=azure-node-latest
|
|
167
|
+
[schema_registry]: https://docs.microsoft.com/javascript/api/overview/azure/schema-registry-readme?view=azure-node-latest
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var schemaRegistry = require('@azure/schema-registry');
|
|
6
|
+
var LRUCache = require('lru-cache');
|
|
7
|
+
var logger$1 = require('@azure/logger');
|
|
8
|
+
|
|
9
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
|
+
|
|
11
|
+
var LRUCache__default = /*#__PURE__*/_interopDefaultLegacy(LRUCache);
|
|
12
|
+
|
|
13
|
+
// Copyright (c) Microsoft Corporation.
|
|
14
|
+
// Licensed under the MIT license.
|
|
15
|
+
function isMessageContent(message) {
|
|
16
|
+
const castMessage = message;
|
|
17
|
+
return castMessage.data !== undefined && castMessage.contentType !== undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Copyright (c) Microsoft Corporation.
|
|
21
|
+
// Licensed under the MIT license.
|
|
22
|
+
/** @internal */
|
|
23
|
+
function wrapError(f, message) {
|
|
24
|
+
let result;
|
|
25
|
+
try {
|
|
26
|
+
result = f();
|
|
27
|
+
}
|
|
28
|
+
catch (cause) {
|
|
29
|
+
throw errorWithCause(message, cause);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
/** @internal */
|
|
34
|
+
function errorWithCause(message, cause) {
|
|
35
|
+
return new Error(message,
|
|
36
|
+
// TS v4.6 and below do not yet recognize the cause option in the Error constructor
|
|
37
|
+
// see https://medium.com/ovrsea/power-up-your-node-js-debugging-and-error-handling-with-the-new-error-cause-feature-4136c563126a
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
{ cause });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Copyright (c) Microsoft Corporation.
|
|
44
|
+
/**
|
|
45
|
+
* The \@azure/logger configuration for the schema-registry-json package.
|
|
46
|
+
*/
|
|
47
|
+
const logger = logger$1.createClientLogger("schema-registry-json");
|
|
48
|
+
|
|
49
|
+
// Copyright (c) Microsoft Corporation.
|
|
50
|
+
const jsonMimeType = "application/json";
|
|
51
|
+
const encoder = new TextEncoder();
|
|
52
|
+
const decoder = new TextDecoder();
|
|
53
|
+
function getSchemaObject(schema) {
|
|
54
|
+
return wrapError(() => JSON.parse(schema), `Parsing Json schema failed:\n\n\t${schema}\n\nSee 'cause' for more details.`);
|
|
55
|
+
}
|
|
56
|
+
const cacheOptions = {
|
|
57
|
+
max: 128,
|
|
58
|
+
/**
|
|
59
|
+
* This is needed in order to specify `sizeCalculation` but we do not intend
|
|
60
|
+
* to limit the size just yet.
|
|
61
|
+
*/
|
|
62
|
+
maxSize: Number.MAX_VALUE,
|
|
63
|
+
sizeCalculation: (_value, key) => {
|
|
64
|
+
return key.length;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Json serializer that obtains schemas from a schema registry and does not
|
|
69
|
+
* pack schemas into its payloads.
|
|
70
|
+
*/
|
|
71
|
+
class JsonSerializer {
|
|
72
|
+
/**
|
|
73
|
+
* Creates a new serializer.
|
|
74
|
+
*
|
|
75
|
+
* @param client - Schema Registry where schemas are registered and obtained.
|
|
76
|
+
* Usually this is a SchemaRegistryClient instance.
|
|
77
|
+
*/
|
|
78
|
+
constructor(client, options) {
|
|
79
|
+
this.cacheIdByDefinition = new LRUCache__default["default"](cacheOptions);
|
|
80
|
+
this.cacheById = new LRUCache__default["default"](cacheOptions);
|
|
81
|
+
this.registry = client;
|
|
82
|
+
this.schemaGroup = options === null || options === void 0 ? void 0 : options.groupName;
|
|
83
|
+
this.messageAdapter = options === null || options === void 0 ? void 0 : options.messageAdapter;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* serializes the value parameter according to the input schema and creates a message
|
|
87
|
+
* with the serialized data.
|
|
88
|
+
*
|
|
89
|
+
* @param value - The value to serialize.
|
|
90
|
+
* @param schema - The Json schema to use.
|
|
91
|
+
* @returns A new message with the serialized value. The structure of message is
|
|
92
|
+
* constrolled by the message factory option.
|
|
93
|
+
* @throws {@link Error}
|
|
94
|
+
* Thrown if the schema can not be parsed or the value does not match the schema.
|
|
95
|
+
*/
|
|
96
|
+
async serialize(value, schema) {
|
|
97
|
+
const entry = await this.getSchemaByDefinition(schema);
|
|
98
|
+
const data = wrapError(() => encoder.encode(JSON.stringify(value)), `Json serialization failed. See 'cause' for more details. Schema ID: ${entry.id}`);
|
|
99
|
+
const contentType = `${jsonMimeType}+${entry.id}`;
|
|
100
|
+
return this.messageAdapter
|
|
101
|
+
? this.messageAdapter.produce({
|
|
102
|
+
contentType,
|
|
103
|
+
data,
|
|
104
|
+
})
|
|
105
|
+
: /**
|
|
106
|
+
* If no message consumer was provided, then a MessageContent will be
|
|
107
|
+
* returned. This should work because the MessageT type parameter defaults
|
|
108
|
+
* to MessageContent.
|
|
109
|
+
*/
|
|
110
|
+
{
|
|
111
|
+
data,
|
|
112
|
+
contentType,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Deserializes the payload of the message using the schema ID in the content type
|
|
117
|
+
* field if no schema was provided.
|
|
118
|
+
*
|
|
119
|
+
* @param message - The message with the payload to be deserialized.
|
|
120
|
+
* @returns The deserialized value.
|
|
121
|
+
* @throws {@link Error}
|
|
122
|
+
* Thrown if the deserialization failed, e.g. because reader and writer schemas are incompatible.
|
|
123
|
+
*/
|
|
124
|
+
async deserialize(message, options) {
|
|
125
|
+
const { data, contentType } = convertMessage(message, this.messageAdapter);
|
|
126
|
+
const schemaId = getSchemaId(contentType);
|
|
127
|
+
const schema = await this.getSchemaById(schemaId);
|
|
128
|
+
const returnedMessage = wrapError(() => JSON.parse(decoder.decode(data)), `Json deserialization failed with schema ID (${schemaId}). See 'cause' for more details.`);
|
|
129
|
+
const validate = options === null || options === void 0 ? void 0 : options.validateCallback;
|
|
130
|
+
if (validate) {
|
|
131
|
+
wrapError(() => validate(returnedMessage, schema), `Json validation failed. See 'cause' for more details. Schema ID: ${schemaId}`);
|
|
132
|
+
}
|
|
133
|
+
return returnedMessage;
|
|
134
|
+
}
|
|
135
|
+
async getSchemaById(schemaId) {
|
|
136
|
+
const cached = this.cacheById.get(schemaId);
|
|
137
|
+
if (cached) {
|
|
138
|
+
return cached;
|
|
139
|
+
}
|
|
140
|
+
const schemaResponse = await this.registry.getSchema(schemaId);
|
|
141
|
+
if (!schemaResponse) {
|
|
142
|
+
throw new Error(`Schema with ID '${schemaId}' not found.`);
|
|
143
|
+
}
|
|
144
|
+
if (!schemaResponse.properties.format.match(/^json$/i)) {
|
|
145
|
+
throw new Error(`Schema with ID '${schemaResponse.properties.id}' has format '${schemaResponse.properties.format}', not 'json'.`);
|
|
146
|
+
}
|
|
147
|
+
return this.cache(schemaResponse.definition, schemaId).schema;
|
|
148
|
+
}
|
|
149
|
+
async getSchemaByDefinition(definition) {
|
|
150
|
+
const schemaId = this.cacheIdByDefinition.get(definition);
|
|
151
|
+
if (schemaId) {
|
|
152
|
+
return { id: schemaId, schema: definition };
|
|
153
|
+
}
|
|
154
|
+
if (!this.schemaGroup) {
|
|
155
|
+
throw new Error("Schema group must have been specified in the constructor options when the client was created in order to serialize.");
|
|
156
|
+
}
|
|
157
|
+
const schemaObj = getSchemaObject(definition);
|
|
158
|
+
const description = {
|
|
159
|
+
groupName: this.schemaGroup,
|
|
160
|
+
name: getSchemaName(schemaObj),
|
|
161
|
+
format: schemaRegistry.KnownSchemaFormats.Json,
|
|
162
|
+
definition,
|
|
163
|
+
};
|
|
164
|
+
let id;
|
|
165
|
+
try {
|
|
166
|
+
id = (await this.registry.getSchemaProperties(description)).id;
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
if (e.statusCode === 404) {
|
|
170
|
+
throw errorWithCause(`Schema '${description.name}' not found in registry group '${description.groupName}', or not found to have matching definition.`, e);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
throw e;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return this.cache(definition, id);
|
|
177
|
+
}
|
|
178
|
+
cache(schema, id) {
|
|
179
|
+
const entry = { schema, id };
|
|
180
|
+
this.cacheIdByDefinition.set(schema, id);
|
|
181
|
+
this.cacheById.set(id, schema);
|
|
182
|
+
logger.verbose(`Cache entry added or updated. Total number of entries: ${this.cacheIdByDefinition.size}; Total schema length ${this.cacheIdByDefinition.calculatedSize}`);
|
|
183
|
+
return entry;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function getSchemaId(contentType) {
|
|
187
|
+
const contentTypeParts = contentType.split("+");
|
|
188
|
+
if (contentTypeParts.length !== 2) {
|
|
189
|
+
throw new Error("Content type was not in the expected format of MIME type + schema ID");
|
|
190
|
+
}
|
|
191
|
+
if (contentTypeParts[0] !== jsonMimeType) {
|
|
192
|
+
throw new Error(`Received content of type ${contentTypeParts[0]} but an json serializer may only be used on content that is of '${jsonMimeType}' type`);
|
|
193
|
+
}
|
|
194
|
+
return contentTypeParts[1];
|
|
195
|
+
}
|
|
196
|
+
function convertMessage(message, adapter) {
|
|
197
|
+
const messageConsumer = adapter === null || adapter === void 0 ? void 0 : adapter.consume;
|
|
198
|
+
if (messageConsumer) {
|
|
199
|
+
return messageConsumer(message);
|
|
200
|
+
}
|
|
201
|
+
else if (isMessageContent(message)) {
|
|
202
|
+
return message;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
throw new Error(`Expected either a message adapter to be provided to the serializer or the input message to have data and contentType fields`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function getSchemaName(schema) {
|
|
209
|
+
const id = schema.$id || schema.id;
|
|
210
|
+
if (!id) {
|
|
211
|
+
throw new Error("Schema must have an ID.");
|
|
212
|
+
}
|
|
213
|
+
return id;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
exports.JsonSerializer = JsonSerializer;
|
|
217
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utility.ts","../src/errors.ts","../src/logger.ts","../src/jsonSerializer.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { MessageContent } from \"./models\";\n\nexport function isMessageContent(message: unknown): message is MessageContent {\n const castMessage = message as MessageContent;\n return castMessage.data !== undefined && castMessage.contentType !== undefined;\n}\n","// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\n/** @internal */\nexport function wrapError<T>(f: () => T, message: string): T {\n let result: T;\n try {\n result = f();\n } catch (cause) {\n throw errorWithCause(message, cause as Error);\n }\n return result;\n}\n\n/** @internal */\nexport function errorWithCause(message: string, cause: Error): Error {\n return new Error(\n message,\n // TS v4.6 and below do not yet recognize the cause option in the Error constructor\n // see https://medium.com/ovrsea/power-up-your-node-js-debugging-and-error-handling-with-the-new-error-cause-feature-4136c563126a\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n { cause }\n );\n}\n","// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { createClientLogger } from \"@azure/logger\";\n\n/**\n * The \\@azure/logger configuration for the schema-registry-json package.\n */\nexport const logger = createClientLogger(\"schema-registry-json\");\n","// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport {\n DeserializeOptions,\n JsonSerializerOptions,\n MessageAdapter,\n MessageContent,\n} from \"./models\";\nimport { KnownSchemaFormats, SchemaDescription, SchemaRegistry } from \"@azure/schema-registry\";\nimport { isMessageContent } from \"./utility\";\nimport { errorWithCause, wrapError } from \"./errors\";\nimport LRUCache from \"lru-cache\";\nimport LRUCacheOptions = LRUCache.Options;\nimport { logger } from \"./logger\";\n\nconst jsonMimeType = \"application/json\";\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\ninterface CacheEntry {\n /** Schema ID */\n id: string;\n /** Schema string */\n schema: string;\n}\ninterface SchemaObject {\n id?: string;\n $id?: string;\n $schema?: string;\n}\nfunction getSchemaObject(schema: string): SchemaObject {\n return wrapError(\n () => JSON.parse(schema),\n `Parsing Json schema failed:\\n\\n\\t${schema}\\n\\nSee 'cause' for more details.`\n );\n}\n\nconst cacheOptions: LRUCacheOptions<string, any> = {\n max: 128,\n /**\n * This is needed in order to specify `sizeCalculation` but we do not intend\n * to limit the size just yet.\n */\n maxSize: Number.MAX_VALUE,\n sizeCalculation: (_value: any, key: string) => {\n return key.length;\n },\n};\n\n/**\n * Json serializer that obtains schemas from a schema registry and does not\n * pack schemas into its payloads.\n */\nexport class JsonSerializer<MessageT = MessageContent> {\n /**\n * Creates a new serializer.\n *\n * @param client - Schema Registry where schemas are registered and obtained.\n * Usually this is a SchemaRegistryClient instance.\n */\n constructor(client: SchemaRegistry, options?: JsonSerializerOptions<MessageT>) {\n this.registry = client;\n this.schemaGroup = options?.groupName;\n this.messageAdapter = options?.messageAdapter;\n }\n\n private readonly schemaGroup?: string;\n private readonly registry: SchemaRegistry;\n private readonly messageAdapter?: MessageAdapter<MessageT>;\n private readonly cacheIdByDefinition = new LRUCache<string, string>(cacheOptions);\n private readonly cacheById = new LRUCache<string, string>(cacheOptions);\n\n /**\n * serializes the value parameter according to the input schema and creates a message\n * with the serialized data.\n *\n * @param value - The value to serialize.\n * @param schema - The Json schema to use.\n * @returns A new message with the serialized value. The structure of message is\n * constrolled by the message factory option.\n * @throws {@link Error}\n * Thrown if the schema can not be parsed or the value does not match the schema.\n */\n async serialize(value: unknown, schema: string): Promise<MessageT> {\n const entry = await this.getSchemaByDefinition(schema);\n const data = wrapError(\n () => encoder.encode(JSON.stringify(value)),\n `Json serialization failed. See 'cause' for more details. Schema ID: ${entry.id}`\n );\n const contentType = `${jsonMimeType}+${entry.id}`;\n return this.messageAdapter\n ? this.messageAdapter.produce({\n contentType,\n data,\n })\n : /**\n * If no message consumer was provided, then a MessageContent will be\n * returned. This should work because the MessageT type parameter defaults\n * to MessageContent.\n */\n ({\n data,\n contentType,\n } as MessageContent as unknown as MessageT);\n }\n\n /**\n * Deserializes the payload of the message using the schema ID in the content type\n * field if no schema was provided.\n *\n * @param message - The message with the payload to be deserialized.\n * @returns The deserialized value.\n * @throws {@link Error}\n * Thrown if the deserialization failed, e.g. because reader and writer schemas are incompatible.\n */\n async deserialize(message: MessageT, options?: DeserializeOptions): Promise<unknown> {\n const { data, contentType } = convertMessage(message, this.messageAdapter);\n const schemaId = getSchemaId(contentType);\n const schema = await this.getSchemaById(schemaId);\n const returnedMessage = wrapError(\n () => JSON.parse(decoder.decode(data)),\n `Json deserialization failed with schema ID (${schemaId}). See 'cause' for more details.`\n );\n const validate = options?.validateCallback;\n if (validate) {\n wrapError(\n () => validate(returnedMessage, schema),\n `Json validation failed. See 'cause' for more details. Schema ID: ${schemaId}`\n );\n }\n return returnedMessage;\n }\n\n private async getSchemaById(schemaId: string): Promise<string> {\n const cached = this.cacheById.get(schemaId);\n if (cached) {\n return cached;\n }\n const schemaResponse = await this.registry.getSchema(schemaId);\n if (!schemaResponse) {\n throw new Error(`Schema with ID '${schemaId}' not found.`);\n }\n\n if (!schemaResponse.properties.format.match(/^json$/i)) {\n throw new Error(\n `Schema with ID '${schemaResponse.properties.id}' has format '${schemaResponse.properties.format}', not 'json'.`\n );\n }\n return this.cache(schemaResponse.definition, schemaId).schema;\n }\n\n private async getSchemaByDefinition(definition: string): Promise<CacheEntry> {\n const schemaId = this.cacheIdByDefinition.get(definition);\n if (schemaId) {\n return { id: schemaId, schema: definition };\n }\n if (!this.schemaGroup) {\n throw new Error(\n \"Schema group must have been specified in the constructor options when the client was created in order to serialize.\"\n );\n }\n const schemaObj = getSchemaObject(definition);\n const description: SchemaDescription = {\n groupName: this.schemaGroup,\n name: getSchemaName(schemaObj),\n format: KnownSchemaFormats.Json,\n definition,\n };\n let id: string;\n\n try {\n id = (await this.registry.getSchemaProperties(description)).id;\n } catch (e) {\n if ((e as any).statusCode === 404) {\n throw errorWithCause(\n `Schema '${description.name}' not found in registry group '${description.groupName}', or not found to have matching definition.`,\n e as Error\n );\n } else {\n throw e;\n }\n }\n\n return this.cache(definition, id);\n }\n\n private cache(schema: string, id: string): CacheEntry {\n const entry = { schema, id };\n this.cacheIdByDefinition.set(schema, id);\n this.cacheById.set(id, schema);\n logger.verbose(\n `Cache entry added or updated. Total number of entries: ${this.cacheIdByDefinition.size}; Total schema length ${this.cacheIdByDefinition.calculatedSize}`\n );\n return entry;\n }\n}\n\nfunction getSchemaId(contentType: string): string {\n const contentTypeParts = contentType.split(\"+\");\n if (contentTypeParts.length !== 2) {\n throw new Error(\"Content type was not in the expected format of MIME type + schema ID\");\n }\n if (contentTypeParts[0] !== jsonMimeType) {\n throw new Error(\n `Received content of type ${contentTypeParts[0]} but an json serializer may only be used on content that is of '${jsonMimeType}' type`\n );\n }\n return contentTypeParts[1];\n}\n\nfunction convertMessage<MessageT>(\n message: MessageT,\n adapter?: MessageAdapter<MessageT>\n): MessageContent {\n const messageConsumer = adapter?.consume;\n if (messageConsumer) {\n return messageConsumer(message);\n } else if (isMessageContent(message)) {\n return message;\n } else {\n throw new Error(\n `Expected either a message adapter to be provided to the serializer or the input message to have data and contentType fields`\n );\n }\n}\n\nfunction getSchemaName(schema: SchemaObject): string {\n const id = schema.$id || schema.id;\n if (!id) {\n throw new Error(\"Schema must have an ID.\");\n }\n return id;\n}\n"],"names":["createClientLogger","LRUCache","KnownSchemaFormats"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
/** @internal */
|
|
4
|
+
export function wrapError(f, message) {
|
|
5
|
+
let result;
|
|
6
|
+
try {
|
|
7
|
+
result = f();
|
|
8
|
+
}
|
|
9
|
+
catch (cause) {
|
|
10
|
+
throw errorWithCause(message, cause);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
/** @internal */
|
|
15
|
+
export function errorWithCause(message, cause) {
|
|
16
|
+
return new Error(message,
|
|
17
|
+
// TS v4.6 and below do not yet recognize the cause option in the Error constructor
|
|
18
|
+
// see https://medium.com/ovrsea/power-up-your-node-js-debugging-and-error-handling-with-the-new-error-cause-feature-4136c563126a
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
{ cause });
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,gBAAgB;AAChB,MAAM,UAAU,SAAS,CAAI,CAAU,EAAE,OAAe;IACtD,IAAI,MAAS,CAAC;IACd,IAAI;QACF,MAAM,GAAG,CAAC,EAAE,CAAC;KACd;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,cAAc,CAAC,OAAO,EAAE,KAAc,CAAC,CAAC;KAC/C;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gBAAgB;AAChB,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,KAAY;IAC1D,OAAO,IAAI,KAAK,CACd,OAAO;IACP,mFAAmF;IACnF,iIAAiI;IACjI,6DAA6D;IAC7D,aAAa;IACb,EAAE,KAAK,EAAE,CACV,CAAC;AACJ,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\n/** @internal */\nexport function wrapError<T>(f: () => T, message: string): T {\n let result: T;\n try {\n result = f();\n } catch (cause) {\n throw errorWithCause(message, cause as Error);\n }\n return result;\n}\n\n/** @internal */\nexport function errorWithCause(message: string, cause: Error): Error {\n return new Error(\n message,\n // TS v4.6 and below do not yet recognize the cause option in the Error constructor\n // see https://medium.com/ovrsea/power-up-your-node-js-debugging-and-error-handling-with-the-new-error-cause-feature-4136c563126a\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n { cause }\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,cAAc,UAAU,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nexport { JsonSerializer } from \"./jsonSerializer\";\n\nexport * from \"./models\";\n"]}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
import { KnownSchemaFormats } from "@azure/schema-registry";
|
|
4
|
+
import { isMessageContent } from "./utility";
|
|
5
|
+
import { errorWithCause, wrapError } from "./errors";
|
|
6
|
+
import LRUCache from "lru-cache";
|
|
7
|
+
import { logger } from "./logger";
|
|
8
|
+
const jsonMimeType = "application/json";
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
const decoder = new TextDecoder();
|
|
11
|
+
function getSchemaObject(schema) {
|
|
12
|
+
return wrapError(() => JSON.parse(schema), `Parsing Json schema failed:\n\n\t${schema}\n\nSee 'cause' for more details.`);
|
|
13
|
+
}
|
|
14
|
+
const cacheOptions = {
|
|
15
|
+
max: 128,
|
|
16
|
+
/**
|
|
17
|
+
* This is needed in order to specify `sizeCalculation` but we do not intend
|
|
18
|
+
* to limit the size just yet.
|
|
19
|
+
*/
|
|
20
|
+
maxSize: Number.MAX_VALUE,
|
|
21
|
+
sizeCalculation: (_value, key) => {
|
|
22
|
+
return key.length;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Json serializer that obtains schemas from a schema registry and does not
|
|
27
|
+
* pack schemas into its payloads.
|
|
28
|
+
*/
|
|
29
|
+
export class JsonSerializer {
|
|
30
|
+
/**
|
|
31
|
+
* Creates a new serializer.
|
|
32
|
+
*
|
|
33
|
+
* @param client - Schema Registry where schemas are registered and obtained.
|
|
34
|
+
* Usually this is a SchemaRegistryClient instance.
|
|
35
|
+
*/
|
|
36
|
+
constructor(client, options) {
|
|
37
|
+
this.cacheIdByDefinition = new LRUCache(cacheOptions);
|
|
38
|
+
this.cacheById = new LRUCache(cacheOptions);
|
|
39
|
+
this.registry = client;
|
|
40
|
+
this.schemaGroup = options === null || options === void 0 ? void 0 : options.groupName;
|
|
41
|
+
this.messageAdapter = options === null || options === void 0 ? void 0 : options.messageAdapter;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* serializes the value parameter according to the input schema and creates a message
|
|
45
|
+
* with the serialized data.
|
|
46
|
+
*
|
|
47
|
+
* @param value - The value to serialize.
|
|
48
|
+
* @param schema - The Json schema to use.
|
|
49
|
+
* @returns A new message with the serialized value. The structure of message is
|
|
50
|
+
* constrolled by the message factory option.
|
|
51
|
+
* @throws {@link Error}
|
|
52
|
+
* Thrown if the schema can not be parsed or the value does not match the schema.
|
|
53
|
+
*/
|
|
54
|
+
async serialize(value, schema) {
|
|
55
|
+
const entry = await this.getSchemaByDefinition(schema);
|
|
56
|
+
const data = wrapError(() => encoder.encode(JSON.stringify(value)), `Json serialization failed. See 'cause' for more details. Schema ID: ${entry.id}`);
|
|
57
|
+
const contentType = `${jsonMimeType}+${entry.id}`;
|
|
58
|
+
return this.messageAdapter
|
|
59
|
+
? this.messageAdapter.produce({
|
|
60
|
+
contentType,
|
|
61
|
+
data,
|
|
62
|
+
})
|
|
63
|
+
: /**
|
|
64
|
+
* If no message consumer was provided, then a MessageContent will be
|
|
65
|
+
* returned. This should work because the MessageT type parameter defaults
|
|
66
|
+
* to MessageContent.
|
|
67
|
+
*/
|
|
68
|
+
{
|
|
69
|
+
data,
|
|
70
|
+
contentType,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Deserializes the payload of the message using the schema ID in the content type
|
|
75
|
+
* field if no schema was provided.
|
|
76
|
+
*
|
|
77
|
+
* @param message - The message with the payload to be deserialized.
|
|
78
|
+
* @returns The deserialized value.
|
|
79
|
+
* @throws {@link Error}
|
|
80
|
+
* Thrown if the deserialization failed, e.g. because reader and writer schemas are incompatible.
|
|
81
|
+
*/
|
|
82
|
+
async deserialize(message, options) {
|
|
83
|
+
const { data, contentType } = convertMessage(message, this.messageAdapter);
|
|
84
|
+
const schemaId = getSchemaId(contentType);
|
|
85
|
+
const schema = await this.getSchemaById(schemaId);
|
|
86
|
+
const returnedMessage = wrapError(() => JSON.parse(decoder.decode(data)), `Json deserialization failed with schema ID (${schemaId}). See 'cause' for more details.`);
|
|
87
|
+
const validate = options === null || options === void 0 ? void 0 : options.validateCallback;
|
|
88
|
+
if (validate) {
|
|
89
|
+
wrapError(() => validate(returnedMessage, schema), `Json validation failed. See 'cause' for more details. Schema ID: ${schemaId}`);
|
|
90
|
+
}
|
|
91
|
+
return returnedMessage;
|
|
92
|
+
}
|
|
93
|
+
async getSchemaById(schemaId) {
|
|
94
|
+
const cached = this.cacheById.get(schemaId);
|
|
95
|
+
if (cached) {
|
|
96
|
+
return cached;
|
|
97
|
+
}
|
|
98
|
+
const schemaResponse = await this.registry.getSchema(schemaId);
|
|
99
|
+
if (!schemaResponse) {
|
|
100
|
+
throw new Error(`Schema with ID '${schemaId}' not found.`);
|
|
101
|
+
}
|
|
102
|
+
if (!schemaResponse.properties.format.match(/^json$/i)) {
|
|
103
|
+
throw new Error(`Schema with ID '${schemaResponse.properties.id}' has format '${schemaResponse.properties.format}', not 'json'.`);
|
|
104
|
+
}
|
|
105
|
+
return this.cache(schemaResponse.definition, schemaId).schema;
|
|
106
|
+
}
|
|
107
|
+
async getSchemaByDefinition(definition) {
|
|
108
|
+
const schemaId = this.cacheIdByDefinition.get(definition);
|
|
109
|
+
if (schemaId) {
|
|
110
|
+
return { id: schemaId, schema: definition };
|
|
111
|
+
}
|
|
112
|
+
if (!this.schemaGroup) {
|
|
113
|
+
throw new Error("Schema group must have been specified in the constructor options when the client was created in order to serialize.");
|
|
114
|
+
}
|
|
115
|
+
const schemaObj = getSchemaObject(definition);
|
|
116
|
+
const description = {
|
|
117
|
+
groupName: this.schemaGroup,
|
|
118
|
+
name: getSchemaName(schemaObj),
|
|
119
|
+
format: KnownSchemaFormats.Json,
|
|
120
|
+
definition,
|
|
121
|
+
};
|
|
122
|
+
let id;
|
|
123
|
+
try {
|
|
124
|
+
id = (await this.registry.getSchemaProperties(description)).id;
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
if (e.statusCode === 404) {
|
|
128
|
+
throw errorWithCause(`Schema '${description.name}' not found in registry group '${description.groupName}', or not found to have matching definition.`, e);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
throw e;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return this.cache(definition, id);
|
|
135
|
+
}
|
|
136
|
+
cache(schema, id) {
|
|
137
|
+
const entry = { schema, id };
|
|
138
|
+
this.cacheIdByDefinition.set(schema, id);
|
|
139
|
+
this.cacheById.set(id, schema);
|
|
140
|
+
logger.verbose(`Cache entry added or updated. Total number of entries: ${this.cacheIdByDefinition.size}; Total schema length ${this.cacheIdByDefinition.calculatedSize}`);
|
|
141
|
+
return entry;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function getSchemaId(contentType) {
|
|
145
|
+
const contentTypeParts = contentType.split("+");
|
|
146
|
+
if (contentTypeParts.length !== 2) {
|
|
147
|
+
throw new Error("Content type was not in the expected format of MIME type + schema ID");
|
|
148
|
+
}
|
|
149
|
+
if (contentTypeParts[0] !== jsonMimeType) {
|
|
150
|
+
throw new Error(`Received content of type ${contentTypeParts[0]} but an json serializer may only be used on content that is of '${jsonMimeType}' type`);
|
|
151
|
+
}
|
|
152
|
+
return contentTypeParts[1];
|
|
153
|
+
}
|
|
154
|
+
function convertMessage(message, adapter) {
|
|
155
|
+
const messageConsumer = adapter === null || adapter === void 0 ? void 0 : adapter.consume;
|
|
156
|
+
if (messageConsumer) {
|
|
157
|
+
return messageConsumer(message);
|
|
158
|
+
}
|
|
159
|
+
else if (isMessageContent(message)) {
|
|
160
|
+
return message;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
throw new Error(`Expected either a message adapter to be provided to the serializer or the input message to have data and contentType fields`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function getSchemaName(schema) {
|
|
167
|
+
const id = schema.$id || schema.id;
|
|
168
|
+
if (!id) {
|
|
169
|
+
throw new Error("Schema must have an ID.");
|
|
170
|
+
}
|
|
171
|
+
return id;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=jsonSerializer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonSerializer.js","sourceRoot":"","sources":["../../src/jsonSerializer.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport {\n DeserializeOptions,\n JsonSerializerOptions,\n MessageAdapter,\n MessageContent,\n} from \"./models\";\nimport { KnownSchemaFormats, SchemaDescription, SchemaRegistry } from \"@azure/schema-registry\";\nimport { isMessageContent } from \"./utility\";\nimport { errorWithCause, wrapError } from \"./errors\";\nimport LRUCache from \"lru-cache\";\nimport LRUCacheOptions = LRUCache.Options;\nimport { logger } from \"./logger\";\n\nconst jsonMimeType = \"application/json\";\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\ninterface CacheEntry {\n /** Schema ID */\n id: string;\n /** Schema string */\n schema: string;\n}\ninterface SchemaObject {\n id?: string;\n $id?: string;\n $schema?: string;\n}\nfunction getSchemaObject(schema: string): SchemaObject {\n return wrapError(\n () => JSON.parse(schema),\n `Parsing Json schema failed:\\n\\n\\t${schema}\\n\\nSee 'cause' for more details.`\n );\n}\n\nconst cacheOptions: LRUCacheOptions<string, any> = {\n max: 128,\n /**\n * This is needed in order to specify `sizeCalculation` but we do not intend\n * to limit the size just yet.\n */\n maxSize: Number.MAX_VALUE,\n sizeCalculation: (_value: any, key: string) => {\n return key.length;\n },\n};\n\n/**\n * Json serializer that obtains schemas from a schema registry and does not\n * pack schemas into its payloads.\n */\nexport class JsonSerializer<MessageT = MessageContent> {\n /**\n * Creates a new serializer.\n *\n * @param client - Schema Registry where schemas are registered and obtained.\n * Usually this is a SchemaRegistryClient instance.\n */\n constructor(client: SchemaRegistry, options?: JsonSerializerOptions<MessageT>) {\n this.registry = client;\n this.schemaGroup = options?.groupName;\n this.messageAdapter = options?.messageAdapter;\n }\n\n private readonly schemaGroup?: string;\n private readonly registry: SchemaRegistry;\n private readonly messageAdapter?: MessageAdapter<MessageT>;\n private readonly cacheIdByDefinition = new LRUCache<string, string>(cacheOptions);\n private readonly cacheById = new LRUCache<string, string>(cacheOptions);\n\n /**\n * serializes the value parameter according to the input schema and creates a message\n * with the serialized data.\n *\n * @param value - The value to serialize.\n * @param schema - The Json schema to use.\n * @returns A new message with the serialized value. The structure of message is\n * constrolled by the message factory option.\n * @throws {@link Error}\n * Thrown if the schema can not be parsed or the value does not match the schema.\n */\n async serialize(value: unknown, schema: string): Promise<MessageT> {\n const entry = await this.getSchemaByDefinition(schema);\n const data = wrapError(\n () => encoder.encode(JSON.stringify(value)),\n `Json serialization failed. See 'cause' for more details. Schema ID: ${entry.id}`\n );\n const contentType = `${jsonMimeType}+${entry.id}`;\n return this.messageAdapter\n ? this.messageAdapter.produce({\n contentType,\n data,\n })\n : /**\n * If no message consumer was provided, then a MessageContent will be\n * returned. This should work because the MessageT type parameter defaults\n * to MessageContent.\n */\n ({\n data,\n contentType,\n } as MessageContent as unknown as MessageT);\n }\n\n /**\n * Deserializes the payload of the message using the schema ID in the content type\n * field if no schema was provided.\n *\n * @param message - The message with the payload to be deserialized.\n * @returns The deserialized value.\n * @throws {@link Error}\n * Thrown if the deserialization failed, e.g. because reader and writer schemas are incompatible.\n */\n async deserialize(message: MessageT, options?: DeserializeOptions): Promise<unknown> {\n const { data, contentType } = convertMessage(message, this.messageAdapter);\n const schemaId = getSchemaId(contentType);\n const schema = await this.getSchemaById(schemaId);\n const returnedMessage = wrapError(\n () => JSON.parse(decoder.decode(data)),\n `Json deserialization failed with schema ID (${schemaId}). See 'cause' for more details.`\n );\n const validate = options?.validateCallback;\n if (validate) {\n wrapError(\n () => validate(returnedMessage, schema),\n `Json validation failed. See 'cause' for more details. Schema ID: ${schemaId}`\n );\n }\n return returnedMessage;\n }\n\n private async getSchemaById(schemaId: string): Promise<string> {\n const cached = this.cacheById.get(schemaId);\n if (cached) {\n return cached;\n }\n const schemaResponse = await this.registry.getSchema(schemaId);\n if (!schemaResponse) {\n throw new Error(`Schema with ID '${schemaId}' not found.`);\n }\n\n if (!schemaResponse.properties.format.match(/^json$/i)) {\n throw new Error(\n `Schema with ID '${schemaResponse.properties.id}' has format '${schemaResponse.properties.format}', not 'json'.`\n );\n }\n return this.cache(schemaResponse.definition, schemaId).schema;\n }\n\n private async getSchemaByDefinition(definition: string): Promise<CacheEntry> {\n const schemaId = this.cacheIdByDefinition.get(definition);\n if (schemaId) {\n return { id: schemaId, schema: definition };\n }\n if (!this.schemaGroup) {\n throw new Error(\n \"Schema group must have been specified in the constructor options when the client was created in order to serialize.\"\n );\n }\n const schemaObj = getSchemaObject(definition);\n const description: SchemaDescription = {\n groupName: this.schemaGroup,\n name: getSchemaName(schemaObj),\n format: KnownSchemaFormats.Json,\n definition,\n };\n let id: string;\n\n try {\n id = (await this.registry.getSchemaProperties(description)).id;\n } catch (e) {\n if ((e as any).statusCode === 404) {\n throw errorWithCause(\n `Schema '${description.name}' not found in registry group '${description.groupName}', or not found to have matching definition.`,\n e as Error\n );\n } else {\n throw e;\n }\n }\n\n return this.cache(definition, id);\n }\n\n private cache(schema: string, id: string): CacheEntry {\n const entry = { schema, id };\n this.cacheIdByDefinition.set(schema, id);\n this.cacheById.set(id, schema);\n logger.verbose(\n `Cache entry added or updated. Total number of entries: ${this.cacheIdByDefinition.size}; Total schema length ${this.cacheIdByDefinition.calculatedSize}`\n );\n return entry;\n }\n}\n\nfunction getSchemaId(contentType: string): string {\n const contentTypeParts = contentType.split(\"+\");\n if (contentTypeParts.length !== 2) {\n throw new Error(\"Content type was not in the expected format of MIME type + schema ID\");\n }\n if (contentTypeParts[0] !== jsonMimeType) {\n throw new Error(\n `Received content of type ${contentTypeParts[0]} but an json serializer may only be used on content that is of '${jsonMimeType}' type`\n );\n }\n return contentTypeParts[1];\n}\n\nfunction convertMessage<MessageT>(\n message: MessageT,\n adapter?: MessageAdapter<MessageT>\n): MessageContent {\n const messageConsumer = adapter?.consume;\n if (messageConsumer) {\n return messageConsumer(message);\n } else if (isMessageContent(message)) {\n return message;\n } else {\n throw new Error(\n `Expected either a message adapter to be provided to the serializer or the input message to have data and contentType fields`\n );\n }\n}\n\nfunction getSchemaName(schema: SchemaObject): string {\n const id = schema.$id || schema.id;\n if (!id) {\n throw new Error(\"Schema must have an ID.\");\n }\n return id;\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
import { createClientLogger } from "@azure/logger";
|
|
4
|
+
/**
|
|
5
|
+
* The \@azure/logger configuration for the schema-registry-json package.
|
|
6
|
+
*/
|
|
7
|
+
export const logger = createClientLogger("schema-registry-json");
|
|
8
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,kBAAkB,CAAC,sBAAsB,CAAC,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { createClientLogger } from \"@azure/logger\";\n\n/**\n * The \\@azure/logger configuration for the schema-registry-json package.\n */\nexport const logger = createClientLogger(\"schema-registry-json\");\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/models.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\n/**\n * A message that contains binary data and a content type.\n */\nexport interface MessageContent {\n /**\n * The message's binary data\n */\n data: Uint8Array;\n /**\n * The message's content type\n */\n contentType: string;\n}\n\n/**\n * MessageAdapter is an interface that converts to/from a concrete message type\n * to a MessageContent\n */\nexport interface MessageAdapter<MessageT> {\n /**\n * defines how to create a message from a payload and a content type\n */\n produce: (messageContent: MessageContent) => MessageT;\n /**\n * defines how to access the payload and the content type of a message\n */\n consume: (message: MessageT) => MessageContent;\n}\n\n/**\n * Options for Schema\n */\nexport interface JsonSerializerOptions<MessageT> {\n /**\n * The group name to be used when registering/looking up a schema. Must be specified\n * if `serialize` will be called.\n */\n groupName?: string;\n /**\n * Message Adapter enables the serializer to produce and consume custom messages.\n */\n messageAdapter?: MessageAdapter<MessageT>;\n}\n\n/**\n * The options to the deserialize method.\n */\nexport interface DeserializeOptions {\n /**\n * Validate the value against the schema. Raise an error if the validation is not successful.\n */\n validateCallback?: (value: unknown, schema: string) => void;\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
export function isMessageContent(message) {
|
|
4
|
+
const castMessage = message;
|
|
5
|
+
return castMessage.data !== undefined && castMessage.contentType !== undefined;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=utility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utility.js","sourceRoot":"","sources":["../../src/utility.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAIlC,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,MAAM,WAAW,GAAG,OAAyB,CAAC;IAC9C,OAAO,WAAW,CAAC,IAAI,KAAK,SAAS,IAAI,WAAW,CAAC,WAAW,KAAK,SAAS,CAAC;AACjF,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { MessageContent } from \"./models\";\n\nexport function isMessageContent(message: unknown): message is MessageContent {\n const castMessage = message as MessageContent;\n return castMessage.data !== undefined && castMessage.contentType !== undefined;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@azure/schema-registry-json",
|
|
3
|
+
"version": "1.0.0-alpha.20230809.1",
|
|
4
|
+
"description": "Schema Registry JSON Serializer Library with typescript type definitions for node.js and browser.",
|
|
5
|
+
"sdk-type": "client",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist-esm/src/index.js",
|
|
8
|
+
"types": "types/schema-registry-json.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit",
|
|
11
|
+
"build:browser": "tsc -p . && rollup -c rollup.test.config.js 2>&1",
|
|
12
|
+
"build:node": "tsc -p . && dev-tool run bundle --browser-test=false",
|
|
13
|
+
"build:samples": "echo Obsolete.",
|
|
14
|
+
"build:test": "tsc -p . && rollup -c rollup.test.config.js 2>&1",
|
|
15
|
+
"build": "npm run clean && tsc -p . && dev-tool run bundle && api-extractor run --local",
|
|
16
|
+
"check-format": "prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"",
|
|
17
|
+
"clean": "rimraf dist dist-* temp types *.tgz *.log",
|
|
18
|
+
"execute:samples": "dev-tool samples run samples-dev",
|
|
19
|
+
"extract-api": "tsc -p . && api-extractor run --local",
|
|
20
|
+
"format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"",
|
|
21
|
+
"integration-test:browser": "dev-tool run test:browser",
|
|
22
|
+
"integration-test:node": "dev-tool run test:node-js-input -- --timeout 5000000 'dist-esm/test/**/*.spec.js'",
|
|
23
|
+
"integration-test": "npm run integration-test:node && npm run integration-test:browser",
|
|
24
|
+
"lint:fix": "eslint package.json api-extractor.json README.md src test --ext .ts,.javascript,.js --fix --fix-type [problem,suggestion]",
|
|
25
|
+
"lint": "eslint package.json api-extractor.json README.md src test --ext .ts,.javascript,.js",
|
|
26
|
+
"pack": "npm pack 2>&1",
|
|
27
|
+
"test:browser": "npm run build:test && npm run unit-test:browser && npm run integration-test:browser",
|
|
28
|
+
"test:node": "npm run build:test && npm run unit-test:node && npm run integration-test:node",
|
|
29
|
+
"test": "npm run build:test && npm run unit-test && npm run integration-test",
|
|
30
|
+
"unit-test:browser": "dev-tool run test:browser",
|
|
31
|
+
"unit-test:node": "dev-tool run test:node-ts-input -- --timeout 1200000 \"test/{,!(browser)/**/}*.spec.ts\"",
|
|
32
|
+
"unit-test": "npm run unit-test:node && npm run unit-test:browser"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist/",
|
|
36
|
+
"dist-esm/src/",
|
|
37
|
+
"types/schema-registry-json.d.ts",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"repository": "github:Azure/azure-sdk-for-js",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=14.0.0"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"azure",
|
|
47
|
+
"cloud",
|
|
48
|
+
"typescript"
|
|
49
|
+
],
|
|
50
|
+
"author": "Microsoft Corporation",
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/Azure/azure-sdk-for-js/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/schemaregistry/schema-registry-json/",
|
|
56
|
+
"sideEffects": false,
|
|
57
|
+
"prettier": "@azure/eslint-plugin-azure-sdk/prettier.json",
|
|
58
|
+
"//sampleConfiguration": {
|
|
59
|
+
"disableDocsMs": true,
|
|
60
|
+
"productName": "Azure Schema Registry",
|
|
61
|
+
"productSlugs": [
|
|
62
|
+
"azure",
|
|
63
|
+
"azure-schema-registry-json"
|
|
64
|
+
],
|
|
65
|
+
"requiredResources": {
|
|
66
|
+
"Azure Schema Registry resource": "https://aka.ms/schemaregistry"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@azure/logger": "^1.0.0",
|
|
71
|
+
"@azure/schema-registry": "1.3.0-beta.1",
|
|
72
|
+
"lru-cache": "^7.4.1",
|
|
73
|
+
"tslib": "^2.2.0"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@azure/core-util": "^1.3.0",
|
|
77
|
+
"@azure/dev-tool": ">=1.0.0-alpha <1.0.0-alphb",
|
|
78
|
+
"@azure/eslint-plugin-azure-sdk": ">=3.0.0-alpha <3.0.0-alphb",
|
|
79
|
+
"@azure/event-hubs": "^5.8.0",
|
|
80
|
+
"@azure/identity": "^2.0.1",
|
|
81
|
+
"@azure/test-utils": ">=1.0.0-alpha <1.0.0-alphb",
|
|
82
|
+
"@azure-tools/test-credential": "^1.0.0",
|
|
83
|
+
"@azure-tools/test-recorder": "^3.0.0",
|
|
84
|
+
"@microsoft/api-extractor": "^7.31.1",
|
|
85
|
+
"@rollup/plugin-commonjs": "^24.0.0",
|
|
86
|
+
"@rollup/plugin-inject": "^5.0.0",
|
|
87
|
+
"@types/ajv": "^1.0.0",
|
|
88
|
+
"@types/mocha": "^7.0.2",
|
|
89
|
+
"@types/node": "^16.0.0",
|
|
90
|
+
"ajv": "^8.12.0",
|
|
91
|
+
"cross-env": "^7.0.2",
|
|
92
|
+
"dotenv": "^16.0.0",
|
|
93
|
+
"eslint": "^8.0.0",
|
|
94
|
+
"karma": "^6.2.0",
|
|
95
|
+
"karma-chrome-launcher": "^3.0.0",
|
|
96
|
+
"karma-coverage": "^2.0.0",
|
|
97
|
+
"karma-env-preprocessor": "^0.1.1",
|
|
98
|
+
"karma-firefox-launcher": "^1.1.0",
|
|
99
|
+
"karma-json-preprocessor": "^0.3.3",
|
|
100
|
+
"karma-json-to-file-reporter": "^1.0.1",
|
|
101
|
+
"karma-junit-reporter": "^2.0.1",
|
|
102
|
+
"karma-mocha": "^2.0.1",
|
|
103
|
+
"karma-mocha-reporter": "^2.2.5",
|
|
104
|
+
"karma-sourcemap-loader": "^0.3.8",
|
|
105
|
+
"karma-source-map-support": "~1.4.0",
|
|
106
|
+
"mocha": "^7.1.1",
|
|
107
|
+
"mocha-junit-reporter": "^2.0.0",
|
|
108
|
+
"nyc": "^15.0.0",
|
|
109
|
+
"prettier": "^2.5.1",
|
|
110
|
+
"rimraf": "^3.0.0",
|
|
111
|
+
"rollup": "^2.0.0",
|
|
112
|
+
"source-map-support": "^0.5.9",
|
|
113
|
+
"typescript": "~5.0.0"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { SchemaRegistry } from '@azure/schema-registry';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The options to the deserialize method.
|
|
5
|
+
*/
|
|
6
|
+
export declare interface DeserializeOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Validate the value against the schema. Raise an error if the validation is not successful.
|
|
9
|
+
*/
|
|
10
|
+
validateCallback?: (value: unknown, schema: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Json serializer that obtains schemas from a schema registry and does not
|
|
15
|
+
* pack schemas into its payloads.
|
|
16
|
+
*/
|
|
17
|
+
export declare class JsonSerializer<MessageT = MessageContent> {
|
|
18
|
+
/**
|
|
19
|
+
* Creates a new serializer.
|
|
20
|
+
*
|
|
21
|
+
* @param client - Schema Registry where schemas are registered and obtained.
|
|
22
|
+
* Usually this is a SchemaRegistryClient instance.
|
|
23
|
+
*/
|
|
24
|
+
constructor(client: SchemaRegistry, options?: JsonSerializerOptions<MessageT>);
|
|
25
|
+
private readonly schemaGroup?;
|
|
26
|
+
private readonly registry;
|
|
27
|
+
private readonly messageAdapter?;
|
|
28
|
+
private readonly cacheIdByDefinition;
|
|
29
|
+
private readonly cacheById;
|
|
30
|
+
/**
|
|
31
|
+
* serializes the value parameter according to the input schema and creates a message
|
|
32
|
+
* with the serialized data.
|
|
33
|
+
*
|
|
34
|
+
* @param value - The value to serialize.
|
|
35
|
+
* @param schema - The Json schema to use.
|
|
36
|
+
* @returns A new message with the serialized value. The structure of message is
|
|
37
|
+
* constrolled by the message factory option.
|
|
38
|
+
* @throws {@link Error}
|
|
39
|
+
* Thrown if the schema can not be parsed or the value does not match the schema.
|
|
40
|
+
*/
|
|
41
|
+
serialize(value: unknown, schema: string): Promise<MessageT>;
|
|
42
|
+
/**
|
|
43
|
+
* Deserializes the payload of the message using the schema ID in the content type
|
|
44
|
+
* field if no schema was provided.
|
|
45
|
+
*
|
|
46
|
+
* @param message - The message with the payload to be deserialized.
|
|
47
|
+
* @returns The deserialized value.
|
|
48
|
+
* @throws {@link Error}
|
|
49
|
+
* Thrown if the deserialization failed, e.g. because reader and writer schemas are incompatible.
|
|
50
|
+
*/
|
|
51
|
+
deserialize(message: MessageT, options?: DeserializeOptions): Promise<unknown>;
|
|
52
|
+
private getSchemaById;
|
|
53
|
+
private getSchemaByDefinition;
|
|
54
|
+
private cache;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Options for Schema
|
|
59
|
+
*/
|
|
60
|
+
export declare interface JsonSerializerOptions<MessageT> {
|
|
61
|
+
/**
|
|
62
|
+
* The group name to be used when registering/looking up a schema. Must be specified
|
|
63
|
+
* if `serialize` will be called.
|
|
64
|
+
*/
|
|
65
|
+
groupName?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Message Adapter enables the serializer to produce and consume custom messages.
|
|
68
|
+
*/
|
|
69
|
+
messageAdapter?: MessageAdapter<MessageT>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* MessageAdapter is an interface that converts to/from a concrete message type
|
|
74
|
+
* to a MessageContent
|
|
75
|
+
*/
|
|
76
|
+
export declare interface MessageAdapter<MessageT> {
|
|
77
|
+
/**
|
|
78
|
+
* defines how to create a message from a payload and a content type
|
|
79
|
+
*/
|
|
80
|
+
produce: (messageContent: MessageContent) => MessageT;
|
|
81
|
+
/**
|
|
82
|
+
* defines how to access the payload and the content type of a message
|
|
83
|
+
*/
|
|
84
|
+
consume: (message: MessageT) => MessageContent;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A message that contains binary data and a content type.
|
|
89
|
+
*/
|
|
90
|
+
export declare interface MessageContent {
|
|
91
|
+
/**
|
|
92
|
+
* The message's binary data
|
|
93
|
+
*/
|
|
94
|
+
data: Uint8Array;
|
|
95
|
+
/**
|
|
96
|
+
* The message's content type
|
|
97
|
+
*/
|
|
98
|
+
contentType: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { }
|