
Security News
/Research
Wallet-Draining npm Package Impersonates Nodemailer to Hijack Crypto Transactions
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
@protoplasm/recall
Advanced tools
npm i @protoplasm/recall
this package lets you memoize functions and generators.
main entry points:
recall
memoizes functionsreplay
memoizes generatorsreport
reports non-fatal errors and messages which will also be memoized by recall
and replay
recall
memoizes functionsrecall
takes a function and returns a memoized version:
import recall from '@protoplasm/recall'
let calls = 0;
const hi = recall((data?: any) => {
++calls
return { hello: 'world', data }
})
expect(hi()).toBe(hi())
expect(calls).toBe(1)
expect(hi(42)).toBe(hi(42))
expect(calls).toBe(2)
recall
ed functions cache both normal and exceptional return paths. if the underlying function throws the first time it's called for a set of arguments, it will always throw the same error when invoked again.
arguments are shallowly compared by ===
. there is no way to change this.
getResult
returns the stored resultrecall
ed functions also make Result
s available via a .getResult
method. getResult
has the same signature as the underlying function, but returns Result<T>
rather than T
:
const evenSquare = recall((n: number) => {
if (n % 2 !== 0) throw new Error('odd numbers unsupported')
return n ** 2
})
evenSquare.getResult(2).isReturn() // -> true
evenSquare.getResult(2).data // -> 4
evenSquare.getResult(2).unwrap() // -> 4
evenSquare.getResult(3).isReturn() // -> false
evenSquare.getResult(3).isThrow() // -> true
evenSquare.getResult(3).error // -> Error: odd numbers unsupported
evenSquare.getResult(3).unwrap() // !! throws Error: odd numbers unsupported
getExisting
soft queries the cachethe .getExisting
method works just like .getResult
, only it will return undefined
rather than calling the underlying if no entry for the arguments exists in the cache.
replay
memoizes generatorsif you try to use recall
on a generator, you're gonna have a bad time:
const gen = recall(function *() {
yield 1
yield 2
yield 3
})
gen() === gen() // -> true
[...gen()] // -> [1, 2, 3]
[...gen()] // -> [] (shit.)
this happens because generators return iterators, which are consumed as you iterate over them.
replay
fixes this:
const gen = replay(function *() {
yield 1
yield 2
yield 3
})
gen() === gen() // -> true
[...gen()] // -> [1, 2, 3]
[...gen()] // -> [1, 2, 3]
replay
wraps the generator in an Iterable
which lazily stores each value the generator emits.
report
ing errorsreport
lets functions report messages independent of how they return. for example, you can report
errors while still returning data:
import { report } from '@protoplasm/recall'
function errorsAndData() {
report(new Error('something bad happened'))
report(new Error('something else bad happened'))
return "but it's still ok"
}
getResult
collects all messages report
ed from a block. it returns these as part of a Result
:
import { Result, getResult } from '@protoplasm/recall'
const result: Result<string> = getResult(() => errorsAndData())
for (const error of result.errors()) {
console.log(error) // -> 'something bad happened'
// -> 'something else bad happened'
}
result.unwrap() // -> 'but it's still ok'
report
s bubble upreport
works no matter how deep you are in the call tree:
function a() {
report(new Error("error from a"))
b()
}
function b() {
report(new Error("error from b"));
threeThingsFail();
}
function threeThingsFail() {
report(new Error("a"));
report(new Error("b"));
report(new Error("c"));
}
const result = getResult(() => {
a()
report(new Error('one last problem'))
})
result.errors()
// -> error from a
// -> error from b
// -> a
// -> b
// -> c
// -> one last problem
recall
memoizes pure functionsthe simplest case:
const sum = recall((ary: number[]) => ary.reduce((a, b) => a + b))
const a = [1, 2, 3, 4]
sum(a)
sum(a) // cache hit
recall
composite keysyou can use recall
as a composite key map:
const song = recall((artist: string, title: string) => new Song(artist, title))
const coversOf = recall((song: Song) => [])
coversOf(song("Cher", "Believe")).push(song("Okay Kaya", "Believe"))
coversOf(song("Cher", "Believe")).find(song("Okay Kaya", "Believe"))
recall
a cacherecall can accept async functions. it simply caches the promise result:
const textOf = recall(async (url: string) => await (await fetch(url)).response.text)
const result = await textOf("https://...)
report
multiple errors while returning dataconst doStuff = recall(things => {
let goodThings = []
for (const thing of things) {
if (isBad(thing)) {
report(new BadThingError("this thing is bad:", thing))
} else {
goodThings.push(thing)
}
}
if (!goodThings.length)
throw new Error("no good things")
return processKnownGoodThings(goodThings)
})
const output = doStuff
.getResult(someThings)
// prints non-fatal errors to console.log and unwraps:
.unwrap(console.log)
report
errors while yielding dataconst processedThings = replay(function *(things) {
for (const thing of things) {
if (isBad(thing)) {
report(new BadThingError("this thing is bad:", thing))
} else {
yield processGoodThing(thing)
goodThings.push(thing)
}
}
})
const output = getResult(() =>
// iterate over all processedThings to collect
// errors on all of them
[...processedThings(someThings)]
).unwrap(console.log)
getResult
as a reporting boundarygetResult
and fn.getResult
do not bubble report
s up, so you can use them as error boundaries:
function importantStuff() {
report(new Error('error in importantStuff'))
}
function optionalSetup() {
report(new Error('error in optionalStuff'))
}
getResult(() => {
importantStuff()
optionalStuff()
}).errors()
// -> [Error: error in importantStuff]
// -> [Error: error in optionalStuff]
getResult(() => {
importantStuff()
getResult(() => optionalStuff())
// swallowing the result swallows the error
}).errors()
// -> [Error: error in importantStuff]
you can report(someResult.log)
to explicitly bubble up messages from someResult
.
this is useful if you want to use getResult
(for example, to inspect the errors) but still want bubbling to happen:
getResult(() => {
importantStuff()
const result = getResult(() => optionalStuff())
for (const error in result.errors()) doSomethingElseWith(error)
report(result.log)
}).errors()
// -> [Error: error in importantStuff]
// -> [Error: error in optionalStuff]
FAQs
lightweight memoization
The npm package @protoplasm/recall receives a total of 2,257 weekly downloads. As such, @protoplasm/recall popularity was classified as popular.
We found that @protoplasm/recall demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
/Research
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.