@trayai/helix-sdk 0.88.0 → 0.90.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/CHANGELOG.md CHANGED
@@ -9,6 +9,36 @@ general availability, minor versions may include breaking changes.
9
9
  The SDK and [`@trayai/helix-cli`](https://www.npmjs.com/package/@trayai/helix-cli) are
10
10
  released together and share a version — install matching versions.
11
11
 
12
+ ## 0.89.0
13
+
14
+ ### Changed
15
+
16
+ - **`getContext()` now returns `PlatformContext`.** It previously returned `TrayContext`;
17
+ it now returns the canonical execution context, ahead of execution kinds that are not
18
+ yet generally available. The members present in every execution (`http`, `db()`,
19
+ `kv()`, `files()`, `log`, `config`, `state`, `executionId`, `deploymentId`, `traceId`)
20
+ are unchanged. Request-specific fields (`input`, `method`, `path`, `headers`,
21
+ `request`, `status()`, `error()`, `userId`, `identity`) are now optional — check the
22
+ field you read, e.g. `const ctx = getContext(); if (ctx.method !== undefined) { ctx.method; }`.
23
+ Handler parameters are unaffected: `defineFunction` handlers still receive the exact
24
+ `TrayContext`.
25
+ - **`runWithContext` mocks mirror production for optional fields.** Accessing an unset
26
+ optional `PlatformContext` field in a mock now returns `undefined` (so presence-guard
27
+ code is testable) instead of throwing; unset always-present fields still throw the
28
+ descriptive error.
29
+ - The error thrown when `getContext()` is called outside an execution now reads
30
+ "getContext() called outside of a Helix function execution…".
31
+
32
+ ### Removed
33
+
34
+ - **`ctx.trigger.isRetry` for scheduled functions.** The flag was reserved and
35
+ always `false`: no platform signal reliably distinguishes a retried *execution* from a
36
+ redelivered *message* (SQS's receive count measures deliveries, not runs, and can under-
37
+ as well as over-report), so rather than promise a boolean the platform cannot honestly
38
+ populate, the contract now states retry-once semantics and requires idempotent handlers.
39
+ The runtime still accepts (and ignores) `isRetry` on the shim invocation payload, so
40
+ bundles built with older shims keep working.
41
+
12
42
  ## 0.87.0
13
43
 
14
44
  No user-visible changes in this release — co-versioned with `@trayai/helix-cli`'s changes.
package/README.md CHANGED
@@ -210,7 +210,7 @@ const result = await runWithContext({ input: { name: 'Ada' } }, async () => {
210
210
  });
211
211
  ```
212
212
 
213
- The mock context is a Proxy: any field your code touches that you didn't provide throws a descriptive error, so tests fail loudly instead of silently reading `undefined`.
213
+ The mock context is a Proxy: touching an always-present field (`http`, `db`, `log`, `executionId`, …) you didn't provide throws a descriptive error, so tests fail loudly — as does touching a field that isn't part of the context at all. Fields that aren't guaranteed on every execution (for example `userId` and `identity`) instead return `undefined` when unset, just as they can at runtime.
214
214
 
215
215
  ## Entry points
216
216
 
@@ -0,0 +1 @@
1
+ import{AsyncLocalStorage as e}from"async_hooks";var o=new e;function r(){let t=o.getStore();if(!t)throw new Error("getContext() called outside of a Helix function execution. It is available only while a defineFunction or defineSchedule invocation is executing \u2014 from any code the handler calls.");return t}export{o as a,r as b};
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as h3 from 'h3';
2
2
  import { ZodType } from 'zod';
3
- import { F as FunctionHandler, D as DefineFunctionOptions, a as DefineScheduleConfig, S as ScheduleDefinition, T as TrayContext } from './types-D-yJbYWp.js';
4
- export { b as DbCallParameters, c as DbClient, d as FileSystemClient, H as HttpClient, K as KeyValueStore, L as Logger, e as ScheduleContext, f as ScheduleHandler, g as ScheduleTrigger, h as SqlBuilder, U as UnvalidatedInput } from './types-D-yJbYWp.js';
3
+ import { F as FunctionHandler, D as DefineFunctionOptions, a as DefineScheduleConfig, S as ScheduleDefinition, P as PlatformContext } from './types-D01XG3C4.js';
4
+ export { b as DbCallParameters, c as DbClient, d as FileSystemClient, H as HttpClient, K as KeyValueStore, L as Logger, e as ScheduleContext, f as ScheduleHandler, g as ScheduleTrigger, h as SqlBuilder, T as TrayContext, U as UnvalidatedInput } from './types-D01XG3C4.js';
5
5
  import { T as TrayConfig, L as LoadedTrayConfig, W as WatchErrorPhase } from './sdk-config-_xIe9XvP.js';
6
6
  export { P as ProvisionedTrayConfig, S as SdkRuntimeConfig, U as UnprovisionedTrayConfig, a as WORKSPACE_ID_REQUIRED_CODE, b as WorkspaceIdRequiredError, g as getSdkConfig, l as loadConfig, c as loadedTrayConfigSchema, p as projectIdSchema, r as requireWorkspaceId, d as resolveConfigFile, w as workspaceIdSchema } from './sdk-config-_xIe9XvP.js';
7
7
  export { a as ENVIRONMENT_VALUES, E as Environment, e as environmentSchema } from './env-schema-DkgTG5b0.js';
@@ -75,7 +75,7 @@ declare function runWithinAppSpan<T extends {
75
75
  };
76
76
  } | null>(args: RunWithinAppSpanArgs, serve: () => Promise<T>): Promise<T>;
77
77
 
78
- declare function getContext(): TrayContext;
78
+ declare function getContext(): PlatformContext;
79
79
 
80
80
  /**
81
81
  * Type-level helper for helix.config.ts — provides autocomplete in the editor.
@@ -134,4 +134,4 @@ interface ConfigService {
134
134
  */
135
135
  declare function createConfigService(options: CreateConfigServiceOptions): Promise<ConfigService>;
136
136
 
137
- export { type ConfigService, type CreateConfigServiceOptions, DefineFunctionOptions, DefineScheduleConfig, FunctionHandler, LoadedTrayConfig, type RunWithinAppSpanArgs, SCHEDULE_DEFINITION_KIND, ScheduleConfigError, ScheduleDefinition, TrayConfig, TrayContext, WatchErrorPhase, createConfigService, defineConfig, defineFunction, defineSchedule, getContext, isScheduleDefinition, runWithinAppSpan };
137
+ export { type ConfigService, type CreateConfigServiceOptions, DefineFunctionOptions, DefineScheduleConfig, FunctionHandler, LoadedTrayConfig, PlatformContext, type RunWithinAppSpanArgs, SCHEDULE_DEFINITION_KIND, ScheduleConfigError, ScheduleDefinition, TrayConfig, WatchErrorPhase, createConfigService, defineConfig, defineFunction, defineSchedule, getContext, isScheduleDefinition, runWithinAppSpan };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{a as B,b as me}from"./chunk-L6J5L3JQ.js";import{A as Ce,B as ve,b as T,g as U,h as I,i as N,j as C,k as q,l as M,m as $,n as Z,o as ge,p as K,q as z,r as G,s as J,t as Q,x as X,y as he,z as ye}from"./chunk-3SDUCOAT.js";import{b as H,e as A,i as W,l as g,m as V}from"./chunk-H4L4UWIK.js";import{randomUUID as _e}from"crypto";import{SpanStatusCode as ne,SpanKind as Re}from"@opentelemetry/api";import{defineEventHandler as Pe,getMethod as Le,getRequestURL as Fe,getHeaders as se,getHeader as oe}from"h3";import{readBody as we,getQuery as Ee,getRouterParams as ke}from"h3";async function Y(e,t){let r=await Se(e),n=Ee(e),o=ke(e)??{},s={...n,...r,...o};return t?xe(s,t):s}async function Se(e){try{let t=await we(e);return t&&typeof t=="object"?t:(t!=null&&t!==""&&console.warn("[helix-sdk] Request body present but not a JSON object \u2014 ignored"),{})}catch(t){if(t instanceof Error&&"statusCode"in t&&t.statusCode===400){let n=new Error("Malformed request body");throw n.statusCode=400,n.data={message:"Request body is not valid JSON"},n}let r=e.method?.toUpperCase();if(r==="GET"||r==="HEAD"||r==="DELETE")return{};throw t}}function xe(e,t){let r=t.safeParse(e);if(!r.success){let n=new Error("Input validation failed");throw n.statusCode=400,n.data=r.error.flatten(),n}return r.data}var v=class extends Error{statusCode;constructor(t,r){super(r),this.statusCode=t,this.name="HttpError"}};function ee(e){return t=>{e.node.res.statusCode=t}}function te(){return(e,t)=>{throw new v(e,t)}}function re(e,t){let{statusCode:r,message:n}=Te(t.detail);return e.node.res.statusCode=r,{error:n,statusCode:r}}function Te(e){switch(e.kind){case"unknown_alias":return{statusCode:400,message:"Unknown authentication alias"};case"input_validation_failed":return{statusCode:400,message:"Input validation failed"};case"output_validation_failed":return{statusCode:500,message:"Internal server error"};case"config_load_failed":return{statusCode:500,message:"Configuration error"};case"broker_error":return{statusCode:502,message:"Broker error"};case"unsupported_body_type":return{statusCode:400,message:"Unsupported request body type"};case"unsupported_scheme":return{statusCode:400,message:"Unsupported URL scheme"};case"missing_execution_credentials":return{statusCode:401,message:"Missing execution credentials"};case"missing_tray_token":return{statusCode:500,message:"Missing Tray token"};case"db_call_failed":return{statusCode:502,message:e.message};case"db_query_failed":return{statusCode:502,message:`Database query failed: ${e.message}`};case"db_invalid_parameters":return{statusCode:400,message:e.message};case"kv_key_invalid":return{statusCode:400,message:e.message};case"kv_value_invalid":return{statusCode:400,message:e.message};case"kv_value_too_large":return{statusCode:400,message:e.message};case"kv_not_available":return{statusCode:501,message:"Key-value store is not available in this environment"};case"kv_call_failed":return{statusCode:502,message:"Key-value store request failed"};case"kv_requires_provisioning":return{statusCode:500,message:"Internal server error"};case"files_path_invalid":return{statusCode:400,message:e.message};case"files_value_invalid":return{statusCode:400,message:e.message};case"files_value_too_large":return{statusCode:400,message:e.message};case"files_not_available":return{statusCode:501,message:"File storage is not available in this environment"};case"files_call_failed":return{statusCode:502,message:"File storage request failed"};case"files_requires_provisioning":return{statusCode:500,message:"Internal server error"};default:return{statusCode:500,message:`Unhandled error kind: ${e.kind}`}}}var Ie=["authorization","x-tray-token","set-cookie","x-kong-request-id","traceparent","x-real-ip","cookie"];function be(e){return e.toLowerCase().includes("tray_lid")}function b(e){let t={};for(let[r,n]of Object.entries(e))if(!Ie.includes(r.toLowerCase())){if(r.toLowerCase()==="cookie"&&n){let o=n.split(";").map(s=>s.trim()).filter(s=>{let a=s.split("=")[0].trim();return!be(a)}).join("; ");o&&(t[r]=o);continue}t[r]=n}return t}function je(e,t){let{inputSchema:r,outputSchema:n}=Ue(t),o=e;return Pe(async s=>{let a=T(),{executionId:i,userId:c,deploymentId:f}=Ne(a,s),p=Date.now(),y=new Date().toISOString(),m=Le(s),l=Fe(s).pathname,u=z(),h=K(),E=`${m} ${l}`;q();let O={hasDb:!1,hasExternal:!1,hasAuth:!1,hasError:!1};return De(()=>I.run(O,()=>U.run(i,async()=>{let k=await Oe(s,r);if(!k.ok)return{response:k.response,errored:!1,errorMessage:null};let pe=Me(s,k.input,u,h,m,l,i,f,c);return He(o,pe,n,s,h,i,f,{method:m,path:l,rawInput:k.input})})),{executionId:i,deploymentId:f,routeOrJob:E,startedAt:y,startMs:p,userId:c,flags:O})})}async function De(e,t){let r=!1,n=null;try{let o=await e();return r=o.errored,n=o.errorMessage,o.response}finally{Z({...t,errored:r,errorMessage:n}),await M()}}async function Oe(e,t){try{return{ok:!0,input:await Y(e,t)}}catch(r){let n=qe(e,r);if(n)return{ok:!1,response:n};throw r}}async function He(e,t,r,n,o,s,a,i){return $.startActiveSpan("function",{root:!0,kind:Re.SERVER,attributes:{method:i.method,route:i.path,executionId:s,...a?{deploymentId:a}:{}}},async c=>{t.traceId=c.spanContext().traceId;let f=b(se(n));C(c,"input",i.rawInput),C(c,"request",{method:i.method,path:i.path,headers:f,body:i.rawInput});let p=!1,y=null,m,l;try{let u=await B.run(t,()=>e(t));if(m=u,u==null)n.node.res.statusCode=204,l="";else if(u instanceof Response)l=u;else if(r){let h=r.safeParse(u);if(!h.success){let E=Object.keys(h.error.flatten().fieldErrors);throw o.error("Output validation failed",{fields:E,executionId:s}),g.outputValidationFailed(E)}l=h.data}else l=u}catch(u){p=!0,y=u instanceof Error?u.message:String(u),N("hasError"),l=Ae(u,n,o,s)}finally{We(c,n,{errored:p,errorMessage:y,handlerResult:m,response:l})}return{response:l,errored:p,errorMessage:y}})}function Ae(e,t,r,n){if(e instanceof v)return t.node.res.statusCode=e.statusCode,{error:e.message,statusCode:e.statusCode};if(V(e))return re(t,e);r.error("Unhandled error in handler",{error:e instanceof Error?e.message:String(e),executionId:n}),t.node.res.statusCode=500;let o=process.env.NODE_ENV==="development";return{error:o&&e instanceof Error?e.message:"Internal server error",statusCode:500,...o&&e instanceof Error&&e.stack?{stack:e.stack}:{}}}function We(e,t,r){e.setAttribute("statusCode",t.node.res.statusCode),!r.errored&&r.handlerResult!==void 0&&C(e,"output",r.handlerResult),C(e,"response",r.response);let n=I.getStore();e.setAttribute("hasAuth",n?.hasAuth??!1),e.setAttribute("hasExternal",n?.hasExternal??!1),e.setAttribute("hasDb",n?.hasDb??!1),e.setAttribute("status",r.errored?"error":"success");let o=r.errored||(n?.hasError??!1);e.setStatus({code:o?ne.ERROR:ne.OK,...r.errorMessage?{message:r.errorMessage}:{}}),e.end()}function Ue(e){if(!e)return{inputSchema:void 0,outputSchema:void 0};if(e instanceof Object&&"_def"in e)return{inputSchema:e,outputSchema:void 0};if(typeof e=="object"&&("input"in e||"output"in e)){let t=e;return{inputSchema:t.input,outputSchema:t.output}}return{inputSchema:e,outputSchema:void 0}}function Ne(e,t){let r=e.mode==="lambda",n=(r?e.executionId:oe(t,"x-tray-execution-id"))??_e(),o=(r?e.userId:oe(t,"x-tray-user-id"))??null,s=r?e.deploymentId??null:null;return{executionId:n,userId:o,deploymentId:s}}function qe(e,t){if(!(t instanceof Error)||!("statusCode"in t)||!("data"in t))return null;let r=t.statusCode,n=t.data;e.node.res.statusCode=r;let o=n&&typeof n=="object"&&"fieldErrors"in n?n.fieldErrors:void 0;return{error:"Input validation failed",statusCode:r,...o?{fields:Object.keys(o)}:{}}}function Me(e,t,r,n,o,s,a,i,c){return{input:t,method:o,path:s,headers:b(se(e)),http:G(),db:f=>J(f),kv:()=>Q(),files:()=>X(),log:n,config:r,status:ee(e),error:te(),state:{},request:e,executionId:a,deploymentId:i,userId:c,identity:c!==null?{user:{id:c}}:null,traceId:""}}function $e(e){return e}import{resolve as _}from"path";import{existsSync as ae}from"fs";import{loadConfig as Ke}from"c12";import{z as d}from"zod";import{z as Ze}from"zod";var Be=["production"],S=Ze.string().refine(e=>H(e),e=>({message:`Invalid enum value. Expected ${A().map(t=>`'${t}'`).join(" | ")}, received '${e}'`}));var R=d.string().uuid("projectId must be a valid UUID"),P=d.string().min(1,"workspaceId must be a non-empty string"),ze=d.object({name:d.string().min(1,"name is required"),projectId:R.optional(),workspaceId:P.optional(),env:S.default("production"),authentications:d.record(d.string()).optional().default({}),config:d.record(d.unknown()).optional().default({})}),ie=d.object({name:d.string().min(1,"name is required"),workspaceId:P.optional(),env:S,authentications:d.record(d.string()),config:d.record(d.unknown())}),Ve=d.discriminatedUnion("kind",[ie.extend({kind:d.literal("provisioned"),projectId:R}),ie.extend({kind:d.literal("unprovisioned")})]),de=["helix","tray"],ce=["ts","mts","cts","js","mjs","cjs"];function x(e){for(let t of de)for(let r of ce){let n=_(e,`${t}.config.${r}`);if(ae(n))return n}}function Ge(e){for(let t of de)if(ce.some(r=>ae(_(e,`${t}.config.${r}`))))return t;return"helix"}function L(e){return{name:Ge(e),cwd:e,rcFile:!1,globalRc:!1,packageJson:!1,dotenv:!1,extend:!1}}function F(e,t){if(e!==null&&typeof e=="object"&&"extends"in e)throw g.configLoadFailed(`helix.config.ts at ${t}: 'extends' is not supported \u2014 remove the field`);let r=ze.safeParse(e);if(!r.success){let f=r.error.issues.map(p=>p.message).join("; ");throw g.configLoadFailed(`Invalid helix.config.ts at ${t}: ${f}`)}let{projectId:n,workspaceId:o,...s}=r.data,a=o!==void 0?{workspaceId:o}:{},i=W(),c=i!==void 0?{...s,env:i}:s;return n!==void 0?{kind:"provisioned",projectId:n,...c,...a}:{kind:"unprovisioned",...c,...a}}async function w(e){let t=e??process.cwd(),r;try{r=await Ke(L(t))}catch(o){throw g.configLoadFailed(`Failed to load helix.config.ts: ${o instanceof Error?o.message:String(o)}`)}let n=r._configFile??x(t);if(!n)throw g.configLoadFailed(`helix.config.ts not found at ${_(t,"helix.config.ts")}`);return F(r.config,n)}var ue="WORKSPACE_ID_REQUIRED";function Je(e){if(e.workspaceId===void 0)throw Object.assign(new Error("No workspaceId found in helix.config.ts. Discover & choose one \u2014 self-serve fix: run `helix workspace select`; agent-driven fix: call the `tray_list_workspaces` MCP tool. Already know the id? Run `helix workspace set <id>` or call the `tray_set_workspace_id` MCP tool. deploy cannot proceed without a workspace."),{code:ue});return e.workspaceId}import{existsSync as Qe,watch as Xe,writeSync as Ye}from"fs";import{join as et,resolve as tt}from"path";import{watchConfig as rt}from"c12";function nt(e,t){let r=e instanceof Error?e.message:String(e);Ye(2,`[helix-watch] ${t.phase} error: ${r}
1
+ import{A as Ce,B as ve,b as T,g as U,h as I,i as N,j as C,k as q,l as M,m as $,n as Z,o as ge,p as K,q as z,r as G,s as J,t as Q,x as X,y as he,z as ye}from"./chunk-3SDUCOAT.js";import{a as B,b as me}from"./chunk-WUCX7RZO.js";import{b as H,e as A,i as W,l as g,m as V}from"./chunk-H4L4UWIK.js";import{randomUUID as _e}from"crypto";import{SpanStatusCode as ne,SpanKind as Re}from"@opentelemetry/api";import{defineEventHandler as Pe,getMethod as Le,getRequestURL as Fe,getHeaders as se,getHeader as oe}from"h3";import{readBody as we,getQuery as Ee,getRouterParams as ke}from"h3";async function Y(e,t){let r=await Se(e),n=Ee(e),o=ke(e)??{},s={...n,...r,...o};return t?xe(s,t):s}async function Se(e){try{let t=await we(e);return t&&typeof t=="object"?t:(t!=null&&t!==""&&console.warn("[helix-sdk] Request body present but not a JSON object \u2014 ignored"),{})}catch(t){if(t instanceof Error&&"statusCode"in t&&t.statusCode===400){let n=new Error("Malformed request body");throw n.statusCode=400,n.data={message:"Request body is not valid JSON"},n}let r=e.method?.toUpperCase();if(r==="GET"||r==="HEAD"||r==="DELETE")return{};throw t}}function xe(e,t){let r=t.safeParse(e);if(!r.success){let n=new Error("Input validation failed");throw n.statusCode=400,n.data=r.error.flatten(),n}return r.data}var v=class extends Error{statusCode;constructor(t,r){super(r),this.statusCode=t,this.name="HttpError"}};function ee(e){return t=>{e.node.res.statusCode=t}}function te(){return(e,t)=>{throw new v(e,t)}}function re(e,t){let{statusCode:r,message:n}=Te(t.detail);return e.node.res.statusCode=r,{error:n,statusCode:r}}function Te(e){switch(e.kind){case"unknown_alias":return{statusCode:400,message:"Unknown authentication alias"};case"input_validation_failed":return{statusCode:400,message:"Input validation failed"};case"output_validation_failed":return{statusCode:500,message:"Internal server error"};case"config_load_failed":return{statusCode:500,message:"Configuration error"};case"broker_error":return{statusCode:502,message:"Broker error"};case"unsupported_body_type":return{statusCode:400,message:"Unsupported request body type"};case"unsupported_scheme":return{statusCode:400,message:"Unsupported URL scheme"};case"missing_execution_credentials":return{statusCode:401,message:"Missing execution credentials"};case"missing_tray_token":return{statusCode:500,message:"Missing Tray token"};case"db_call_failed":return{statusCode:502,message:e.message};case"db_query_failed":return{statusCode:502,message:`Database query failed: ${e.message}`};case"db_invalid_parameters":return{statusCode:400,message:e.message};case"kv_key_invalid":return{statusCode:400,message:e.message};case"kv_value_invalid":return{statusCode:400,message:e.message};case"kv_value_too_large":return{statusCode:400,message:e.message};case"kv_not_available":return{statusCode:501,message:"Key-value store is not available in this environment"};case"kv_call_failed":return{statusCode:502,message:"Key-value store request failed"};case"kv_requires_provisioning":return{statusCode:500,message:"Internal server error"};case"files_path_invalid":return{statusCode:400,message:e.message};case"files_value_invalid":return{statusCode:400,message:e.message};case"files_value_too_large":return{statusCode:400,message:e.message};case"files_not_available":return{statusCode:501,message:"File storage is not available in this environment"};case"files_call_failed":return{statusCode:502,message:"File storage request failed"};case"files_requires_provisioning":return{statusCode:500,message:"Internal server error"};default:return{statusCode:500,message:`Unhandled error kind: ${e.kind}`}}}var Ie=["authorization","x-tray-token","set-cookie","x-kong-request-id","traceparent","x-real-ip","cookie"];function be(e){return e.toLowerCase().includes("tray_lid")}function b(e){let t={};for(let[r,n]of Object.entries(e))if(!Ie.includes(r.toLowerCase())){if(r.toLowerCase()==="cookie"&&n){let o=n.split(";").map(s=>s.trim()).filter(s=>{let a=s.split("=")[0].trim();return!be(a)}).join("; ");o&&(t[r]=o);continue}t[r]=n}return t}function je(e,t){let{inputSchema:r,outputSchema:n}=Ue(t),o=e;return Pe(async s=>{let a=T(),{executionId:i,userId:c,deploymentId:f}=Ne(a,s),p=Date.now(),y=new Date().toISOString(),m=Le(s),l=Fe(s).pathname,u=z(),h=K(),E=`${m} ${l}`;q();let O={hasDb:!1,hasExternal:!1,hasAuth:!1,hasError:!1};return De(()=>I.run(O,()=>U.run(i,async()=>{let k=await Oe(s,r);if(!k.ok)return{response:k.response,errored:!1,errorMessage:null};let pe=Me(s,k.input,u,h,m,l,i,f,c);return He(o,pe,n,s,h,i,f,{method:m,path:l,rawInput:k.input})})),{executionId:i,deploymentId:f,routeOrJob:E,startedAt:y,startMs:p,userId:c,flags:O})})}async function De(e,t){let r=!1,n=null;try{let o=await e();return r=o.errored,n=o.errorMessage,o.response}finally{Z({...t,errored:r,errorMessage:n}),await M()}}async function Oe(e,t){try{return{ok:!0,input:await Y(e,t)}}catch(r){let n=qe(e,r);if(n)return{ok:!1,response:n};throw r}}async function He(e,t,r,n,o,s,a,i){return $.startActiveSpan("function",{root:!0,kind:Re.SERVER,attributes:{method:i.method,route:i.path,executionId:s,...a?{deploymentId:a}:{}}},async c=>{t.traceId=c.spanContext().traceId;let f=b(se(n));C(c,"input",i.rawInput),C(c,"request",{method:i.method,path:i.path,headers:f,body:i.rawInput});let p=!1,y=null,m,l;try{let u=await B.run(t,()=>e(t));if(m=u,u==null)n.node.res.statusCode=204,l="";else if(u instanceof Response)l=u;else if(r){let h=r.safeParse(u);if(!h.success){let E=Object.keys(h.error.flatten().fieldErrors);throw o.error("Output validation failed",{fields:E,executionId:s}),g.outputValidationFailed(E)}l=h.data}else l=u}catch(u){p=!0,y=u instanceof Error?u.message:String(u),N("hasError"),l=Ae(u,n,o,s)}finally{We(c,n,{errored:p,errorMessage:y,handlerResult:m,response:l})}return{response:l,errored:p,errorMessage:y}})}function Ae(e,t,r,n){if(e instanceof v)return t.node.res.statusCode=e.statusCode,{error:e.message,statusCode:e.statusCode};if(V(e))return re(t,e);r.error("Unhandled error in handler",{error:e instanceof Error?e.message:String(e),executionId:n}),t.node.res.statusCode=500;let o=process.env.NODE_ENV==="development";return{error:o&&e instanceof Error?e.message:"Internal server error",statusCode:500,...o&&e instanceof Error&&e.stack?{stack:e.stack}:{}}}function We(e,t,r){e.setAttribute("statusCode",t.node.res.statusCode),!r.errored&&r.handlerResult!==void 0&&C(e,"output",r.handlerResult),C(e,"response",r.response);let n=I.getStore();e.setAttribute("hasAuth",n?.hasAuth??!1),e.setAttribute("hasExternal",n?.hasExternal??!1),e.setAttribute("hasDb",n?.hasDb??!1),e.setAttribute("status",r.errored?"error":"success");let o=r.errored||(n?.hasError??!1);e.setStatus({code:o?ne.ERROR:ne.OK,...r.errorMessage?{message:r.errorMessage}:{}}),e.end()}function Ue(e){if(!e)return{inputSchema:void 0,outputSchema:void 0};if(e instanceof Object&&"_def"in e)return{inputSchema:e,outputSchema:void 0};if(typeof e=="object"&&("input"in e||"output"in e)){let t=e;return{inputSchema:t.input,outputSchema:t.output}}return{inputSchema:e,outputSchema:void 0}}function Ne(e,t){let r=e.mode==="lambda",n=(r?e.executionId:oe(t,"x-tray-execution-id"))??_e(),o=(r?e.userId:oe(t,"x-tray-user-id"))??null,s=r?e.deploymentId??null:null;return{executionId:n,userId:o,deploymentId:s}}function qe(e,t){if(!(t instanceof Error)||!("statusCode"in t)||!("data"in t))return null;let r=t.statusCode,n=t.data;e.node.res.statusCode=r;let o=n&&typeof n=="object"&&"fieldErrors"in n?n.fieldErrors:void 0;return{error:"Input validation failed",statusCode:r,...o?{fields:Object.keys(o)}:{}}}function Me(e,t,r,n,o,s,a,i,c){return{input:t,method:o,path:s,headers:b(se(e)),http:G(),db:f=>J(f),kv:()=>Q(),files:()=>X(),log:n,config:r,status:ee(e),error:te(),state:{},request:e,executionId:a,deploymentId:i,userId:c,identity:c!==null?{user:{id:c}}:null,traceId:""}}function $e(e){return e}import{resolve as _}from"path";import{existsSync as ae}from"fs";import{loadConfig as Ke}from"c12";import{z as d}from"zod";import{z as Ze}from"zod";var Be=["production"],S=Ze.string().refine(e=>H(e),e=>({message:`Invalid enum value. Expected ${A().map(t=>`'${t}'`).join(" | ")}, received '${e}'`}));var R=d.string().uuid("projectId must be a valid UUID"),P=d.string().min(1,"workspaceId must be a non-empty string"),ze=d.object({name:d.string().min(1,"name is required"),projectId:R.optional(),workspaceId:P.optional(),env:S.default("production"),authentications:d.record(d.string()).optional().default({}),config:d.record(d.unknown()).optional().default({})}),ie=d.object({name:d.string().min(1,"name is required"),workspaceId:P.optional(),env:S,authentications:d.record(d.string()),config:d.record(d.unknown())}),Ve=d.discriminatedUnion("kind",[ie.extend({kind:d.literal("provisioned"),projectId:R}),ie.extend({kind:d.literal("unprovisioned")})]),de=["helix","tray"],ce=["ts","mts","cts","js","mjs","cjs"];function x(e){for(let t of de)for(let r of ce){let n=_(e,`${t}.config.${r}`);if(ae(n))return n}}function Ge(e){for(let t of de)if(ce.some(r=>ae(_(e,`${t}.config.${r}`))))return t;return"helix"}function L(e){return{name:Ge(e),cwd:e,rcFile:!1,globalRc:!1,packageJson:!1,dotenv:!1,extend:!1}}function F(e,t){if(e!==null&&typeof e=="object"&&"extends"in e)throw g.configLoadFailed(`helix.config.ts at ${t}: 'extends' is not supported \u2014 remove the field`);let r=ze.safeParse(e);if(!r.success){let f=r.error.issues.map(p=>p.message).join("; ");throw g.configLoadFailed(`Invalid helix.config.ts at ${t}: ${f}`)}let{projectId:n,workspaceId:o,...s}=r.data,a=o!==void 0?{workspaceId:o}:{},i=W(),c=i!==void 0?{...s,env:i}:s;return n!==void 0?{kind:"provisioned",projectId:n,...c,...a}:{kind:"unprovisioned",...c,...a}}async function w(e){let t=e??process.cwd(),r;try{r=await Ke(L(t))}catch(o){throw g.configLoadFailed(`Failed to load helix.config.ts: ${o instanceof Error?o.message:String(o)}`)}let n=r._configFile??x(t);if(!n)throw g.configLoadFailed(`helix.config.ts not found at ${_(t,"helix.config.ts")}`);return F(r.config,n)}var ue="WORKSPACE_ID_REQUIRED";function Je(e){if(e.workspaceId===void 0)throw Object.assign(new Error("No workspaceId found in helix.config.ts. Discover & choose one \u2014 self-serve fix: run `helix workspace select`; agent-driven fix: call the `tray_list_workspaces` MCP tool. Already know the id? Run `helix workspace set <id>` or call the `tray_set_workspace_id` MCP tool. deploy cannot proceed without a workspace."),{code:ue});return e.workspaceId}import{existsSync as Qe,watch as Xe,writeSync as Ye}from"fs";import{join as et,resolve as tt}from"path";import{watchConfig as rt}from"c12";function nt(e,t){let r=e instanceof Error?e.message:String(e);Ye(2,`[helix-watch] ${t.phase} error: ${r}
2
2
  `)}function j(){return{kind:"unprovisioned",name:"",authentications:{},config:{},env:"production"}}var ot=/^(?:helix|tray)\.config\.(?:m|c)?[jt]s$/,fe=new Set;function le(e){return x(e)!==void 0}async function st(e,t){if(le(e.cwd))try{return{current:await w(e.cwd),initialLoadSucceeded:!0}}catch(r){return t(r,{phase:"reload"}),{current:j(),initialLoadSucceeded:!1}}if(e.watch){let r=tt(e.cwd);fe.has(r)||(process.stderr.write(`warning: no helix.config.ts found at ${e.cwd}; tray running with default config \u2014 create one via \`helix init\`
3
3
  `),fe.add(r))}return{current:j(),initialLoadSucceeded:!1}}var D=class{constructor(t,r,n){this.cwd=t;this.onError=r;this.emit=n}cwd;onError;emit;stopped=!1;directoryWatcher=null;c12Unwatch=null;c12AttachPromise=null;updateChain=Promise.resolve();start(t){t?this.attachC12Watcher():this.attachCreationWatcher()}watcherReady(){return this.c12AttachPromise===null?Promise.resolve():this.c12AttachPromise.catch(()=>{})}async stop(){this.stopped=!0,this.directoryWatcher&&(this.directoryWatcher.close(),this.directoryWatcher=null),this.c12AttachPromise&&await this.c12AttachPromise.catch(t=>this.onError(t,{phase:"attach"})),this.c12Unwatch&&(await this.c12Unwatch(),this.c12Unwatch=null)}attachC12Watcher(){this.c12AttachPromise===null&&(this.c12AttachPromise=rt({...L(this.cwd),onUpdate:({newConfig:t})=>{this.stopped||(this.updateChain=this.updateChain.then(async()=>{if(this.stopped)return;let r;try{r=F(t.config,this.cwd)}catch(n){this.onError(n,{phase:"validation"});return}try{this.emit(r)}catch(n){this.onError(n,{phase:"reload"})}}))}}).then(async t=>{if(this.stopped){t.unwatch();return}this.c12Unwatch=t.unwatch;let{watch:r}=await import("chokidar"),n=r(this.cwd,{persistent:!1,depth:0});try{await new Promise((o,s)=>{n.once("ready",o),n.once("error",s)})}finally{await n.close()}}).catch(t=>{this.onError(t,{phase:"attach"})}))}attachCreationWatcher(){try{this.directoryWatcher=Xe(this.cwd,{persistent:!1},(t,r)=>{if(this.stopped||!r)return;let n=r.toString();ot.test(n)&&Qe(et(this.cwd,n))&&(this.updateChain=this.updateChain.then(async()=>{if(this.stopped)return;let o;try{o=await w(this.cwd)}catch(a){this.onError(a,{phase:"reload"});return}if(this.stopped)return;this.emit(o);let s=this.directoryWatcher;s&&(this.directoryWatcher=null,s.close()),this.attachC12Watcher()}))}),this.directoryWatcher.on("error",t=>{this.onError(t,{phase:"attach"})})}catch(t){this.onError(t,{phase:"attach"})}}};async function it(e){let t=e.onError??nt,r=new Set,n=await st(e,t),o=n.current;function s(i){o=i;for(let c of[...r])try{c(i)}catch(f){t(f,{phase:"reload"})}}let a=new D(e.cwd,t,s);return e.watch&&a.start(n.initialLoadSucceeded),{get(){return o},async reload(){if(!le(e.cwd)){let i=j();return s(i),i}try{let i=await w(e.cwd);return s(i),i}catch(i){return t(i,{phase:"reload"}),o}},subscribe(i){return r.add(i),()=>{r.delete(i)}},async stop(){r.clear(),await a.stop()},ready(){return a.watcherReady()}}}export{Be as ENVIRONMENT_VALUES,he as SCHEDULE_DEFINITION_KIND,ye as ScheduleConfigError,ue as WORKSPACE_ID_REQUIRED_CODE,it as createConfigService,$e as defineConfig,je as defineFunction,Ce as defineSchedule,S as environmentSchema,me as getContext,T as getSdkConfig,ve as isScheduleDefinition,w as loadConfig,Ve as loadedTrayConfigSchema,R as projectIdSchema,Je as requireWorkspaceId,x as resolveConfigFile,ge as runWithinAppSpan,P as workspaceIdSchema};
@@ -24,7 +24,6 @@ interface ScheduleInvocation {
24
24
  schedulePath: string;
25
25
  scheduledAt: string;
26
26
  firedAt: string;
27
- isRetry: boolean;
28
27
  isManual: boolean;
29
28
  }
30
29
  /**
@@ -1 +1 @@
1
- import{B as v,a as H,b as f,c as J,d as L,e as T,f as U,g as m,h,i as p,j as g,k as y,l as S,m as E,n as x,p as A,q as b,r as w,s as I,t as C,u as te,v as re,w as oe,x as D}from"../chunk-3SDUCOAT.js";import{a as G,b as V,c as _,d as q,e as B,f as z,g as Q,h as W,i as X,j as Y,k as Z,m as ee}from"../chunk-H4L4UWIK.js";import{randomUUID as ne}from"crypto";import{SpanStatusCode as R,SpanKind as se}from"@opentelemetry/api";var t=class extends Error{constructor(e){super(`runScheduledFunction: ${e}`),this.name="ScheduleInvocationError"}};async function ie(s,e){if(typeof e!="object"||e===null)throw new t("`invocation` must be an object");let{schedulePath:r}=e;if(typeof r!="string"||r.length===0)throw new t("`schedulePath` must be a non-empty string");if(typeof e.isRetry!="boolean"||typeof e.isManual!="boolean")throw new t("`isRetry` and `isManual` must be booleans");if(!v(s))throw new t(`default export of "${r}" is not a defineSchedule definition`);let P=F("scheduledAt",e.scheduledAt),k=F("firedAt",e.firedAt),d=f(),a=d.executionId??ne(),l=d.deploymentId??null,N=Date.now(),O=new Date().toISOString(),M=b(),j=A();y();let i={hasDb:!1,hasExternal:!1,hasAuth:!1,hasError:!1},c=!1,u=null;try{await h.run(i,()=>m.run(a,()=>E.startActiveSpan("schedule",{root:!0,kind:se.SERVER,attributes:{method:"SCHEDULE",route:r,executionId:a,...l?{deploymentId:l}:{}}},async o=>{let $={type:"schedule",scheduledAt:P,firedAt:k,isRetry:e.isRetry,isManual:e.isManual},K={http:w(),db:n=>I(n),kv:()=>C(),files:()=>D(),log:j,config:M,state:{},executionId:a,deploymentId:l,traceId:o.spanContext().traceId,trigger:$};g(o,"input",{scheduledAt:e.scheduledAt,firedAt:e.firedAt,isRetry:e.isRetry,isManual:e.isManual});try{await s.handler(K)}catch(n){throw c=!0,u=n instanceof Error?n.message:String(n),p("hasError"),n}finally{o.setAttribute("hasAuth",i.hasAuth),o.setAttribute("hasExternal",i.hasExternal),o.setAttribute("hasDb",i.hasDb),o.setAttribute("status",c?"error":"success");let n=c||i.hasError;o.setStatus({code:n?R.ERROR:R.OK,...u?{message:u}:{}}),o.end()}})))}finally{x({executionId:a,deploymentId:l,triggerType:"schedule",routeOrJob:r,startedAt:O,startMs:N,errored:c,errorMessage:u,userId:null,flags:i}),await S()}}function F(s,e){if(typeof e!="string"||e.length===0)throw new t(`\`${s}\` must be a timestamp string; got ${JSON.stringify(e)}`);let r=new Date(e);if(Number.isNaN(r.getTime()))throw new t(`\`${s}\` must be a Date-parseable timestamp; got ${JSON.stringify(e)}`);return r}export{J as CONFIG_BRIDGE_CHANNEL,t as ScheduleInvocationError,te as assertValidPath,U as clearActiveConfig,re as contentFilePath,T as getActiveConfig,Y as getAdditionalEnvironmentProfiles,_ as getEnvironmentProfile,V as isKnownEnvironment,ee as isSdkError,B as listEnvironmentNames,z as listExposedEnvNames,oe as metaFilePath,Q as parseExposedEnvironment,G as registerEnvironmentProfile,q as requireEnvironmentProfile,Z as resetEnvironmentRegistryForTests,X as resolveForcedEnvironment,ie as runScheduledFunction,L as setActiveConfig,W as setForcedEnvironment,H as setSdkConfig};
1
+ import{B as P,a as J,b as m,c as L,d as T,e as U,f as G,g as h,h as p,i as g,j as S,k as E,l as x,m as y,n as A,p as w,q as I,r as v,s as C,t as D,u as re,v as oe,w as ne,x as F}from"../chunk-3SDUCOAT.js";import{a as b}from"../chunk-WUCX7RZO.js";import{a as V,b as _,c as q,d as B,e as z,f as Q,g as W,h as X,i as Y,j as Z,k as ee,m as te}from"../chunk-H4L4UWIK.js";import{randomUUID as se}from"crypto";import{SpanStatusCode as k,SpanKind as ie}from"@opentelemetry/api";var t=class extends Error{constructor(e){super(`runScheduledFunction: ${e}`),this.name="ScheduleInvocationError"}};async function ae(s,e){if(typeof e!="object"||e===null)throw new t("`invocation` must be an object");let{schedulePath:r}=e;if(typeof r!="string"||r.length===0)throw new t("`schedulePath` must be a non-empty string");if(typeof e.isManual!="boolean")throw new t("`isManual` must be a boolean");if(!P(s))throw new t(`default export of "${r}" is not a defineSchedule definition`);let O=N("scheduledAt",e.scheduledAt),M=N("firedAt",e.firedAt),d=m(),a=d.executionId??se(),l=d.deploymentId??null,j=Date.now(),R=new Date().toISOString(),$=I(),K=w();E();let i={hasDb:!1,hasExternal:!1,hasAuth:!1,hasError:!1},c=!1,u=null;try{await p.run(i,()=>h.run(a,()=>y.startActiveSpan("schedule",{root:!0,kind:ie.SERVER,attributes:{method:"SCHEDULE",route:r,executionId:a,...l?{deploymentId:l}:{}}},async o=>{let H={type:"schedule",scheduledAt:O,firedAt:M,isManual:e.isManual},f={http:v(),db:n=>C(n),kv:()=>D(),files:()=>F(),log:K,config:$,state:{},executionId:a,deploymentId:l,traceId:o.spanContext().traceId,trigger:H};S(o,"input",{scheduledAt:e.scheduledAt,firedAt:e.firedAt,isManual:e.isManual});try{await b.run(f,()=>s.handler(f))}catch(n){throw c=!0,u=n instanceof Error?n.message:String(n),g("hasError"),n}finally{o.setAttribute("hasAuth",i.hasAuth),o.setAttribute("hasExternal",i.hasExternal),o.setAttribute("hasDb",i.hasDb),o.setAttribute("status",c?"error":"success");let n=c||i.hasError;o.setStatus({code:n?k.ERROR:k.OK,...u?{message:u}:{}}),o.end()}})))}finally{A({executionId:a,deploymentId:l,triggerType:"schedule",routeOrJob:r,startedAt:R,startMs:j,errored:c,errorMessage:u,userId:null,flags:i}),await x()}}function N(s,e){if(typeof e!="string"||e.length===0)throw new t(`\`${s}\` must be a timestamp string; got ${JSON.stringify(e)}`);let r=new Date(e);if(Number.isNaN(r.getTime()))throw new t(`\`${s}\` must be a Date-parseable timestamp; got ${JSON.stringify(e)}`);return r}export{L as CONFIG_BRIDGE_CHANNEL,t as ScheduleInvocationError,re as assertValidPath,G as clearActiveConfig,oe as contentFilePath,U as getActiveConfig,Z as getAdditionalEnvironmentProfiles,q as getEnvironmentProfile,_ as isKnownEnvironment,te as isSdkError,z as listEnvironmentNames,Q as listExposedEnvNames,ne as metaFilePath,W as parseExposedEnvironment,V as registerEnvironmentProfile,B as requireEnvironmentProfile,ee as resetEnvironmentRegistryForTests,Y as resolveForcedEnvironment,ae as runScheduledFunction,T as setActiveConfig,X as setForcedEnvironment,J as setSdkConfig};
@@ -1,13 +1,16 @@
1
- import { T as TrayContext } from '../types-D-yJbYWp.js';
1
+ import { P as PlatformContext } from '../types-D01XG3C4.js';
2
2
  import 'h3';
3
3
  import 'ky';
4
4
  import 'zod';
5
5
  import 'unstorage';
6
6
 
7
7
  /**
8
- * Run code within a mock TrayContext — for unit testing shared code
9
- * that calls getContext(). Unset fields throw on access via Proxy.
8
+ * Run code within a mock PlatformContext — for unit testing shared code that
9
+ * calls getContext(). Unset core fields throw on access via Proxy, as does
10
+ * anything that isn't a context field at all (typo protection); unset
11
+ * optional fields (request surface, trigger) return undefined, as in a real
12
+ * execution of a kind that doesn't carry them.
10
13
  */
11
- declare function runWithContext<T>(partial: Partial<TrayContext>, fn: () => T | Promise<T>): Promise<T>;
14
+ declare function runWithContext<T>(partial: Partial<PlatformContext>, fn: () => T | Promise<T>): Promise<T>;
12
15
 
13
16
  export { runWithContext };
@@ -1 +1 @@
1
- import{a as r}from"../chunk-L6J5L3JQ.js";async function x(o,i){let s=new Proxy(o,{get(e,t,n){if(typeof t=="symbol"||t==="then"||t==="toJSON"||t in e)return Reflect.get(e,t,n);throw new Error(`Accessed unset context field "${String(t)}" in test. Provide it via runWithContext({ ${String(t)}: ... }, fn).`)}});return r.run(s,i)}export{x as runWithContext};
1
+ import{a as n}from"../chunk-WUCX7RZO.js";var f={http:!0,db:!0,kv:!0,files:!0,log:!0,config:!0,state:!0,executionId:!0,deploymentId:!0,traceId:!0},d={input:!0,method:!0,path:!0,headers:!0,status:!0,error:!0,request:!0,userId:!0,identity:!0,trigger:!0};async function l(o,i){let u=new Proxy(o,{get(e,t,r){if(typeof t=="symbol"||t==="then"||t==="toJSON"||t in e)return Reflect.get(e,t,r);if(!(t in d))throw t in f?new Error(`Accessed unset context field "${String(t)}" in test. Provide it via runWithContext({ ${String(t)}: ... }, fn).`):new Error(`Accessed unknown context field "${String(t)}" in test \u2014 not a PlatformContext member.`)}});return n.run(u,i)}export{l as runWithContext};
@@ -123,23 +123,31 @@ interface BrokerAuthedClient {
123
123
  interface HttpClient extends KyInstance {
124
124
  authed(alias: string): BrokerAuthedClient;
125
125
  }
126
- interface TrayContext<T = Record<string, unknown>> {
127
- input: T;
128
- method: string;
129
- path: string;
130
- headers: Record<string, string | undefined>;
126
+ /**
127
+ * Members present in every execution kind (defineFunction, defineSchedule):
128
+ * platform clients, project config, and execution identity. Not readonly —
129
+ * define-function stamps ctx.traceId after the root span exists.
130
+ */
131
+ interface ContextCore {
131
132
  http: HttpClient;
132
133
  db(alias: string): DbClient;
133
134
  kv(): KeyValueStore;
134
135
  files(): FileSystemClient;
135
136
  log: Logger;
136
137
  config: Record<string, unknown>;
137
- status(code: number): void;
138
- error(code: number, message: string): never;
139
138
  state: Record<string, unknown>;
140
- request: H3Event;
141
139
  executionId: string;
142
140
  deploymentId: string | null;
141
+ traceId: string;
142
+ }
143
+ interface TrayContext<T = Record<string, unknown>> extends ContextCore {
144
+ input: T;
145
+ method: string;
146
+ path: string;
147
+ headers: Record<string, string | undefined>;
148
+ status(code: number): void;
149
+ error(code: number, message: string): never;
150
+ request: H3Event;
143
151
  userId: string | null;
144
152
  /**
145
153
  * Minimal user identity context (spec 15). Production currently carries
@@ -152,7 +160,6 @@ interface TrayContext<T = Record<string, unknown>> {
152
160
  id: string;
153
161
  };
154
162
  } | null;
155
- traceId: string;
156
163
  }
157
164
 
158
165
  /** Sentinel type — skip input validation when only output validation is desired */
@@ -169,30 +176,42 @@ interface ScheduleTrigger {
169
176
  scheduledAt: Date;
170
177
  /** When it actually executed (may differ due to cold start, retry, etc.). */
171
178
  firedAt: Date;
172
- /** True if this is a retry of a failed execution. */
173
- isRetry: boolean;
174
179
  /** True if triggered via `helix trigger` or the MCP tool. */
175
180
  isManual: boolean;
176
181
  }
177
182
  /**
178
- * Context for scheduled handlers: the full platform surface of TrayContext
179
- * with every request-derived field (input, method, path, headers, status,
180
- * error, request) structurally absent — scheduled runs have no request and
181
- * no user, so those fields are omitted rather than null.
183
+ * Context for scheduled handlers: the core platform surface with every
184
+ * request-derived field (input, method, path, headers, status, error,
185
+ * request) structurally absent — scheduled runs have no request and no user,
186
+ * so those fields are omitted rather than null.
182
187
  */
183
- interface ScheduleContext {
184
- http: HttpClient;
185
- db(alias: string): DbClient;
186
- kv(): KeyValueStore;
187
- files(): FileSystemClient;
188
- log: Logger;
189
- config: Record<string, unknown>;
190
- state: Record<string, unknown>;
191
- executionId: string;
192
- deploymentId: string | null;
193
- traceId: string;
188
+ interface ScheduleContext extends ContextCore {
194
189
  trigger: ScheduleTrigger;
195
190
  }
191
+ /**
192
+ * The canonical execution context — what contextStore holds and getContext()
193
+ * returns. Every execution kind populates the core; the optional fields are
194
+ * present per kind (request surface for functions, trigger for schedules).
195
+ * Handlers receive the exact subtypes (TrayContext, ScheduleContext), both
196
+ * structurally assignable to this canonical view; shared code checks field
197
+ * presence (e.g. `if (ctx.request)`, `if (ctx.trigger)`) before use.
198
+ */
199
+ interface PlatformContext extends ContextCore {
200
+ input?: Record<string, unknown>;
201
+ method?: string;
202
+ path?: string;
203
+ headers?: Record<string, string | undefined>;
204
+ status?(code: number): void;
205
+ error?(code: number, message: string): never;
206
+ request?: H3Event;
207
+ userId?: string | null;
208
+ identity?: {
209
+ user: {
210
+ id: string;
211
+ };
212
+ } | null;
213
+ trigger?: ScheduleTrigger;
214
+ }
196
215
  type ScheduleHandler = (ctx: ScheduleContext) => Promise<void> | void;
197
216
  interface DefineScheduleConfig {
198
217
  /** 5-field standard cron (minute hour day-of-month month day-of-week). */
@@ -220,4 +239,4 @@ interface ScheduleDefinition {
220
239
  handler: ScheduleHandler;
221
240
  }
222
241
 
223
- export type { DefineFunctionOptions as D, FunctionHandler as F, HttpClient as H, KeyValueStore as K, Logger as L, ScheduleDefinition as S, TrayContext as T, UnvalidatedInput as U, DefineScheduleConfig as a, DbCallParameters as b, DbClient as c, FileSystemClient as d, ScheduleContext as e, ScheduleHandler as f, ScheduleTrigger as g, SqlBuilder as h };
242
+ export type { DefineFunctionOptions as D, FunctionHandler as F, HttpClient as H, KeyValueStore as K, Logger as L, PlatformContext as P, ScheduleDefinition as S, TrayContext as T, UnvalidatedInput as U, DefineScheduleConfig as a, DbCallParameters as b, DbClient as c, FileSystemClient as d, ScheduleContext as e, ScheduleHandler as f, ScheduleTrigger as g, SqlBuilder as h };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trayai/helix-sdk",
3
- "version": "0.88.0",
3
+ "version": "0.90.0",
4
4
  "description": "Helix SDK runtime — defineFunction, ctx, defineConfig, authed HTTP",
5
5
  "license": "Apache-2.0",
6
6
  "author": "trayio",
@@ -1 +0,0 @@
1
- import{AsyncLocalStorage as e}from"async_hooks";var o=new e;function r(){let t=o.getStore();if(!t)throw new Error("getContext() called outside of a defineFunction handler. Ensure this code runs inside a function wrapped with defineFunction().");return t}export{o as a,r as b};