Skip to content

Commit

Permalink
feat: port from TypeScript to JS
Browse files Browse the repository at this point in the history
- port from TS to JS
- ESM only support
- use node built-in test runner
- use JsDoc for types
  • Loading branch information
david-luna authored Jan 4, 2024
2 parents c905c4a + 9d9b1b3 commit ebc5fb1
Show file tree
Hide file tree
Showing 98 changed files with 4,613 additions and 16,396 deletions.
65 changes: 35 additions & 30 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"rules": {
"prefer-arrow-callback": "error",
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-param-reassign": "error",
"no-shadow": "error",
"no-console": "error",
"max-classes-per-file": "error",
"complexity": "error",
"no-empty": "error",
"curly": "error",
"max-len": ["error", {"code": 132, "comments": 162}],
"max-lines": ["error", 1000],
"quotes": ["error", "single"],
"newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-magic-numbers": ["error", { "ignore": [-1, 0, 1, 2] }],
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/explicit-module-boundary-types": "error",
"prettier/prettier": ["warn"]
"root": true,
"parserOptions": {
"ecmaVersion": 2022, // Top-level await, etc.
"sourceType": "module",
"ecmaFeatures": {
}
}
},
"env": {
"node": true,
"es2022": true // Defines `Promise`, etc.
},
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
],
"rules": {
"prefer-arrow-callback": "error",
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-param-reassign": "error",
"no-shadow": "error",
"no-console": "error",
"max-classes-per-file": "error",
"complexity": "error",
"no-empty": "error",
"curly": "error",
"max-len": ["error", {"code": 132, "comments": 162}],
"max-lines": ["error", 1000],
"quotes": ["error", "single"],
"newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }],
"prettier/prettier": ["warn"]
},
"ignorePatterns": [
"node_modules",
".tap"
]
}
6 changes: 2 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,17 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 20
registry-url: 'https://registry.npmjs.org'
- name: Lint & Tests
run: |
npm install
npm run lint
npm test
npm test:ci
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build
run: npm run build
- name: Publish
uses: JS-DevTools/npm-publish@v1
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build.yml → .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 20
registry-url: 'https://registry.npmjs.org'
- name: Lint & Tests
run: |
npm install
npm run lint
npm test
npm test:ci
- name: Coveralls
uses: coverallsapp/github-action@master
with:
Expand Down
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,4 @@
node_modules

# test artifacts
/coverage

# build artifacts
/lib
test-report-*
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.9.0
1 change: 0 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"parser": "typescript",
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
Expand Down
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
5 changes: 0 additions & 5 deletions jest.config.js

This file was deleted.

57 changes: 57 additions & 0 deletions lib/factories/combineLatest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { createObsevable } from '../observable.js';

/**
* @typedef {Object} ValueSlot
* @property {import('../types').Subscription} subscription
* @property {unknown} [value]
*/

/**
* Returns true if te object has a property named "value"
* @param {Object} o
* @returns {boolean}
*/
function withValue(o) {
return Object.prototype.hasOwnProperty.call(o, 'value');
}

/**
* @param {...import('../types').Observable<any>} observables
* @returns {import('../types').Observable<any>}
*/
export function combineLatest(...observables) {
return createObsevable((observer) => {
/** @type {ValueSlot[]} */
const slots = [];
let completedSubscriptions = 0;

/** @type {(i: number) => import('../types').Observer<any>} */
const indexedObserver = (index) => ({
next: (value) => {
slots[index].value = value;
if (slots.every(withValue)) {
observer.next(slots.map((s) => s.value));
}
},
error: (err) => {
observer.error(err);
},
complete: () => {
completedSubscriptions++;
if (completedSubscriptions === slots.length) {
observer.complete();
}
},
});

observables.forEach((observable, index) => {
slots.push({ subscription: observable.subscribe(indexedObserver(index)) });
});

return () => {
slots.forEach((slot) => {
slot.subscription.unsubscribe();
});
};
});
}
37 changes: 37 additions & 0 deletions lib/factories/concat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createObsevable } from '../observable.js';

