@econ-v1/app-sdk 7.0.78 → 7.0.79

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/README.md CHANGED
@@ -42,6 +42,41 @@ runNodeApp(new MyApp());
42
42
 
43
43
  The host launches the Bun process, supplies a Unix socket via the `NODE_APP_SOCKET` environment variable, and routes proxied HTTP requests, events, and capability calls over the socket.
44
44
 
45
+ ## Burger apps
46
+
47
+ The same package serves apps that run on Burger (`app_type: "burger"`), and
48
+ it carries their toolchain, so a Burger app depends on `@econ-v1/app-sdk`
49
+ and nothing else from `@econ-v1`:
50
+
51
+ | Part | How an app uses it |
52
+ |---|---|
53
+ | `burger-build` | The package's `bin`: `burger-build build src/index.ts --outdir dist` and `burger-build test-prepare …` (see `sdk/burger-build/README.md`). `node-app build` / `node-app test` run it for you. Needs Bun ≥ 1.4.2. The same code is importable as `@econ-v1/app-sdk/burger-build` (`buildApp`, `prepareTests`). |
54
+ | `burger-types` | `"types": ["@econ-v1/app-sdk/burger-types"]` in `tsconfig.json`: the `burger:*` modules, their `node:`/`bun:` aliases and the runtime globals. `@econ-v1/app-sdk/burger-types/modules` declares only `burger:*`. |
55
+
56
+ Both are built from `sdk/burger-build` and `sdk/burger-types` in this
57
+ repository by `bun run build` (`scripts/build-burger-toolchain.ts`).
58
+
59
+ Bundling with `burger-build` selects the `burger` export condition,
60
+ which resolves `@econ-v1/app-sdk` to `dist/burger/index.js`: identical API,
61
+ but the host channel is `burger:host` (framing and JSON handled by the
62
+ runtime), memory samples come from the QuickJS runtime, and there is no
63
+ worker-thread detection. Differences an app can observe:
64
+
65
+ - `openDatabase()` defaults to a 256 KiB page cache (Burger's cap) instead
66
+ of 1 MiB.
67
+ - Telemetry stays inactive (`telemetryActive()` is `false`): Burger Phase 1
68
+ has no `fetch` for an OTLP exporter.
69
+ - `heap_snapshot_request` is answered with `success: false`.
70
+ - A nested `invokeCapability` carries `invocation_context_id` only when it
71
+ is made before the handler's first `await` (Burger Phase 1 has no
72
+ `AsyncLocalStorage`); later calls are classified by the host as background
73
+ work, never as foreground. This is the known Phase 1 limitation recorded
74
+ in Burger Contract C6
75
+ (`docs/superpowers/plans/2026-09-17-burger-00-overview-and-contracts.md`)
76
+ and fixed by `burger-08`.
77
+
78
+ Burger apps bundle the SDK into `dist/index.js`; nothing is shared at runtime.
79
+
45
80
  ## Building for production
46
81
 
47
82
  `@econ-v1/app-sdk` is itself a **shared dependency** in the Node deb (installed
@@ -0,0 +1,2 @@
1
+ var ee=Object.defineProperty;var te=(e)=>e;function ne(e,t){this[e]=te.bind(null,t)}var oe=(e,t)=>{for(var o in t)ee(e,o,{get:t[o],enumerable:!0,configurable:!0,set:ne.bind(t,o)})};import{appendFileSync as le,mkdirSync as me,existsSync as fe}from"burger:fs";import{join as ye}from"burger:path";import{Database as ge}from"burger:sqlite";var w={};oe(w,{incrementCounter:()=>S,initAppTelemetry:()=>R,injectTraceContext:()=>re,telemetryActive:()=>A,traceIncomingCapability:()=>ie});async function R(e){}function A(){return!1}function S(e,t=1,o){}function re(e){return e}async function ie(e,t,o){return o()}import{connect as se,dataDir as ae,exit as L,logDir as pe,memoryUsage as ce}from"burger:host";var E=!0,F=256;function k(){return{socket:void 0,logDir:pe,dataDir:ae}}var M=k();function D(){return M.socket}function j(e,t,o=L){o(e)}function m(e){L(e)}function z(){}var ue=/the app channel is (not open|closed)/;function U(e){let t=se(),o=!0,n=!1;function i(r){if(n)return;try{t.send(r)}catch(s){if(s instanceof Error&&ue.test(s.message)){n=!0;return}throw s}}return t.onFrame((r)=>{if(!o)return;e.onFrame(r).catch((s)=>e.onFrameError(s))}),t.onClose((r)=>{if(n=!0,r)e.onError(r);else e.onClose()}),queueMicrotask(()=>e.onConnected("burger:host")),{send(r){i(r)},sendAndFlush(r){return i(r),Promise.resolve()},stopAccepting(){o=!1},end(){n=!0,t.close()}}}function B(){let e;return{run(t,o){let n=e;e=t;try{return o()}finally{e=n}},getStore(){return e}}}function K(){let e=ce();return{heap_used_kb:Math.round(e.heap_used_kb),heap_total_kb:Math.round(e.heap_total_kb),external_kb:Math.round(e.external_kb),heap_capacity_kb:Math.round(e.heap_capacity_kb)}}function H(){throw Error("heap snapshots are not available in the Burger runtime")}async function O(e,t=H,o=H){let n=t();return{size_bytes:await o(e,n)}}var{injectTraceContext:be,traceIncomingCapability:_e}=w,he=R,De=A,$e=S,ve=F;function je(e,t={}){let o=t.cacheSizeKb??ve;if(!Number.isInteger(o)||o<=0)throw RangeError("cacheSizeKb must be a positive integer");let n=new ge(e);try{return n.exec(`PRAGMA cache_size = -${o}`),n}catch(i){throw n.close(),i}}var ze=k,Ue=D,Be=j,y=M.logDir,W=y?ye(y,"app.log"):null,P=!1;function xe(){if(P||!y)return;try{if(!fe(y))me(y,{recursive:!0});P=!0}catch{}}function Ce(e,t){if(!W)return;if(xe(),!P)return;let n=`${new Date().toISOString()} [${e.toUpperCase().padEnd(5)}] ${t}
2
+ `;try{le(W,n)}catch{}}var p=null,Z=B(),g=new Map,c={trace:(e)=>f("trace",e),debug:(e)=>f("debug",e),info:(e)=>f("info",e),warn:(e)=>f("warn",e),error:(e)=>f("error",e),holdLease:()=>{let e=!1;return h(),()=>{if(e)return;e=!0,v()}}};function f(e,t){if(Ce(e,t),p)p({type:"log",level:e,message:t});else console[e==="trace"?"debug":e](t)}function Ke(e,t){if(!p)return Promise.reject(Error("Not connected to host"));let o=t??30000,n=e.timeout_ms===void 0?{...e,timeout_ms:o}:e,i={...n,trace_context:be({...n.trace_context??{}})},r=Z.getStore();if(r?.active&&r.id!==void 0)i.invocation_context_id=r.id;return new Promise((s,a)=>{g.set(e.id,{resolve:s,reject:a}),p({type:"invoke_capability",id:e.id,request:i}),setTimeout(()=>{if(g.has(e.id))g.delete(e.id),a(Error(`Capability invocation timed out after ${o}ms: ${e.capability}`))},o)})}function Oe(e,t){if(!p)throw Error("Not connected to host");p({type:"publish_event",event_name:e,data:t})}var u=0,b,d=null,_=!1,C,V=()=>m(0),T=V,q,Y=(e)=>(p?.(e),Promise.resolve()),N=Y;function G(e){if(b=e,e===void 0)I()}function I(){if(d)clearTimeout(d),d=null}function Ie(){if(_)return;if(b===void 0)return;if(u!==0)return;I();let e=Math.max(0,b*1000),t=setTimeout(()=>{d=null,Re()},e);t.unref?.(),d=t}function h(){u++,I()}function v(){if(u=Math.max(0,u-1),u===0)Ie()}async function Re(){if(_)return;if(_=!0,q?.(),C)await C.shutdown();await N({type:"lease_exit"}),T()}function We(){I(),u=0,b=void 0,_=!1,C=void 0,T=V,q=void 0,N=Y}function Ge(e){T=e}function Qe(){return{inFlight:u,rememberedSecs:b,timerArmed:d!==null,draining:_}}var Ae=30000;function Se(){let e=process.env.NODE_APP_MEMORY_SAMPLE_INTERVAL_MS,t=e?Number.parseInt(e,10):NaN;return Number.isFinite(t)&&t>=1000?t:Ae}var l=null,we=K;function Q(e){e({type:"app_memory",...we()})}function Ee(e){if(l)return;Q(e),l=setInterval(()=>Q(e),Se()),l.unref?.()}function x(){if(l)clearInterval(l),l=null}var ke=O;async function Me(e,t,o=ke){try{let n=await o(e.path);t({type:"heap_snapshot_response",id:e.id,success:!0,size_bytes:n.size_bytes})}catch{t({type:"heap_snapshot_response",id:e.id,success:!1,error:"heap_snapshot_failed"})}}function Pe(e,t){let o=t.indexOf("?"),n=o>=0?t.slice(0,o):t,i=e.split("/"),r=n.split("/"),s=0,a=0;while(s<i.length){if(i[s]==="**")return!0;if(a>=r.length)return!1;if(i[s]!=="*"&&i[s]!==r[a])return!1;s++,a++}return a===r.length}class Te{_routes=[];route(e,t,o,n){this._routes.push({method:e,path:t,policy:o,handler:n})}async init(e){}async shutdown(){}async handleRequest(e){if(this._routes.length>0)return this._dispatchRoute(e);return{status:501,headers:{},body:{error:"Not implemented"}}}async _dispatchRoute(e){let t=e.method.toUpperCase(),o=e.path;for(let n of this._routes){if(!(n.method==="*"||n.method.toUpperCase()===t))continue;if(!Pe(n.path,o))continue;let r=n.policy.requiredPermissions??[],s=e.caller?.granted_permissions??[],a=r.filter((X)=>!s.includes(X));if(a.length>0)return c.warn(`SDK scope denied: [${a.join(", ")}] missing for ${t} ${o}`),{status:403,headers:{},body:{error:`Missing permissions: ${a.join(", ")}`}};return n.handler(e)}return this.notFound()}async handleEvent(e){}providedCapabilities(){return[]}async handleCapability(e){return{id:e.id,success:!1,payload:{error:"Capability handling not implemented"}}}json(e,t=200){return{status:t,headers:{"Content-Type":"application/json"},body:e}}error(e,t=500){return{status:t,headers:{"Content-Type":"application/json"},body:{error:e}}}notFound(){return this.error("Not found",404)}}function Ze(e){z(),he(e.metadata?.name??"node-app");let t=U({onConnected:(n)=>{c.debug(`Connected to host via ${n}`)},onFrame:(n)=>J(e,n,o),onFrameError:(n)=>{c.error(`Failed to parse IPC message: ${n}`)},onError:(n)=>{console.error(`[node-app-sdk] Socket error: ${n.message}`),m(1)},onClose:()=>{x(),m(0),c.debug("Socket closed, shutting down")}});function o(n){t.send(n)}p=o,q=()=>{t.stopAccepting()},N=(n)=>t.sendAndFlush(n),process.on("SIGTERM",async()=>{if(!E)return;x(),await e.shutdown(),t.end(),process.exit(0)}),process.on("SIGINT",async()=>{if(!E)return;x(),await e.shutdown(),t.end(),process.exit(0)})}async function J(e,t,o){switch(C=e,t.type){case"init":{let n=t.config;await e.init(n),o({type:"ready",metadata:e.metadata}),Ee(o);break}case"request":{let{id:n,request:i}=t;h();try{let r=await e.handleRequest(i);o({type:"response",id:n,response:r})}catch(r){o({type:"response",id:n,response:{status:500,headers:{},body:{error:`Internal app error: ${r}`}}})}finally{v()}break}case"event":{let{event:n,lease_secs:i}=t;G(i),h();try{await e.handleEvent(n)}catch(r){c.error(`Event handler error: ${r}`)}finally{v()}break}case"capability_request":{let{id:n,request:i}=t;G(i.lease_secs),h();let r={id:i.invocation_context_id,active:!0};try{let s=await Z.run(r,async()=>{try{return await _e(i.capability,i.trace_context,()=>e.handleCapability(i))}finally{r.active=!1}});o({type:"capability_response",id:n,response:s})}catch(s){o({type:"capability_response",id:n,response:{id:i.id,success:!1,payload:{error:`Capability handler error: ${s}`}}})}finally{v()}break}case"invoke_capability_result":{let{id:n,response:i}=t,r=g.get(n);if(r)g.delete(n),r.resolve(i);break}case"shutdown":{c.info("Received shutdown signal from host"),x(),await e.shutdown(),m(0);break}case"heap_snapshot_request":{await Me(t,o);break}default:c.warn(`Unknown IPC message type: ${t.type}`)}}async function Ve(e,t,o){p=o,await J(e,t,p)}export{Te as NodeApp,ke as _captureHeapSnapshotTo,Me as _handleHeapSnapshotRequest,ze as _resolveCtx,we as _sampleMemory,Be as _testExitWorker,Ve as _testHandleMessage,Qe as _testLeaseState,We as _testResetLeaseState,Ue as _testRunNodeAppPath,Ge as _testSetLeaseExitFn,c as host,$e as incrementCounter,he as initAppTelemetry,Ke as invokeCapability,je as openDatabase,Oe as publishEvent,Ze as runNodeApp,De as telemetryActive};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Contract C6: which import specifiers a Burger bundle may contain, and what
3
+ * they become. Everything here is data; `classifySpecifier` is the only
4
+ * decision function.
5
+ */
6
+ export declare const BURGER_MODULES: readonly ["burger:host", "burger:fs", "burger:fs/promises", "burger:path", "burger:os", "burger:worker_threads", "burger:crypto", "burger:buffer", "burger:sqlite", "burger:test"];
7
+ export type BurgerModule = (typeof BURGER_MODULES)[number];
8
+ /** C5/C6 "Aliased from" columns (plus `fs/promises`, the module form of `fs.promises`). */
9
+ export declare const ALIASES: ReadonlyMap<string, BurgerModule>;
10
+ /**
11
+ * Node's core module names that are importable without the `node:` prefix
12
+ * (Node 22 `module.builtinModules`). Hard-coded rather than read from
13
+ * `node:module` because Bun's list also contains npm package names
14
+ * (`ws`, `undici`) that apps legitimately depend on.
15
+ */
16
+ export declare const NODE_BUILTINS: ReadonlySet<string>;
17
+ /** Named exports of each `burger:*` module (C4, C6). Used to bridge CommonJS `require()`. */
18
+ export declare const BURGER_EXPORTS: Readonly<Record<BurgerModule, readonly string[]>>;
19
+ export type SpecifierClass = {
20
+ kind: "burger";
21
+ target: BurgerModule;
22
+ } | {
23
+ kind: "forbidden";
24
+ reason: string;
25
+ } | {
26
+ kind: "other";
27
+ };
28
+ export declare function classifySpecifier(specifier: string): SpecifierClass;
@@ -0,0 +1,19 @@
1
+ import { type Problem } from "./gate.ts";
2
+ export interface BuildOptions {
3
+ entry: string;
4
+ outdir: string;
5
+ minify: boolean;
6
+ }
7
+ export type BuildOutcome = {
8
+ ok: true;
9
+ outputs: string[];
10
+ } | {
11
+ ok: false;
12
+ report: string;
13
+ problems: Problem[];
14
+ };
15
+ /**
16
+ * Bundle a Burger app into one ES module (`<outdir>/<entry name>.js`).
17
+ * Nothing is written unless the build and the gate both pass.
18
+ */
19
+ export declare function buildApp(options: BuildOptions): Promise<BuildOutcome>;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Bun before 1.4.2 marks a plugin-resolved import external but keeps the
3
+ * ORIGINAL specifier in the output (`import "node:fs"` instead of
4
+ * `import "burger:fs"`) and has no `metafile`, so the alias and the gate
5
+ * would both silently fail. Measured on Bun 1.3.1 (broken) and 1.4.2 (works).
6
+ */
7
+ export declare const MIN_BUN_VERSION = "1.4.2";
8
+ export declare function assertSupportedBun(version?: string): void;