Skip to content

Commit

Permalink
Add JSDoc based types
Browse files Browse the repository at this point in the history
Closes GH-9.

Co-authored-by: Christian Murphy <christian.murphy.42@gmail.com>
  • Loading branch information
wooorm and ChristianMurphy authored May 11, 2021
1 parent 5519591 commit 96293f9
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 114 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
*.d.ts
*.log
coverage/
node_modules/
Expand Down
142 changes: 93 additions & 49 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,131 @@
// Create new middleware.
/**
* @typedef {(error?: Error|null|undefined, ...output: unknown[]) => void} Callback
* @typedef {(...input: unknown[]) => unknown} Middleware
*
* @typedef {(...input: unknown[]) => void} Run Call all middleware.
* @typedef {(fn: Middleware) => Pipeline} Use Add `fn` (middleware) to the list.
* @typedef {{run: Run, use: Use}} Pipeline
*/

/**
* Create new middleware.
*
* @returns {Pipeline}
*/
export function trough() {
/** @type {Middleware[]} */
var fns = []
var middleware = {run, use}
/** @type {Pipeline} */
var pipeline = {run, use}

return middleware
return pipeline

// Run `fns`.
// Last argument must be a completion handler.
function run(...input) {
var index = -1
var done = input.pop()
/** @type {Run} */
function run(...values) {
var middlewareIndex = -1
/** @type {Callback} */
// @ts-expect-error Assume it’s a callback.
var callback = values.pop()

if (typeof done !== 'function') {
throw new TypeError('Expected function as last argument, not ' + done)
if (typeof callback !== 'function') {
throw new TypeError('Expected function as last argument, not ' + callback)
}

next(null, ...input)
next(null, ...values)

// Run the next `fn`, if any.
function next(...values) {
var fn = fns[++index]
var error = values.shift()
var pos = -1
/**
* Run the next `fn`, or we’re done.
*
* @param {Error|null|undefined} error
* @param {unknown[]} output
*/
function next(error, ...output) {
var fn = fns[++middlewareIndex]
var index = -1

if (error) {
done(error)
callback(error)
return
}

// Copy non-nullish input into values.
while (++pos < input.length) {
if (values[pos] === null || values[pos] === undefined) {
values[pos] = input[pos]
while (++index < values.length) {
if (output[index] === null || output[index] === undefined) {
output[index] = values[index]
}
}

// Next or done.
if (fn) {
wrap(fn, next)(...values)
wrap(fn, next)(...output)
} else {
done(null, ...values)
callback(null, ...output)
}
}
}

// Add `fn` to the list.
function use(fn) {
if (typeof fn !== 'function') {
throw new TypeError('Expected `fn` to be a function, not ' + fn)
/** @type {Use} */
function use(middelware) {
if (typeof middelware !== 'function') {
throw new TypeError(
'Expected `middelware` to be a function, not ' + middelware
)
}

fns.push(fn)
return middleware
fns.push(middelware)
return pipeline
}
}

// Wrap `fn`.
// Can be sync or async; return a promise, receive a completion handler, return
// new values and errors.
export function wrap(fn, callback) {
/**
* Wrap `middleware`.
* Can be sync or async; return a promise, receive a callback, or return new
* values and errors.
*
* @param {Middleware} middleware
* @param {Callback} callback
*/
export function wrap(middleware, callback) {
/** @type {boolean} */
var called

return wrapped

/**
* Call `middleware`.
* @param {unknown[]} parameters
* @returns {void}
*/
function wrapped(...parameters) {
var callback = fn.length > parameters.length
var fnExpectsCallback = middleware.length > parameters.length
/** @type {unknown} */
var result
/** @type {Error} */
var exception

if (callback) {
if (fnExpectsCallback) {
parameters.push(done)
}

try {
result = fn(...parameters)
result = middleware(...parameters)
} catch (error) {
exception = error

// Well, this is quite the pickle.
// `fn` received a callback and called it (thus continuing the pipeline),
// but later also threw an error.
// We’re not about to restart the pipeline again, so the only thing left
// to do is to throw the thing instead.
if (callback && called) {
throw error
// `middleware` received a callback and called it synchronously, but that
// threw an error.
// The only thing left to do is to throw the thing instead.
if (fnExpectsCallback && called) {
throw exception
}

return done(error)
return done(exception)
}

if (!callback) {
if (result && typeof result.then === 'function') {
if (!fnExpectsCallback) {
if (result instanceof Promise) {
// type-coverage:ignore-next-line Assume it’s a `Promise<unknown>`
result.then(then, done)
} else if (result instanceof Error) {
done(result)
Expand All @@ -97,16 +135,22 @@ export function wrap(fn, callback) {
}
}

// Call `next`, only once.
function done() {
/**
* Call `callback`, only once.
* @type {Callback}
*/
function done(error, ...output) {
if (!called) {
called = true
callback(...arguments)
callback(error, ...output)
}
}

// Call `done` with one value.
// Tracks if an error is passed, too.
/**
* Call `done` with one value.
*
* @param {unknown} [value]
*/
function then(value) {
done(null, value)
}
Expand Down
19 changes: 17 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,30 @@
"sideEffects": false,
"type": "module",
"main": "index.js",
"types": "index.d.ts",
"files": [
"index.d.ts",
"index.js"
],
"devDependencies": {
"c8": "^7.6.0",
"@types/tape": "^4.0.0",
"c8": "^7.0.0",
"prettier": "^2.0.0",
"remark-cli": "^9.0.0",
"remark-preset-wooorm": "^8.0.0",
"rimraf": "^3.0.0",
"tape": "^5.0.0",
"type-coverage": "^2.0.0",
"typescript": "^4.0.0",
"xo": "^0.38.0"
},
"scripts": {
"prepack": "npm run build && npm run format",
"build": "rimraf \"*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node test.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js",
"test": "npm run format && npm run test-coverage"
"test": "npm run build && npm run format && npm run test-coverage"
},
"prettier": {
"tabWidth": 2,
Expand All @@ -48,6 +56,7 @@
"xo": {
"prettier": true,
"rules": {
"capitalized-comments": "off",
"no-var": "off",
"prefer-arrow-callback": "off"
}
Expand All @@ -56,5 +65,11 @@
"plugins": [
"preset-wooorm"
]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true,
"ignoreCatch": true
}
}
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ There is no default export.

Create a new [`Trough`][trough].

### `wrap(middleware, callback[, …input])`
### `wrap(middleware, callback)(…input)`

Call `middleware` with all input.
If `middleware` accepts more arguments than given in input, an extra `done`
Expand Down
Loading

0 comments on commit 96293f9

Please sign in to comment.