@stacksjs/tinker 0.75.539 → 0.75.542

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,5 @@
1
- import type { Subprocess } from 'bun';
2
- export type { Subprocess };
1
+ import { serialize } from './serialize';
2
+ import type { TinkerValue } from './serialize';
3
3
  /**
4
4
  * Get the path to the tinker history file.
5
5
  */
@@ -22,30 +22,25 @@ export declare function appendHistory(entry: string, config?: TinkerConfig): voi
22
22
  * Clear the tinker history file.
23
23
  */
24
24
  export declare function clearHistory(config?: TinkerConfig): void;
25
+ /** Print a described value for a terminal. The inverse of `serialize`, lossy on purpose. */
26
+ export declare function renderValue(value: TinkerValue, indent?: string): string;
27
+ /** Whether a parse failure means "keep typing" rather than "this is wrong". */
28
+ export declare function isIncompleteSnippet(code: string, cwd: string): boolean;
25
29
  /**
26
- * Start an interactive Stacks tinker session.
30
+ * Start a Stacks tinker session.
27
31
  *
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.
32
+ * Boots the app in this process (config overrides, then models, jobs and
33
+ * framework helpers as globals) and evaluates TypeScript against it. Without
34
+ * `eval`, `print` or `serve` it is an interactive prompt whose declarations
35
+ * persist from line to line.
31
36
  *
32
37
  * @example
33
38
  * ```ts
34
39
  * import { startTinker } from '@stacksjs/tinker'
35
40
  *
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()' })
41
+ * await startTinker() // interactive
42
+ * await startTinker({ print: 'await User.count()' }) // evaluate, print, exit
43
+ * await startTinker({ serve: true }) // speak the frame protocol on stdio
49
44
  * ```
50
45
  */
51
46
  export declare function startTinker(config?: TinkerConfig): Promise<{ exitCode: number }>;
@@ -65,7 +60,11 @@ export declare interface TinkerConfig {
65
60
  cwd?: string
66
61
  banner?: boolean
67
62
  stacksPreload?: boolean
63
+ serve?: boolean
68
64
  eval?: string
69
65
  print?: string
70
66
  verbose?: boolean
71
67
  }
