From 26d457a2bbc7feb93cf8d3a5cefd2dc1b2186697 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Tue, 16 Jun 2020 13:01:18 -0400 Subject: [PATCH 1/5] BREAKING CHANGE: rewrite module in TypeScript This is a major rewrite of the entire codebase into TypeScript. Nearly all tests have been retained except where behavior is significantly different. Some highlights of these changes: * lowercase all CloudEvent properties and fix base64 encoded data Previously there was a format() function that would convert a CloudEvent object into JSON with all of the properties lowercased. With this rewrite a CloudEvent object can be converted to JSON simply with JSON.stringify(). However, in order to be compliant with the JSON representation outlined in the spec here https://github.com/cloudevents/spec/blob/v1.0/json-format.md all of the event properties must be all lowercase. * lib(transport): make transport mode an Enum * src: allow custom headers (#1) * lib(exports): export explicitly versioned names where appropriate * lib(cloudevent): modify ctor to accept extensions inline * lib(cloudevent): make extensions a part of the event object * test: convert all tests to typescript * examples: update all examples with latest API changes * docs: update README with latest API changes * src: add prettier for code style and fix a lot of linting errors * lib: move data decoding to occur within the CloudEvent object Signed-off-by: Lance Ball --- .eslintrc | 14 +- .gitignore | 1 + .prettierrc.js | 7 + README.md | 51 +- examples/express-ex/index.js | 25 +- examples/typescript-ex/package.json | 1 + examples/typescript-ex/src/index.ts | 28 +- examples/websocket/client.js | 10 +- examples/websocket/index.html | 15 +- examples/websocket/server.js | 27 +- package-lock.json | 1061 +++++------------ package.json | 29 +- src/{lib/bindings/http => }/constants.ts | 63 +- src/event/cloudevent.ts | 168 +++ src/event/index.ts | 4 + .../v03/index.ts => event/v03/cloudevent.ts} | 16 +- src/event/v03/index.ts | 3 + src/event/v03/schema.ts | 74 ++ src/event/v03/spec.ts | 42 + .../v1/index.ts => event/v1/cloudevent.ts} | 21 +- src/event/v1/index.ts | 3 + src/event/v1/schema.ts | 82 ++ src/event/v1/spec.ts | 28 + src/event/validation/index.ts | 2 + src/event/validation/is.ts | 87 ++ src/event/validation/validation_error.ts | 14 + src/index.ts | 15 +- src/lib/bindings/http/emitter_binary.js | 88 -- src/lib/bindings/http/emitter_structured.js | 42 - src/lib/bindings/http/http_emitter.ts | 109 -- src/lib/bindings/http/http_receiver.ts | 88 -- src/lib/bindings/http/receiver_binary.js | 55 - src/lib/bindings/http/receiver_structured.js | 55 - .../bindings/http/v03/emitter_binary_0_3.js | 35 - src/lib/bindings/http/v03/index.js | 9 - .../bindings/http/v03/receiver_binary_0_3.js | 70 -- .../http/v03/receiver_structured_0_3.js | 57 - src/lib/bindings/http/v03/spec_0_3.js | 265 ---- src/lib/bindings/http/v1/emitter_binary_1.js | 33 - src/lib/bindings/http/v1/index.js | 9 - src/lib/bindings/http/v1/receiver_binary_1.js | 62 - .../bindings/http/v1/receiver_structured_1.js | 57 - src/lib/bindings/http/v1/spec_1.js | 227 ---- src/lib/bindings/http/validation/binary.js | 91 -- src/lib/bindings/http/validation/commons.js | 51 - src/lib/bindings/http/validation/fun.js | 88 -- .../bindings/http/validation/structured.js | 52 - .../http/validation/validation_error.js | 23 - src/lib/cloudevent.ts | 314 ----- src/lib/extensions.ts | 3 - src/lib/formats/base64.ts | 18 - src/lib/formats/json/formatter.ts | 15 - src/parsers/base64.ts | 18 + src/parsers/date.ts | 7 + src/parsers/index.ts | 25 + .../json/parser.ts => parsers/json.ts} | 23 +- src/parsers/mapped.ts | 6 + src/parsers/parser.ts | 3 + src/parsers/pass_through.ts | 7 + src/transport/emitter.ts | 70 ++ src/transport/http/binary_emitter.ts | 31 + src/transport/http/binary_receiver.ts | 77 ++ src/transport/http/headers.ts | 109 ++ src/transport/http/index.ts | 2 + src/transport/http/structured_emitter.ts | 20 + src/transport/http/structured_receiver.ts | 83 ++ src/transport/http/v03/headers.ts | 23 + src/transport/http/v03/index.ts | 1 + src/transport/http/v03/parsers.ts | 35 + src/transport/http/v1/headers.ts | 22 + src/transport/http/v1/index.ts | 1 + src/transport/http/v1/parsers.ts | 33 + src/transport/index.ts | 6 + src/transport/protocols.ts | 5 + src/transport/receiver.ts | 101 ++ test-ts/cloud_event_test.ts | 214 ---- test-ts/http_emitter_test.ts | 190 --- .../http/receiver_binary_0_3_tests.js | 436 ------- test/bindings/http/receiver_binary_1_tests.js | 462 ------- .../http/receiver_structured_0_3_test.js | 213 ---- .../http/receiver_structured_1_test.js | 196 --- test/cloud_event_test.ts | 174 +++ test/constants_test.js | 191 --- test/constants_test.ts | 150 +++ test/http_binding_03.ts | 206 ++++ test/http_binding_0_3.js | 245 ---- test/http_binding_1.js | 293 ----- test/http_binding_1.ts | 240 ++++ test/http_emitter_test.ts | 225 ++++ {test-ts => test}/http_receiver_test.ts | 104 +- .../json/parser_test.js => parser_test.ts} | 30 +- test/receiver_binary_03_tests.ts | 434 +++++++ test/receiver_binary_1_tests.ts | 459 +++++++ test/receiver_structured_0_3_test.ts | 205 ++++ test/receiver_structured_1_test.ts | 187 +++ test/sdk_test.js | 46 - test/sdk_test.ts | 44 + test/spec_03_tests.ts | 188 +++ test/spec_0_3_tests.js | 218 ---- test/spec_1_tests.js | 237 ---- test/spec_1_tests.ts | 205 ++++ test/{fun_tests.js => utilities_test.ts} | 61 +- tsconfig.browser.json | 2 +- tsconfig.json | 5 +- 104 files changed, 4508 insertions(+), 5867 deletions(-) create mode 100644 .prettierrc.js rename src/{lib/bindings/http => }/constants.ts (63%) create mode 100644 src/event/cloudevent.ts create mode 100644 src/event/index.ts rename src/{lib/v03/index.ts => event/v03/cloudevent.ts} (95%) create mode 100644 src/event/v03/index.ts create mode 100644 src/event/v03/schema.ts create mode 100644 src/event/v03/spec.ts rename src/{lib/v1/index.ts => event/v1/cloudevent.ts} (93%) create mode 100644 src/event/v1/index.ts create mode 100644 src/event/v1/schema.ts create mode 100644 src/event/v1/spec.ts create mode 100644 src/event/validation/index.ts create mode 100644 src/event/validation/is.ts create mode 100644 src/event/validation/validation_error.ts delete mode 100644 src/lib/bindings/http/emitter_binary.js delete mode 100644 src/lib/bindings/http/emitter_structured.js delete mode 100644 src/lib/bindings/http/http_emitter.ts delete mode 100644 src/lib/bindings/http/http_receiver.ts delete mode 100644 src/lib/bindings/http/receiver_binary.js delete mode 100644 src/lib/bindings/http/receiver_structured.js delete mode 100644 src/lib/bindings/http/v03/emitter_binary_0_3.js delete mode 100644 src/lib/bindings/http/v03/index.js delete mode 100644 src/lib/bindings/http/v03/receiver_binary_0_3.js delete mode 100644 src/lib/bindings/http/v03/receiver_structured_0_3.js delete mode 100644 src/lib/bindings/http/v03/spec_0_3.js delete mode 100644 src/lib/bindings/http/v1/emitter_binary_1.js delete mode 100644 src/lib/bindings/http/v1/index.js delete mode 100644 src/lib/bindings/http/v1/receiver_binary_1.js delete mode 100644 src/lib/bindings/http/v1/receiver_structured_1.js delete mode 100644 src/lib/bindings/http/v1/spec_1.js delete mode 100644 src/lib/bindings/http/validation/binary.js delete mode 100644 src/lib/bindings/http/validation/commons.js delete mode 100644 src/lib/bindings/http/validation/fun.js delete mode 100644 src/lib/bindings/http/validation/structured.js delete mode 100644 src/lib/bindings/http/validation/validation_error.js delete mode 100644 src/lib/cloudevent.ts delete mode 100644 src/lib/extensions.ts delete mode 100644 src/lib/formats/base64.ts delete mode 100644 src/lib/formats/json/formatter.ts create mode 100644 src/parsers/base64.ts create mode 100644 src/parsers/date.ts create mode 100644 src/parsers/index.ts rename src/{lib/formats/json/parser.ts => parsers/json.ts} (54%) create mode 100644 src/parsers/mapped.ts create mode 100644 src/parsers/parser.ts create mode 100644 src/parsers/pass_through.ts create mode 100644 src/transport/emitter.ts create mode 100644 src/transport/http/binary_emitter.ts create mode 100644 src/transport/http/binary_receiver.ts create mode 100644 src/transport/http/headers.ts create mode 100644 src/transport/http/index.ts create mode 100644 src/transport/http/structured_emitter.ts create mode 100644 src/transport/http/structured_receiver.ts create mode 100644 src/transport/http/v03/headers.ts create mode 100644 src/transport/http/v03/index.ts create mode 100644 src/transport/http/v03/parsers.ts create mode 100644 src/transport/http/v1/headers.ts create mode 100644 src/transport/http/v1/index.ts create mode 100644 src/transport/http/v1/parsers.ts create mode 100644 src/transport/index.ts create mode 100644 src/transport/protocols.ts create mode 100644 src/transport/receiver.ts delete mode 100644 test-ts/cloud_event_test.ts delete mode 100644 test-ts/http_emitter_test.ts delete mode 100644 test/bindings/http/receiver_binary_0_3_tests.js delete mode 100644 test/bindings/http/receiver_binary_1_tests.js delete mode 100644 test/bindings/http/receiver_structured_0_3_test.js delete mode 100644 test/bindings/http/receiver_structured_1_test.js create mode 100644 test/cloud_event_test.ts delete mode 100644 test/constants_test.js create mode 100644 test/constants_test.ts create mode 100644 test/http_binding_03.ts delete mode 100644 test/http_binding_0_3.js delete mode 100644 test/http_binding_1.js create mode 100644 test/http_binding_1.ts create mode 100644 test/http_emitter_test.ts rename {test-ts => test}/http_receiver_test.ts (56%) rename test/{formats/json/parser_test.js => parser_test.ts} (65%) create mode 100644 test/receiver_binary_03_tests.ts create mode 100644 test/receiver_binary_1_tests.ts create mode 100644 test/receiver_structured_0_3_test.ts create mode 100644 test/receiver_structured_1_test.ts delete mode 100644 test/sdk_test.js create mode 100644 test/sdk_test.ts create mode 100644 test/spec_03_tests.ts delete mode 100644 test/spec_0_3_tests.js delete mode 100644 test/spec_1_tests.js create mode 100644 test/spec_1_tests.ts rename test/{fun_tests.js => utilities_test.ts} (51%) diff --git a/.eslintrc b/.eslintrc index fcbd09aa..50b266f3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,5 +1,14 @@ { - "extends": "eslint:recommended", + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2015, + "sourceType": "module" + }, + "extends": [ + "plugin:@typescript-eslint/recommended", + "prettier/@typescript-eslint", + "plugin:prettier/recommended" + ], "env": { "es6": true, "node": true, @@ -7,7 +16,6 @@ }, "rules": { "no-var": "error", - "space-before-function-paren": ["error", "never"], "standard/no-callback-literal": "off", "arrow-spacing": "error", "arrow-parens": ["error", "always"], @@ -20,7 +28,7 @@ "no-console": ["error", { "allow": ["warn", "error"] }], - "valid-jsdoc": "error", + "valid-jsdoc": "warn", "semi": ["error", "always"], "quotes": ["error", "double", { "allowTemplateLiterals": true }] } diff --git a/.gitignore b/.gitignore index 016ed4a8..8fa1cf23 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ index.js /lib /browser /bundles +/dist # Runtime data pids diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..d86a2f36 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + trailingComma: "all", + dolubleQuote: true, + printWidth: 120, + tabWidth: 2 +} \ No newline at end of file diff --git a/README.md b/README.md index b2b79b1a..7c1dbc88 100644 --- a/README.md +++ b/README.md @@ -36,33 +36,32 @@ binary and structured events in either the 1.0 or 0.3 protocol formats. ```js const { CloudEvent, - HTTPReceiver + Receiver } = require("cloudevents-sdk"); // Create a receiver to accept events over HTTP -const receiver = new HTTPReceiver(); +const receiver = new Receiver(); // body and headers come from an incoming HTTP request, e.g. express.js const receivedEvent = receiver.accept(req.headers, req.body); -console.log(receivedEvent.format()); +console.log(receivedEvent); ``` #### Emitting Events -To emit events, you'll need to decide whether the event should be sent in -binary or structured format, and determine what version of the CloudEvents -specification you want to send the event as. +You can send events over HTTP in either binary or structured format. -By default, the `HTTPEmitter` will emit events over HTTP POST using the -latest supported specification version, in binary mode. You can emit version specific events by providing -the specication version in the constructor to `HTTPEmitter`. To send -structured events, add that string as a parameter to `emitter.send()`. +By default, the `Emitter` will emit events over HTTP POST using the +binary transport protocol. The `Emitter` will examine the `specversion` +of the event being sent, and use the appropriate protocol version. To send +structured events, add `Protocol.HTTPStructured` as a parameter to +`emitter.send()`. ```js -const { CloudEvent, HTTPEmitter } = require("cloudevents-sdk"); +const { CloudEvent, Emitter, Protocol } = require("cloudevents-sdk"); // With only an endpoint URL, this creates a v1 emitter -const v1Emitter = new HTTPEmitter({ +const emitter = new Emitter({ url: "https://cloudevents.io/example" }); const event = new CloudEvent({ @@ -70,39 +69,37 @@ const event = new CloudEvent({ }); // By default, the emitter will send binary events -v1Emitter.send(event).then((response) => { +emitter.send(event).then((response) => { // handle the response }).catch(console.error); // To send a structured event, just add that as an option -v1Emitter.send(event, { mode: "structured" }) +emitter.send(event, { protocol: Protocol.HTTPStructured }) .then((response) => { // handle the response }).catch(console.error); // To send an event to an alternate URL, add that as an option -v1Emitter.send(event, { url: "https://alternate.com/api" }) +emitter.send(event, { url: "https://alternate.com/api" }) .then((response) => { // handle the response }).catch(console.error); -// Sending a v0.3 event works the same, just let the emitter know when -// you create it that you are working with the 0.3 spec -const v03Emitter = new HTTPEmitter({ - url: "https://cloudevents.io/example", - version: "0.3" -}); - -// Again, the default is to send binary events -// To send a structured event or to an alternate URL, provide those -// as parameters in a options object as above -v3Emitter.send(event) +// Sending a v0.3 event works the same, If your event has a +// specversion property of Version.V03, then it will be sent +// using the 0.3 transport protocol +emitter.send(new CloudEvent({ specversion: Version.V03, source, type })) .then((response) => { // handle the response }).catch(console.error); - ``` +### Example Applications + +There are a few trivial example applications in +[the examples folder](https://github.com/cloudevents/sdk-javascript/tree/master/examples). +There you will find Express.js, TypeScript and Websocket examples. + ## Supported specification features | | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | diff --git a/examples/express-ex/index.js b/examples/express-ex/index.js index 3aa3b0d9..da72cf89 100644 --- a/examples/express-ex/index.js +++ b/examples/express-ex/index.js @@ -1,42 +1,39 @@ /* eslint-disable no-console */ const express = require("express"); -const { HTTPReceiver } = require("cloudevents-sdk"); +const { Receiver } = require("cloudevents-sdk"); const app = express(); -const receiver = new HTTPReceiver(); +const receiver = new Receiver(); app.use((req, res, next) => { let data = ""; req.setEncoding("utf8"); - req.on("data", function(chunk) { + req.on("data", function (chunk) { data += chunk; }); - req.on("end", function() { + req.on("end", function () { req.body = data; next(); }); }); -app.post("/", function(req, res) { - console.log(req.headers); - console.log(req.body); +app.post("/", function (req, res) { + console.log("HEADERS", req.headers); + console.log("BODY", req.body); try { const event = receiver.accept(req.headers, req.body); - const asJSON = event.format(); - console.log(`Accepted event: ${JSON.stringify(event.format(), null, 2)}`); - res.status(201).json(asJSON); + console.log(`Accepted event: ${event}`); + res.status(201).json(event); } catch (err) { console.error(err); - res.status(415) - .header("Content-Type", "application/json") - .send(JSON.stringify(err)); + res.status(415).header("Content-Type", "application/json").send(JSON.stringify(err)); } }); -app.listen(3000, function() { +app.listen(3000, function () { console.log("Example app listening on port 3000!"); }); diff --git a/examples/typescript-ex/package.json b/examples/typescript-ex/package.json index 365267e4..c577e5fb 100644 --- a/examples/typescript-ex/package.json +++ b/examples/typescript-ex/package.json @@ -16,6 +16,7 @@ "check": "gts check", "clean": "gts clean", "compile": "tsc -p .", + "watch": "tsc -p . --watch", "fix": "gts fix", "prepare": "npm run compile", "pretest": "npm run compile", diff --git a/examples/typescript-ex/src/index.ts b/examples/typescript-ex/src/index.ts index 27149f6e..0a871ad3 100644 --- a/examples/typescript-ex/src/index.ts +++ b/examples/typescript-ex/src/index.ts @@ -1,35 +1,33 @@ -import { CloudEvent, HTTPReceiver } from "cloudevents-sdk"; -import { CloudEventV1 } from "cloudevents-sdk/lib/v1"; +import { CloudEvent, CloudEventV1, Receiver } from "cloudevents-sdk"; export function doSomeStuff() { - const receiver = new HTTPReceiver(); + const receiver = new Receiver(); const myevent: CloudEventV1 = new CloudEvent({ source: "/source", type: "type", - dataContentType: "text/plain", - dataSchema: "https://d.schema.com/my.json", + datacontenttype: "text/plain", + dataschema: "https://d.schema.com/my.json", subject: "cha.json", - data: "my-data" + data: "my-data", }); - myevent.addExtension("extension-1", "some extension data"); + myevent.extension1 = "some extension data"; - console.log("My structured event:", myevent.toString()); - console.log("My structured event extensions:", myevent.getExtensions()); + console.log("My structured event:", myevent); // ------ receiver structured // The header names should be standarized to use lowercase const headers = { - "content-type": "application/cloudevents+json" + "content-type": "application/cloudevents+json", }; // Typically used with an incoming HTTP request where myevent.format() is the actual // body of the HTTP - console.log("Received structured event:", receiver.accept(headers, myevent.format()).toString()); + console.log("Received structured event:", receiver.accept(headers, myevent)); // ------ receiver binary const data = { - "data": "dataString" + data: "dataString", }; const attributes = { "ce-type": "type", @@ -39,11 +37,11 @@ export function doSomeStuff() { "ce-time": "2019-06-16T11:42:00Z", "ce-dataschema": "http://schema.registry/v1", "Content-Type": "application/json", - "ce-extension1": "extension1" + "ce-extension1": "extension1", }; - console.log("My binary event:", receiver.accept(attributes, data).toString()); - console.log("My binary event extensions:", receiver.accept(attributes, data).toString()); + console.log("My binary event:", receiver.accept(attributes, data)); + console.log("My binary event extensions:", receiver.accept(attributes, data)); return true; } diff --git a/examples/websocket/client.js b/examples/websocket/client.js index ea4c39f3..eb949c8f 100644 --- a/examples/websocket/client.js +++ b/examples/websocket/client.js @@ -7,7 +7,7 @@ const { CloudEvent } = require("cloudevents-sdk"); const rl = readline.createInterface({ input: process.stdin, - output: process.stdout + output: process.stdout, }); rl.on("close", (_) => console.log("\n\nConnection closed! Press CTL-C to exit.")); @@ -25,16 +25,16 @@ ws.on("message", function incoming(message) { function ask() { rl.question("Would you like to see the current weather? Provide a zip code: ", function (zip) { console.log("Fetching weather data from server..."); - ws.send(new CloudEvent({ + const event = new CloudEvent({ type: "weather.query", source: "/weather.client", - data: zip - }).toString()); + data: { zip }, + }); + ws.send(event.toString()); }); } function print(data) { - data = JSON.parse(data); console.log(` Current weather for ${data.name}: ${data.weather[0].main} ------------------------------------------ diff --git a/examples/websocket/index.html b/examples/websocket/index.html index 4a62c4dc..da6ba43d 100644 --- a/examples/websocket/index.html +++ b/examples/websocket/index.html @@ -2,13 +2,14 @@ CloudEvent Example - +