respondable-event 0.1.0-main.fbbbe9d

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 William Wong
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,79 @@
1
+ # `respondable-event`
2
+
3
+ Enables event listeners to send response back to the event dispatcher.
4
+
5
+ ## Background
6
+
7
+ Inspired by the [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent) which is available to [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) only, the `RespondableEvent` allows event listeners to send a response back to the dispatching [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget).
8
+
9
+ This is useful in scenarios where custom elements need to communicate with the host asynchronously or infrequently. For persisted messaging, use `RespondableEvent` to set up [Channel Messaging](https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API/Using_channel_messaging) instead.
10
+
11
+ ## How to use
12
+
13
+ The code snippet below sends an `"authenticate"` event to the hosting page.
14
+
15
+ ```ts
16
+ const event = new RespondableEvent(
17
+ 'authenticate',
18
+ token => {
19
+ if (token) {
20
+ // Responded.
21
+ } else {
22
+ // No response.
23
+ }
24
+ },
25
+ { bubbles: true } // Available to the whole page.
26
+ );
27
+
28
+ element.dispatchEvent(event);
29
+
30
+ // If `respondWith()` is not called, `checkResponse()`
31
+ // function will callback with `undefined`.
32
+ event.checkResponse();
33
+
34
+ const token = await authenticate.promise;
35
+ ```
36
+
37
+ In the hosting page, the following code snippet responds with a token.
38
+
39
+ ```ts
40
+ window.addEventListener('authenticate', event => {
41
+ if (event.target === myTrustedElement && event.request === myTrustedRequest) {
42
+ event.respondWith('Hello, World!');
43
+ event.stopPropagation();
44
+ }
45
+ });
46
+ ```
47
+
48
+ The callback function passed to the constructor will be called *at most once*. Same as `FetchEvent`, the `respondWith()` function will throw if it is being called for more than once.
49
+
50
+ ### New `checkResponse` function
51
+
52
+ To reduce code complexity, the `checkResponse()` function guarantees the `callback` function will be called exactly once. The API design is similar to the [`HTMLFormElement.checkValidity()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/checkValidity) function:
53
+
54
+ - If `respondWith()` was called, `checkResponse()` will return `true`.
55
+ - If `respondWith()` was never called, `checkResponse()` will call the `callback` function with `undefined`, and return `false`.
56
+
57
+ It is recommended to put `checkResponse()` immediately after `dispatchEvent()`.
58
+
59
+ ## Designs
60
+
61
+ ### Callback function in constructor
62
+
63
+ The callback function follows the pattern found in [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/MutationObserver) and other observers. It is designed to limit the number of audience who can look at the response.
64
+
65
+ ## Behaviors
66
+
67
+ ### Differences between `RespondableEvent` and `FetchEvent`
68
+
69
+ - `RespondableEvent` extends from [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event), where [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent) extends from [`ExtendableEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent)
70
+ - `request` property is optional in `RespondableEvent` where it is required in [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/request)
71
+ - [`checkResponse()`](#new-checkresponse-function) function is new in `RespondableEvent`
72
+
73
+ ## Contributions
74
+
75
+ Like us? [Star](https://github.com/compulim/respondable-event/stargazers) us.
76
+
77
+ Want to make it better? [File](https://github.com/compulim/respondable-event/issues) us an issue.
78
+
79
+ Don't like something you see? [Submit](https://github.com/compulim/respondable-event/pulls) a pull request.
@@ -0,0 +1,28 @@
1
+ interface RespondableEventInit<T> extends EventInit {
2
+ /** The object that would have triggered the event handler. */
3
+ request: T;
4
+ }
5
+ declare class RespondableEvent<T, R = undefined> extends Event {
6
+ #private;
7
+ constructor(
8
+ /** A string with the name of the event. */
9
+ type: string,
10
+ /** A function which will be called upon respond. */
11
+ callback: (result: T | undefined, event: RespondableEvent<T, R>) => void,
12
+ /**
13
+ * An object that, in addition of the properties defined in [Event()](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event), can have the following properties:
14
+ *
15
+ * - `request`: The object that would have triggered the event handler.
16
+ */
17
+ eventInitDict?: RespondableEventInit<R> | undefined);
18
+ /** The object that would have triggered the event handler. */
19
+ get request(): R | undefined;
20
+ checkResponse(): boolean;
21
+ respondWith(
22
+ /**
23
+ * An object or a Promise that resolves to an object.
24
+ */
25
+ response: T | Promise<T>): void;
26
+ }
27
+
28
+ export { RespondableEvent };
@@ -0,0 +1,28 @@
1
+ interface RespondableEventInit<T> extends EventInit {
2
+ /** The object that would have triggered the event handler. */
3
+ request: T;
4
+ }
5
+ declare class RespondableEvent<T, R = undefined> extends Event {
6
+ #private;
7
+ constructor(
8
+ /** A string with the name of the event. */
9
+ type: string,
10
+ /** A function which will be called upon respond. */
11
+ callback: (result: T | undefined, event: RespondableEvent<T, R>) => void,
12
+ /**
13
+ * An object that, in addition of the properties defined in [Event()](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event), can have the following properties:
14
+ *
15
+ * - `request`: The object that would have triggered the event handler.
16
+ */
17
+ eventInitDict?: RespondableEventInit<R> | undefined);
18
+ /** The object that would have triggered the event handler. */
19
+ get request(): R | undefined;
20
+ checkResponse(): boolean;
21
+ respondWith(
22
+ /**
23
+ * An object or a Promise that resolves to an object.
24
+ */
25
+ response: T | Promise<T>): void;
26
+ }
27
+
28
+ export { RespondableEvent };
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ RespondableEvent: () => RespondableEvent_default
24
+ });
25
+ module.exports = __toCommonJS(src_exports);
26
+
27
+ // src/RespondableEvent.ts
28
+ var RespondableEvent = class extends Event {
29
+ constructor(type, callback, eventInitDict) {
30
+ super(type, eventInitDict);
31
+ this.#request = eventInitDict?.request;
32
+ const resolvers = Promise.withResolvers();
33
+ resolvers.promise.then((result) => callback(result, this));
34
+ this.#resolve = (result) => {
35
+ if (this.#resolved) {
36
+ throw new Error("respondWith() has already been invoked.");
37
+ } else if (!this.target) {
38
+ throw new Error("respondWith() can only be called after dispatched.");
39
+ }
40
+ resolvers.resolve(result);
41
+ this.#resolved = true;
42
+ };
43
+ }
44
+ #request;
45
+ #resolve;
46
+ #resolved = false;
47
+ /** The object that would have triggered the event handler. */
48
+ get request() {
49
+ return this.#request;
50
+ }
51
+ checkResponse() {
52
+ const wasResolved = this.#resolved;
53
+ wasResolved || this.#resolve(void 0);
54
+ return wasResolved;
55
+ }
56
+ respondWith(response) {
57
+ this.#resolve(response);
58
+ }
59
+ };
60
+ var RespondableEvent_default = RespondableEvent;
61
+ // Annotate the CommonJS export names for ESM import in node:
62
+ 0 && (module.exports = {
63
+ RespondableEvent
64
+ });
65
+ //# sourceMappingURL=respondable-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/RespondableEvent.ts"],"sourcesContent":["import RespondableEvent from './RespondableEvent.ts';\n\nexport { RespondableEvent };\n","interface RespondableEventInit<T> extends EventInit {\n /** The object that would have triggered the event handler. */\n request: T;\n}\n\nclass RespondableEvent<T, R = undefined> extends Event {\n constructor(\n /** A string with the name of the event. */\n type: string,\n /** A function which will be called upon respond. */\n callback: (result: T | undefined, event: RespondableEvent<T, R>) => void,\n /**\n * An object that, in addition of the properties defined in [Event()](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event), can have the following properties:\n *\n * - `request`: The object that would have triggered the event handler.\n */\n eventInitDict?: RespondableEventInit<R> | undefined\n ) {\n super(type, eventInitDict);\n\n this.#request = eventInitDict?.request;\n\n const resolvers = Promise.withResolvers<T | undefined>();\n\n resolvers.promise.then(result => callback(result, this));\n\n this.#resolve = (result: Promise<T> | T | undefined) => {\n if (this.#resolved) {\n throw new Error('respondWith() has already been invoked.');\n } else if (!this.target) {\n throw new Error('respondWith() can only be called after dispatched.');\n }\n\n resolvers.resolve(result);\n\n this.#resolved = true;\n };\n }\n\n #request: R | undefined;\n #resolve: (result: Promise<T> | T | undefined) => void;\n #resolved: boolean = false;\n\n /** The object that would have triggered the event handler. */\n get request(): R | undefined {\n return this.#request;\n }\n\n checkResponse(): boolean {\n const wasResolved = this.#resolved;\n\n wasResolved || this.#resolve(undefined);\n\n return wasResolved;\n }\n\n respondWith(\n /**\n * An object or a Promise that resolves to an object.\n */\n response: T | Promise<T>\n ): void {\n this.#resolve(response);\n }\n}\n\nexport default RespondableEvent;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,IAAM,mBAAN,cAAiD,MAAM;AAAA,EACrD,YAEE,MAEA,UAMA,eACA;AACA,UAAM,MAAM,aAAa;AAEzB,SAAK,WAAW,eAAe;AAE/B,UAAM,YAAY,QAAQ,cAA6B;AAEvD,cAAU,QAAQ,KAAK,YAAU,SAAS,QAAQ,IAAI,CAAC;AAEvD,SAAK,WAAW,CAAC,WAAuC;AACtD,UAAI,KAAK,WAAW;AAClB,cAAM,IAAI,MAAM,yCAAyC;AAAA,MAC3D,WAAW,CAAC,KAAK,QAAQ;AACvB,cAAM,IAAI,MAAM,oDAAoD;AAAA,MACtE;AAEA,gBAAU,QAAQ,MAAM;AAExB,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA;AAAA,EACA;AAAA,EACA,YAAqB;AAAA;AAAA,EAGrB,IAAI,UAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,gBAAyB;AACvB,UAAM,cAAc,KAAK;AAEzB,mBAAe,KAAK,SAAS,MAAS;AAEtC,WAAO;AAAA,EACT;AAAA,EAEA,YAIE,UACM;AACN,SAAK,SAAS,QAAQ;AAAA,EACxB;AACF;AAEA,IAAO,2BAAQ;","names":[]}
@@ -0,0 +1,38 @@
1
+ // src/RespondableEvent.ts
2
+ var RespondableEvent = class extends Event {
3
+ constructor(type, callback, eventInitDict) {
4
+ super(type, eventInitDict);
5
+ this.#request = eventInitDict?.request;
6
+ const resolvers = Promise.withResolvers();
7
+ resolvers.promise.then((result) => callback(result, this));
8
+ this.#resolve = (result) => {
9
+ if (this.#resolved) {
10
+ throw new Error("respondWith() has already been invoked.");
11
+ } else if (!this.target) {
12
+ throw new Error("respondWith() can only be called after dispatched.");
13
+ }
14
+ resolvers.resolve(result);
15
+ this.#resolved = true;
16
+ };
17
+ }
18
+ #request;
19
+ #resolve;
20
+ #resolved = false;
21
+ /** The object that would have triggered the event handler. */
22
+ get request() {
23
+ return this.#request;
24
+ }
25
+ checkResponse() {
26
+ const wasResolved = this.#resolved;
27
+ wasResolved || this.#resolve(void 0);
28
+ return wasResolved;
29
+ }
30
+ respondWith(response) {
31
+ this.#resolve(response);
32
+ }
33
+ };
34
+ var RespondableEvent_default = RespondableEvent;
35
+ export {
36
+ RespondableEvent_default as RespondableEvent
37
+ };
38
+ //# sourceMappingURL=respondable-event.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/RespondableEvent.ts"],"sourcesContent":["interface RespondableEventInit<T> extends EventInit {\n /** The object that would have triggered the event handler. */\n request: T;\n}\n\nclass RespondableEvent<T, R = undefined> extends Event {\n constructor(\n /** A string with the name of the event. */\n type: string,\n /** A function which will be called upon respond. */\n callback: (result: T | undefined, event: RespondableEvent<T, R>) => void,\n /**\n * An object that, in addition of the properties defined in [Event()](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event), can have the following properties:\n *\n * - `request`: The object that would have triggered the event handler.\n */\n eventInitDict?: RespondableEventInit<R> | undefined\n ) {\n super(type, eventInitDict);\n\n this.#request = eventInitDict?.request;\n\n const resolvers = Promise.withResolvers<T | undefined>();\n\n resolvers.promise.then(result => callback(result, this));\n\n this.#resolve = (result: Promise<T> | T | undefined) => {\n if (this.#resolved) {\n throw new Error('respondWith() has already been invoked.');\n } else if (!this.target) {\n throw new Error('respondWith() can only be called after dispatched.');\n }\n\n resolvers.resolve(result);\n\n this.#resolved = true;\n };\n }\n\n #request: R | undefined;\n #resolve: (result: Promise<T> | T | undefined) => void;\n #resolved: boolean = false;\n\n /** The object that would have triggered the event handler. */\n get request(): R | undefined {\n return this.#request;\n }\n\n checkResponse(): boolean {\n const wasResolved = this.#resolved;\n\n wasResolved || this.#resolve(undefined);\n\n return wasResolved;\n }\n\n respondWith(\n /**\n * An object or a Promise that resolves to an object.\n */\n response: T | Promise<T>\n ): void {\n this.#resolve(response);\n }\n}\n\nexport default RespondableEvent;\n"],"mappings":";AAKA,IAAM,mBAAN,cAAiD,MAAM;AAAA,EACrD,YAEE,MAEA,UAMA,eACA;AACA,UAAM,MAAM,aAAa;AAEzB,SAAK,WAAW,eAAe;AAE/B,UAAM,YAAY,QAAQ,cAA6B;AAEvD,cAAU,QAAQ,KAAK,YAAU,SAAS,QAAQ,IAAI,CAAC;AAEvD,SAAK,WAAW,CAAC,WAAuC;AACtD,UAAI,KAAK,WAAW;AAClB,cAAM,IAAI,MAAM,yCAAyC;AAAA,MAC3D,WAAW,CAAC,KAAK,QAAQ;AACvB,cAAM,IAAI,MAAM,oDAAoD;AAAA,MACtE;AAEA,gBAAU,QAAQ,MAAM;AAExB,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA;AAAA,EACA;AAAA,EACA,YAAqB;AAAA;AAAA,EAGrB,IAAI,UAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,gBAAyB;AACvB,UAAM,cAAc,KAAK;AAEzB,mBAAe,KAAK,SAAS,MAAS;AAEtC,WAAO;AAAA,EACT;AAAA,EAEA,YAIE,UACM;AACN,SAAK,SAAS,QAAQ;AAAA,EACxB;AACF;AAEA,IAAO,2BAAQ;","names":[]}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "respondable-event",
3
+ "version": "0.1.0-main.fbbbe9d",
4
+ "description": "Enables event listeners to send zero-or-one response back to the event dispatcher.",
5
+ "files": [
6
+ "./dist/"
7
+ ],
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/respondable-event.d.mts",
12
+ "default": "./dist/respondable-event.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/respondable-event.d.ts",
16
+ "default": "./dist/respondable-event.js"
17
+ }
18
+ }
19
+ },
20
+ "main": "./dist/respondable-event.js",
21
+ "typings": "./dist/respondable-event.d.ts",
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "bump": "npm run bump:prod && npm run bump:dev",
25
+ "bump:dev": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.devDependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true",
26
+ "bump:prod": "PACKAGES_TO_BUMP=$(cat package.json | jq -r '(.pinDependencies // {}) as $P | (.localPeerDependencies // {}) as $L | (.dependencies // {}) | to_entries | map(select(.key as $K | $L | has($K) | not)) | map(.key + \"@\" + ($P[.key] // [\"latest\"])[0]) | join(\" \")') && [ ! -z \"$PACKAGES_TO_BUMP\" ] && npm install $PACKAGES_TO_BUMP || true",
27
+ "precommit": "npm run precommit:eslint && npm run precommit:publint && npm run precommit:typescript:production && npm run precommit:typescript:test",
28
+ "precommit:eslint": "ESLINT_USE_FLAT_CONFIG=false eslint ./src/",
29
+ "precommit:publint": "publint",
30
+ "precommit:typescript:production": "tsc --noEmit --project ./src/tsconfig.precommit.production.json",
31
+ "precommit:typescript:test": "tsc --noEmit --project ./src/tsconfig.precommit.test.json",
32
+ "prepack": "cp ../../CHANGELOG.md . && cp ../../LICENSE . && cp ../../README.md .",
33
+ "start": "npm run build -- --onSuccess \"touch ../pages/package.json\" --watch",
34
+ "switch": "cat package.json | jq --arg SWITCH_NAME $SWITCH_NAME -r '(.[\"switch:\" + $SWITCH_NAME] // {}) as $TEMPLATE | .devDependencies += ($TEMPLATE.devDependencies // {}) | .dependencies += ($TEMPLATE.dependencies // {})' | tee ./package.json.tmp && mv ./package.json.tmp ./package.json",
35
+ "test": "jest"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/compulim/respondable-event.git"
40
+ },
41
+ "keywords": [
42
+ "event-target",
43
+ "extendable-event",
44
+ "fetch-event",
45
+ "respond-with"
46
+ ],
47
+ "author": "William Wong (https://github.com/compulim)",
48
+ "license": "MIT",
49
+ "bugs": {
50
+ "url": "https://github.com/compulim/respondable-event/issues"
51
+ },
52
+ "homepage": "https://github.com/compulim/respondable-event#readme",
53
+ "devDependencies": {
54
+ "@babel/core": "^7.26.0",
55
+ "@babel/preset-env": "^7.26.0",
56
+ "@babel/preset-typescript": "^7.26.0",
57
+ "@testing-library/react": "^16.0.1",
58
+ "@tsconfig/recommended": "^1.0.7",
59
+ "@tsconfig/strictest": "^2.0.5",
60
+ "@types/jest": "^29.5.13",
61
+ "babel-jest": "^29.7.0",
62
+ "esbuild": "^0.24.0",
63
+ "jest": "^29.7.0",
64
+ "nodemon": "^3.1.7",
65
+ "prettier": "^3.3.3",
66
+ "publint": "^0.2.11",
67
+ "tsup": "^8.3.0",
68
+ "typescript": "^5.6.3"
69
+ },
70
+ "dependencies": {
71
+ "respondable-event": "^0.1.0-main.fbbbe9d"
72
+ }
73
+ }