68
+ export * from './protocol';
69
+ export * from './serialize';
70
+ export * from './session';
package/dist/index.js CHANGED
@@ -1,138 +1,42 @@
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 de,chmodSync as q,existsSync as J,readFileSync as fe,writeFileSync as K,writeSync as pe}from"fs";import{homedir as ge}from"os";import{join as ke,resolve as ye}from"path";import F from"process";import{writeSync as ue}from"fs";import{isAbsolute as ne,resolve as D}from"path";import E from"process";var Y={maxDepth:8,maxItems:200,maxString:20000,maxNodes:50000},G=/^\s*at\s+(?:(.*?)\s+\()?(.+?):(\d+):(\d+)\)?\s*$/;function Q(e){if(!e)return[];let r=[];for(let n of e.split(`
3
+ `)){let i=G.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 X(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 j(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}var _=2000000;class A{kind;html;title;text;constructor(e,r,n,i){this.kind=e;this.html=r;this.title=n;this.text=i}}var Z=["hasOne","hasMany","belongsTo","belongsToMany","hasOneThrough","hasManyThrough","morphOne","morphMany","morphTo","morphToMany"];function R(e){let r=[];for(let n of Z){let i=e[n];if(!i)continue;if(Array.isArray(i)){for(let o of i)if(typeof o==="string")r.push({kind:n,model:o});else if(o&&typeof o==="object"&&typeof o.model==="string")r.push({kind:n,model:o.model,...typeof o.relationName==="string"?{name:o.relationName}:{}})}else if(typeof i==="object"){for(let[o,s]of Object.entries(i))if(typeof s==="string")r.push({kind:n,model:s,name:o})}else if(typeof i==="string")r.push({kind:n,model:i})}return r}function ee(e){let r=e._definition;return typeof r==="object"&&r!==null&&typeof r.name==="string"&&typeof e.toJSON==="function"&&typeof e.getAttributes==="function"}function O(e,r={}){let n={...Y,...r.limits},i=r.describers??[],o=new Map,s=0,b=1;function g(a,y){if(s++,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:X(a)}}let t=a,d=o.get(t);if(d!==void 0)return{t:"ref",id:d};if(t instanceof Date)return{t:"date",v:Number.isNaN(t.getTime())?null:t.toISOString()};if(t instanceof RegExp)return{t:"regexp",v:t.toString()};if(t instanceof URL)return{t:"url",v:t.href};if(t instanceof Promise)return{t:"promise"};if(ArrayBuffer.isView(t)||t instanceof ArrayBuffer){let c=t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer,t.byteOffset,t.byteLength);return{t:"bytes",ctor:t.constructor?.name??"ArrayBuffer",length:c.byteLength,hex:Buffer.from(c.subarray(0,64)).toString("hex")}}if(y>=n.maxDepth)return{t:"cut",reason:"depth",ctor:j(t)??(Array.isArray(t)?"Array":"Object")};if(s>=n.maxNodes)return{t:"cut",reason:"nodes",ctor:j(t)};let u=b++;o.set(t,u);let l=(c)=>g(c,y+1);if(t instanceof Error)return k(t,l);if(t instanceof A){let c=t.html.length-_;return{t:"html",kind:t.kind,...t.title?{title:t.title}:{},html:c>0?t.html.slice(0,_):t.html,...t.text?{text:t.text.slice(0,n.maxString)}:{},...c>0?{truncated:c}:{}}}if(ee(t))return N(t,u,l);for(let c of i){let p;try{p=c(t,l)}catch{p=void 0}if(p)return p}if(Array.isArray(t)){let c=[],p=Math.min(t.length,n.maxItems);for(let S=0;S<p&&s<n.maxNodes;S++)c.push(l(t[S]));let f=t.length-c.length,m=j(t);return{t:"array",id:u,...m&&m!=="Array"?{ctor:m}:{},length:t.length,items:c,...f>0?{more:f}:{}}}if(t instanceof Map){let c=[];for(let[f,m]of t){if(c.length>=n.maxItems||s>=n.maxNodes)break;c.push([l(f),l(m)])}let p=t.size-c.length;return{t:"map",id:u,size:t.size,entries:c,...p>0?{more:p}:{}}}if(t instanceof Set){let c=[];for(let f of t){if(c.length>=n.maxItems||s>=n.maxNodes)break;c.push(l(f))}let p=t.size-c.length;return{t:"set",id:u,size:t.size,items:c,...p>0?{more:p}:{}}}return V(t,u,l)}function N(a,y,t){let d=a._definition,u={};try{let h=a.toJSON();if(h&&typeof h==="object")u=h}catch{}let l={};try{l=a.getLoadedRelations?.()??{}}catch{l={}}let c=Object.keys(u).filter((h)=>!(h in l)),p=[];for(let h of c){if(p.length>=n.maxItems||s>=n.maxNodes)break;p.push([h,t(u[h])])}let f=Object.keys(a).filter((h)=>!(h in u)),m=[];for(let[h,H]of Object.entries(l)){if(m.length>=n.maxItems||s>=n.maxNodes)break;m.push([h,t(H)])}let S=R(d),w=typeof d.primaryKey==="string"?d.primaryKey:"id",T=c.length-p.length;return{t:"model",id:y,model:d.name,...typeof d.table==="string"?{table:d.table}:{},primaryKey:w,...u[w]!==void 0?{key:t(u[w])}:{},attributes:p,...f.length?{hidden:f}:{},...m.length?{loaded:m}:{},...S.length?{relations:S}:{},...T>0?{more:T}:{}}}function V(a,y,t){let d=j(a),u=a,l=Object.keys(a);if(l.length===0&&d&&typeof a.toJSON==="function")try{let f=a.toJSON();if(Array.isArray(f)){let m=t(f);return m.t==="array"?{...m,ctor:d}:m}if(f&&typeof f==="object")u=f,l=Object.keys(u)}catch{}let c=[];for(let f of l){if(c.length>=n.maxItems||s>=n.maxNodes)break;let m;try{m=u[f]}catch(S){m=S}c.push([f,t(m)])}let p=l.length-c.length;return{t:"object",id:y,...d?{ctor:d}:{},entries:c,...p>0?{more:p}:{}}}function k(a,y){let t={t:"error",name:a.name||"Error",message:String(a.message??"")};if(typeof a.stack==="string"){t.stack=a.stack;let u=Q(a.stack);if(u.length)t.frames=u}if(a.cause!==void 0)t.cause=y(a.cause);let d=[];for(let u of Object.keys(a)){if(u==="name"||u==="message"||u==="stack"||u==="cause")continue;if(d.length>=n.maxItems)break;d.push([u,y(a[u])])}if(d.length)t.entries=d;return t}return g(e,0)}function P(e,r={}){if(e instanceof Error)return O(e,r);return{t:"error",name:"Thrown",message:typeof e==="string"?e:Bun.inspect(e),cause:O(e,r)}}function te(){let e=[],r=new Set;for(let n of Object.getOwnPropertyNames(globalThis)){let i;try{i=globalThis[n]}catch{continue}if(!i||typeof i!=="object"&&typeof i!=="function")continue;let o=i.getDefinition;if(typeof o!=="function")continue;let s;try{s=o.call(i)}catch{continue}if(!s||typeof s.name!=="string"||r.has(s.name))continue;r.add(s.name),e.push({name:s.name,...typeof s.table==="string"?{table:s.table}:{},...typeof s.primaryKey==="string"?{primaryKey:s.primaryKey}:{},attributes:s.attributes&&typeof s.attributes==="object"?Object.keys(s.attributes):[],relations:R(s)})}return e.sort((n,i)=>n.name.localeCompare(i.name))}function re(){return{html(e,r){return new A("html",String(e),r)},async mail(e,r={}){let{templateByName:i}=await import("@stacksjs/email"),o=await i(e,{variables:r});if(!o?.html)throw Error(`No mail template named "${e}" rendered. Templates live in resources/emails.`);return new A("mail",o.html,e,o.text)}}}class I extends Error{constructor(){super("dd");this.name="TinkerHalt"}}var ie=/\bimport\((["'])(\.{1,2}\/[^"']*)\1\)/g;function B(e,r,n="tinker"){return`${new Bun.Transpiler({loader:"ts",replMode:!0}).transformSync(e).replace(ie,(s,b,g)=>`import(${b}${D(r,g)}${b})`)}
4
+ //# sourceURL=${n}.ts`}function oe(e){let r=e instanceof AggregateError?e.errors:[e],n=[];for(let i of r){if(!i||typeof i!=="object")continue;let{message:o,position:s}=i;n.push({message:typeof o==="string"?o:String(i),...s?.line!==void 0?{line:s.line}:{},...s?.column!==void 0?{column:s.column}:{},...s?.lineText?{lineText:s.lineText}:{}})}return n}async function se(e){let i=!0;try{let{overridesReady:o}=await import("@stacksjs/config");await o}catch(o){i=!1,e.push(`config not loaded: ${o instanceof Error?o.message:String(o)}`)}try{let{injectGlobalAutoImports:o}=await import("@stacksjs/server");await o()}catch(o){i=!1,e.push(`models and framework globals not injected: ${o instanceof Error?o.message:String(o)}`)}return i}var ae=["log","info","debug","dir","table"],ce=["warn","error","trace"];async function v(e={}){let r=e.cwd?ne(e.cwd)?e.cwd:D(E.cwd(),e.cwd):E.cwd();if(r!==E.cwd())E.chdir(r);let n={limits:e.limits,describers:e.describers},i=[],o=[],s="boot",b,g=(l)=>{b?.push(l);try{e.onFrame?.(l)}catch{}},N=console,V=(l)=>l.map((c)=>typeof c==="string"?c:Bun.inspect(c,{depth:6})).join(" ");for(let[l,c]of[[ae,"stdout"],[ce,"stderr"]])for(let p of l){let f=N[p];N[p]=(...m)=>g({type:c,id:s,text:`${V(m)}
5
+ `}),o.push(()=>{N[p]=f})}for(let[l,c]of[[E.stdout,"stdout"],[E.stderr,"stderr"]]){let p=l.write;l.write=(f,...m)=>(g({type:c,id:s,text:typeof f==="string"?f:Buffer.from(f).toString("utf8")}),m.find((w)=>typeof w==="function")?.(),!0),o.push(()=>{l.write=p})}let k=globalThis;if(k.dump=(...l)=>{for(let c of l)g({type:"dump",id:s,value:O(c,n)})},k.dd=(...l)=>{throw k.dump(...l),new I},k.preview=re(),e.globals)Object.assign(k,e.globals);let a=!1;if(typeof e.boot==="function")try{await e.boot(),a=!0}catch(l){i.push(`boot failed: ${l instanceof Error?l.message:String(l)}`)}else if(e.boot!==!1)a=await se(i);if(a&&e.boot!==!1&&typeof e.boot!=="function"){let l=(...f)=>{for(let m of f)g({type:"dump",id:s,value:O(m,n)})};k.dump=l,k.dd=(...f)=>{throw l(...f),new I};let c="@stacksjs/logging",p="@stacksjs/query-builder";try{let{registerTransport:f}=await import(c);o.push(f({name:"tinker",log(m){g({type:"log",id:s,level:m.level,message:m.message,timestamp:m.timestamp,...m.context?{context:O(m.context,n)}:{}})}}))}catch(f){i.push(`log capture unavailable: ${f instanceof Error?f.message:String(f)}`)}try{let{registerPersistentQueryHooks:f}=await import(p);o.push(f({onQueryEnd(m){g({type:"query",id:s,sql:m.sql,...m.params?.length?{params:O(m.params,n)}:{},...m.kind?{kind:m.kind}:{},durationMs:m.durationMs,...m.rowCount!==void 0?{rowCount:m.rowCount}:{}})},onQueryError(m){g({type:"query",id:s,sql:m.sql,...m.params?.length?{params:O(m.params,n)}:{},...m.kind?{kind:m.kind}:{},durationMs:m.durationMs,error:m.error instanceof Error?m.error.message:String(m.error)})}}))}catch(f){i.push(`query capture unavailable: ${f instanceof Error?f.message:String(f)}`)}}let y=eval,t=0,d=Promise.resolve();async function u(l,c){let p=[];b=p,s=c;let f=performance.now(),m=()=>Math.round((performance.now()-f)*100)/100,S;try{S=B(l,r,`tinker-${c}`)}catch(w){let T=m();return g({type:"error",id:c,phase:"parse",error:P(w,n),diagnostics:oe(w),durationMs:T}),g({type:"done",id:c,ok:!1}),b=void 0,{id:c,ok:!1,thrown:w,frames:p,durationMs:T}}try{let T=(await y(S))?.value,h=m();return g({type:"result",id:c,value:O(T,n),durationMs:h}),g({type:"done",id:c,ok:!0}),{id:c,ok:!0,value:T,frames:p,durationMs:h}}catch(w){let T=m();if(w instanceof I)return g({type:"result",id:c,value:{t:"undefined"},durationMs:T}),g({type:"done",id:c,ok:!0}),{id:c,ok:!0,frames:p,durationMs:T};return g({type:"error",id:c,phase:"run",error:P(w,n),durationMs:T}),g({type:"done",id:c,ok:!1}),{id:c,ok:!1,thrown:w,frames:p,durationMs:T}}finally{b=void 0}}return{info:{cwd:r,booted:a,bun:Bun.version,warnings:i},models:te,evaluate(l,c={}){let p=c.id??String(++t),f=d.then(()=>u(l,p));return d=f.catch(()=>{}),f},dispose(){while(o.length)o.pop()?.()}}}var z="\x1E",me=2;function le(e){return`${z}${JSON.stringify(e)}
6
+ `}function Ie(){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 L(e={}){let r=e.write??((d)=>{ue(1,d)}),n=(d)=>r(le(d)),{input:i,write:o,once:s,...b}=e,g=await v({...b,onFrame:n});n({type:"ready",session:g.info,protocol:me});let N=!0,V=!1;if(s!==void 0)return{ok:(await g.evaluate(s)).ok,session:g};let k=new TextDecoder,a="",y=i??Bun.stdin.stream(),t=async(d)=>{if(!d.trim())return!0;let u;try{u=JSON.parse(d)}catch{return n({type:"protocol-error",message:"request is not valid JSON"}),!0}if(u.op==="exit")return!1;if(u.op==="ping")return n({type:"pong",...u.id?{id:u.id}:{}}),!0;if(u.op==="models")return n({type:"models",...u.id?{id:u.id}:{},models:g.models()}),!0;if(u.op==="eval"&&typeof u.code==="string"){if(!(await g.evaluate(u.code,{id:u.id})).ok)N=!1;return!0}return n({type:"protocol-error",message:`unknown request: ${String(u.op)}`}),!0};e:for await(let d of y){a+=typeof d==="string"?d:k.decode(d,{stream:!0});let u=a.indexOf(`
8
+ `);while(u!==-1){let l=a.slice(0,u);if(a=a.slice(u+1),!await t(l)){V=!0;break e}u=a.indexOf(`
9
+ `)}}if(!V&&a.trim())await t(a);return{ok:N,session:g}}function C(e){return e?.historyFile??ke(ge(),".stacks_tinker_history")}function U(e){let r=C(e);if(!J(r))return[];return fe(r,"utf-8").split(`
10
+ `).filter(Boolean)}function be(e,r){let n=C(r),i=!J(n);if(de(n,`${e}
11
+ `),i)try{q(n,384)}catch{}let o=r?.historySize??5000,s=U(r);if(s.length>o){let b=s.slice(s.length-o);K(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 he(e){let r=C(e);K(r,"")}function we(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 x(e,r=1){pe(r,e)}function M(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((o)=>`${n}${M(o,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((o)=>`${n}${M(o,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(([o,s])=>`${n}${M(o,n)} => ${M(s,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"html":return`[${e.kind}${e.title?` ${e.title}`:""}: ${e.html.length} characters of markup]`;case"model":{let i=e.attributes.map(([o,s])=>`${n}${o}: ${M(s,n)}`);for(let o of e.hidden??[])i.push(`${n}${o}: [hidden]`);for(let[o,s]of e.loaded??[])i.push(`${n}${o}: ${M(s,n)}`);if(e.more)i.push(`${n}... ${e.more} more`);return`${e.model} {
24
+ ${i.join(`,
25
+ `)}
26
+ ${r}}`}case"object":{if(!e.entries.length&&!e.more)return`${e.ctor?`${e.ctor} `:""}{}`;let i=e.entries.map(([o,s])=>`${n}${o}: ${M(s,n)}`);if(e.more)i.push(`${n}... ${e.more} more`);return`${e.ctor?`${e.ctor} `:""}{
27
+ ${i.join(`,
28
+ `)}
29
+ ${r}}`}}}function Te(e,r){switch(e.type){case"stdout":x(e.text);break;case"stderr":x(e.text,2);break;case"dump":x(`${M(e.value)}
30
+ `);break;case"query":if(r)x(`\x1B[2m ${e.durationMs??"?"}ms ${e.sql}\x1B[0m
31
+ `,2);break;case"log":if(r)x(`\x1B[2m [${e.level}] ${e.message}\x1B[0m
32
+ `,2);break}}async function xe(e,r){let n=[];for(let i of e??[]){let o=i.replace(/[@/\-.]/g,"_").replace(/^_+/,"");try{let s=i.startsWith(".")?ye(r,i):i;globalThis[o]=await import(s)}catch(s){n.push(`${i}: ${s instanceof Error?s.message:String(s)}`)}}return n}var Se=/Unexpected end of file|Unterminated|Expected "\}" but found end of file|Expected "\)" but found end of file|Expected "\]" but found end of file/;function $e(e,r){try{return B(e,r),!1}catch(n){return(n instanceof AggregateError?n.errors.map((o)=>String(o?.message??o)):[String(n?.message??n)]).some((o)=>Se.test(o))}}async function W(e={}){let r=e.cwd??F.cwd(),n=e.stacksPreload!==!1;if(e.serve){let t=e.eval??e.print,{ok:d}=await L({cwd:r,boot:n,...t!==void 0?{once:t}:{}});return{exitCode:d?0:1}}let i=e.verbose===!0,o=await v({cwd:r,boot:n,onFrame:(t)=>Te(t,i)});for(let t of await xe(e.preload,o.info.cwd))x(`\x1B[33mpreload failed: ${t}\x1B[0m
33
+ `,2);let s=(t,d)=>{if(!t.ok){let u=t.thrown;x(`${u instanceof Error?Bun.inspect(u,{colors:Boolean(F.stderr.isTTY)}):`Uncaught ${Bun.inspect(u)}`}
34
+ `,2);return}if(d&&t.value!==void 0)x(`${M(O(t.value))}
35
+ `)};if(e.eval!==void 0||e.print!==void 0){for(let d of o.info.warnings)x(`\x1B[33m${d}\x1B[0m
36
+ `,2);let t=await o.evaluate(e.print??e.eval??"");return s(t,e.print!==void 0),o.dispose(),{exitCode:t.ok?0:1}}if(e.banner!==!1)x(we(o.info));let{createInterface:b}=await import("readline"),{Writable:g}=await import("stream"),N=new g({write(t,d,u){x(typeof t==="string"?t:Buffer.from(t).toString("utf8")),u()}}),V=e.historySize??5000,k=b({input:F.stdin,output:N,terminal:Boolean(F.stdin.isTTY),history:U(e).slice(-V).reverse(),historySize:V,prompt:"> "}),a="",y=0;k.prompt();try{for await(let t of k){let d=t.trim();if(!a&&d.startsWith(".")){if(d===".exit")break;if(d===".help")x(` .exit leave
37
+ .clear forget the history file
38
+ .help this list
39
+ dump(v), dd(v) inspect a value; dd also stops the line
40
+ `);else if(d===".clear")he(e);else x(` unknown command ${d}
41
+ `);k.prompt();continue}if(a=a?`${a}
42
+ ${t}`:t,!a.trim()){a="",k.prompt();continue}if($e(a,o.info.cwd)){k.setPrompt("... "),k.prompt();continue}let u=a;a="";try{be(u.replace(/\n/g," "),e)}catch{}let l=await o.evaluate(u);s(l,!0),y=l.ok?0:y,k.setPrompt("> "),k.prompt()}}finally{k.close(),o.dispose()}return{exitCode:y}}async function De(e,r={}){return W({...r,eval:e})}async function Le(e,r={}){return W({...r,print:e})}export{z as FRAME_MARKER,me as PROTOCOL_VERSION,A as TinkerPreview,be as appendHistory,he as clearHistory,B as compileSnippet,Ie as createFrameReader,v as createTinkerSession,Y as defaultSerializeLimits,le as encodeFrame,C as getHistoryPath,$e as isIncompleteSnippet,ee as isModelInstance,te as listModels,Q as parseStack,U as readHistory,R as relationsOf,M as renderValue,O as serialize,P as serializeThrown,L as serveTinker,W as startTinker,De as tinkerEval,Le as tinkerPrint};
@@ -0,0 +1,48 @@
1
+ import type { TinkerFrame, TinkerModelSummary, 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: 2;
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: 'models', id?: string }
43
+ | { op: 'exit' }
44
+ export type TinkerWireFrame = | TinkerFrame
45
+ | { type: 'ready', session: TinkerSessionInfo, protocol: number }
46
+ | { type: 'pong', id?: string }
47
+ | { type: 'models', id?: string, models: TinkerModelSummary[] }
48
+ | { type: 'protocol-error', message: string }
@@ -0,0 +1,126 @@
1
+ /** Parse a V8/JavaScriptCore `at fn (file:line:col)` stack into frames. */
2
+ export declare function parseStack(stack: string | undefined): TinkerStackFrame[];
3
+ /** The relations a model definition declares, flattened. */
4
+ export declare function relationsOf(definition: Record<string, unknown>): Array<{ kind: string, model: string, name?: string }>;
5
+ /**
6
+ * Whether a value is a Stacks ORM model instance. Told by shape, not by
7
+ * class, so this package needs no dependency on the ORM and a model from any
8
+ * copy of it is recognised.
9
+ */
10
+ export declare function isModelInstance(value: object): value is ModelLike;
11
+ export declare function serialize(root: unknown, options?: SerializeOptions): TinkerValue;
12
+ /** Describe anything thrown, including non-Error throws, as an error value. */
13
+ export declare function serializeThrown(thrown: unknown, options?: SerializeOptions): TinkerErrorValue;
14
+ export declare const defaultSerializeLimits: SerializeLimits;
15
+ /**
16
+ * Turn any runtime value into a plain, JSON-safe description of it.
17
+ *
18
+ * A tinker result has to leave the process that produced it: over a pipe to a
19
+ * desktop app, over SSH from a server, out of a container. `JSON.stringify`
20
+ * loses most of what makes a result worth looking at (undefined, bigint, Date,
21
+ * Map, Set, class names, cycles, errors), and `Bun.inspect` produces a string a
22
+ * UI can only print. This produces a tree a UI can expand, sort and tabulate,
23
+ * with every node tagged by what it was.
24
+ *
25
+ * It is bounded on purpose. A model collection with a million rows or an
26
+ * object graph that reaches the whole app must not hang the session or flood
27
+ * the pipe, so depth, items per container, string length and total nodes all
28
+ * have limits, and every cut is recorded on the node (`more`, `truncated`)
29
+ * rather than silently dropped.
30
+ */
31
+ export declare interface SerializeLimits {
32
+ maxDepth: number
33
+ maxItems: number
34
+ maxString: number
35
+ maxNodes: number
36
+ }
37
+ export declare interface TinkerStackFrame {
38
+ function?: string
39
+ file?: string
40
+ line?: number
41
+ column?: number
42
+ }
43
+ export declare interface TinkerErrorValue {
44
+ t: 'error'
45
+ name: string
46
+ message: string
47
+ stack?: string
48
+ frames?: TinkerStackFrame[]
49
+ cause?: TinkerValue
50
+ entries?: Array<[string, TinkerValue]>
51
+ }
52
+ /** An ORM model instance: what it is, its visible attributes, and what it relates to. */
53
+ export declare interface TinkerModelValue {
54
+ t: 'model'
55
+ id: number
56
+ model: string
57
+ table?: string
58
+ primaryKey?: string
59
+ key?: TinkerValue
60
+ attributes: Array<[string, TinkerValue]>
61
+ hidden?: string[]
62
+ loaded?: Array<[string, TinkerValue]>
63
+ relations?: Array<{ kind: string, model: string, name?: string }>
64
+ more?: number
65
+ }
66
+ /** Rendered markup to show as a page instead of as text: a mail, a view, any HTML. */
67
+ export declare interface TinkerHtmlValue {
68
+ t: 'html'
69
+ kind: 'mail' | 'view' | 'html'
70
+ title?: string
71
+ html: string
72
+ text?: string
73
+ truncated?: number
74
+ }
75
+ export declare interface SerializeOptions {
76
+ limits?: Partial<SerializeLimits>
77
+ describers?: TinkerDescriber[]
78
+ }
79
+ declare interface ModelLike {
80
+ _definition: { name: string, table?: string, primaryKey?: string } & Record<string, unknown>
81
+ toJSON: () => unknown
82
+ getLoadedRelations?: () => Record<string, unknown>
83
+ }
84
+ export type TinkerValue = | { t: 'undefined' }
85
+ | { t: 'null' }
86
+ | { t: 'boolean', v: boolean }
87
+ | { t: 'number', v: number | 'NaN' | 'Infinity' | '-Infinity' | '-0' }
88
+ | { t: 'bigint', v: string }
89
+ | { t: 'string', v: string, truncated?: number }
90
+ | { t: 'symbol', v: string }
91
+ | { t: 'function', name: string, kind: 'function' | 'async' | 'class' | 'generator' }
92
+ | { t: 'date', v: string | null }
93
+ | { t: 'regexp', v: string }
94
+ | { t: 'url', v: string }
95
+ | { t: 'promise' }
96
+ | { t: 'bytes', ctor: string, length: number, hex: string }
97
+ | { t: 'array', id: number, ctor?: string, length: number, items: TinkerValue[], more?: number }
98
+ | { t: 'object', id: number, ctor?: string, entries: Array<[string, TinkerValue]>, more?: number }
99
+ | { t: 'map', id: number, size: number, entries: Array<[TinkerValue, TinkerValue]>, more?: number }
100
+ | { t: 'set', id: number, size: number, items: TinkerValue[], more?: number }
101
+ | TinkerModelValue
102
+ | TinkerHtmlValue
103
+ | { t: 'ref', id: number }
104
+ | { t: 'cut', reason: 'depth' | 'nodes', ctor?: string }
105
+ | TinkerErrorValue;
106
+ /**
107
+ * Describes one kind of value the generic walk does not know about, such as an
108
+ * ORM model. Return `undefined` to pass. `describe` continues the walk for
109
+ * nested values so limits and cycle tracking stay in force.
110
+ */
111
+ export type TinkerDescriber = (
112
+ value: object,
113
+ describe: (value: unknown) => TinkerValue,
114
+ ) => TinkerValue | undefined;
115
+ /**
116
+ * A value that says "show me as rendered markup". Returned by the session's
117
+ * `preview` helpers; a snippet can also construct one.
118
+ */
119
+ export declare class TinkerPreview {
120
+ readonly kind: TinkerHtmlValue['kind'];
121
+ readonly html: string;
122
+ readonly title?: string;
123
+ readonly text?: string;
124
+ constructor(kind: TinkerHtmlValue['kind'], html: string, title?: string, text?: string);
125
+ }
126
+ export {};
@@ -0,0 +1,74 @@
1
+ import type { SerializeOptions, TinkerErrorValue, TinkerValue } from './serialize';
2
+ /**
3
+ * The models reachable as globals in this process. After a Stacks boot that
4
+ * is every model the app loaded; in a bare session it is none.
5
+ */
6
+ export declare function listModels(): TinkerModelSummary[];
7
+ /**
8
+ * Compile a snippet into JavaScript that, evaluated at global scope, runs it
9
+ * and yields `{ value }` for its last expression.
10
+ *
11
+ * Evaluated code has no file, so a relative `import('./app/Actions/Foo')`
12
+ * would resolve against this package instead of the project. Those specifiers
13
+ * are made absolute against the session's directory.
14
+ */
15
+ export declare function compileSnippet(code: string, cwd: string, sourceName?: string): string;
16
+ export declare function createTinkerSession(options?: TinkerSessionOptions): Promise<TinkerSession>;
17
+ export declare interface TinkerDiagnostic {
18
+ message: string
19
+ line?: number
20
+ column?: number
21
+ lineText?: string
22
+ }
23
+ export declare interface TinkerSessionInfo {
24
+ cwd: string
25
+ booted: boolean
26
+ bun: string
27
+ warnings: string[]
28
+ }
29
+ export declare interface TinkerSessionOptions extends SerializeOptions {
30
+ cwd?: string
31
+ boot?: boolean | (() => Promise<void> | void)
32
+ globals?: Record<string, unknown>
33
+ onFrame?: (frame: TinkerFrame) => void
34
+ }
35
+ export declare interface TinkerEvaluation {
36
+ id: string
37
+ ok: boolean
38
+ value?: unknown
39
+ thrown?: unknown
40
+ frames: TinkerFrame[]
41
+ durationMs: number
42
+ }
43
+ /** One model the app knows, as a relation graph and a completion list need it. */
44
+ export declare interface TinkerModelSummary {
45
+ name: string
46
+ table?: string
47
+ primaryKey?: string
48
+ attributes: string[]
49
+ relations: Array<{ kind: string, model: string, name?: string }>
50
+ }
51
+ export declare interface TinkerSession {
52
+ info: TinkerSessionInfo
53
+ models: () => TinkerModelSummary[]
54
+ evaluate: (code: string, options?: { id?: string }) => Promise<TinkerEvaluation>
55
+ dispose: () => void
56
+ }
57
+ /**
58
+ * A tinker session: one booted app, many evaluations, state kept between them.
59
+ *
60
+ * The old tinker wrote the snippet to a file in the project and ran a fresh
61
+ * `bun` on it, so nothing survived from one run to the next, a snippet with
62
+ * two statements was a syntax error, and the caller learned only an exit code.
63
+ * A session evaluates in its own process instead. Declarations are hoisted to
64
+ * globals by Bun's REPL transform, so `const user = await User.find(1)` in one
65
+ * run is still `user` in the next, and each run reports what happened as
66
+ * frames: output, logs, queries, dumps, then a typed result or an error.
67
+ */
68
+ export type TinkerFrame = | { type: 'stdout' | 'stderr', id: string, text: string }
69
+ | { type: 'log', id: string, level: string, message: string, timestamp: string, context?: TinkerValue }
70
+ | { type: 'query', id: string, sql: string, params?: TinkerValue, kind?: string, durationMs?: number, rowCount?: number, error?: string }
71
+ | { type: 'dump', id: string, value: TinkerValue }
72
+ | { type: 'result', id: string, value: TinkerValue, durationMs: number }
73
+ | { type: 'error', id: string, error: TinkerErrorValue, phase: 'parse' | 'run', diagnostics?: TinkerDiagnostic[], durationMs: number }
74
+ | { 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.539",
5
+ "version": "0.75.542",
6
6
  "description": "Interactive REPL with Stacks framework preloaded.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [