Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds JSDoc support for EventManager #327

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 57 additions & 27 deletions src/events.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,98 @@
// @ts-check
const e = require("./errors");
const v = require("./validations");

/**
* @typedef {import("..").ElectroEventListener} ElectroEventListener
* @typedef {import("..").ElectroEvent} ElectroEvent
*/

/**
* @see {@link https://electrodb.dev/en/reference/events-logging/ | Events and Logging} for more information.
* @class
*/
class EventManager {
/**
* @type {Array<ElectroEventListener>}
* */
#listeners;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the class to use private properties


/**
* Wraps the provided listener in order to safely invoke it.
* @static
* @template {(...args: any[]) => void} T
* @param {T} [listener] - The listener to wrap.
*/
static createSafeListener(listener) {
if (listener === undefined) {
return undefined;
// no-op
return () => {};
Comment on lines +28 to +29
Copy link
Author

@niklaswallerstedt niklaswallerstedt Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not get the filter to understand that undefined would have been removed even with type guards, so changed it to a no-op and removed the filter entirely.

}

if (!v.isFunction(listener)) {
throw new e.ElectroError(
e.ErrorCodes.InvalidListenerProvided,
`Provided listener is not of type 'function'`,
);
} else {
return (...params) => {
try {
listener(...params);
} catch (err) {
console.error(`Error invoking user supplied listener`, err);
}
};
}

/** @param {Parameters<T>} args */
return (...args) => {
try {
listener(...args);
} catch (err) {
console.error(`Error invoking user supplied listener`, err);
}
};
}

/**
* @static
* @template {(...args: any[]) => void} T
* @param {T[]} [listeners=[]]
*/
static normalizeListeners(listeners = []) {
if (!Array.isArray(listeners)) {
throw new e.ElectroError(
e.ErrorCodes.InvalidListenerProvided,
`Listeners must be provided as an array of functions`,
);
}
return listeners
.map((listener) => EventManager.createSafeListener(listener))
.filter((listener) => {
switch (typeof listener) {
case "function":
return true;
case "undefined":
return false;
default:
throw new e.ElectroError(
e.ErrorCodes.InvalidListenerProvided,
`Provided listener is not of type 'function`,
);
}
});

return listeners.map((listener) =>
EventManager.createSafeListener(listener),
);
}

/**
* @constructor
* @param {Object} [config={}]
* @param {Array<ElectroEventListener>} [config.listeners=[]] An array of listeners to be invoked after certain request lifecycles.
*/
constructor({ listeners = [] } = {}) {
this.listeners = EventManager.normalizeListeners(listeners);
this.#listeners = EventManager.normalizeListeners(listeners);
}

/**
* @param {ElectroEventListener | Array<ElectroEventListener>} [listeners=[]]
*/
add(listeners = []) {
if (!Array.isArray(listeners)) {
listeners = [listeners];
}

this.listeners = this.listeners.concat(
this.#listeners = this.#listeners.concat(
EventManager.normalizeListeners(listeners),
);
}

/**
* @param {ElectroEvent} event
* @param {Array<ElectroEventListener>} [adHocListeners=[]]
*/
trigger(event, adHocListeners = []) {
const allListeners = [
...this.listeners,
...this.#listeners,
...EventManager.normalizeListeners(adHocListeners),
];

Expand Down
14 changes: 10 additions & 4 deletions test/events.spec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
const { expect } = require("chai");
const { EventManager } = require("../src/events");

Expand All @@ -13,24 +14,29 @@ describe("safe listeners", () => {
it("should ignore undefined callbacks", () => {
const fn = undefined;
const safeFn = EventManager.createSafeListener(fn);
expect(safeFn).to.equal(undefined);
expect(safeFn).to.be.a("function");
});

it("should filter out undefined elements", () => {
it("should convert undefined elements to no-op functions", () => {
/** @type {any[]} */
const fns = [() => {}, () => {}, undefined, () => {}];
const normalized = EventManager.normalizeListeners(fns);
expect(normalized).to.be.an("array").with.length(3);
expect(normalized).to.be.an("array").with.length(4);
expect(normalized).to.not.include(undefined);
});

it("should throw if element is not function or undefined", () => {
/** @type {any[]} */
const fns = [() => {}, () => {}, undefined, 123];
expect(() => EventManager.normalizeListeners(fns)).to.throw(
`Provided listener is not of type 'function' - For more detail on this error reference: https://electrodb.dev/en/reference/errors/#invalid-listener-provided`,
);
});

it("should throw if provided parameter is not array", () => {
expect(() => EventManager.normalizeListeners(1234)).to.throw(
/** @type {any} */
const arg = 1234;
expect(() => EventManager.normalizeListeners(arg)).to.throw(
`Listeners must be provided as an array of functions - For more detail on this error reference: https://electrodb.dev/en/reference/errors/#invalid-listener-provided`,
);
});
Expand Down
7 changes: 4 additions & 3 deletions test/offline.entity.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1751,8 +1751,8 @@ describe("Entity", () => {
let unit = "B54";
let leaseEnd = "2020-01-20";
let rent = "0.00";
buildingOne = "BuildingA";
buildingTwo = "BuildingF";
let buildingOne = "BuildingA";
let buildingTwo = "BuildingF";
let get = MallStores.get({ id });
expect(get).to.have.keys(
"commit",
Expand Down Expand Up @@ -5403,7 +5403,8 @@ describe("Entity", () => {
});
it("Should add filtered fields to the between params", () => {
let mall = "EastPointe";
let building = "BuildingA";
let buildingOne = "BuildingA";
let buildingTwo = "BuildingF";
let lowRent = "50.00";
let beginning = "20200101";
let end = "20200401";
Expand Down
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": ["ESNext"], /* Specify library files to be included in the compilation. */
"resolveJsonModule": true,
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"allowJs": true, /* Allow javascript files to be compiled. */
//"checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
Expand Down
4 changes: 3 additions & 1 deletion www/src/pages/en/modeling/indexes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ indexes: {

### Composite Attribute Templates

You may have found examples online that demonstrate how to make keys for Single Table Design. These patterns often look like `user#${id}` or `org#${id}`. ElectroDB creates keys similar to these patterns out of the box without the need for using "template". It is _highly_ recommended to only use "template" when you are attempting to use ElectroDB on an existing table/dataset. If you are starting a new project, you should not need to use "template", and using it will limit some protections and features granted by ElectroDB.
export const key = "id"

You may have found examples online that demonstrate how to make keys for Single Table Design. These patterns often look like <code>user#{key}</code> or <code>org#{key}</code>. ElectroDB creates keys similar to these patterns out of the box without the need for using "template". It is _highly_ recommended to only use "template" when you are attempting to use ElectroDB on an existing table/dataset. If you are starting a new project, you should not need to use "template", and using it will limit some protections and features granted by ElectroDB.

With a Composite Template, you provide a formatted template for ElectroDB to use when making keys. Composite Attribute Templates allow for potential ElectroDB adoption on already established tables and records.

Expand Down