diff --git a/.github/workflows/api-docs.yaml b/.github/workflows/api-docs.yaml index e0b2d769..c5f38fd3 100644 --- a/.github/workflows/api-docs.yaml +++ b/.github/workflows/api-docs.yaml @@ -11,7 +11,7 @@ jobs: uses: actions/checkout@v2 - name: Generate API documentation - run: npm install && npm run generate-docs + run: npm install && npm run build:schema && npm run generate-docs - name: Deploy to GitHub Pages if: success() diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index e588b676..a9dce9ee 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -13,4 +13,5 @@ jobs: token: ${{ secrets.CLOUDEVENTS_RELEASES_TOKEN }} release-type: node package-name: cloudevents + signoff: "Lucas Holmquist " changelog-types: '[{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"docs","section":"Documentation","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":false},{"type":"src","section":"Miscellaneous","hidden":false},{"type":"style","section":"Miscellaneous","hidden":false},{"type":"refactor","section":"Miscellaneous","hidden":false},{"type":"perf","section":"Performance","hidden":false},{"type":"test","section":"Tests","hidden":false}]' diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c198457..2b26112a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [8.0.0](https://github.com/cloudevents/sdk-javascript/compare/v7.0.2...v8.0.0) (2023-07-24) + +### ⚠ BREAKING CHANGES + +* use string instead of enum for Version ([#561](https://github.com/cloudevents/sdk-javascript/issues/561)) ([15f6505](https://github.com/cloudevents/sdk-javascript/commit/15f6505a580b2bbf8d6b2e89feea10cbd40ab827)) +TypeScript does not consider enum values equivalent, even if the string +representation is the same. So, when a module imports `cloudevents` and +also has a dependency on `cloudevents` this can cause conflicts where +the `CloudEvent.version` attribute is not considered equal when, in +fact, it is. + +### Miscellaneous + +* add `npm run build:schema` to the doc generation action ([#557](https://github.com/cloudevents/sdk-javascript/issues/557)) ([fa388f7](https://github.com/cloudevents/sdk-javascript/commit/fa388f7dc65c1739864d7a885d6d28111ce07775)) +* modify release-please to use Signed-Off-By on commits ([#559](https://github.com/cloudevents/sdk-javascript/issues/559)) ([089520a](https://github.com/cloudevents/sdk-javascript/commit/089520a4cc8304e39ac9bfccf0ed59c76ea8c11a)) +* release 8.0.0 ([#563](https://github.com/cloudevents/sdk-javascript/issues/563)) ([1ed43c8](https://github.com/cloudevents/sdk-javascript/commit/1ed43c84868ccfd18531deaf6cc9d4e4fcb21a08)) + ## [7.0.2](https://github.com/cloudevents/sdk-javascript/compare/v7.0.1...v7.0.2) (2023-07-05) diff --git a/package-lock.json b/package-lock.json index b9475469..7911bd1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cloudevents", - "version": "7.0.2", + "version": "8.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cloudevents", - "version": "7.0.2", + "version": "8.0.0", "license": "Apache-2.0", "dependencies": { "ajv": "^8.11.0", @@ -55,7 +55,7 @@ "webpack-cli": "^4.10.0" }, "engines": { - "node": ">=16 <=20.0.0" + "node": ">=16 <=20" } }, "node_modules/@ampproject/remapping": { diff --git a/package.json b/package.json index 6cc812cc..3832bfb1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudevents", - "version": "7.0.2", + "version": "8.0.0", "description": "CloudEvents SDK for JavaScript", "main": "dist/index.js", "scripts": { diff --git a/src/event/cloudevent.ts b/src/event/cloudevent.ts index d3cf281d..4da7075a 100644 --- a/src/event/cloudevent.ts +++ b/src/event/cloudevent.ts @@ -12,12 +12,10 @@ import { validateCloudEvent } from "./spec"; import { ValidationError, isBinary, asBase64, isValidType, base64AsBinary } from "./validation"; /** - * An enum representing the CloudEvent specification version + * Constants representing the CloudEvent specification version */ -export const enum Version { - V1 = "1.0", - V03 = "0.3", -} +export const V1 = "1.0"; +export const V03 = "0.3"; /** * A CloudEvent describes event data in common formats to provide @@ -28,7 +26,7 @@ export class CloudEvent implements CloudEventV1 { id: string; type: string; source: string; - specversion: Version; + specversion: string; datacontenttype?: string; dataschema?: string; subject?: string; @@ -69,7 +67,7 @@ export class CloudEvent implements CloudEventV1 { this.source = properties.source as string; delete (properties as any).source; - this.specversion = (properties.specversion as Version) || Version.V1; + this.specversion = (properties.specversion) || V1; delete properties.specversion; this.datacontenttype = properties.datacontenttype; @@ -103,9 +101,9 @@ export class CloudEvent implements CloudEventV1 { delete properties.data; // sanity checking - if (this.specversion === Version.V1 && this.schemaurl) { + if (this.specversion === V1 && this.schemaurl) { throw new TypeError("cannot set schemaurl on version 1.0 event"); - } else if (this.specversion === Version.V03 && this.dataschema) { + } else if (this.specversion === V03 && this.dataschema) { throw new TypeError("cannot set dataschema on version 0.3 event"); } diff --git a/src/event/spec.ts b/src/event/spec.ts index bfab4f3d..042da8ed 100644 --- a/src/event/spec.ts +++ b/src/event/spec.ts @@ -6,12 +6,12 @@ import { ValidationError } from "./validation"; import { CloudEventV1 } from "./interfaces"; -import { Version } from "./cloudevent"; +import { V1 } from "./cloudevent"; import validate from "../schema/v1"; export function validateCloudEvent(event: CloudEventV1): boolean { - if (event.specversion === Version.V1) { + if (event.specversion === V1) { if (!validate(event)) { throw new ValidationError("invalid payload", (validate as any).errors); } diff --git a/src/index.ts b/src/index.ts index e09e9d1a..ab0148db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,13 +3,13 @@ SPDX-License-Identifier: Apache-2.0 */ -import { CloudEvent, Version } from "./event/cloudevent"; +import { CloudEvent, V1, V03 } from "./event/cloudevent"; import { ValidationError } from "./event/validation"; import { CloudEventV1, CloudEventV1Attributes } from "./event/interfaces"; import { Options, TransportFunction, EmitterFunction, emitterFor, Emitter } from "./transport/emitter"; import { httpTransport } from "./transport/http"; -import { +import { Headers, Mode, Binding, HTTP, Kafka, KafkaEvent, KafkaMessage, Message, MQTT, MQTTMessage, MQTTMessageFactory, Serializer, Deserializer } from "./message"; @@ -18,7 +18,8 @@ import CONSTANTS from "./constants"; export { // From event CloudEvent, - Version, + V1, + V03, ValidationError, Mode, HTTP, diff --git a/src/message/http/headers.ts b/src/message/http/headers.ts index 770832f3..b0cc519b 100644 --- a/src/message/http/headers.ts +++ b/src/message/http/headers.ts @@ -6,7 +6,7 @@ import { PassThroughParser, DateParser, MappedParser } from "../../parsers"; import { CloudEventV1 } from "../.."; import { Headers } from "../"; -import { Version } from "../../event/cloudevent"; +import { V1 } from "../../event/cloudevent"; import CONSTANTS from "../../constants"; export const allowedContentTypes = [CONSTANTS.DEFAULT_CONTENT_TYPE, CONSTANTS.MIME_JSON, CONSTANTS.MIME_OCTET_STREAM]; @@ -27,7 +27,7 @@ export const requiredHeaders = [ export function headersFor(event: CloudEventV1): Headers { const headers: Headers = {}; let headerMap: Readonly<{ [key: string]: MappedParser }>; - if (event.specversion === Version.V1) { + if (event.specversion === V1) { headerMap = v1headerMap; } else { headerMap = v03headerMap; diff --git a/src/message/http/index.ts b/src/message/http/index.ts index e4cd7387..cdba2629 100644 --- a/src/message/http/index.ts +++ b/src/message/http/index.ts @@ -5,7 +5,7 @@ import { types } from "util"; -import { CloudEvent, CloudEventV1, CONSTANTS, Mode, Version } from "../.."; +import { CloudEvent, CloudEventV1, CONSTANTS, Mode, V1, V03 } from "../.."; import { Message, Headers, Binding } from ".."; import { @@ -147,7 +147,7 @@ function getVersion(mode: Mode, headers: Headers, body: string | Record).specversion; } } - return Version.V1; + return V1; } /** @@ -155,11 +155,11 @@ function getVersion(mode: Mode, headers: Headers, body: string | Record(message: Message, version: Version): CloudEvent { +function parseBinary(message: Message, version: string): CloudEvent { const headers = { ...message.headers }; let body = message.body; @@ -169,7 +169,7 @@ function parseBinary(message: Message, version: Version): CloudEvent { const sanitizedHeaders = sanitize(headers); const eventObj: { [key: string]: unknown | string | Record } = {}; - const parserMap: Record = version === Version.V03 ? v03binaryParsers : v1binaryParsers; + const parserMap: Record = version === V03 ? v03binaryParsers : v1binaryParsers; for (const header in parserMap) { if (sanitizedHeaders[header]) { @@ -206,11 +206,11 @@ function parseBinary(message: Message, version: Version): CloudEvent { * Creates a new CloudEvent instance based on the provided payload and headers. * * @param {Message} message the incoming Message - * @param {Version} version the spec version of this message (v1 or v03) + * @param {string} version the spec version of this message (v1 or v03) * @returns {CloudEvent} a new CloudEvent instance for the provided headers and payload * @throws {ValidationError} if the payload and header combination do not conform to the spec */ -function parseStructured(message: Message, version: Version): CloudEvent { +function parseStructured(message: Message, version: string): CloudEvent { const payload = message.body; const headers = message.headers; @@ -227,7 +227,7 @@ function parseStructured(message: Message, version: Version): CloudEvent { const incoming = { ...(parser.parse(payload as string) as Record) }; const eventObj: { [key: string]: unknown } = {}; - const parserMap: Record = version === Version.V03 ? v03structuredParsers : v1structuredParsers; + const parserMap: Record = version === V03 ? v03structuredParsers : v1structuredParsers; for (const key in parserMap) { const property = incoming[key]; diff --git a/test/integration/cloud_event_test.ts b/test/integration/cloud_event_test.ts index 829afff8..f45a6f68 100644 --- a/test/integration/cloud_event_test.ts +++ b/test/integration/cloud_event_test.ts @@ -7,7 +7,7 @@ import path from "path"; import fs from "fs"; import { expect } from "chai"; -import { CloudEvent, CloudEventV1, ValidationError, Version } from "../../src"; +import { CloudEvent, CloudEventV1, ValidationError, V1 } from "../../src"; import { asBase64 } from "../../src/event/validation"; const type = "org.cncf.cloudevents.example"; @@ -16,7 +16,7 @@ const id = "b46cf653-d48a-4b90-8dfa-355c01061361"; const fixture = Object.freeze({ id, - specversion: Version.V1, + specversion: V1, source, type, data: `"some data"` @@ -165,7 +165,7 @@ describe("A 1.0 CloudEvent", () => { }); it("can be constructed with an ID", () => { - const ce = new CloudEvent({ id: "1234", specversion: Version.V1, source, type }); + const ce = new CloudEvent({ id: "1234", specversion: V1, source, type }); expect(ce.id).to.equal("1234"); }); @@ -280,7 +280,7 @@ describe("A 1.0 CloudEvent", () => { const obj = JSON.parse(json as string); expect(obj.type).to.equal(type); expect(obj.source).to.equal(source); - expect(obj.specversion).to.equal(Version.V1); + expect(obj.specversion).to.equal(V1); }); it("throws if the provded source is empty string", () => { diff --git a/test/integration/kafka_tests.ts b/test/integration/kafka_tests.ts index ac0e0c47..cb858f49 100644 --- a/test/integration/kafka_tests.ts +++ b/test/integration/kafka_tests.ts @@ -7,7 +7,7 @@ import path from "path"; import fs from "fs"; import { expect } from "chai"; -import { CloudEvent, CONSTANTS, Version } from "../../src"; +import { CloudEvent, CONSTANTS, V1 } from "../../src"; import { asBase64 } from "../../src/event/validation"; import { Message, Kafka, KafkaMessage, KafkaEvent } from "../../src/message"; import { KAFKA_CE_HEADERS } from "../../src/message/kafka/headers"; @@ -43,7 +43,7 @@ const imageData = new Uint32Array(fs.readFileSync(path.join(process.cwd(), "test const image_base64 = asBase64(imageData); const fixture = new CloudEvent({ - specversion: Version.V1, + specversion: V1, id, type, source, @@ -233,7 +233,7 @@ describe("Kafka transport", () => { expect(message.body).to.equal(data); // validate all headers expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype); - expect(message.headers[KAFKA_CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1); + expect(message.headers[KAFKA_CE_HEADERS.SPEC_VERSION]).to.equal(V1); expect(message.headers[KAFKA_CE_HEADERS.ID]).to.equal(id); expect(message.headers[KAFKA_CE_HEADERS.TYPE]).to.equal(type); expect(message.headers[KAFKA_CE_HEADERS.SOURCE]).to.equal(source); @@ -249,7 +249,7 @@ describe("Kafka transport", () => { expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE); // Parse the message body as JSON, then validate the attributes const body = JSON.parse(message.body as string); - expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V1); + expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(V1); expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id); expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type); expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source); diff --git a/test/integration/message_test.ts b/test/integration/message_test.ts index 83502d4f..93699c61 100644 --- a/test/integration/message_test.ts +++ b/test/integration/message_test.ts @@ -8,7 +8,7 @@ import fs from "fs"; import { expect } from "chai"; import { IncomingHttpHeaders } from "http"; -import { CloudEvent, CONSTANTS, Version } from "../../src"; +import { CloudEvent, CONSTANTS, V1, V03 } from "../../src"; import { asBase64 } from "../../src/event/validation"; import { Message, HTTP } from "../../src/message"; @@ -154,7 +154,7 @@ describe("HTTP transport", () => { [CONSTANTS.CE_HEADERS.ID]: "1234", [CONSTANTS.CE_HEADERS.SOURCE]: "test", [CONSTANTS.CE_HEADERS.TYPE]: "test.event", - [CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1, + [CONSTANTS.CE_HEADERS.SPEC_VERSION]: V1, "ce-LUNCH": "tacos", }, }; @@ -237,7 +237,7 @@ describe("HTTP transport", () => { id, type, source, - specversion: Version.V1, + specversion: V1, data: { lunch: "tacos" }, }); const message: Message = { @@ -250,7 +250,7 @@ describe("HTTP transport", () => { describe("Specification version V1", () => { const fixture = new CloudEvent({ - specversion: Version.V1, + specversion: V1, id, type, source, @@ -268,7 +268,7 @@ describe("HTTP transport", () => { expect(message.body).to.equal(JSON.stringify(data)); // validate all headers expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype); - expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1); + expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(V1); expect(message.headers[CONSTANTS.CE_HEADERS.ID]).to.equal(id); expect(message.headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(type); expect(message.headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(source); @@ -284,7 +284,7 @@ describe("HTTP transport", () => { expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE); // Parse the message body as JSON, then validate the attributes const body = JSON.parse(message.body as string); - expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V1); + expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(V1); expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id); expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type); expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source); @@ -353,7 +353,7 @@ describe("HTTP transport", () => { describe("Specification version V03", () => { const fixture = new CloudEvent({ - specversion: Version.V03, + specversion: V03, id, type, source, @@ -371,7 +371,7 @@ describe("HTTP transport", () => { expect(message.body).to.equal(JSON.stringify(data)); // validate all headers expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype); - expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V03); + expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(V03); expect(message.headers[CONSTANTS.CE_HEADERS.ID]).to.equal(id); expect(message.headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(type); expect(message.headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(source); @@ -387,7 +387,7 @@ describe("HTTP transport", () => { expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE); // Parse the message body as JSON, then validate the attributes const body = JSON.parse(message.body as string); - expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V03); + expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(V03); expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id); expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type); expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source); diff --git a/test/integration/mqtt_tests.ts b/test/integration/mqtt_tests.ts index 62d9a8bb..bd4b73fe 100644 --- a/test/integration/mqtt_tests.ts +++ b/test/integration/mqtt_tests.ts @@ -7,7 +7,7 @@ import path from "path"; import fs from "fs"; import { expect } from "chai"; -import { CloudEvent, CONSTANTS, Version, Headers } from "../../src"; +import { CloudEvent, CONSTANTS, V1, Headers } from "../../src"; import { asBase64 } from "../../src/event/validation"; import { Message, MQTT, MQTTMessage } from "../../src/message"; @@ -43,7 +43,7 @@ const image_base64 = asBase64(imageData); const PUBLISH = {"Content Type": "application/json; charset=utf-8"}; const fixture = new CloudEvent({ - specversion: Version.V1, + specversion: V1, id, type, source, @@ -216,7 +216,7 @@ describe("MQTT transport", () => { expect(message.body).to.equal(data); // validate all headers expect(message.headers.datacontenttype).to.equal(datacontenttype); - expect(message.headers.specversion).to.equal(Version.V1); + expect(message.headers.specversion).to.equal(V1); expect(message.headers.id).to.equal(id); expect(message.headers.type).to.equal(type); expect(message.headers.source).to.equal(source); @@ -232,7 +232,7 @@ describe("MQTT transport", () => { expect(message.body).to.equal(data); // validate all headers expect(message["User Properties"]?.datacontenttype).to.equal(datacontenttype); - expect(message["User Properties"]?.specversion).to.equal(Version.V1); + expect(message["User Properties"]?.specversion).to.equal(V1); expect(message["User Properties"]?.id).to.equal(id); expect(message["User Properties"]?.type).to.equal(type); expect(message["User Properties"]?.source).to.equal(source); @@ -249,7 +249,7 @@ describe("MQTT transport", () => { expect(message.body).to.deep.equal(message.payload); expect(message.payload).to.deep.equal(fixture.toJSON()); const body = message.body as Record; - expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V1); + expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(V1); expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id); expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type); expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source); diff --git a/test/integration/sdk_test.ts b/test/integration/sdk_test.ts index 04483c07..de853648 100644 --- a/test/integration/sdk_test.ts +++ b/test/integration/sdk_test.ts @@ -5,13 +5,13 @@ import "mocha"; import { expect } from "chai"; -import { CloudEvent, CloudEventV1, Version } from "../../src"; +import { CloudEvent, CloudEventV1, V1, V03 } from "../../src"; const fixture: CloudEventV1 = { id: "123", type: "org.cloudevents.test", source: "http://cloudevents.io", - specversion: Version.V1, + specversion: V1, }; describe("The SDK Requirements", () => { @@ -25,15 +25,15 @@ describe("The SDK Requirements", () => { expect( new CloudEvent({ ...fixture, - specversion: Version.V03, + specversion: V03, }, false).specversion, - ).to.equal(Version.V03); + ).to.equal(V03); }); }); describe("v1.0", () => { it("should create an event using the right spec version", () => { - expect(new CloudEvent(fixture).specversion).to.equal(Version.V1); + expect(new CloudEvent(fixture).specversion).to.equal(V1); }); }); diff --git a/test/integration/spec_1_tests.ts b/test/integration/spec_1_tests.ts index af899223..7bd3b919 100644 --- a/test/integration/spec_1_tests.ts +++ b/test/integration/spec_1_tests.ts @@ -5,7 +5,7 @@ import "mocha"; import { expect } from "chai"; -import { CloudEvent, Version, ValidationError } from "../../src"; +import { CloudEvent, V1, ValidationError } from "../../src"; import { asBase64 } from "../../src/event/validation"; import Constants from "../../src/constants"; @@ -20,7 +20,7 @@ const data = { const subject = "subject-x0"; const cloudevent = new CloudEvent({ - specversion: Version.V1, + specversion: V1, id, source, type,