/**
* @param {...import('../types').Observable<any>} observables
* @returns {import('../types').Observable<any>}
*/
export function concat(...observables) {
return createObsevable((observer) => {
/** @type {import('../types').Observer<any>} */
const innerObserver = {
next: (value) => {
observer.next(value);
},
error: (err) => {
observer.error(err);
},
complete: () => {
activeSubscription.unsubscribe();
observables.shift();
if (observables.length === 0) {
observer.complete();
activeSubscription = null;
} else {
activeSubscription = observables[0].subscribe(innerObserver);
}
},
};

let activeSubscription = observables[0].subscribe(innerObserver);

return () => {
if (activeSubscription) {
activeSubscription.unsubscribe();
}
};
});
}
65 changes: 65 additions & 0 deletions lib/factories/from.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Observable } from '../observable.js';

function isPromiseLike(target) {
return typeof target?.then === 'function';
}
function isArrayLike(target) {
return typeof target?.length === 'number';
}
function isObservable(target) {
return typeof target?.subscribe === 'function';
}

/**
* @template T
* @param {PromiseLike<T>} promise
* @returns {import('../types').Observable<T>}
*/
function fromPromise(promise) {
return new Observable((observer) => {
promise.then(
(value) => {
observer.next(value);
observer.complete();
},
(error) => {
observer.error(error);
},
);
});
}

/**
* @template T
* @param {ArrayLike<T>} array
* @returns {import('../types').Observable<T>}
*/
function fromArray(array) {
return new Observable((observer) => {
for (let i = 0; i < array.length; i++) {
observer.next(array[i]);
}
observer.complete();
});
}

/**
* @template T
* @param {import('../types').ObservableInput<T>} input
* @returns {import('../types').Observable<T>}
*/
export function from(input) {
if (isPromiseLike(input)) {
// @ts-ignore
return fromPromise(input);
}
if (isArrayLike(input)) {
// @ts-ignore
return fromArray(input);
}
if (isObservable(input)) {
// @ts-ignore
return input;
}
throw new Error('cannot create an observable for the given input');
}
49 changes: 49 additions & 0 deletions lib/factories/fromEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { createObsevable } from '../observable.js';

/**
* @param {import('../types').EventTargetLike<any>} target
* @param {string} eventName
* @returns {import('../types').Observable<any>}
*/
function fromEventTarget(target, eventName) {
return createObsevable((observer) => {
const handler = (event) => observer.next(event);

target.addEventListener(eventName, handler);
return () => {
target.removeEventListener(eventName, handler);
};
});
}

/**
* @param {import('../types').EventEmitterLike<any>} target
* @param {string} eventName
* @returns {import('../types').Observable<any>}
*/
function fromEventEmitter(target, eventName) {
return createObsevable((observer) => {
const handler = (...args) => observer.next(args);

target.addListener(eventName, handler);
return () => {
target.removeListener(eventName, handler);
};
});
}

// /**
// * Returns an observable from
// * @param {any} target
// * @param {string} eventName
// * @returns {import('../types').Observable<any>}
// */
export function fromEvent(target, eventName) {
if (typeof target.addEventListener === 'function') {
return fromEventTarget(target, eventName);
} else if (typeof target.addListener === 'function') {
return fromEventEmitter(target, eventName);
}

throw new Error('cannot create an observable for the given input');
}
22 changes: 22 additions & 0 deletions lib/factories/fromEventPattern.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createObsevable } from '../observable.js';

/**
* @typedef {(...args: unknown[]) => void} EventHandlerLike
*/

/**
* @template T
* @param {(handler: EventHandlerLike) => unknown} addHandler
* @param {(handler: EventHandlerLike, signal: unknown) => unknown} removeHandler
* @param {(...args: any[]) => T} [project]
* @returns {import('../types').Observable<T>}
*/
export function fromEventPattern(addHandler, removeHandler, project) {
return createObsevable((observer) => {
const handler = (...args) => observer.next(project ? project(...args) : args[0]);
const signal = addHandler(handler);
return () => {
removeHandler(handler, signal);
};
});
}
10 changes: 10 additions & 0 deletions lib/factories/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export * from './combineLatest.js';
export * from './concat.js';
export * from './from.js';
export * from './fromEvent.js';
export * from './fromEventPattern.js';
export * from './interval.js';
export * from './merge.js';
export * from './of.js';
export * from './sequence.js';
export * from './timer.js';
15 changes: 15 additions & 0 deletions lib/factories/interval.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createObsevable } from '../observable.js';

/**
* @param {number} period
* @returns {import('../types').Observable<number>}
*/
export function interval(period) {
return createObsevable((observer) => {
let index = 0;
const id = setInterval(() => observer.next(index++), period);
return () => {
clearInterval(id);
};
});
}
Loading

0 comments on commit ebc5fb1

Please sign in to comment.