@stacksjs/tinker 0.75.538 → 0.75.541
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +17 -19
- package/dist/index.js +37 -136
- package/dist/protocol.d.ts +46 -0
- package/dist/serialize.d.ts +76 -0
- package/dist/session.d.ts +60 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export type { Subprocess };
|
|
1
|
+
import type { TinkerValue } from './serialize';
|
|
3
2
|
/**
|
|
4
3
|
* Get the path to the tinker history file.
|
|
5
4
|
*/
|
|
@@ -22,30 +21,25 @@ export declare function appendHistory(entry: string, config?: TinkerConfig): voi
|
|
|
22
21
|
* Clear the tinker history file.
|
|
23
22
|
*/
|
|
24
23
|
export declare function clearHistory(config?: TinkerConfig): void;
|
|
24
|
+
/** Print a described value for a terminal. The inverse of `serialize`, lossy on purpose. */
|
|
25
|
+
export declare function renderValue(value: TinkerValue, indent?: string): string;
|
|
26
|
+
/** Whether a parse failure means "keep typing" rather than "this is wrong". */
|
|
27
|
+
export declare function isIncompleteSnippet(code: string, cwd: string): boolean;
|
|
25
28
|
/**
|
|
26
|
-
* Start
|
|
29
|
+
* Start a Stacks tinker session.
|
|
27
30
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
+
* Boots the app in this process (config overrides, then models, jobs and
|
|
32
|
+
* framework helpers as globals) and evaluates TypeScript against it. Without
|
|
33
|
+
* `eval`, `print` or `serve` it is an interactive prompt whose declarations
|
|
34
|
+
* persist from line to line.
|
|
31
35
|
*
|
|
32
36
|
* @example
|
|
33
37
|
* ```ts
|
|
34
38
|
* import { startTinker } from '@stacksjs/tinker'
|
|
35
39
|
*
|
|
36
|
-
* await startTinker()
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```ts
|
|
41
|
-
* // Evaluate a single expression
|
|
42
|
-
* await startTinker({ eval: 'await User.count()' })
|
|
43
|
-
* ```
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* // Evaluate and print
|
|
48
|
-
* await startTinker({ print: 'await User.all()' })
|
|
40
|
+
* await startTinker() // interactive
|
|
41
|
+
* await startTinker({ print: 'await User.count()' }) // evaluate, print, exit
|
|
42
|
+
* await startTinker({ serve: true }) // speak the frame protocol on stdio
|
|
49
43
|
* ```
|
|
50
44
|
*/
|
|
51
45
|
export declare function startTinker(config?: TinkerConfig): Promise<{ exitCode: number }>;
|
|
@@ -65,7 +59,11 @@ export declare interface TinkerConfig {
|
|
|
65
59
|
cwd?: string
|
|
66
60
|
banner?: boolean
|
|
67
61
|
stacksPreload?: boolean
|
|
62
|
+
serve?: boolean
|
|
68
63
|
eval?: string
|
|
69
64
|
print?: string
|
|
70
65
|
verbose?: boolean
|
|
71
66
|
}
|
|
67
|
+
export * from './protocol';
|
|
68
|
+
export * from './serialize';
|
|
69
|
+
export * from './session';
|
package/dist/index.js
CHANGED
|
@@ -1,138 +1,39 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
try {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Expose all ORM exports globally (User, Post, etc.)
|
|
13
|
-
for (const [key, value] of Object.entries(orm)) {
|
|
14
|
-
if (key !== 'default' && key !== 'db') {
|
|
15
|
-
globalThis[key] = value
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
} catch {}
|
|
19
|
-
|
|
20
|
-
// Config
|
|
21
|
-
try {
|
|
22
|
-
const config = await import('@stacksjs/config')
|
|
23
|
-
globalThis.config = config
|
|
24
|
-
} catch {}
|
|
25
|
-
|
|
26
|
-
// Path utilities
|
|
27
|
-
try {
|
|
28
|
-
const path = await import('@stacksjs/path')
|
|
29
|
-
globalThis.path = path
|
|
30
|
-
} catch {}
|
|
31
|
-
|
|
32
|
-
// Storage
|
|
33
|
-
try {
|
|
34
|
-
const storage = await import('@stacksjs/storage')
|
|
35
|
-
globalThis.storage = storage
|
|
36
|
-
} catch {}
|
|
37
|
-
|
|
38
|
-
// Validation
|
|
39
|
-
try {
|
|
40
|
-
const validation = await import('@stacksjs/validation')
|
|
41
|
-
globalThis.validation = validation
|
|
42
|
-
} catch {}
|
|
43
|
-
|
|
44
|
-
// Logging
|
|
45
|
-
try {
|
|
46
|
-
const { log } = await import('@stacksjs/cli')
|
|
47
|
-
globalThis.log = log
|
|
48
|
-
} catch {}
|
|
49
|
-
|
|
50
|
-
// Collections
|
|
51
|
-
try {
|
|
52
|
-
const collections = await import('@stacksjs/collections')
|
|
53
|
-
globalThis.collect = collections.collect ?? collections.default
|
|
54
|
-
} catch {}
|
|
55
|
-
|
|
56
|
-
// Strings
|
|
57
|
-
try {
|
|
58
|
-
const strings = await import('@stacksjs/strings')
|
|
59
|
-
globalThis.Str = strings
|
|
60
|
-
} catch {}
|
|
61
|
-
|
|
62
|
-
// Cache
|
|
63
|
-
try {
|
|
64
|
-
const cache = await import('@stacksjs/cache')
|
|
65
|
-
globalThis.cache = cache
|
|
66
|
-
} catch {}
|
|
67
|
-
|
|
68
|
-
// Queue / Jobs
|
|
69
|
-
try {
|
|
70
|
-
const queue = await import('@stacksjs/queue')
|
|
71
|
-
globalThis.queue = queue
|
|
72
|
-
} catch {}
|
|
73
|
-
|
|
74
|
-
// Events
|
|
75
|
-
try {
|
|
76
|
-
const events = await import('@stacksjs/events')
|
|
77
|
-
globalThis.events = events
|
|
78
|
-
} catch {}
|
|
79
|
-
|
|
80
|
-
// Router
|
|
81
|
-
try {
|
|
82
|
-
const router = await import('@stacksjs/router')
|
|
83
|
-
globalThis.router = router
|
|
84
|
-
} catch {}
|
|
85
|
-
|
|
86
|
-
// Auth
|
|
87
|
-
try {
|
|
88
|
-
const auth = await import('@stacksjs/auth')
|
|
89
|
-
globalThis.auth = auth
|
|
90
|
-
} catch {}
|
|
91
|
-
|
|
92
|
-
// Notifications
|
|
93
|
-
try {
|
|
94
|
-
const notifications = await import('@stacksjs/notifications')
|
|
95
|
-
globalThis.notifications = notifications
|
|
96
|
-
} catch {}
|
|
97
|
-
|
|
98
|
-
// Env / environment helpers
|
|
99
|
-
try {
|
|
100
|
-
const env = await import('@stacksjs/env')
|
|
101
|
-
globalThis.env = env
|
|
102
|
-
} catch {}
|
|
103
|
-
|
|
104
|
-
// validate() shortcut \u2014 paired with schema for fluent validation in REPL.
|
|
105
|
-
try {
|
|
106
|
-
const { validate } = await import('@stacksjs/validation')
|
|
107
|
-
globalThis.validate = validate
|
|
108
|
-
} catch {}
|
|
109
|
-
|
|
110
|
-
// Jobs facade \u2014 lets you fire jobs without remembering the dispatch builder.
|
|
111
|
-
try {
|
|
112
|
-
const { Jobs } = await import('@stacksjs/queue')
|
|
113
|
-
globalThis.Jobs = Jobs
|
|
114
|
-
} catch {}
|
|
115
|
-
|
|
116
|
-
// Request introspection \u2014 only useful inside an active request, but
|
|
117
|
-
// the proxy is safe to expose: outside a request scope every property
|
|
118
|
-
// returns a typed default (see request-context.ts).
|
|
119
|
-
try {
|
|
120
|
-
const { request, listRegisteredRoutes } = await import('@stacksjs/router')
|
|
121
|
-
globalThis.request = request
|
|
122
|
-
globalThis.routes = listRegisteredRoutes
|
|
123
|
-
} catch {}
|
|
124
|
-
|
|
125
|
-
// dump/dd helpers for quick inspection \u2014 a Laravel-ism that's always
|
|
126
|
-
// nice to have at the REPL when investigating a value.
|
|
127
|
-
globalThis.dump = (...args) => { console.dir(args.length === 1 ? args[0] : args, { depth: 6, colors: true }) }
|
|
128
|
-
globalThis.dd = (...args) => { console.dir(args.length === 1 ? args[0] : args, { depth: 6, colors: true }); process.exit(0) }
|
|
129
|
-
`);if(t.preload?.length)for(let s of t.preload){let o=s.replace(/[@/\-\.]/g,"_").replace(/^_+/,"");e.push(`try { globalThis.${o} = await import('${s}') } catch {}`)}return e.join(`
|
|
130
|
-
`)}function h(t){return t?.historyFile??u(k(),".stacks_tinker_history")}function v(t){let e=h(t);if(!d(e))return[];return y(e,"utf-8").split(`
|
|
131
|
-
`).filter(Boolean)}function C(t,e){let s=h(e),o=!d(s);if(f(s,`${t}
|
|
132
|
-
`),o)try{p(s,384)}catch{}let i=e?.historySize??5000,r=v(e);if(r.length>i){let a=r.slice(r.length-i);c(s,a.join(`
|
|
2
|
+
import{appendFileSync as oe,chmodSync as q,existsSync as D,readFileSync as ie,writeFileSync as _,writeSync as se}from"fs";import{homedir as ae}from"os";import{join as ce,resolve as ue}from"path";import v from"process";import{writeSync as ne}from"fs";import{isAbsolute as H,resolve as C}from"path";import O from"process";var W={maxDepth:8,maxItems:200,maxString:20000,maxNodes:50000},J=/^\s*at\s+(?:(.*?)\s+\()?(.+?):(\d+):(\d+)\)?\s*$/;function Y(e){if(!e)return[];let r=[];for(let n of e.split(`
|
|
3
|
+
`)){let i=J.exec(n);if(!i)continue;r.push({function:i[1]||void 0,file:i[2],line:Number(i[3]),column:Number(i[4])})}return r}function G(e){if(Function.prototype.toString.call(e).startsWith("class"))return"class";let n=e.constructor?.name;if(n==="AsyncFunction"||n==="AsyncGeneratorFunction")return"async";if(n==="GeneratorFunction")return"generator";return"function"}function N(e){let r=Object.getPrototypeOf(e);if(r===null)return"null prototype";let n=r.constructor?.name;return typeof n==="string"&&n&&n!=="Object"?n:void 0}function E(e,r={}){let n={...W,...r.limits},i=r.describers??[],s=new Map,m=0,b=1;function y(a,g){if(m++,a===void 0)return{t:"undefined"};if(a===null)return{t:"null"};switch(typeof a){case"boolean":return{t:"boolean",v:a};case"number":if(Number.isNaN(a))return{t:"number",v:"NaN"};if(a===Number.POSITIVE_INFINITY)return{t:"number",v:"Infinity"};if(a===Number.NEGATIVE_INFINITY)return{t:"number",v:"-Infinity"};if(Object.is(a,-0))return{t:"number",v:"-0"};return{t:"number",v:a};case"bigint":return{t:"bigint",v:a.toString()};case"string":return a.length>n.maxString?{t:"string",v:a.slice(0,n.maxString),truncated:a.length-n.maxString}:{t:"string",v:a};case"symbol":return{t:"symbol",v:a.description??""};case"function":return{t:"function",name:a.name||"(anonymous)",kind:G(a)}}let o=a,l=s.get(o);if(l!==void 0)return{t:"ref",id:l};if(o instanceof Date)return{t:"date",v:Number.isNaN(o.getTime())?null:o.toISOString()};if(o instanceof RegExp)return{t:"regexp",v:o.toString()};if(o instanceof URL)return{t:"url",v:o.href};if(o instanceof Promise)return{t:"promise"};if(ArrayBuffer.isView(o)||o instanceof ArrayBuffer){let t=o instanceof ArrayBuffer?new Uint8Array(o):new Uint8Array(o.buffer,o.byteOffset,o.byteLength);return{t:"bytes",ctor:o.constructor?.name??"ArrayBuffer",length:t.byteLength,hex:Buffer.from(t.subarray(0,64)).toString("hex")}}if(g>=n.maxDepth)return{t:"cut",reason:"depth",ctor:N(o)??(Array.isArray(o)?"Array":"Object")};if(m>=n.maxNodes)return{t:"cut",reason:"nodes",ctor:N(o)};let c=b++;s.set(o,c);let p=(t)=>y(t,g+1);if(o instanceof Error)return S(o,p);for(let t of i){let u;try{u=t(o,p)}catch{u=void 0}if(u)return u}if(Array.isArray(o)){let t=[],u=Math.min(o.length,n.maxItems);for(let d=0;d<u&&m<n.maxNodes;d++)t.push(p(o[d]));let k=o.length-t.length,f=N(o);return{t:"array",id:c,...f&&f!=="Array"?{ctor:f}:{},length:o.length,items:t,...k>0?{more:k}:{}}}if(o instanceof Map){let t=[];for(let[k,f]of o){if(t.length>=n.maxItems||m>=n.maxNodes)break;t.push([p(k),p(f)])}let u=o.size-t.length;return{t:"map",id:c,size:o.size,entries:t,...u>0?{more:u}:{}}}if(o instanceof Set){let t=[];for(let k of o){if(t.length>=n.maxItems||m>=n.maxNodes)break;t.push(p(k))}let u=o.size-t.length;return{t:"set",id:c,size:o.size,items:t,...u>0?{more:u}:{}}}return w(o,c,p)}function w(a,g,o){let l=N(a),c=a,p=Object.keys(a);if(p.length===0&&l&&typeof a.toJSON==="function")try{let k=a.toJSON();if(Array.isArray(k)){let f=o(k);return f.t==="array"?{...f,ctor:l}:f}if(k&&typeof k==="object")c=k,p=Object.keys(c)}catch{}let t=[];for(let k of p){if(t.length>=n.maxItems||m>=n.maxNodes)break;let f;try{f=c[k]}catch(d){f=d}t.push([k,o(f)])}let u=p.length-t.length;return{t:"object",id:g,...l?{ctor:l}:{},entries:t,...u>0?{more:u}:{}}}function S(a,g){let o={t:"error",name:a.name||"Error",message:String(a.message??"")};if(typeof a.stack==="string"){o.stack=a.stack;let c=Y(a.stack);if(c.length)o.frames=c}if(a.cause!==void 0)o.cause=g(a.cause);let l=[];for(let c of Object.keys(a)){if(c==="name"||c==="message"||c==="stack"||c==="cause")continue;if(l.length>=n.maxItems)break;l.push([c,g(a[c])])}if(l.length)o.entries=l;return o}return y(e,0)}function B(e,r={}){if(e instanceof Error)return E(e,r);return{t:"error",name:"Thrown",message:typeof e==="string"?e:Bun.inspect(e),cause:E(e,r)}}class F extends Error{constructor(){super("dd");this.name="TinkerHalt"}}var K=/\bimport\((["'])(\.{1,2}\/[^"']*)\1\)/g;function j(e,r,n="tinker"){return`${new Bun.Transpiler({loader:"ts",replMode:!0}).transformSync(e).replace(K,(m,b,y)=>`import(${b}${C(r,y)}${b})`)}
|
|
4
|
+
//# sourceURL=${n}.ts`}function Q(e){let r=e instanceof AggregateError?e.errors:[e],n=[];for(let i of r){if(!i||typeof i!=="object")continue;let{message:s,position:m}=i;n.push({message:typeof s==="string"?s:String(i),...m?.line!==void 0?{line:m.line}:{},...m?.column!==void 0?{column:m.column}:{},...m?.lineText?{lineText:m.lineText}:{}})}return n}async function X(e){let i=!0;try{let{overridesReady:s}=await import("@stacksjs/config");await s}catch(s){i=!1,e.push(`config not loaded: ${s instanceof Error?s.message:String(s)}`)}try{let{injectGlobalAutoImports:s}=await import("@stacksjs/server");await s()}catch(s){i=!1,e.push(`models and framework globals not injected: ${s instanceof Error?s.message:String(s)}`)}return i}var Z=["log","info","debug","dir","table"],ee=["warn","error","trace"];async function I(e={}){let r=e.cwd?H(e.cwd)?e.cwd:C(O.cwd(),e.cwd):O.cwd();if(r!==O.cwd())O.chdir(r);let n={limits:e.limits,describers:e.describers},i=[],s=[],m="boot",b,y=(t)=>{b?.push(t);try{e.onFrame?.(t)}catch{}},w=console,S=(t)=>t.map((u)=>typeof u==="string"?u:Bun.inspect(u,{depth:6})).join(" ");for(let[t,u]of[[Z,"stdout"],[ee,"stderr"]])for(let k of t){let f=w[k];w[k]=(...d)=>y({type:u,id:m,text:`${S(d)}
|
|
5
|
+
`}),s.push(()=>{w[k]=f})}for(let[t,u]of[[O.stdout,"stdout"],[O.stderr,"stderr"]]){let k=t.write;t.write=(f,...d)=>(y({type:u,id:m,text:typeof f==="string"?f:Buffer.from(f).toString("utf8")}),d.find((T)=>typeof T==="function")?.(),!0),s.push(()=>{t.write=k})}let a=globalThis;if(a.dump=(...t)=>{for(let u of t)y({type:"dump",id:m,value:E(u,n)})},a.dd=(...t)=>{throw a.dump(...t),new F},e.globals)Object.assign(a,e.globals);let g=!1;if(typeof e.boot==="function")try{await e.boot(),g=!0}catch(t){i.push(`boot failed: ${t instanceof Error?t.message:String(t)}`)}else if(e.boot!==!1)g=await X(i);if(g&&e.boot!==!1&&typeof e.boot!=="function"){let t=(...f)=>{for(let d of f)y({type:"dump",id:m,value:E(d,n)})};a.dump=t,a.dd=(...f)=>{throw t(...f),new F};let u="@stacksjs/logging",k="@stacksjs/query-builder";try{let{registerTransport:f}=await import(u);s.push(f({name:"tinker",log(d){y({type:"log",id:m,level:d.level,message:d.message,timestamp:d.timestamp,...d.context?{context:E(d.context,n)}:{}})}}))}catch(f){i.push(`log capture unavailable: ${f instanceof Error?f.message:String(f)}`)}try{let{registerPersistentQueryHooks:f}=await import(k);s.push(f({onQueryEnd(d){y({type:"query",id:m,sql:d.sql,...d.params?.length?{params:E(d.params,n)}:{},...d.kind?{kind:d.kind}:{},durationMs:d.durationMs,...d.rowCount!==void 0?{rowCount:d.rowCount}:{}})},onQueryError(d){y({type:"query",id:m,sql:d.sql,...d.params?.length?{params:E(d.params,n)}:{},...d.kind?{kind:d.kind}:{},durationMs:d.durationMs,error:d.error instanceof Error?d.error.message:String(d.error)})}}))}catch(f){i.push(`query capture unavailable: ${f instanceof Error?f.message:String(f)}`)}}let o=eval,l=0,c=Promise.resolve();async function p(t,u){let k=[];b=k,m=u;let f=performance.now(),d=()=>Math.round((performance.now()-f)*100)/100,A;try{A=j(t,r,`tinker-${u}`)}catch(T){let x=d();return y({type:"error",id:u,phase:"parse",error:B(T,n),diagnostics:Q(T),durationMs:x}),y({type:"done",id:u,ok:!1}),b=void 0,{id:u,ok:!1,thrown:T,frames:k,durationMs:x}}try{let x=(await o(A))?.value,M=d();return y({type:"result",id:u,value:E(x,n),durationMs:M}),y({type:"done",id:u,ok:!0}),{id:u,ok:!0,value:x,frames:k,durationMs:M}}catch(T){let x=d();if(T instanceof F)return y({type:"result",id:u,value:{t:"undefined"},durationMs:x}),y({type:"done",id:u,ok:!0}),{id:u,ok:!0,frames:k,durationMs:x};return y({type:"error",id:u,phase:"run",error:B(T,n),durationMs:x}),y({type:"done",id:u,ok:!1}),{id:u,ok:!1,thrown:T,frames:k,durationMs:x}}finally{b=void 0}}return{info:{cwd:r,booted:g,bun:Bun.version,warnings:i},evaluate(t,u={}){let k=u.id??String(++l),f=c.then(()=>p(t,k));return c=f.catch(()=>{}),f},dispose(){while(s.length)s.pop()?.()}}}var z="\x1E",re=1;function te(e){return`${z}${JSON.stringify(e)}
|
|
6
|
+
`}function $e(){let e="",r=(n)=>{if(n.startsWith(z))try{return{frame:JSON.parse(n.slice(z.length))}}catch{return{stray:n}}return{stray:n}};return{push(n){e+=n;let i=e.split(`
|
|
7
|
+
`);return e=i.pop()??"",i.map(r)},flush(){if(!e)return[];let n=e;return e="",[{stray:n}]}}}async function R(e={}){let r=e.write??((c)=>{ne(1,c)}),n=(c)=>r(te(c)),{input:i,write:s,once:m,...b}=e,y=await I({...b,onFrame:n});n({type:"ready",session:y.info,protocol:re});let w=!0,S=!1;if(m!==void 0)return{ok:(await y.evaluate(m)).ok,session:y};let a=new TextDecoder,g="",o=i??Bun.stdin.stream(),l=async(c)=>{if(!c.trim())return!0;let p;try{p=JSON.parse(c)}catch{return n({type:"protocol-error",message:"request is not valid JSON"}),!0}if(p.op==="exit")return!1;if(p.op==="ping")return n({type:"pong",...p.id?{id:p.id}:{}}),!0;if(p.op==="eval"&&typeof p.code==="string"){if(!(await y.evaluate(p.code,{id:p.id})).ok)w=!1;return!0}return n({type:"protocol-error",message:`unknown request: ${String(p.op)}`}),!0};e:for await(let c of o){g+=typeof c==="string"?c:a.decode(c,{stream:!0});let p=g.indexOf(`
|
|
8
|
+
`);while(p!==-1){let t=g.slice(0,p);if(g=g.slice(p+1),!await l(t)){S=!0;break e}p=g.indexOf(`
|
|
9
|
+
`)}}if(!S&&g.trim())await l(g);return{ok:w,session:y}}function P(e){return e?.historyFile??ce(ae(),".stacks_tinker_history")}function L(e){let r=P(e);if(!D(r))return[];return ie(r,"utf-8").split(`
|
|
10
|
+
`).filter(Boolean)}function me(e,r){let n=P(r),i=!D(n);if(oe(n,`${e}
|
|
11
|
+
`),i)try{q(n,384)}catch{}let s=r?.historySize??5000,m=L(r);if(m.length>s){let b=m.slice(m.length-s);_(n,b.join(`
|
|
133
12
|
`)+`
|
|
134
|
-
`);try{
|
|
135
|
-
`)}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
${
|
|
13
|
+
`);try{q(n,384)}catch{}}}function le(e){let r=P(e);_(r,"")}function fe(e){let r=["",` \x1B[36mStacks Tinker\x1B[0m (Bun v${e.bun})`,e.booted?" Your app is booted: models, jobs, config and framework helpers are globals.":" Bare session: the app context is not booted."];for(let n of e.warnings)r.push(` \x1B[33m${n}\x1B[0m`);return r.push(""," \x1B[2mDeclarations persist between lines. dump(value) inspects, dd(value) inspects and stops."," .help for commands, .exit or Ctrl+D to leave.\x1B[0m","",""),r.join(`
|
|
14
|
+
`)}function h(e,r=1){se(r,e)}function V(e,r=""){let n=`${r} `;switch(e.t){case"undefined":return"undefined";case"null":return"null";case"boolean":return String(e.v);case"number":return String(e.v);case"bigint":return`${e.v}n`;case"string":return JSON.stringify(e.v)+(e.truncated?` ... ${e.truncated} more characters`:"");case"symbol":return`Symbol(${e.v})`;case"function":return`[${e.kind==="class"?"class":"Function"}: ${e.name}]`;case"date":return e.v??"Invalid Date";case"regexp":return e.v;case"url":return e.v;case"promise":return"Promise {}";case"bytes":return`${e.ctor}(${e.length}) <${e.hex}${e.length>64?" ...":""}>`;case"ref":return`[Circular *${e.id}]`;case"cut":return`[${e.ctor??"Object"}]`;case"error":return e.stack??`${e.name}: ${e.message}`;case"array":{if(!e.items.length&&!e.more)return`${e.ctor?`${e.ctor} `:""}[]`;let i=e.items.map((s)=>`${n}${V(s,n)}`);if(e.more)i.push(`${n}... ${e.more} more`);return`${e.ctor?`${e.ctor} `:""}[
|
|
15
|
+
${i.join(`,
|
|
16
|
+
`)}
|
|
17
|
+
${r}]`}case"set":{let i=e.items.map((s)=>`${n}${V(s,n)}`);if(e.more)i.push(`${n}... ${e.more} more`);return i.length?`Set(${e.size}) {
|
|
18
|
+
${i.join(`,
|
|
19
|
+
`)}
|
|
20
|
+
${r}}`:"Set(0) {}"}case"map":{let i=e.entries.map(([s,m])=>`${n}${V(s,n)} => ${V(m,n)}`);if(e.more)i.push(`${n}... ${e.more} more`);return i.length?`Map(${e.size}) {
|
|
21
|
+
${i.join(`,
|
|
22
|
+
`)}
|
|
23
|
+
${r}}`:"Map(0) {}"}case"object":{if(!e.entries.length&&!e.more)return`${e.ctor?`${e.ctor} `:""}{}`;let i=e.entries.map(([s,m])=>`${n}${s}: ${V(m,n)}`);if(e.more)i.push(`${n}... ${e.more} more`);return`${e.ctor?`${e.ctor} `:""}{
|
|
24
|
+
${i.join(`,
|
|
25
|
+
`)}
|
|
26
|
+
${r}}`}}}function de(e,r){switch(e.type){case"stdout":h(e.text);break;case"stderr":h(e.text,2);break;case"dump":h(`${V(e.value)}
|
|
27
|
+
`);break;case"query":if(r)h(`\x1B[2m ${e.durationMs??"?"}ms ${e.sql}\x1B[0m
|
|
28
|
+
`,2);break;case"log":if(r)h(`\x1B[2m [${e.level}] ${e.message}\x1B[0m
|
|
29
|
+
`,2);break}}async function pe(e,r){let n=[];for(let i of e??[]){let s=i.replace(/[@/\-.]/g,"_").replace(/^_+/,"");try{let m=i.startsWith(".")?ue(r,i):i;globalThis[s]=await import(m)}catch(m){n.push(`${i}: ${m instanceof Error?m.message:String(m)}`)}}return n}var ge=/Unexpected end of file|Unterminated|Expected "\}" but found end of file|Expected "\)" but found end of file|Expected "\]" but found end of file/;function ke(e,r){try{return j(e,r),!1}catch(n){return(n instanceof AggregateError?n.errors.map((s)=>String(s?.message??s)):[String(n?.message??n)]).some((s)=>ge.test(s))}}async function U(e={}){let r=e.cwd??v.cwd(),n=e.stacksPreload!==!1;if(e.serve){let l=e.eval??e.print,{ok:c}=await R({cwd:r,boot:n,...l!==void 0?{once:l}:{}});return{exitCode:c?0:1}}let i=e.verbose===!0,s=await I({cwd:r,boot:n,onFrame:(l)=>de(l,i)});for(let l of await pe(e.preload,s.info.cwd))h(`\x1B[33mpreload failed: ${l}\x1B[0m
|
|
30
|
+
`,2);let m=(l,c)=>{if(!l.ok){let p=l.thrown;h(`${p instanceof Error?Bun.inspect(p,{colors:Boolean(v.stderr.isTTY)}):`Uncaught ${Bun.inspect(p)}`}
|
|
31
|
+
`,2);return}if(c&&l.value!==void 0)h(`${Bun.inspect(l.value,{colors:Boolean(v.stdout.isTTY),depth:6})}
|
|
32
|
+
`)};if(e.eval!==void 0||e.print!==void 0){for(let c of s.info.warnings)h(`\x1B[33m${c}\x1B[0m
|
|
33
|
+
`,2);let l=await s.evaluate(e.print??e.eval??"");return m(l,e.print!==void 0),s.dispose(),{exitCode:l.ok?0:1}}if(e.banner!==!1)h(fe(s.info));let{createInterface:b}=await import("readline"),{Writable:y}=await import("stream"),w=new y({write(l,c,p){h(typeof l==="string"?l:Buffer.from(l).toString("utf8")),p()}}),S=e.historySize??5000,a=b({input:v.stdin,output:w,terminal:Boolean(v.stdin.isTTY),history:L(e).slice(-S).reverse(),historySize:S,prompt:"> "}),g="",o=0;a.prompt();try{for await(let l of a){let c=l.trim();if(!g&&c.startsWith(".")){if(c===".exit")break;if(c===".help")h(` .exit leave
|
|
34
|
+
.clear forget the history file
|
|
35
|
+
.help this list
|
|
36
|
+
dump(v), dd(v) inspect a value; dd also stops the line
|
|
37
|
+
`);else if(c===".clear")le(e);else h(` unknown command ${c}
|
|
38
|
+
`);a.prompt();continue}if(g=g?`${g}
|
|
39
|
+
${l}`:l,!g.trim()){g="",a.prompt();continue}if(ke(g,s.info.cwd)){a.setPrompt("... "),a.prompt();continue}let p=g;g="";try{me(p.replace(/\n/g," "),e)}catch{}let t=await s.evaluate(p);m(t,!0),o=t.ok?0:o,a.setPrompt("> "),a.prompt()}}finally{a.close(),s.dispose()}return{exitCode:o}}async function Ae(e,r={}){return U({...r,eval:e})}async function Be(e,r={}){return U({...r,print:e})}export{z as FRAME_MARKER,re as PROTOCOL_VERSION,me as appendHistory,le as clearHistory,j as compileSnippet,$e as createFrameReader,I as createTinkerSession,W as defaultSerializeLimits,te as encodeFrame,P as getHistoryPath,ke as isIncompleteSnippet,Y as parseStack,L as readHistory,V as renderValue,E as serialize,B as serializeThrown,R as serveTinker,U as startTinker,Ae as tinkerEval,Be as tinkerPrint};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TinkerFrame, TinkerSession, TinkerSessionInfo, TinkerSessionOptions } from './session';
|
|
2
|
+
export declare function encodeFrame(frame: TinkerWireFrame): string;
|
|
3
|
+
/**
|
|
4
|
+
* Split a chunk of a session's output into frames and stray text.
|
|
5
|
+
*
|
|
6
|
+
* Feed it chunks as they arrive. A trailing partial line is held until the
|
|
7
|
+
* rest of it comes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createFrameReader(): {
|
|
10
|
+
push: (chunk: string) => Array<{ frame: TinkerWireFrame } | { stray: string }>
|
|
11
|
+
flush: () => Array<{ stray: string }>
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Run a session that answers requests until its input closes or it is told to
|
|
15
|
+
* exit. Resolves with whether every evaluation succeeded.
|
|
16
|
+
*/
|
|
17
|
+
export declare function serveTinker(options?: ServeOptions): Promise<{ ok: boolean, session: TinkerSession }>;
|
|
18
|
+
/**
|
|
19
|
+
* The wire format a tinker session speaks over a pipe.
|
|
20
|
+
*
|
|
21
|
+
* One process, one session, driven by whoever holds its stdin: a desktop app
|
|
22
|
+
* on the same machine, or the far end of `ssh -T`, `docker exec -i` or
|
|
23
|
+
* `kubectl exec -i`. Requests come in as one JSON object per line. Frames go
|
|
24
|
+
* out as one JSON object per line, each line starting with the record
|
|
25
|
+
* separator (0x1E).
|
|
26
|
+
*
|
|
27
|
+
* The marker is what keeps the stream readable when something writes to file
|
|
28
|
+
* descriptor 1 behind the session's back (a native addon, a child process that
|
|
29
|
+
* inherited stdio). A reader treats a line that starts with the marker as a
|
|
30
|
+
* frame and anything else as stray output, instead of failing to parse it.
|
|
31
|
+
*/
|
|
32
|
+
export declare const FRAME_MARKER: '\x1E';
|
|
33
|
+
/** Bumped when a frame changes shape in a way an older reader would misread. */
|
|
34
|
+
export declare const PROTOCOL_VERSION: 1;
|
|
35
|
+
export declare interface ServeOptions extends Omit<TinkerSessionOptions, 'onFrame'> {
|
|
36
|
+
input?: AsyncIterable<Uint8Array | string>
|
|
37
|
+
write?: (text: string) => void
|
|
38
|
+
once?: string
|
|
39
|
+
}
|
|
40
|
+
export type TinkerRequest = | { op: 'eval', id?: string, code: string }
|
|
41
|
+
| { op: 'ping', id?: string }
|
|
42
|
+
| { op: 'exit' }
|
|
43
|
+
export type TinkerWireFrame = | TinkerFrame
|
|
44
|
+
| { type: 'ready', session: TinkerSessionInfo, protocol: number }
|
|
45
|
+
| { type: 'pong', id?: string }
|
|
46
|
+
| { type: 'protocol-error', message: string }
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Parse a V8/JavaScriptCore `at fn (file:line:col)` stack into frames. */
|
|
2
|
+
export declare function parseStack(stack: string | undefined): TinkerStackFrame[];
|
|
3
|
+
export declare function serialize(root: unknown, options?: SerializeOptions): TinkerValue;
|
|
4
|
+
/** Describe anything thrown, including non-Error throws, as an error value. */
|
|
5
|
+
export declare function serializeThrown(thrown: unknown, options?: SerializeOptions): TinkerErrorValue;
|
|
6
|
+
export declare const defaultSerializeLimits: SerializeLimits;
|
|
7
|
+
/**
|
|
8
|
+
* Turn any runtime value into a plain, JSON-safe description of it.
|
|
9
|
+
*
|
|
10
|
+
* A tinker result has to leave the process that produced it: over a pipe to a
|
|
11
|
+
* desktop app, over SSH from a server, out of a container. `JSON.stringify`
|
|
12
|
+
* loses most of what makes a result worth looking at (undefined, bigint, Date,
|
|
13
|
+
* Map, Set, class names, cycles, errors), and `Bun.inspect` produces a string a
|
|
14
|
+
* UI can only print. This produces a tree a UI can expand, sort and tabulate,
|
|
15
|
+
* with every node tagged by what it was.
|
|
16
|
+
*
|
|
17
|
+
* It is bounded on purpose. A model collection with a million rows or an
|
|
18
|
+
* object graph that reaches the whole app must not hang the session or flood
|
|
19
|
+
* the pipe, so depth, items per container, string length and total nodes all
|
|
20
|
+
* have limits, and every cut is recorded on the node (`more`, `truncated`)
|
|
21
|
+
* rather than silently dropped.
|
|
22
|
+
*/
|
|
23
|
+
export declare interface SerializeLimits {
|
|
24
|
+
maxDepth: number
|
|
25
|
+
maxItems: number
|
|
26
|
+
maxString: number
|
|
27
|
+
maxNodes: number
|
|
28
|
+
}
|
|
29
|
+
export declare interface TinkerStackFrame {
|
|
30
|
+
function?: string
|
|
31
|
+
file?: string
|
|
32
|
+
line?: number
|
|
33
|
+
column?: number
|
|
34
|
+
}
|
|
35
|
+
export declare interface TinkerErrorValue {
|
|
36
|
+
t: 'error'
|
|
37
|
+
name: string
|
|
38
|
+
message: string
|
|
39
|
+
stack?: string
|
|
40
|
+
frames?: TinkerStackFrame[]
|
|
41
|
+
cause?: TinkerValue
|
|
42
|
+
entries?: Array<[string, TinkerValue]>
|
|
43
|
+
}
|
|
44
|
+
export declare interface SerializeOptions {
|
|
45
|
+
limits?: Partial<SerializeLimits>
|
|
46
|
+
describers?: TinkerDescriber[]
|
|
47
|
+
}
|
|
48
|
+
export type TinkerValue = | { t: 'undefined' }
|
|
49
|
+
| { t: 'null' }
|
|
50
|
+
| { t: 'boolean', v: boolean }
|
|
51
|
+
| { t: 'number', v: number | 'NaN' | 'Infinity' | '-Infinity' | '-0' }
|
|
52
|
+
| { t: 'bigint', v: string }
|
|
53
|
+
| { t: 'string', v: string, truncated?: number }
|
|
54
|
+
| { t: 'symbol', v: string }
|
|
55
|
+
| { t: 'function', name: string, kind: 'function' | 'async' | 'class' | 'generator' }
|
|
56
|
+
| { t: 'date', v: string | null }
|
|
57
|
+
| { t: 'regexp', v: string }
|
|
58
|
+
| { t: 'url', v: string }
|
|
59
|
+
| { t: 'promise' }
|
|
60
|
+
| { t: 'bytes', ctor: string, length: number, hex: string }
|
|
61
|
+
| { t: 'array', id: number, ctor?: string, length: number, items: TinkerValue[], more?: number }
|
|
62
|
+
| { t: 'object', id: number, ctor?: string, entries: Array<[string, TinkerValue]>, more?: number }
|
|
63
|
+
| { t: 'map', id: number, size: number, entries: Array<[TinkerValue, TinkerValue]>, more?: number }
|
|
64
|
+
| { t: 'set', id: number, size: number, items: TinkerValue[], more?: number }
|
|
65
|
+
| { t: 'ref', id: number }
|
|
66
|
+
| { t: 'cut', reason: 'depth' | 'nodes', ctor?: string }
|
|
67
|
+
| TinkerErrorValue;
|
|
68
|
+
/**
|
|
69
|
+
* Describes one kind of value the generic walk does not know about, such as an
|
|
70
|
+
* ORM model. Return `undefined` to pass. `describe` continues the walk for
|
|
71
|
+
* nested values so limits and cycle tracking stay in force.
|
|
72
|
+
*/
|
|
73
|
+
export type TinkerDescriber = (
|
|
74
|
+
value: object,
|
|
75
|
+
describe: (value: unknown) => TinkerValue,
|
|
76
|
+
) => TinkerValue | undefined;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { SerializeOptions, TinkerErrorValue, TinkerValue } from './serialize';
|
|
2
|
+
/**
|
|
3
|
+
* Compile a snippet into JavaScript that, evaluated at global scope, runs it
|
|
4
|
+
* and yields `{ value }` for its last expression.
|
|
5
|
+
*
|
|
6
|
+
* Evaluated code has no file, so a relative `import('./app/Actions/Foo')`
|
|
7
|
+
* would resolve against this package instead of the project. Those specifiers
|
|
8
|
+
* are made absolute against the session's directory.
|
|
9
|
+
*/
|
|
10
|
+
export declare function compileSnippet(code: string, cwd: string, sourceName?: string): string;
|
|
11
|
+
export declare function createTinkerSession(options?: TinkerSessionOptions): Promise<TinkerSession>;
|
|
12
|
+
export declare interface TinkerDiagnostic {
|
|
13
|
+
message: string
|
|
14
|
+
line?: number
|
|
15
|
+
column?: number
|
|
16
|
+
lineText?: string
|
|
17
|
+
}
|
|
18
|
+
export declare interface TinkerSessionInfo {
|
|
19
|
+
cwd: string
|
|
20
|
+
booted: boolean
|
|
21
|
+
bun: string
|
|
22
|
+
warnings: string[]
|
|
23
|
+
}
|
|
24
|
+
export declare interface TinkerSessionOptions extends SerializeOptions {
|
|
25
|
+
cwd?: string
|
|
26
|
+
boot?: boolean | (() => Promise<void> | void)
|
|
27
|
+
globals?: Record<string, unknown>
|
|
28
|
+
onFrame?: (frame: TinkerFrame) => void
|
|
29
|
+
}
|
|
30
|
+
export declare interface TinkerEvaluation {
|
|
31
|
+
id: string
|
|
32
|
+
ok: boolean
|
|
33
|
+
value?: unknown
|
|
34
|
+
thrown?: unknown
|
|
35
|
+
frames: TinkerFrame[]
|
|
36
|
+
durationMs: number
|
|
37
|
+
}
|
|
38
|
+
export declare interface TinkerSession {
|
|
39
|
+
info: TinkerSessionInfo
|
|
40
|
+
evaluate: (code: string, options?: { id?: string }) => Promise<TinkerEvaluation>
|
|
41
|
+
dispose: () => void
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A tinker session: one booted app, many evaluations, state kept between them.
|
|
45
|
+
*
|
|
46
|
+
* The old tinker wrote the snippet to a file in the project and ran a fresh
|
|
47
|
+
* `bun` on it, so nothing survived from one run to the next, a snippet with
|
|
48
|
+
* two statements was a syntax error, and the caller learned only an exit code.
|
|
49
|
+
* A session evaluates in its own process instead. Declarations are hoisted to
|
|
50
|
+
* globals by Bun's REPL transform, so `const user = await User.find(1)` in one
|
|
51
|
+
* run is still `user` in the next, and each run reports what happened as
|
|
52
|
+
* frames: output, logs, queries, dumps, then a typed result or an error.
|
|
53
|
+
*/
|
|
54
|
+
export type TinkerFrame = | { type: 'stdout' | 'stderr', id: string, text: string }
|
|
55
|
+
| { type: 'log', id: string, level: string, message: string, timestamp: string, context?: TinkerValue }
|
|
56
|
+
| { type: 'query', id: string, sql: string, params?: TinkerValue, kind?: string, durationMs?: number, rowCount?: number, error?: string }
|
|
57
|
+
| { type: 'dump', id: string, value: TinkerValue }
|
|
58
|
+
| { type: 'result', id: string, value: TinkerValue, durationMs: number }
|
|
59
|
+
| { type: 'error', id: string, error: TinkerErrorValue, phase: 'parse' | 'run', diagnostics?: TinkerDiagnostic[], durationMs: number }
|
|
60
|
+
| { type: 'done', id: string, ok: boolean }
|
package/package.json
CHANGED