@plinthjs/tinker
v0.1.0
Published
Mason REPL (the Laravel Tinker equivalent).
Maintainers
Readme
@plinthjs/tinker
The Mason REPL, the Laravel Tinker equivalent. It is split into a testable engine and a thin interactive wrapper:
Tinkerruns snippets in a persistentnode:vmcontext, returning the value of the last expression and carrying top-level declarations across calls (REPL scope semantics).start()boots anode:replwhose evaluator delegates to a sharedTinker, so the live REPL andTinker.evaluate()follow the same evaluation path.
Bindings (app, registered helpers, variables you set) are exposed to evaluated code as bare
identifiers. The package depends only on Node built-ins, @plinthjs/core and @plinthjs/support.
Install
npm install @plinthjs/tinkerUsage
import { Application } from '@plinthjs/core'
import { start } from '@plinthjs/tinker'
const app = new Application({ config: { app: { env: 'local' } } })
// Launch an interactive session with `app` in scope.
start({ app, prompt: 'mason> ' })mason> app.environment()
'local'
mason> const total = [1, 2, 3].reduce((a, b) => a + b)
mason> total * 2
12start() returns the running repl.REPLServer. It throws when called under a test runner (when
VITEST is set or NODE_ENV=test), so it never attaches to the process during tests.
The engine
import { Tinker } from '@plinthjs/tinker'
const tinker = new Tinker({ context: { greet: (who: string) => `hi ${who}` } })
tinker.evaluate('1 + 1') // 2
tinker.evaluate('greet("ada")') // 'hi ada'
// Declarations persist across calls.
tinker.evaluate('const x = 5')
tinker.evaluate('function double(n) { return n * 2 }')
tinker.evaluate('double(x)') // 10
// Statement-only snippets return undefined.
tinker.evaluate('let y = 1') // undefined
// A returned promise can be awaited by the caller.
await (tinker.evaluate('Promise.resolve(7)') as Promise<number>) // 7The vm context is seeded with host standard globals (console, JSON, Promise, fetch, URL,
timers, error constructors and so on), so snippets behave as they would in a normal Node REPL.
Bindings
tinker
.register('users', usersRepository) // a helper or service
.setVariable('limit', 10) // a plain variable
tinker.evaluate('users.take(limit)')
tinker.getVariable('limit') // 10
tinker.evaluate('const created = 1')
tinker.getVariable('created') // 1, top-level declarations land in the scope
tinker.context() // the live bindings recordPass an existing engine to start({ tinker }) to open a REPL over a scope you have already
prepared.
Errors
Errors are never swallowed. A malformed snippet throws a real SyntaxError, and runtime errors are
host Error instances, so instanceof checks work on the caller side:
tinker.evaluate('null.foo') // throws TypeError
tinker.evaluate('missingName') // throws ReferenceError
tinker.evaluate('throw "oops"') // throws Error('oops'), with the original value as `cause`