@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 CHANGED
@@ -1,5 +1,4 @@
1
- import type { Subprocess } from 'bun';
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 an interactive Stacks tinker session.
29
+ * Start a Stacks tinker session.
27
30
  *
28
- * Launches Bun's built-in REPL with all Stacks framework modules
29
- * preloaded into the global scope. ORM models, database, config,
30
- * logging, and other framework utilities are immediately available.
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{existsSync as d,readFileSync as y,writeFileSync as c,appendFileSync as f,chmodSync as p}from"fs";import{homedir as k}from"os";import{join as u}from"path";import n from"process";function g(t){let e=[];if(t.stacksPreload!==!1)e.push(`
3
- // ---- Stacks Framework Preload ----
4
- // ORM & Database
5
- try {
6
- const { db } = await import('@stacksjs/database')
7
- globalThis.db = db
8
- } catch {}
9
-
10
- try {
11
- const orm = await import('@stacksjs/orm')
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{p(s,384)}catch{}}}function q(t){let e=h(t);c(e,"")}function w(){return["",` \x1B[36mStacks Tinker\x1B[0m (Bun v${typeof Bun<"u"?Bun.version:"unknown"})`," Interactive REPL with Stacks framework preloaded.",""," \x1B[2mAvailable globals: db, config, path, storage, log, Str,"," cache, queue, events, router, auth, collect, env, request,"," validate, Jobs, routes, dump, dd"," + all ORM models (User, Post, etc.)\x1B[0m",""," \x1B[2mType .help for REPL commands. Press Ctrl+D to exit.\x1B[0m",""].join(`
135
- `)}async function m(t={}){let e=t.cwd??n.cwd();if(t.eval||t.print)return T(t,e);if(t.banner!==!1)n.stdout.write(w());let s=g(t),o=u(e,".tinker-preload.ts");c(o,s);let i=["repl"],a=await Bun.spawn(["bun",...i],{cwd:e,stdin:"inherit",stdout:"inherit",stderr:"inherit",env:{...n.env,BUN_PRELOAD:o}}).exited;try{let{unlinkSync:l}=await import("fs");l(o)}catch{}return{exitCode:a}}async function T(t,e){let s=g(t),o=t.print??t.eval??"",i=`${s}
136
-
137
- const __result__ = await (async () => { return ${o} })()
138
- ${t.print?"console.log(__result__)":""}`,r=u(e,".tinker-eval.ts");c(r,i);let l=await Bun.spawn(["bun","run",r],{cwd:e,stdin:"inherit",stdout:"inherit",stderr:"inherit",env:n.env}).exited;try{let{unlinkSync:b}=await import("fs");b(r)}catch{}return{exitCode:l}}async function R(t,e={}){return m({...e,eval:t})}async function _(t,e={}){return m({...e,print:t})}export{C as appendHistory,q as clearHistory,h as getHistoryPath,v as readHistory,m as startTinker,R as tinkerEval,_ as tinkerPrint};
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
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/tinker",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.538",
5
+ "version": "0.75.541",
6
6
  "description": "Interactive REPL with Stacks framework preloaded.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [