@forinda/kickjs-devtools 7.7.0 → 7.9.0
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.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter.ts","../src/request-log.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter.ts","../src/request-log.ts"],"mappings":";;;;;;;UA+GU;EACR;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;;UA4Fe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,uBAAuB;;EAEvB;;EAEA;;;;;;;;;;;;;EAcA;;;;;;;;;;EAWA;;IAEE;;IAEA;;IAEA;;;;;;EAMF;;;;;;;UAQe;;WAEN,cAAc;;WAEd,YAAY;;WAEZ,kBAAkB;;WAElB,WAAW;;WAEX,WAAW;;WAEX,eAAe;;WAEf,cAAc,eAAe;;;;;;WAM7B,gBAAgB;;;;;;WAMhB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA8Bd,iBAAe,KAAA,eAAA,iBAAA;;;;;;;;;;;UC/TX;;EAEf;;EAEA;EACA;EACA;;EAEA;EACA;EACA;EACA;;EAEA;IAAU;IAAc"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-devtools v7.
|
|
2
|
+
* @forinda/kickjs-devtools v7.9.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import"reflect-metadata";import{dirname,join,relative,sep}from"node:path";import{tmpdir}from"node:os";import{fileURLToPath}from"node:url";import{createReadStream,existsSync,readFileSync,readdirSync,statSync,unlink}from"node:fs";import{Readable}from"node:stream";import{randomBytes}from"node:crypto";import{writeHeapSnapshot}from"node:v8";import*as kick from"@forinda/kickjs";import{METADATA,computed,createLogger,defineAdapter,getClassMeta,getMethodMeta,getRouteFlags,reactive,ref,watch}from"@forinda/kickjs";import{MemoryAnalyzer,PROTOCOL_VERSION,RuntimeSampler}from"@forinda/kickjs-devtools-kit";import{DEVTOOLS_BUS}from"@forinda/kickjs-devtools-kit/bus/token";import{createBusCore}from"@forinda/kickjs-devtools-kit/bus";import{WebSocketServer}from"ws";import{detectSchema}from"@forinda/kickjs-schema";async function collectTopologySnapshot(opts){let{app,container,introspectTimeoutMs=100}=opts,errors=[],plugins=await Promise.all(app.getPlugins().map(p=>snapshotFor(p,`plugin`,introspectTimeoutMs,errors))),adapters=await Promise.all(app.getAdapters().map(a=>snapshotFor(a,`adapter`,introspectTimeoutMs,errors)));return{protocolVersion:PROTOCOL_VERSION,timestamp:Date.now(),plugins,adapters,contributors:collectContributors(app),diTokens:collectTokens(container),errors}}async function snapshotFor(primitive,kind,timeoutMs,errors){let name=primitive.name??`(unnamed)`,stub={protocolVersion:PROTOCOL_VERSION,name,kind};if(typeof primitive.introspect!=`function`)return stub;try{let result=await Promise.race([Promise.resolve(primitive.introspect()),new Promise((_,reject)=>setTimeout(()=>reject(Error(`introspect() timed out after ${timeoutMs}ms`)),timeoutMs))]);if(result&&typeof result==`object`){let snap=result;return{protocolVersion:PROTOCOL_VERSION,name:snap.name??name,kind:snap.kind??kind,version:snap.version,state:snap.state,tokens:snap.tokens,metrics:snap.metrics,memoryBytes:snap.memoryBytes}}return stub}catch(err){return errors.push({name,kind,message:err instanceof Error?err.message:String(err)}),stub}}function collectTokens(container){return container.getRegistrations().filter(r=>!r.token.startsWith(`__hmr__`)).map(r=>({token:r.token,scope:r.scope,kind:r.kind,instantiated:r.instantiated}))}function collectContributors(app){let seen=new Map;if(typeof app.getContributors==`function`){for(let c of app.getContributors())seen.has(c.key)||seen.set(c.key,{key:c.key,source:c.source,dependsOn:c.dependsOn});return[...seen.values()]}let ingest=(list,source)=>{if(list)for(let reg of list){let r=reg,key=typeof r.key==`string`?r.key:null;key&&!seen.has(key)&&seen.set(key,{key,source,dependsOn:Array.isArray(r.dependsOn)?r.dependsOn:[]})}};for(let plugin of app.getPlugins())ingest(plugin.contributors?.(),`adapter`);for(let adapter of app.getAdapters())ingest(adapter.contributors?.(),`adapter`);return[...seen.values()]}const VALID_VIEW_TYPES=new Set([`iframe`,`launch`,`html`,`module`]);function collectDevtoolsTabs(app){let tabs=[],seen=new Map,errors=[],ingest=(sourceName,raw)=>{if(!Array.isArray(raw)){errors.push({source:sourceName,reason:`devtoolsTabs() did not return an array`});return}for(let entry of raw){let validation=validateTab(entry);if(!validation.ok){errors.push({source:sourceName,reason:validation.reason});continue}let tab=validation.tab,owner=seen.get(tab.id);if(owner){errors.push({source:sourceName,reason:`tab id '${tab.id}' already registered by ${owner} — first-source wins`});continue}seen.set(tab.id,sourceName),tabs.push(tab)}};for(let plugin of app.getPlugins())if(typeof plugin.devtoolsTabs==`function`)try{ingest(plugin.name??`(unnamed plugin)`,plugin.devtoolsTabs())}catch(err){errors.push({source:plugin.name??`(unnamed plugin)`,reason:err instanceof Error?err.message:String(err)})}for(let adapter of app.getAdapters())if(typeof adapter.devtoolsTabs==`function`)try{ingest(adapter.name??`(unnamed adapter)`,adapter.devtoolsTabs())}catch(err){errors.push({source:adapter.name??`(unnamed adapter)`,reason:err instanceof Error?err.message:String(err)})}return{tabs,errors}}function validateTab(raw){if(!raw||typeof raw!=`object`)return{ok:!1,reason:`tab entry is not an object`};let obj=raw;if(typeof obj.id!=`string`||obj.id.length===0)return{ok:!1,reason:`tab.id must be a non-empty string`};if(typeof obj.title!=`string`||obj.title.length===0)return{ok:!1,reason:`tab '${obj.id}': title must be a non-empty string`};let view=obj.view;if(!view||typeof view!=`object`)return{ok:!1,reason:`tab '${obj.id}': view object is required`};if(typeof view.type!=`string`||!VALID_VIEW_TYPES.has(view.type))return{ok:!1,reason:`tab '${obj.id}': view.type must be one of iframe / launch / html / module`};switch(view.type){case`iframe`:if(typeof view.src!=`string`||view.src.length===0)return{ok:!1,reason:`tab '${obj.id}': iframe view requires a non-empty src`};break;case`html`:if(typeof view.html!=`string`)return{ok:!1,reason:`tab '${obj.id}': html view requires html string`};break;case`module`:if(typeof view.src!=`string`||view.src.length===0)return{ok:!1,reason:`tab '${obj.id}': module view requires a non-empty src`};break;case`launch`:if(!Array.isArray(view.actions))return{ok:!1,reason:`tab '${obj.id}': launch view requires actions array`}}return{ok:!0,tab:obj}}async function runTabAction(app,tabId,actionId){let tab=collectDevtoolsTabs(app).tabs.find(t=>t.id===tabId),action=tab?.view.type===`launch`?tab.view.actions.find(a=>a.id===actionId):void 0;if(!action)return{status:404,body:{error:`no action '${actionId}' on tab '${tabId}'`}};if(typeof action.run!=`function`)return{status:404,body:{error:`action '${actionId}' has no run()`}};try{return{status:200,body:{result:await action.run()??null}}}catch(err){return{status:500,body:{error:err instanceof Error?err.message:String(err)}}}}var RequestLog=class{capacity;entries=[];pendingErrors=new Map;seq=0;constructor(capacity){this.capacity=capacity}record(info,now=Date.now()){if(this.capacity<=0)return;let entry={seq:++this.seq,at:now,...info};info.requestId&&this.pendingErrors.has(info.requestId)&&(entry.error=this.pendingErrors.get(info.requestId),this.pendingErrors.delete(info.requestId)),this.entries.push(entry),this.entries.length>this.capacity&&this.entries.shift()}recordError(requestId,err){if(this.capacity<=0||!requestId)return;let error=err instanceof Error?{name:err.name,message:err.message}:{name:`Error`,message:String(err)};for(let i=this.entries.length-1;i>=0;i--)if(this.entries[i].requestId===requestId){this.entries[i].error=error;return}this.pendingErrors.set(requestId,error),this.pendingErrors.size>100&&this.pendingErrors.delete(this.pendingErrors.keys().next().value)}latest(){return this.seq}after(since=0){return this.entries.filter(e=>e.seq>since)}};const STATES=new Set([`waiting`,`active`,`delayed`,`completed`,`failed`,`paused`]),ACTIONS=[`retry`,`remove`,`retryAll`,`clean`,`pause`,`resume`],bad=(error,status=400)=>({status,body:{error}});function findJobInspectors(app){let found=new Map;for(let owner of[...app.getAdapters(),...app.getPlugins()]){let get=owner.jobInspector;if(typeof get!=`function`)continue;let inspector=get.call(owner);inspector&&found.set(owner.name??`jobs`,inspector)}return found}async function listJobSources(inspectors){return{status:200,body:{sources:await Promise.all([...inspectors].map(async([source,inspector])=>{let actions=ACTIONS.filter(a=>typeof inspector[a]==`function`);try{return{source,actions,queues:await inspector.queues()}}catch(err){return{source,actions,queues:[],error:message(err)}}}))}}}async function listJobs(inspectors,q){let inspector=inspectors.get(String(q.source??``));if(!inspector)return bad(`unknown source`,404);let state=String(q.state??`failed`);if(!STATES.has(state))return bad(`unknown state '${state}'`);let start=Math.max(0,Number(q.start??0)||0),end=Math.min(start+100-1,Math.max(start,Number(q.end??start+49)||0));try{return{status:200,body:{jobs:await inspector.jobs(String(q.queue??``),state,{start,end})}}}catch(err){return bad(message(err),500)}}async function getJob(inspectors,q){let inspector=inspectors.get(String(q.source??``));if(!inspector)return bad(`unknown source`,404);try{let job=await inspector.job(String(q.queue??``),String(q.id??``));return job?{status:200,body:job}:bad(`job not found`,404)}catch(err){return bad(message(err),500)}}async function runJobAction(inspectors,q){let inspector=inspectors.get(String(q.source??``));if(!inspector)return bad(`unknown source`,404);let action=String(q.action??``);if(!ACTIONS.includes(action))return bad(`unknown action '${action}'`);let queue=String(q.queue??``),id=String(q.id??``),state=String(q.state??``);try{switch(action){case`retry`:case`remove`:return id?inspector[action]?(await inspector[action](queue,id),{status:200,body:{ok:!0}}):bad(`${action} is not supported`,501):bad(`id is required`);case`clean`:return STATES.has(state)?inspector.clean?{status:200,body:{ok:!0,count:await inspector.clean(queue,state)}}:bad(`clean is not supported`,501):bad(`state is required`);default:return inspector[action]?{status:200,body:{ok:!0,count:await inspector[action](queue)??null}}:bad(`${action} is not supported`,501)}}catch(err){return bad(message(err),500)}}const message=err=>err instanceof Error?err.message:String(err),escape=name=>name.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);function findHandlerLine(source,controller,handler){let cls=RegExp(`\\bclass\\s+${escape(controller)}\\b`).exec(source);if(!cls)return;let lineAt=offset=>source.slice(0,offset).split(`
|
|
12
|
-
`).length,start=cls.index+cls[0].length,rest=source.slice(start),next=rest.search(/^(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s/m),body=next===-1?rest:rest.slice(0,next),method=RegExp(`^[ \\t]*(?:(?:public|private|protected|static|async|override)\\s+)*${escape(handler)}\\s*[(<]`,`m`).exec(body);return lineAt(method?start+method.index+method[0].search(/\S/):cls.index)}function locateHandler(root,controller,handler){let dir=existsSync(join(root,`src`))?join(root,`src`):root,files=readdirSync(dir,{recursive:!0,encoding:`utf-8`}).filter(f=>/\.[mc]?tsx?$/.test(f)&&!/\.d\.[mc]?ts$/.test(f)&&!f.split(sep).includes(`node_modules`)&&!f.split(sep).includes(`dist`));for(let f of files){let file=join(dir,f),source;try{source=readFileSync(file,`utf-8`)}catch{continue}if(!source.includes(controller))continue;let line=findHandlerLine(source,controller,handler);if(line!==void 0)return{file,relative:relative(root,file).split(sep).join(`/`),line}}}function createServerBus(opts={}){let wsPath=opts.wsPath??`/_debug/_bus`,secret=opts.secret??!1,core=createBusCore(),wss=new WebSocketServer({noServer:!0}),clients=new Set,attachedServer=null,upgradeHandler=null;wss.on(`connection`,ws=>{clients.add(ws),ws.on(`message`,data=>{try{let text=typeof data==`string`?data:data.toString(`utf-8`),parsed=JSON.parse(text);if(!parsed||typeof parsed.type!=`string`||typeof parsed.ts!=`number`)return;core.dispatch({...parsed,__kick_origin:`ws`})}catch{}}),ws.on(`close`,()=>{clients.delete(ws)}),ws.on(`error`,()=>{clients.delete(ws)})});let fanOut=envelope=>{if(clients.size===0)return;let text=JSON.stringify(envelope)
|
|
11
|
+
import{createRequire}from"node:module";import"reflect-metadata";import{dirname,join,relative,sep}from"node:path";import{tmpdir}from"node:os";import{fileURLToPath}from"node:url";import{createReadStream,existsSync,readFileSync,readdirSync,statSync,unlink}from"node:fs";import{Readable}from"node:stream";import{randomBytes}from"node:crypto";import{writeHeapSnapshot}from"node:v8";import*as kick from"@forinda/kickjs";import{METADATA,computed,createLogger,defineAdapter,getClassMeta,getMethodMeta,getRouteFlags,isCronJobEnabled,listCronJobs,reactive,ref,runCronJob,watch}from"@forinda/kickjs";import{MemoryAnalyzer,PROTOCOL_VERSION,RuntimeSampler}from"@forinda/kickjs-devtools-kit";import{DEVTOOLS_BUS,getActiveDevtoolsBus,setActiveDevtoolsBus}from"@forinda/kickjs-devtools-kit/bus/token";import{createBusCore}from"@forinda/kickjs-devtools-kit/bus";import{WebSocketServer}from"ws";import{detectSchema}from"@forinda/kickjs-schema";const TRACKED=Symbol.for(`kickjs.devtools.cronTracked`),stats=new Map,keyOf=job=>`${job.target.name}.${job.handlerName}`;function statsFor(key){let s=stats.get(key);return s||stats.set(key,s={runs:0,failures:0,running:0}),s}function trackCronJobs(jobs){for(let job of jobs){let proto=job.target.prototype,original=proto[job.handlerName];if(typeof original!=`function`||original[TRACKED])continue;let s=statsFor(keyOf(job)),settle=(startedAt,error)=>{s.running--,s.runs++,s.lastDurationMs=performance.now()-startedAt,s.lastOutcome=error===void 0?`ok`:`failed`,error!==void 0&&(s.failures++,s.lastError=error instanceof Error?error.message:String(error))},wrapped=function(...args){s.running++,s.lastStartedAt=Date.now();let startedAt=performance.now(),result;try{result=original.apply(this,args)}catch(err){throw settle(startedAt,err??Error(`threw`)),err}return result&&typeof result.then==`function`?result.then(value=>(settle(startedAt),value),err=>{throw settle(startedAt,err??Error(`rejected`)),err}):(settle(startedAt),result)};Object.defineProperty(wrapped,TRACKED,{value:!0}),Object.defineProperty(wrapped,"name",{value:original.name}),proto[job.handlerName]=wrapped}}function trackContainerCronJobs(container){trackCronJobs(listCronJobs(container))}function loadNextRun(){try{let{Cron}=createRequire(join(process.cwd(),`noop.js`))(`croner`);return(expression,timezone)=>{try{let c=new Cron(expression,{paused:!0,...timezone?{timezone}:{}}),next=c.nextRun();return c.stop(),next?next.getTime():null}catch{return null}}}catch{return()=>null}}let nextRun;function cronSnapshot(container){let jobs=listCronJobs(container);return trackCronJobs(jobs),nextRun??=loadNextRun(),jobs.map(job=>({name:job.name,className:job.target.name,handler:job.handlerName,expression:job.expression,...job.timezone?{timezone:job.timezone}:{},...job.description?{description:job.description}:{},enabled:isCronJobEnabled(job),overlap:!!job.overlap,runOnInit:!!job.runOnInit,nextRunAt:nextRun(job.expression,job.timezone),stats:{...statsFor(keyOf(job))}}))}function runCronJobNow(container,name){let job=listCronJobs(container).find(j=>j.name===name);return job?isCronJobEnabled(job)?(trackCronJobs([job]),!job.overlap&&statsFor(keyOf(job)).running>0?{status:409,body:{error:`${name} is already running`}}:(runCronJob(job,container,`manual`).catch(()=>{}),{status:202,body:{started:!0}})):{status:409,body:{error:`${name} is disabled`}}:{status:404,body:{error:`No cron job named "${name}"`}}}const HOOKS=[`register`,`modules`,`adapters`,`setup`,`middleware`,`contributors`,`beforeMount`,`onRouteMount`,`beforeStart`,`afterStart`,`onReady`,`onResponse`,`onError`,`onHealthCheck`,`shutdown`];async function collectTopologySnapshot(opts){let{app,container,introspectTimeoutMs=100}=opts,errors=[],plugins=await Promise.all(app.getPlugins().map(p=>snapshotFor(p,`plugin`,introspectTimeoutMs,errors))),adapters=await Promise.all(app.getAdapters().map(a=>snapshotFor(a,`adapter`,introspectTimeoutMs,errors)));return{protocolVersion:PROTOCOL_VERSION,timestamp:Date.now(),plugins,adapters,contributors:collectContributors(app),diTokens:collectTokens(container),errors}}async function snapshotFor(primitive,kind,timeoutMs,errors){let name=primitive.name??`(unnamed)`,hooks=HOOKS.filter(h=>typeof primitive[h]==`function`),dependsOn=Array.isArray(primitive.dependsOn)?[...primitive.dependsOn]:[],stub={protocolVersion:PROTOCOL_VERSION,name,kind,hooks,dependsOn};if(typeof primitive.introspect!=`function`)return stub;try{let result=await Promise.race([Promise.resolve(primitive.introspect()),new Promise((_,reject)=>setTimeout(()=>reject(Error(`introspect() timed out after ${timeoutMs}ms`)),timeoutMs))]);if(result&&typeof result==`object`){let snap=result;return{protocolVersion:PROTOCOL_VERSION,name:snap.name??name,kind:snap.kind??kind,version:snap.version,state:snap.state,tokens:snap.tokens,metrics:snap.metrics,memoryBytes:snap.memoryBytes,hooks,dependsOn}}return stub}catch(err){return errors.push({name,kind,message:err instanceof Error?err.message:String(err)}),stub}}function collectTokens(container){return container.getRegistrations().filter(r=>!r.token.startsWith(`__hmr__`)).map(r=>({token:r.token,scope:r.scope,kind:r.kind,instantiated:r.instantiated}))}function collectContributors(app){let seen=new Map;if(typeof app.getContributors==`function`){for(let c of app.getContributors())seen.has(c.key)||seen.set(c.key,{key:c.key,source:c.source,dependsOn:c.dependsOn});return[...seen.values()]}let ingest=(list,source)=>{if(list)for(let reg of list){let r=reg,key=typeof r.key==`string`?r.key:null;key&&!seen.has(key)&&seen.set(key,{key,source,dependsOn:Array.isArray(r.dependsOn)?r.dependsOn:[]})}};for(let plugin of app.getPlugins())ingest(plugin.contributors?.(),`adapter`);for(let adapter of app.getAdapters())ingest(adapter.contributors?.(),`adapter`);return[...seen.values()]}const VALID_VIEW_TYPES=new Set([`iframe`,`launch`,`html`,`module`]);function collectDevtoolsTabs(app){let tabs=[],seen=new Map,errors=[],ingest=(sourceName,raw)=>{if(!Array.isArray(raw)){errors.push({source:sourceName,reason:`devtoolsTabs() did not return an array`});return}for(let entry of raw){let validation=validateTab(entry);if(!validation.ok){errors.push({source:sourceName,reason:validation.reason});continue}let tab=validation.tab,owner=seen.get(tab.id);if(owner){errors.push({source:sourceName,reason:`tab id '${tab.id}' already registered by ${owner} — first-source wins`});continue}seen.set(tab.id,sourceName),tabs.push(tab)}};for(let plugin of app.getPlugins())if(typeof plugin.devtoolsTabs==`function`)try{ingest(plugin.name??`(unnamed plugin)`,plugin.devtoolsTabs())}catch(err){errors.push({source:plugin.name??`(unnamed plugin)`,reason:err instanceof Error?err.message:String(err)})}for(let adapter of app.getAdapters())if(typeof adapter.devtoolsTabs==`function`)try{ingest(adapter.name??`(unnamed adapter)`,adapter.devtoolsTabs())}catch(err){errors.push({source:adapter.name??`(unnamed adapter)`,reason:err instanceof Error?err.message:String(err)})}return{tabs,errors}}function validateTab(raw){if(!raw||typeof raw!=`object`)return{ok:!1,reason:`tab entry is not an object`};let obj=raw;if(typeof obj.id!=`string`||obj.id.length===0)return{ok:!1,reason:`tab.id must be a non-empty string`};if(typeof obj.title!=`string`||obj.title.length===0)return{ok:!1,reason:`tab '${obj.id}': title must be a non-empty string`};let view=obj.view;if(!view||typeof view!=`object`)return{ok:!1,reason:`tab '${obj.id}': view object is required`};if(typeof view.type!=`string`||!VALID_VIEW_TYPES.has(view.type))return{ok:!1,reason:`tab '${obj.id}': view.type must be one of iframe / launch / html / module`};switch(view.type){case`iframe`:if(typeof view.src!=`string`||view.src.length===0)return{ok:!1,reason:`tab '${obj.id}': iframe view requires a non-empty src`};break;case`html`:if(typeof view.html!=`string`)return{ok:!1,reason:`tab '${obj.id}': html view requires html string`};break;case`module`:if(typeof view.src!=`string`||view.src.length===0)return{ok:!1,reason:`tab '${obj.id}': module view requires a non-empty src`};break;case`launch`:if(!Array.isArray(view.actions))return{ok:!1,reason:`tab '${obj.id}': launch view requires actions array`}}return{ok:!0,tab:obj}}async function runTabAction(app,tabId,actionId){let tab=collectDevtoolsTabs(app).tabs.find(t=>t.id===tabId),action=tab?.view.type===`launch`?tab.view.actions.find(a=>a.id===actionId):void 0;if(!action)return{status:404,body:{error:`no action '${actionId}' on tab '${tabId}'`}};if(typeof action.run!=`function`)return{status:404,body:{error:`action '${actionId}' has no run()`}};try{return{status:200,body:{result:await action.run()??null}}}catch(err){return{status:500,body:{error:err instanceof Error?err.message:String(err)}}}}var RequestLog=class{capacity;entries=[];pendingErrors=new Map;seq=0;constructor(capacity){this.capacity=capacity}record(info,now=Date.now()){if(this.capacity<=0)return;let entry={seq:++this.seq,at:now,...info};info.requestId&&this.pendingErrors.has(info.requestId)&&(entry.error=this.pendingErrors.get(info.requestId),this.pendingErrors.delete(info.requestId)),this.entries.push(entry),this.entries.length>this.capacity&&this.entries.shift()}recordError(requestId,err){if(this.capacity<=0||!requestId)return;let error=err instanceof Error?{name:err.name,message:err.message}:{name:`Error`,message:String(err)};for(let i=this.entries.length-1;i>=0;i--)if(this.entries[i].requestId===requestId){this.entries[i].error=error;return}this.pendingErrors.set(requestId,error),this.pendingErrors.size>100&&this.pendingErrors.delete(this.pendingErrors.keys().next().value)}latest(){return this.seq}after(since=0){return this.entries.filter(e=>e.seq>since)}};const STATES=new Set([`waiting`,`active`,`delayed`,`completed`,`failed`,`paused`]),ACTIONS=[`retry`,`remove`,`retryAll`,`clean`,`pause`,`resume`],bad=(error,status=400)=>({status,body:{error}});function findJobInspectors(app){let found=new Map;for(let owner of[...app.getAdapters(),...app.getPlugins()]){let get=owner.jobInspector;if(typeof get!=`function`)continue;let inspector=get.call(owner);inspector&&found.set(owner.name??`jobs`,inspector)}return found}async function listJobSources(inspectors){return{status:200,body:{sources:await Promise.all([...inspectors].map(async([source,inspector])=>{let actions=ACTIONS.filter(a=>typeof inspector[a]==`function`);try{return{source,actions,queues:await inspector.queues()}}catch(err){return{source,actions,queues:[],error:message(err)}}}))}}}async function listJobs(inspectors,q){let inspector=inspectors.get(String(q.source??``));if(!inspector)return bad(`unknown source`,404);let state=String(q.state??`failed`);if(!STATES.has(state))return bad(`unknown state '${state}'`);let start=Math.max(0,Number(q.start??0)||0),end=Math.min(start+100-1,Math.max(start,Number(q.end??start+49)||0));try{return{status:200,body:{jobs:await inspector.jobs(String(q.queue??``),state,{start,end})}}}catch(err){return bad(message(err),500)}}async function getJob(inspectors,q){let inspector=inspectors.get(String(q.source??``));if(!inspector)return bad(`unknown source`,404);try{let job=await inspector.job(String(q.queue??``),String(q.id??``));return job?{status:200,body:job}:bad(`job not found`,404)}catch(err){return bad(message(err),500)}}async function runJobAction(inspectors,q){let inspector=inspectors.get(String(q.source??``));if(!inspector)return bad(`unknown source`,404);let action=String(q.action??``);if(!ACTIONS.includes(action))return bad(`unknown action '${action}'`);let queue=String(q.queue??``),id=String(q.id??``),state=String(q.state??``);try{switch(action){case`retry`:case`remove`:return id?inspector[action]?(await inspector[action](queue,id),{status:200,body:{ok:!0}}):bad(`${action} is not supported`,501):bad(`id is required`);case`clean`:return STATES.has(state)?inspector.clean?{status:200,body:{ok:!0,count:await inspector.clean(queue,state)}}:bad(`clean is not supported`,501):bad(`state is required`);default:return inspector[action]?{status:200,body:{ok:!0,count:await inspector[action](queue)??null}}:bad(`${action} is not supported`,501)}}catch(err){return bad(message(err),500)}}const message=err=>err instanceof Error?err.message:String(err),escape=name=>name.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);function findHandlerLine(source,controller,handler){let cls=RegExp(`\\bclass\\s+${escape(controller)}\\b`).exec(source);if(!cls)return;let lineAt=offset=>source.slice(0,offset).split(`
|
|
12
|
+
`).length,start=cls.index+cls[0].length,rest=source.slice(start),next=rest.search(/^(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s/m),body=next===-1?rest:rest.slice(0,next),method=RegExp(`^[ \\t]*(?:(?:public|private|protected|static|async|override)\\s+)*${escape(handler)}\\s*[(<]`,`m`).exec(body);return lineAt(method?start+method.index+method[0].search(/\S/):cls.index)}function locateHandler(root,controller,handler){let dir=existsSync(join(root,`src`))?join(root,`src`):root,files=readdirSync(dir,{recursive:!0,encoding:`utf-8`}).filter(f=>/\.[mc]?tsx?$/.test(f)&&!/\.d\.[mc]?ts$/.test(f)&&!f.split(sep).includes(`node_modules`)&&!f.split(sep).includes(`dist`));for(let f of files){let file=join(dir,f),source;try{source=readFileSync(file,`utf-8`)}catch{continue}if(!source.includes(controller))continue;let line=findHandlerLine(source,controller,handler);if(line!==void 0)return{file,relative:relative(root,file).split(sep).join(`/`),line}}}function createServerBus(opts={}){let wsPath=opts.wsPath??`/_debug/_bus`,secret=opts.secret??!1,core=createBusCore(),wss=new WebSocketServer({noServer:!0}),clients=new Set,attachedServer=null,upgradeHandler=null;wss.on(`connection`,ws=>{clients.add(ws),ws.on(`message`,data=>{try{let text=typeof data==`string`?data:data.toString(`utf-8`),parsed=JSON.parse(text);if(!parsed||typeof parsed.type!=`string`||typeof parsed.ts!=`number`)return;core.dispatch({...parsed,__kick_origin:`ws`})}catch{}}),ws.on(`close`,()=>{clients.delete(ws)}),ws.on(`error`,()=>{clients.delete(ws)})});let fanOut=envelope=>{if(clients.size===0)return;let text;try{text=JSON.stringify(envelope,(_key,value)=>typeof value==`bigint`?value.toString():value)}catch{return}for(let client of clients)if(client.readyState===client.OPEN)try{client.send(text)}catch{}},checkAuth=req=>{if(secret===!1)return!0;let header=req.headers[`x-devtools-token`];if(typeof header==`string`&&header===secret)return!0;let url=req.url??``,queryIdx=url.indexOf(`?`);return queryIdx!==-1&&new URLSearchParams(url.slice(queryIdx+1)).get(`token`)===secret};return{on(type,handler){return core.on(type,handler)},onAny(handler){return core.onAny(handler)},emit(type,payload){let envelope={type,payload,ts:Date.now(),__kick_origin:`local`};core.dispatch(envelope),fanOut(envelope)},attachUpgrade:httpServer=>{attachedServer||(attachedServer=httpServer,upgradeHandler=(req,socket,head)=>{if((req.url??``).split(`?`)[0]===wsPath){if(!checkAuth(req)){socket.write(`HTTP/1.1 401 Unauthorized\r
|
|
13
13
|
\r
|
|
14
|
-
`),socket.destroy();return}wss.handleUpgrade(req,socket,head,ws=>{wss.emit(`connection`,ws,req)})}},httpServer.on(`upgrade`,upgradeHandler))},close:()=>{attachedServer&&upgradeHandler&&attachedServer.off(`upgrade`,upgradeHandler),attachedServer=null,upgradeHandler=null;for(let client of clients)try{client.close()}catch{}clients.clear(),wss.close()},clientCount:()=>clients.size}}const log=createLogger(`DevTools`);function uploadInfo(controllerClass,handlerName){let config=getMethodMeta(METADATA.FILE_UPLOAD,controllerClass,handlerName,void 0);if(!config)return{};let{mode,fieldName,maxCount}=config;return{upload:{mode,...fieldName?{fieldName}:{},...maxCount?{maxCount}:{}}}}function requestSchemas(validation){if(!validation)return{};let schemas={};for(let part of[`body`,`query`,`params`])if(validation[part])try{schemas[part]=detectSchema(validation[part]).toJsonSchema({target:`openapi-3.0`,io:`input`})}catch{}return Object.keys(schemas).length>0?{schemas}:{}}const LATENCY_BUCKETS_MS=[1,2,5,10,25,50,100,250,500,1e3];function latencyHistogram(samples){let counts=Array.from({length:LATENCY_BUCKETS_MS.length+1},()=>0);for(let ms of samples){let i=LATENCY_BUCKETS_MS.findIndex(bound=>ms<=bound);counts[i===-1?LATENCY_BUCKETS_MS.length:i]++}return counts}const MATCHED_ROUTE_SLOT=Symbol.for(`kick.route`),FRAMEWORK_REPORTS_RESPONSES=typeof kick.reportResponse==`function`;function percentile(sorted,p){if(sorted.length===0)return 0;let idx=Math.ceil(p*sorted.length)-1;return sorted[Math.max(0,idx)]}function computePercentiles(stats){let sorted=[...stats.samples].toSorted((a,b)=>a-b);return{p50:percentile(sorted,.5),p95:percentile(sorted,.95),p99:percentile(sorted,.99)}}function raceWithTimeout(promise,ms,label){let timer,timeout=new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error(`${label} timed out after ${ms}ms`)),ms)});return Promise.race([promise,timeout]).finally(()=>{timer&&clearTimeout(timer)})}let snapshotInProgress=!1;const DevToolsAdapter=defineAdapter({name:`DevToolsAdapter`,defaults:{basePath:`/_debug`,errorRateThreshold:.5,exposeConfig:!1,configPrefixes:[`APP_`,`NODE_ENV`]},build:options=>{let basePath=options.basePath,enabled=options.enabled??process.env.NODE_ENV!==`production`,exposeConfig=options.exposeConfig,configPrefixes=options.configPrefixes,errorRateThreshold=options.errorRateThreshold,peerAdapters=options.adapters??[],requestLog=new RequestLog(options.requestLog??200),logRequest=info=>{info.path!==basePath&&!info.path.startsWith(`${basePath}/`)&&requestLog.record(info)},secret;secret=options.secret===!1?!1:options.secret?options.secret:randomBytes(16).toString(`hex`);let runtimeEnabled=options.runtime?.enabled??!0,runtimeSampler=runtimeEnabled?new RuntimeSampler({intervalMs:options.runtime?.intervalMs??1e3,bufferSize:options.runtime?.bufferSize??60}):null,memoryAnalyzer=runtimeEnabled?new MemoryAnalyzer:null,bus=null,ensureBus=container=>container.has(DEVTOOLS_BUS)?(bus=container.resolve(DEVTOOLS_BUS),bus):(bus=enabled?createServerBus({wsPath:`${basePath}/_bus`,secret:secret!==!1&&secret}):{on:()=>()=>{},onAny:()=>()=>{},emit:()=>{},attachUpgrade:()=>{},close:()=>{},clientCount:()=>0},container.registerInstance(DEVTOOLS_BUS,bus),bus),requestCount=ref(0),errorCount=ref(0),clientErrorCount=ref(0),startedAt=ref(Date.now()),routeLatency=reactive({}),errorRate=computed(()=>requestCount.value>0?errorCount.value/requestCount.value:0),uptimeSeconds=computed(()=>(startedAt.value,Math.floor(process.uptime()))),routes=[],container=null,appRef=null,recordResponse=(method,route,status,elapsedMs)=>{requestCount.value++,status>=500?errorCount.value++:status>=400&&clientErrorCount.value++;let routeKey=`${method} ${route??`<unmatched>`}`;routeLatency[routeKey]||(routeLatency[routeKey]={count:0,totalMs:0,minMs:1/0,maxMs:0,serverErrors:0,clientErrors:0,samples:[]});let stats=routeLatency[routeKey];stats.count++,status>=500?stats.serverErrors++:status>=400&&stats.clientErrors++,stats.totalMs+=elapsedMs,stats.minMs=Math.min(stats.minMs,elapsedMs),stats.maxMs=Math.max(stats.maxMs,elapsedMs),stats.samples.push(elapsedMs),stats.samples.length>1e3&&stats.samples.shift()},adapterStatuses={},stopErrorWatch=null;if(options.onErrorRateExceeded){let callback=options.onErrorRateExceeded;stopErrorWatch=watch(errorRate,rate=>{rate>errorRateThreshold&&callback(rate)})}else stopErrorWatch=watch(errorRate,rate=>{rate>errorRateThreshold&&log.warn(`Error rate elevated: ${(rate*100).toFixed(1)}%`)});let resolvePublicDir=()=>{let thisDir=dirname(fileURLToPath(import.meta.url)),candidates=[join(thisDir,`..`,`public`,`spa`),join(thisDir,`..`,`..`,`public`,`spa`),join(thisDir,`..`,`public`,`devtools`),join(thisDir,`..`,`..`,`public`,`devtools`)];for(let dir of candidates)if(existsSync(join(dir,`index.html`)))return dir;return null},getPeerAdapters=()=>{let kickApp=appRef?.__kickApp;return kickApp&&typeof kickApp.getAdapters==`function`?kickApp.getAdapters():peerAdapters},readActiveRuntime=()=>{let kickApp=appRef?.__kickApp;if(kickApp&&typeof kickApp.getActiveRuntime==`function`)try{return kickApp.getActiveRuntime()}catch{return null}return null};return{requestCount,errorCount,clientErrorCount,startedAt,errorRate,uptimeSeconds,routeLatency,runtimeSampler,memoryAnalyzer,introspect(){return{protocolVersion:PROTOCOL_VERSION,name:`DevToolsAdapter`,kind:`adapter`,state:{basePath,enabled,runtimeEnabled,secret:secret!==!1&&`present`},metrics:{requestCount:requestCount.value,serverErrors:errorCount.value,clientErrors:clientErrorCount.value,uptimeSeconds:uptimeSeconds.value,routesTracked:routes.length}}},beforeMount({app,http,container:containerArg}){if(!enabled)return;appRef=app,container=containerArg,ensureBus(containerArg),startedAt.value=Date.now(),routes=[],adapterStatuses.DevToolsAdapter=`running`,runtimeSampler?.start(),memoryAnalyzer?.start();let joinBase=p=>p===`/`?basePath:`${basePath}${p}`,authorized=ctx=>secret===!1||(ctx.headers[`x-devtools-token`]??ctx.query?.token)===secret||(ctx.json({error:`Forbidden — invalid or missing devtools token`},403),!1),guarded=handler=>ctx=>authorized(ctx)?handler(ctx):void 0,router={get:(path,handler)=>http.route(`GET`,joinBase(path),guarded(handler)),post:(path,handler)=>http.route(`POST`,joinBase(path),guarded(handler))};router.get(`/routes`,ctx=>{ctx.json({routes})}),router.get(`/source`,ctx=>{let{controller,handler}=ctx.query,found=routes.some(r=>r.controller===controller&&r.handler===handler)&&controller&&handler?locateHandler(process.cwd(),controller,handler):void 0;if(!found){ctx.json({error:`Handler source not found`},404);return}ctx.json(found)}),router.get(`/container`,ctx=>{let registrations=(container?.getRegistrations()??[]).filter(r=>!r.token.startsWith(`__hmr__`));ctx.json({registrations,count:registrations.length})}),router.get(`/requests`,ctx=>{let since=Number(ctx.query?.since??0);ctx.json({requests:requestLog.after(Number.isFinite(since)?since:0),latest:requestLog.latest()})}),router.get(`/metrics`,ctx=>{let latency={};for(let[key,stats]of Object.entries(routeLatency)){let{samples,...rest}=stats;latency[key]={...rest,...computePercentiles(stats),histogram:latencyHistogram(samples)}}ctx.json({requests:requestCount.value,serverErrors:errorCount.value,clientErrors:clientErrorCount.value,errorRate:errorRate.value,uptimeSeconds:uptimeSeconds.value,startedAt:new Date(startedAt.value).toISOString(),routeLatency:latency,latencyBucketsMs:LATENCY_BUCKETS_MS})}),router.get(`/health`,async ctx=>{let healthy=errorRate.value<errorRateThreshold,status=healthy?`healthy`:`degraded`,live={...adapterStatuses};await Promise.all(getPeerAdapters().map(async peer=>{if(typeof peer?.onHealthCheck!=`function`)return;let name=peer?.name;if(typeof name==`string`&&name!==`DevToolsAdapter`)try{let result=await raceWithTimeout(Promise.resolve(peer.onHealthCheck()),1500,`onHealthCheck(${name})`);result&&typeof result.status==`string`&&(live[name]=result.status)}catch{live[name]=`down`}})),ctx.json({status,errorRate:errorRate.value,uptime:uptimeSeconds.value,runtime:readActiveRuntime(),adapters:live},healthy?200:503)}),router.get(`/state`,ctx=>{let wsAdapter=getPeerAdapters().find(a=>a.name===`WsAdapter`&&typeof a.getStats==`function`);ctx.json({reactive:{requestCount:requestCount.value,errorCount:errorCount.value,clientErrorCount:clientErrorCount.value,errorRate:errorRate.value,uptimeSeconds:uptimeSeconds.value,startedAt:new Date(startedAt.value).toISOString()},routes:routes.length,container:container?.getRegistrations().length??0,routeLatency,...wsAdapter?{ws:wsAdapter.getStats()}:{}})}),router.get(`/runtime`,ctx=>{if(!runtimeSampler||!memoryAnalyzer){ctx.json({error:`runtime sampler disabled — set runtime.enabled = true`},404);return}let latest=runtimeSampler.latest(),history=runtimeSampler.history(),health=memoryAnalyzer.health(history);ctx.json({protocolVersion:PROTOCOL_VERSION,process:{nodeVersion:process.version,pid:process.pid,platform:process.platform,arch:process.arch,runtime:readActiveRuntime()},latest,history,health})}),router.get(`/runtime/stream`,ctx=>{if(!runtimeSampler){ctx.json({error:`runtime sampler disabled`},404);return}let sse=ctx.sse(),intervalMs=options.runtime?.intervalMs??1e3,tick=()=>{let snap=runtimeSampler.latest();snap&&sse.send(snap)};tick();let interval=setInterval(tick,intervalMs),heartbeat=setInterval(()=>sse.comment(`heartbeat`),3e4);sse.onClose(()=>{clearInterval(interval),clearInterval(heartbeat)})}),router.get(`/memory/stream`,ctx=>{if(!runtimeSampler||!memoryAnalyzer){ctx.json({error:`runtime sampler disabled`},404);return}let sse=ctx.sse(),intervalMs=options.runtime?.intervalMs??1e3,tick=()=>{let snap=runtimeSampler.latest();if(!snap)return;let health=memoryAnalyzer.health(runtimeSampler.history());sse.send({snapshot:snap,health})};tick();let interval=setInterval(tick,intervalMs),heartbeat=setInterval(()=>sse.comment(`heartbeat`),3e4);sse.onClose(()=>{clearInterval(interval),clearInterval(heartbeat)})}),router.post(`/memory/snapshot`,async ctx=>{if(snapshotInProgress){ctx.json({error:`heap snapshot already in progress — wait for it to complete`},503);return}snapshotInProgress=!0;let startedAt=Date.now(),filename=`kickjs-heap-${new Date().toISOString().replace(/[:.]/g,`-`)}.heapsnapshot`,targetPath=join(tmpdir(),filename),written;try{written=writeHeapSnapshot(targetPath)}catch(err){snapshotInProgress=!1,log.error({err},`heap snapshot capture failed`),ctx.json({error:`snapshot capture failed`,message:err.message},500);return}let size=0;try{size=statSync(written).size}catch{}let elapsedMs=Date.now()-startedAt;log.info(`Heap snapshot captured: ${written} (${(size/1024/1024).toFixed(1)} MiB in ${elapsedMs}ms)`);let headers={"Content-Type":`application/json`,"Content-Disposition":`attachment; filename="${filename}"`,"X-Snapshot-Capture-Ms":String(elapsedMs)};size>0&&(headers[`Content-Length`]=String(size));let stream=createReadStream(written);try{await ctx.sendResponse(new Response(Readable.toWeb(stream),{headers}))}catch(err){log.error({err},`heap snapshot stream failed`)}finally{stream.destroy(),unlink(written,err=>{err&&log.warn(`Failed to delete heap snapshot ${written}: ${err.message}`),snapshotInProgress=!1})}}),router.post(`/memory/gc`,ctx=>{let gc=globalThis.gc;if(typeof gc!=`function`){ctx.json({error:`global.gc unavailable — start Node with --expose-gc to enable this endpoint`},412);return}let before=process.memoryUsage().heapUsed,startedAt=Date.now();try{gc()}catch(err){ctx.json({error:`forced GC failed`,message:err instanceof Error?err.message:String(err)},500);return}let after=process.memoryUsage().heapUsed,reclaimedBytes=Math.max(0,before-after),elapsedMs=Date.now()-startedAt;log.info(`Forced GC reclaimed ${(reclaimedBytes/1024/1024).toFixed(2)} MiB in ${elapsedMs}ms`),ctx.json({before,after,reclaimedBytes,elapsedMs})}),router.get(`/tabs`,ctx=>{let kickApp=appRef?.__kickApp;if(!kickApp){ctx.json({error:`tabs unavailable — application surface not exposed`},503);return}ctx.json(collectDevtoolsTabs(kickApp))}),router.post(`/tabs/run`,async ctx=>{let kickApp=appRef?.__kickApp;if(!kickApp){ctx.json({error:`tabs unavailable — application surface not exposed`},503);return}let{tab,action}=ctx.query??{},{status,body}=await runTabAction(kickApp,String(tab??``),String(action??``));ctx.json(body,status)});let inspectors=()=>{let kickApp=appRef?.__kickApp;return kickApp?findJobInspectors(kickApp):new Map},query=ctx=>ctx.query??{};router.get(`/jobs`,async ctx=>{let r=await listJobSources(inspectors());ctx.json(r.body,r.status)}),router.get(`/jobs/list`,async ctx=>{let r=await listJobs(inspectors(),query(ctx));ctx.json(r.body,r.status)}),router.get(`/jobs/job`,async ctx=>{let r=await getJob(inspectors(),query(ctx));ctx.json(r.body,r.status)}),router.post(`/jobs/action`,async ctx=>{let r=await runJobAction(inspectors(),query(ctx));ctx.json(r.body,r.status)}),router.get(`/topology`,async ctx=>{if(!container){ctx.json({error:`topology unavailable — container not bound yet`},503);return}let kickApp=appRef?.__kickApp;if(!kickApp){ctx.json({error:`topology unavailable — application surface not exposed`},503);return}try{let snapshot=await collectTopologySnapshot({app:kickApp,container});ctx.json(snapshot)}catch(err){let message=err instanceof Error?err.message:String(err);ctx.json({error:`topology collection failed`,message},500)}}),router.get(`/ws`,ctx=>{let wsAdapter=getPeerAdapters().find(a=>a.name===`WsAdapter`&&typeof a.getStats==`function`);if(!wsAdapter){ctx.json({enabled:!1,message:`WsAdapter not found`});return}ctx.json({enabled:!0,...wsAdapter.getStats()})}),router.get(`/queues`,async ctx=>{let queueAdapter=getPeerAdapters().find(a=>a.name===`QueueAdapter`&&typeof a.getQueueNames==`function`);if(!queueAdapter){ctx.json({enabled:!1,message:`QueueAdapter not found`});return}try{let names=queueAdapter.getQueueNames?.()??[],queues=[];for(let name of names){let stats=await queueAdapter.getQueueStats?.(name);queues.push({name,...stats})}ctx.json({enabled:!0,queues})}catch{ctx.json({enabled:!0,queues:[],error:`Failed to fetch queue stats`})}}),router.get(`/graph`,ctx=>{let registrations=container?.getRegistrations()??[],nodes=registrations.filter(r=>!r.token.startsWith(`__hmr__`)).map(r=>({id:r.token,kind:r.kind,scope:r.scope,resolveCount:r.resolveCount})),nodeIds=new Set(nodes.map(n=>n.id)),edges=[];for(let r of registrations)if(!r.token.startsWith(`__hmr__`))for(let dep of r.dependencies)nodeIds.has(dep)&&edges.push({from:r.token,to:dep});ctx.json({nodes,edges})}),router.get(`/stream`,ctx=>{let sse=ctx.sse(),sendMetrics=()=>{sse.send({type:`metrics`,requestCount:requestCount.value,errorCount:errorCount.value,clientErrorCount:clientErrorCount.value,errorRate:errorRate.value,uptimeSeconds:uptimeSeconds.value})};sendMetrics();let unsubContainer=container?.onChange?.(changes=>{sse.send({type:`container`,changes,timestamp:Date.now()}),sendMetrics()}),stopRequestWatch=watch(requestCount,()=>sendMetrics()),stopErrWatch=watch(errorCount,()=>sendMetrics()),heartbeat=setInterval(()=>sse.comment(`heartbeat`),3e4);sse.onClose(()=>{unsubContainer?.(),stopRequestWatch(),stopErrWatch(),clearInterval(heartbeat)})}),exposeConfig&&router.get(`/config`,ctx=>{let config={};for(let[key,value]of Object.entries(process.env))value!==void 0&&(config[key]=configPrefixes.some(prefix=>key.startsWith(prefix))?value:`[REDACTED]`);ctx.json({config})});let publicDir=resolvePublicDir(),dashboard=html=>ctx=>ctx.query?.token===void 0||authorized(ctx)?ctx.html(html):void 0;if(publicDir){let assetsDir=join(publicDir,`assets`);existsSync(assetsDir)?http.serveStatic(`${basePath}/assets`,assetsDir):http.serveStatic(basePath,publicDir);let indexHtml=readFileSync(join(publicDir,`index.html`),`utf-8`);http.route(`GET`,basePath,dashboard(indexHtml.replace(`<body`,`<body data-base="${basePath}"`)))}else http.route(`GET`,basePath,dashboard(`<h1>DevTools: public directory not found</h1>`));secret?(log.info(`DevTools mounted at ${basePath} [token: ${secret}]`),log.info(`Access: ${basePath}?token=${secret}`)):log.info(`DevTools mounted at ${basePath} [no guard]`)},onResponse(info){enabled&&(recordResponse(info.method,info.route,info.status,info.durationMs),logRequest(info))},onError(err,info){enabled&&info.source===`request`&&requestLog.recordError(info.requestId,err)},middleware(){return!enabled||FRAMEWORK_REPORTS_RESPONSES?[]:[{handler:(req,res,next)=>{let start=Date.now();res.on(`finish`,()=>{let matched=req[MATCHED_ROUTE_SLOT],route=matched?.pattern??matched?.path,durationMs=Date.now()-start;recordResponse(req.method??`GET`,route,res.statusCode,durationMs),logRequest({method:req.method??`GET`,path:(req.url??`/`).split(`?`)[0],route,status:res.statusCode,durationMs})}),next()},phase:`beforeGlobal`}]},onRouteMount(controllerClass,mountPath){if(!enabled)return;let collectedRoutes=getClassMeta(METADATA.ROUTES,controllerClass,[]),classMiddleware=getClassMeta(METADATA.CLASS_MIDDLEWARES,controllerClass,[]);for(let route of collectedRoutes){let methodMiddleware=getMethodMeta(METADATA.METHOD_MIDDLEWARES,controllerClass.prototype,route.handlerName,[]);routes.push({method:route.method.toUpperCase(),path:`${mountPath}${route.path===`/`?``:route.path}`,controller:controllerClass.name,handler:route.handlerName,middleware:[...classMiddleware.map(m=>m.name||`anonymous`),...methodMiddleware.map(m=>m.name||`anonymous`)],flags:Object.fromEntries(getRouteFlags(controllerClass,route.handlerName)),...uploadInfo(controllerClass,route.handlerName),...requestSchemas(route.validation)})}},afterStart({server}){if(enabled){server&&bus?.attachUpgrade(server);for(let peer of getPeerAdapters()){let name=peer?.name;typeof name==`string`&&name!==`DevToolsAdapter`&&(adapterStatuses[name]=`running`)}log.info(`DevTools ready — ${routes.length} routes tracked, ${container?.getRegistrations().length??0} DI bindings, event bus on ${basePath}/_bus`)}},shutdown(){stopErrorWatch?.(),runtimeSampler?.stop(),memoryAnalyzer?.stop(),bus?.close(),adapterStatuses.DevToolsAdapter=`stopped`}}}});export{DevToolsAdapter};
|
|
14
|
+
`),socket.destroy();return}wss.handleUpgrade(req,socket,head,ws=>{wss.emit(`connection`,ws,req)})}},httpServer.on(`upgrade`,upgradeHandler))},close:()=>{attachedServer&&upgradeHandler&&attachedServer.off(`upgrade`,upgradeHandler),attachedServer=null,upgradeHandler=null;for(let client of clients)try{client.close()}catch{}clients.clear(),wss.close()},clientCount:()=>clients.size}}const log=createLogger(`DevTools`);function uploadInfo(controllerClass,handlerName){let config=getMethodMeta(METADATA.FILE_UPLOAD,controllerClass,handlerName,void 0);if(!config)return{};let{mode,fieldName,maxCount}=config;return{upload:{mode,...fieldName?{fieldName}:{},...maxCount?{maxCount}:{}}}}function requestSchemas(validation){if(!validation)return{};let schemas={};for(let part of[`body`,`query`,`params`])if(validation[part])try{schemas[part]=detectSchema(validation[part]).toJsonSchema({target:`openapi-3.0`,io:`input`})}catch{}return Object.keys(schemas).length>0?{schemas}:{}}const LATENCY_BUCKETS_MS=[1,2,5,10,25,50,100,250,500,1e3];function latencyHistogram(samples){let counts=Array.from({length:LATENCY_BUCKETS_MS.length+1},()=>0);for(let ms of samples){let i=LATENCY_BUCKETS_MS.findIndex(bound=>ms<=bound);counts[i===-1?LATENCY_BUCKETS_MS.length:i]++}return counts}const MATCHED_ROUTE_SLOT=Symbol.for(`kick.route`),FRAMEWORK_REPORTS_RESPONSES=typeof kick.reportResponse==`function`;function percentile(sorted,p){if(sorted.length===0)return 0;let idx=Math.ceil(p*sorted.length)-1;return sorted[Math.max(0,idx)]}function computePercentiles(stats){let sorted=[...stats.samples].toSorted((a,b)=>a-b);return{p50:percentile(sorted,.5),p95:percentile(sorted,.95),p99:percentile(sorted,.99)}}function raceWithTimeout(promise,ms,label){let timer,timeout=new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error(`${label} timed out after ${ms}ms`)),ms)});return Promise.race([promise,timeout]).finally(()=>{timer&&clearTimeout(timer)})}let snapshotInProgress=!1;const DevToolsAdapter=defineAdapter({name:`DevToolsAdapter`,defaults:{basePath:`/_debug`,errorRateThreshold:.5,exposeConfig:!1,configPrefixes:[`APP_`,`NODE_ENV`]},build:options=>{let basePath=options.basePath,enabled=options.enabled??process.env.NODE_ENV!==`production`,exposeConfig=options.exposeConfig,configPrefixes=options.configPrefixes,errorRateThreshold=options.errorRateThreshold,peerAdapters=options.adapters??[],requestLog=new RequestLog(options.requestLog??200),logRequest=info=>{info.path!==basePath&&!info.path.startsWith(`${basePath}/`)&&requestLog.record(info)},secret;secret=options.secret===!1?!1:options.secret?options.secret:randomBytes(16).toString(`hex`);let runtimeEnabled=options.runtime?.enabled??!0,runtimeSampler=runtimeEnabled?new RuntimeSampler({intervalMs:options.runtime?.intervalMs??1e3,bufferSize:options.runtime?.bufferSize??60}):null,memoryAnalyzer=runtimeEnabled?new MemoryAnalyzer:null,bus=null,ensureBus=container=>container.has(DEVTOOLS_BUS)?(bus=container.resolve(DEVTOOLS_BUS),bus):(bus=enabled?createServerBus({wsPath:`${basePath}/_bus`,secret:secret!==!1&&secret}):{on:()=>()=>{},onAny:()=>()=>{},emit:()=>{},attachUpgrade:()=>{},close:()=>{},clientCount:()=>0},container.registerInstance(DEVTOOLS_BUS,bus),bus),requestCount=ref(0),errorCount=ref(0),clientErrorCount=ref(0),startedAt=ref(Date.now()),routeLatency=reactive({}),errorRate=computed(()=>requestCount.value>0?errorCount.value/requestCount.value:0),uptimeSeconds={get value(){return Math.floor(process.uptime())},toJSON(){return this.value},dispose(){}},routes=[],container=null,appRef=null,recordResponse=(method,route,status,elapsedMs)=>{requestCount.value++,status>=500?errorCount.value++:status>=400&&clientErrorCount.value++;let routeKey=`${method} ${route??`<unmatched>`}`;routeLatency[routeKey]||(routeLatency[routeKey]={count:0,totalMs:0,minMs:1/0,maxMs:0,serverErrors:0,clientErrors:0,samples:[]});let stats=routeLatency[routeKey];stats.count++,status>=500?stats.serverErrors++:status>=400&&stats.clientErrors++,stats.totalMs+=elapsedMs,stats.minMs=Math.min(stats.minMs,elapsedMs),stats.maxMs=Math.max(stats.maxMs,elapsedMs),stats.samples.push(elapsedMs),stats.samples.length>1e3&&stats.samples.shift()},adapterStatuses={},stopErrorWatch=null;if(options.onErrorRateExceeded){let callback=options.onErrorRateExceeded;stopErrorWatch=watch(errorRate,rate=>{rate>errorRateThreshold&&callback(rate)})}else stopErrorWatch=watch(errorRate,rate=>{rate>errorRateThreshold&&log.warn(`Error rate elevated: ${(rate*100).toFixed(1)}%`)});let resolvePublicDir=()=>{let thisDir=dirname(fileURLToPath(import.meta.url)),candidates=[join(thisDir,`..`,`public`,`spa`),join(thisDir,`..`,`..`,`public`,`spa`),join(thisDir,`..`,`public`,`devtools`),join(thisDir,`..`,`..`,`public`,`devtools`)];for(let dir of candidates)if(existsSync(join(dir,`index.html`)))return dir;return null},getPeerAdapters=()=>{let kickApp=appRef?.__kickApp;return kickApp&&typeof kickApp.getAdapters==`function`?kickApp.getAdapters():peerAdapters},readActiveRuntime=()=>{let kickApp=appRef?.__kickApp;if(kickApp&&typeof kickApp.getActiveRuntime==`function`)try{return kickApp.getActiveRuntime()}catch{return null}return null};return{requestCount,errorCount,clientErrorCount,startedAt,errorRate,uptimeSeconds,routeLatency,runtimeSampler,memoryAnalyzer,introspect(){return{protocolVersion:PROTOCOL_VERSION,name:`DevToolsAdapter`,kind:`adapter`,state:{basePath,enabled,runtimeEnabled,secret:secret!==!1&&`present`},metrics:{requestCount:requestCount.value,serverErrors:errorCount.value,clientErrors:clientErrorCount.value,uptimeSeconds:uptimeSeconds.value,routesTracked:routes.length}}},beforeMount({app,http,container:containerArg}){if(!enabled)return;appRef=app,container=containerArg,setActiveDevtoolsBus(ensureBus(containerArg)),startedAt.value=Date.now(),routes=[],adapterStatuses.DevToolsAdapter=`running`,runtimeSampler?.start(),memoryAnalyzer?.start();let joinBase=p=>p===`/`?basePath:`${basePath}${p}`,authorized=ctx=>secret===!1||(ctx.headers[`x-devtools-token`]??ctx.query?.token)===secret||(ctx.json({error:`Forbidden — invalid or missing devtools token`},403),!1),guarded=handler=>ctx=>authorized(ctx)?handler(ctx):void 0,router={get:(path,handler)=>http.route(`GET`,joinBase(path),guarded(handler)),post:(path,handler)=>http.route(`POST`,joinBase(path),guarded(handler))};router.get(`/routes`,ctx=>{ctx.json({routes})}),router.get(`/source`,ctx=>{let{controller,handler}=ctx.query,found=routes.some(r=>r.controller===controller&&r.handler===handler)&&controller&&handler?locateHandler(process.cwd(),controller,handler):void 0;if(!found){ctx.json({error:`Handler source not found`},404);return}ctx.json(found)}),router.get(`/container`,ctx=>{let registrations=(container?.getRegistrations()??[]).filter(r=>!r.token.startsWith(`__hmr__`));ctx.json({registrations,count:registrations.length})}),router.get(`/requests`,ctx=>{let since=Number(ctx.query?.since??0);ctx.json({requests:requestLog.after(Number.isFinite(since)?since:0),latest:requestLog.latest()})}),router.get(`/metrics`,ctx=>{let latency={};for(let[key,stats]of Object.entries(routeLatency)){let{samples,...rest}=stats;latency[key]={...rest,...computePercentiles(stats),histogram:latencyHistogram(samples)}}ctx.json({requests:requestCount.value,serverErrors:errorCount.value,clientErrors:clientErrorCount.value,errorRate:errorRate.value,uptimeSeconds:uptimeSeconds.value,startedAt:new Date(startedAt.value).toISOString(),routeLatency:latency,latencyBucketsMs:LATENCY_BUCKETS_MS})}),router.get(`/health`,async ctx=>{let healthy=errorRate.value<errorRateThreshold,status=healthy?`healthy`:`degraded`,live={...adapterStatuses};await Promise.all(getPeerAdapters().map(async peer=>{if(typeof peer?.onHealthCheck!=`function`)return;let name=peer?.name;if(typeof name==`string`&&name!==`DevToolsAdapter`)try{let result=await raceWithTimeout(Promise.resolve(peer.onHealthCheck()),1500,`onHealthCheck(${name})`);result&&typeof result.status==`string`&&(live[name]=result.status)}catch{live[name]=`down`}})),ctx.json({status,errorRate:errorRate.value,uptime:uptimeSeconds.value,runtime:readActiveRuntime(),adapters:live},healthy?200:503)}),router.get(`/state`,ctx=>{let wsAdapter=getPeerAdapters().find(a=>a.name===`WsAdapter`&&typeof a.getStats==`function`);ctx.json({reactive:{requestCount:requestCount.value,errorCount:errorCount.value,clientErrorCount:clientErrorCount.value,errorRate:errorRate.value,uptimeSeconds:uptimeSeconds.value,startedAt:new Date(startedAt.value).toISOString()},routes:routes.length,container:container?.getRegistrations().length??0,routeLatency,...wsAdapter?{ws:wsAdapter.getStats()}:{}})}),router.get(`/runtime`,ctx=>{if(!runtimeSampler||!memoryAnalyzer){ctx.json({error:`runtime sampler disabled — set runtime.enabled = true`},404);return}let latest=runtimeSampler.latest(),history=runtimeSampler.history(),health=memoryAnalyzer.health(history);ctx.json({protocolVersion:PROTOCOL_VERSION,process:{nodeVersion:process.version,pid:process.pid,platform:process.platform,arch:process.arch,runtime:readActiveRuntime()},latest,history,health})}),router.get(`/runtime/stream`,ctx=>{if(!runtimeSampler){ctx.json({error:`runtime sampler disabled`},404);return}let sse=ctx.sse(),intervalMs=options.runtime?.intervalMs??1e3,tick=()=>{let snap=runtimeSampler.latest();snap&&sse.send(snap)};tick();let interval=setInterval(tick,intervalMs),heartbeat=setInterval(()=>sse.comment(`heartbeat`),3e4);sse.onClose(()=>{clearInterval(interval),clearInterval(heartbeat)})}),router.get(`/memory/stream`,ctx=>{if(!runtimeSampler||!memoryAnalyzer){ctx.json({error:`runtime sampler disabled`},404);return}let sse=ctx.sse(),intervalMs=options.runtime?.intervalMs??1e3,tick=()=>{let snap=runtimeSampler.latest();if(!snap)return;let health=memoryAnalyzer.health(runtimeSampler.history());sse.send({snapshot:snap,health})};tick();let interval=setInterval(tick,intervalMs),heartbeat=setInterval(()=>sse.comment(`heartbeat`),3e4);sse.onClose(()=>{clearInterval(interval),clearInterval(heartbeat)})}),router.post(`/memory/snapshot`,async ctx=>{if(snapshotInProgress){ctx.json({error:`heap snapshot already in progress — wait for it to complete`},503);return}snapshotInProgress=!0;let startedAt=Date.now(),filename=`kickjs-heap-${new Date().toISOString().replace(/[:.]/g,`-`)}.heapsnapshot`,targetPath=join(tmpdir(),filename),written;try{written=writeHeapSnapshot(targetPath)}catch(err){snapshotInProgress=!1,log.error({err},`heap snapshot capture failed`),ctx.json({error:`snapshot capture failed`,message:err.message},500);return}let size=0;try{size=statSync(written).size}catch{}let elapsedMs=Date.now()-startedAt;log.info(`Heap snapshot captured: ${written} (${(size/1024/1024).toFixed(1)} MiB in ${elapsedMs}ms)`);let headers={"Content-Type":`application/json`,"Content-Disposition":`attachment; filename="${filename}"`,"X-Snapshot-Capture-Ms":String(elapsedMs)};size>0&&(headers[`Content-Length`]=String(size));let stream=createReadStream(written);try{await ctx.sendResponse(new Response(Readable.toWeb(stream),{headers}))}catch(err){log.error({err},`heap snapshot stream failed`)}finally{stream.destroy(),unlink(written,err=>{err&&log.warn(`Failed to delete heap snapshot ${written}: ${err.message}`),snapshotInProgress=!1})}}),router.post(`/memory/gc`,ctx=>{let gc=globalThis.gc;if(typeof gc!=`function`){ctx.json({error:`global.gc unavailable — start Node with --expose-gc to enable this endpoint`},412);return}let before=process.memoryUsage().heapUsed,startedAt=Date.now();try{gc()}catch(err){ctx.json({error:`forced GC failed`,message:err instanceof Error?err.message:String(err)},500);return}let after=process.memoryUsage().heapUsed,reclaimedBytes=Math.max(0,before-after),elapsedMs=Date.now()-startedAt;log.info(`Forced GC reclaimed ${(reclaimedBytes/1024/1024).toFixed(2)} MiB in ${elapsedMs}ms`),ctx.json({before,after,reclaimedBytes,elapsedMs})}),router.get(`/tabs`,ctx=>{let kickApp=appRef?.__kickApp;if(!kickApp){ctx.json({error:`tabs unavailable — application surface not exposed`},503);return}ctx.json(collectDevtoolsTabs(kickApp))}),router.post(`/tabs/run`,async ctx=>{let kickApp=appRef?.__kickApp;if(!kickApp){ctx.json({error:`tabs unavailable — application surface not exposed`},503);return}let{tab,action}=ctx.query??{},{status,body}=await runTabAction(kickApp,String(tab??``),String(action??``));ctx.json(body,status)});let inspectors=()=>{let kickApp=appRef?.__kickApp;return kickApp?findJobInspectors(kickApp):new Map},query=ctx=>ctx.query??{};router.get(`/jobs`,async ctx=>{let r=await listJobSources(inspectors());ctx.json(r.body,r.status)}),router.get(`/jobs/list`,async ctx=>{let r=await listJobs(inspectors(),query(ctx));ctx.json(r.body,r.status)}),router.get(`/jobs/job`,async ctx=>{let r=await getJob(inspectors(),query(ctx));ctx.json(r.body,r.status)}),router.post(`/jobs/action`,async ctx=>{let r=await runJobAction(inspectors(),query(ctx));ctx.json(r.body,r.status)}),router.get(`/cron`,ctx=>{ctx.json({jobs:container?cronSnapshot(container):[]})}),router.post(`/cron/run`,ctx=>{let name=query(ctx).name;if(!container||typeof name!=`string`){ctx.json({error:`name is required`},400);return}let r=runCronJobNow(container,name);ctx.json(r.body,r.status)}),router.get(`/topology`,async ctx=>{if(!container){ctx.json({error:`topology unavailable — container not bound yet`},503);return}let kickApp=appRef?.__kickApp;if(!kickApp){ctx.json({error:`topology unavailable — application surface not exposed`},503);return}try{let snapshot=await collectTopologySnapshot({app:kickApp,container});ctx.json(snapshot)}catch(err){let message=err instanceof Error?err.message:String(err);ctx.json({error:`topology collection failed`,message},500)}}),router.get(`/ws`,ctx=>{let wsAdapter=getPeerAdapters().find(a=>a.name===`WsAdapter`&&typeof a.getStats==`function`);if(!wsAdapter){ctx.json({enabled:!1,message:`WsAdapter not found`});return}ctx.json({enabled:!0,...wsAdapter.getStats()})}),router.get(`/queues`,async ctx=>{let queueAdapter=getPeerAdapters().find(a=>a.name===`QueueAdapter`&&typeof a.getQueueNames==`function`);if(!queueAdapter){ctx.json({enabled:!1,message:`QueueAdapter not found`});return}try{let names=queueAdapter.getQueueNames?.()??[],queues=[];for(let name of names){let stats=await queueAdapter.getQueueStats?.(name);queues.push({name,...stats})}ctx.json({enabled:!0,queues})}catch{ctx.json({enabled:!0,queues:[],error:`Failed to fetch queue stats`})}}),router.get(`/graph`,ctx=>{let registrations=container?.getRegistrations()??[],nodes=registrations.filter(r=>!r.token.startsWith(`__hmr__`)).map(r=>({id:r.token,kind:r.kind,scope:r.scope,resolveCount:r.resolveCount})),nodeIds=new Set(nodes.map(n=>n.id)),edges=[];for(let r of registrations)if(!r.token.startsWith(`__hmr__`))for(let dep of r.dependencies)nodeIds.has(dep)&&edges.push({from:r.token,to:dep});ctx.json({nodes,edges})}),router.get(`/stream`,ctx=>{let sse=ctx.sse(),sendMetrics=()=>{sse.send({type:`metrics`,requestCount:requestCount.value,errorCount:errorCount.value,clientErrorCount:clientErrorCount.value,errorRate:errorRate.value,uptimeSeconds:uptimeSeconds.value})};sendMetrics();let unsubContainer=container?.onChange?.(changes=>{sse.send({type:`container`,changes,timestamp:Date.now()}),sendMetrics()}),stopRequestWatch=watch(requestCount,()=>sendMetrics()),stopErrWatch=watch(errorCount,()=>sendMetrics()),heartbeat=setInterval(()=>sse.comment(`heartbeat`),3e4),tick=setInterval(sendMetrics,5e3);sse.onClose(()=>{unsubContainer?.(),stopRequestWatch(),stopErrWatch(),clearInterval(heartbeat),clearInterval(tick)})}),exposeConfig&&router.get(`/config`,ctx=>{let config={};for(let[key,value]of Object.entries(process.env))value!==void 0&&(config[key]=configPrefixes.some(prefix=>key.startsWith(prefix))?value:`[REDACTED]`);ctx.json({config})});let publicDir=resolvePublicDir(),dashboard=html=>ctx=>ctx.query?.token===void 0||authorized(ctx)?ctx.html(html):void 0;if(publicDir){let assetsDir=join(publicDir,`assets`);existsSync(assetsDir)?http.serveStatic(`${basePath}/assets`,assetsDir):http.serveStatic(basePath,publicDir);let indexHtml=readFileSync(join(publicDir,`index.html`),`utf-8`);http.route(`GET`,basePath,dashboard(indexHtml.replace(`<body`,`<body data-base="${basePath}"`)))}else http.route(`GET`,basePath,dashboard(`<h1>DevTools: public directory not found</h1>`));secret?(log.info(`DevTools mounted at ${basePath} [token: ${secret}]`),log.info(`Access: ${basePath}?token=${secret}`)):log.info(`DevTools mounted at ${basePath} [no guard]`)},onResponse(info){enabled&&(recordResponse(info.method,info.route,info.status,info.durationMs),logRequest(info))},onError(err,info){enabled&&info.source===`request`&&requestLog.recordError(info.requestId,err)},middleware(){return!enabled||FRAMEWORK_REPORTS_RESPONSES?[]:[{handler:(req,res,next)=>{let start=Date.now();res.on(`finish`,()=>{let matched=req[MATCHED_ROUTE_SLOT],route=matched?.pattern??matched?.path,durationMs=Date.now()-start;recordResponse(req.method??`GET`,route,res.statusCode,durationMs),logRequest({method:req.method??`GET`,path:(req.url??`/`).split(`?`)[0],route,status:res.statusCode,durationMs})}),next()},phase:`beforeGlobal`}]},onRouteMount(controllerClass,mountPath){if(!enabled)return;let collectedRoutes=getClassMeta(METADATA.ROUTES,controllerClass,[]),classMiddleware=getClassMeta(METADATA.CLASS_MIDDLEWARES,controllerClass,[]);for(let route of collectedRoutes){let methodMiddleware=getMethodMeta(METADATA.METHOD_MIDDLEWARES,controllerClass.prototype,route.handlerName,[]);routes.push({method:route.method.toUpperCase(),path:`${mountPath}${route.path===`/`?``:route.path}`,controller:controllerClass.name,handler:route.handlerName,middleware:[...classMiddleware.map(m=>m.name||`anonymous`),...methodMiddleware.map(m=>m.name||`anonymous`)],flags:Object.fromEntries(getRouteFlags(controllerClass,route.handlerName)),...uploadInfo(controllerClass,route.handlerName),...requestSchemas(route.validation)})}},afterStart({server}){if(enabled){server&&bus?.attachUpgrade(server);for(let peer of getPeerAdapters()){let name=peer?.name;typeof name==`string`&&name!==`DevToolsAdapter`&&(adapterStatuses[name]=`running`)}container&&trackContainerCronJobs(container),log.info(`DevTools ready — ${routes.length} routes tracked, ${container?.getRegistrations().length??0} DI bindings, event bus on ${basePath}/_bus`)}},shutdown(){stopErrorWatch?.(),runtimeSampler?.stop(),memoryAnalyzer?.stop(),bus&&getActiveDevtoolsBus()===bus&&setActiveDevtoolsBus(void 0),bus?.close(),adapterStatuses.DevToolsAdapter=`stopped`}}}});export{DevToolsAdapter};
|
|
15
15
|
//# sourceMappingURL=index.mjs.map
|