@trayai/helix-sdk 0.96.0 → 0.97.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,18 @@ 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.97.0
13
+
14
+ ### Added
15
+
16
+ - **Config-file override for multi-target projects.** `loadConfig`,
17
+ `createConfigService`, and `resolveConfigFile` accept an optional `{ configFile }`
18
+ naming a config file other than `helix.config.ts`, falling back to the
19
+ `HELIX_CONFIG` environment variable when the option is omitted (explicit option →
20
+ env var → default). New exports: `resolveConfigOverride`, `HELIX_CONFIG_ENV`, and
21
+ the `ConfigFileOptions` type. Pairs with the CLI's new `--config <path>` flag for
22
+ one-config-file-per-target workflows.
23
+
12
24
  ## 0.96.0
13
25
 
14
26
  ### Added
package/README.md CHANGED
@@ -244,6 +244,15 @@ await service.stop();
244
244
 
245
245
  Use the CLI (`helix project set`, `helix workspace set`, `helix auth add`) or the Helix MCP tools to write config fields — you rarely edit these by hand.
246
246
 
247
+ ### Multiple config files
248
+
249
+ A config file describes exactly one target. Projects with several targets keep one config file per target (`helix.config.ts` for the one you develop against, `helix.<target>.config.ts` for the rest) and select one with the CLI's `--config` flag. `loadConfig`, `createConfigService`, and `resolveConfigFile` accept the same override as an option, and fall back to the `HELIX_CONFIG` environment variable when the option is omitted:
250
+
251
+ ```typescript
252
+ // explicit option → HELIX_CONFIG env var → ./helix.config.ts
253
+ const config = await loadConfig(process.cwd(), { configFile: 'helix.production.config.ts' });
254
+ ```
255
+
247
256
  ## Logging
248
257
 
249
258
  Use `ctx.log.info/warn/error/debug(message, properties?)` for structured logging from your handlers. `helix dev` prints human-readable logs by default.
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import * as h3 from 'h3';
2
2
  import { ZodType } from 'zod';
3
3
  import { F as FunctionHandler, D as DefineFunctionOptions, a as DefineScheduleConfig, S as ScheduleDefinition, P as PlatformContext } from './types-DCCVrmZ3.js';
4
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-DCCVrmZ3.js';
5
- import { T as TrayConfig, L as LoadedTrayConfig, W as WatchErrorPhase } from './sdk-config-_xIe9XvP.js';
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';
5
+ import { T as TrayConfig, L as LoadedTrayConfig, W as WatchErrorPhase } from './sdk-config-vY_-Reh3.js';
6
+ export { C as ConfigFileOptions, H as HELIX_CONFIG_ENV, 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, e as resolveConfigOverride, w as workspaceIdSchema } from './sdk-config-vY_-Reh3.js';
7
7
  export { a as ENVIRONMENT_VALUES, E as Environment, e as environmentSchema } from './env-schema-DkgTG5b0.js';
8
8
  import 'ky';
9
9
 
@@ -85,6 +85,13 @@ declare function defineConfig(config: TrayConfig): TrayConfig;
85
85
  interface CreateConfigServiceOptions {
86
86
  /** Directory containing `helix.config.ts`. */
87
87
  cwd: string;
88
+ /**
89
+ * Explicit config file path (the CLI's `--config` flag), overriding the
90
+ * `helix.config.ts` default. Falls back to the `HELIX_CONFIG` env var; the
91
+ * effective override is pinned once at service creation, so later env-var
92
+ * changes never re-target a live service.
93
+ */
94
+ configFile?: string;
88
95
  /** If true, attach a c12 watcher and emit subscribers on each change. */
89
96
  watch?: boolean;
90
97
  /**
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
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-6OZ47QHO.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-Q6GJ4HQZ.js";import{randomUUID as be}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_ttl_invalid":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 _e(e){return e.toLowerCase().includes("tray_lid")}function _(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!_e(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=_(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"))??be(),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:_(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 b}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=b(e,`${t}.config.${r}`);if(ae(n))return n}}function Ge(e){for(let t of de)if(ce.some(r=>ae(b(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 ${b(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
- `)}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
- `),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};
1
+ import{A as ke,B as Se,b as R,g as M,h as F,i as Z,j as E,k as K,l as B,m as V,n as z,o as Ce,p as J,q as Q,r as Y,s as ee,t as te,x as re,y as Ee,z as we}from"./chunk-6OZ47QHO.js";import{a as G,b as ve}from"./chunk-WUCX7RZO.js";import{b as U,e as $,i as q,l as g,m as X}from"./chunk-Q6GJ4HQZ.js";import{randomUUID as Le}from"crypto";import{SpanStatusCode as ae,SpanKind as Oe}from"@opentelemetry/api";import{defineEventHandler as je,getMethod as De,getRequestURL as He,getHeaders as ce,getHeader as de}from"h3";import{readBody as xe,getQuery as Ie,getRouterParams as _e}from"h3";async function ne(e,t){let r=await be(e),n=Ie(e),o=_e(e)??{},i={...n,...r,...o};return t?Te(i,t):i}async function be(e){try{let t=await xe(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 Te(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 w=class extends Error{statusCode;constructor(t,r){super(r),this.statusCode=t,this.name="HttpError"}};function oe(e){return t=>{e.node.res.statusCode=t}}function ie(){return(e,t)=>{throw new w(e,t)}}function se(e,t){let{statusCode:r,message:n}=Re(t.detail);return e.node.res.statusCode=r,{error:n,statusCode:r}}function Re(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_ttl_invalid":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 Fe=["authorization","x-tray-token","set-cookie","x-kong-request-id","traceparent","x-real-ip","cookie"];function Pe(e){return e.toLowerCase().includes("tray_lid")}function P(e){let t={};for(let[r,n]of Object.entries(e))if(!Fe.includes(r.toLowerCase())){if(r.toLowerCase()==="cookie"&&n){let o=n.split(";").map(i=>i.trim()).filter(i=>{let d=i.split("=")[0].trim();return!Pe(d)}).join("; ");o&&(t[r]=o);continue}t[r]=n}return t}function Ae(e,t){let{inputSchema:r,outputSchema:n}=Me(t),o=e;return je(async i=>{let d=R(),{executionId:s,userId:a,deploymentId:f}=Ze(d,i),p=Date.now(),v=new Date().toISOString(),h=De(i),l=He(i).pathname,u=Q(),y=J(),S=`${h} ${l}`;K();let N={hasDb:!1,hasExternal:!1,hasAuth:!1,hasError:!1};return We(()=>F.run(N,()=>M.run(s,async()=>{let x=await Ne(i,r);if(!x.ok)return{response:x.response,errored:!1,errorMessage:null};let ye=Be(i,x.input,u,y,h,l,s,f,a);return Ue(o,ye,n,i,y,s,f,{method:h,path:l,rawInput:x.input})})),{executionId:s,deploymentId:f,routeOrJob:S,startedAt:v,startMs:p,userId:a,flags:N})})}async function We(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 B()}}async function Ne(e,t){try{return{ok:!0,input:await ne(e,t)}}catch(r){let n=Ke(e,r);if(n)return{ok:!1,response:n};throw r}}async function Ue(e,t,r,n,o,i,d,s){return V.startActiveSpan("function",{root:!0,kind:Oe.SERVER,attributes:{method:s.method,route:s.path,executionId:i,...d?{deploymentId:d}:{}}},async a=>{t.traceId=a.spanContext().traceId;let f=P(ce(n));E(a,"input",s.rawInput),E(a,"request",{method:s.method,path:s.path,headers:f,body:s.rawInput});let p=!1,v=null,h,l;try{let u=await G.run(t,()=>e(t));if(h=u,u==null)n.node.res.statusCode=204,l="";else if(u instanceof Response)l=u;else if(r){let y=r.safeParse(u);if(!y.success){let S=Object.keys(y.error.flatten().fieldErrors);throw o.error("Output validation failed",{fields:S,executionId:i}),g.outputValidationFailed(S)}l=y.data}else l=u}catch(u){p=!0,v=u instanceof Error?u.message:String(u),Z("hasError"),l=$e(u,n,o,i)}finally{qe(a,n,{errored:p,errorMessage:v,handlerResult:h,response:l})}return{response:l,errored:p,errorMessage:v}})}function $e(e,t,r,n){if(e instanceof w)return t.node.res.statusCode=e.statusCode,{error:e.message,statusCode:e.statusCode};if(X(e))return se(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 qe(e,t,r){e.setAttribute("statusCode",t.node.res.statusCode),!r.errored&&r.handlerResult!==void 0&&E(e,"output",r.handlerResult),E(e,"response",r.response);let n=F.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?ae.ERROR:ae.OK,...r.errorMessage?{message:r.errorMessage}:{}}),e.end()}function Me(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 Ze(e,t){let r=e.mode==="lambda",n=(r?e.executionId:de(t,"x-tray-execution-id"))??Le(),o=(r?e.userId:de(t,"x-tray-user-id"))??null,i=r?e.deploymentId??null:null;return{executionId:n,userId:o,deploymentId:i}}function Ke(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 Be(e,t,r,n,o,i,d,s,a){return{input:t,method:o,path:i,headers:P(ce(e)),http:Y(),db:f=>ee(f),kv:()=>te(),files:()=>re(),log:n,config:r,status:oe(e),error:ie(),state:{},request:e,executionId:d,deploymentId:s,userId:a,identity:a!==null?{user:{id:a}}:null,traceId:""}}function Ve(e){return e}import{resolve as m}from"path";import{existsSync as _,realpathSync as ue}from"fs";import{loadConfig as Je}from"c12";import{z as c}from"zod";import{z as ze}from"zod";var Ge=["production"],I=ze.string().refine(e=>U(e),e=>({message:`Invalid enum value. Expected ${$().map(t=>`'${t}'`).join(" | ")}, received '${e}'`}));var L=c.string().uuid("projectId must be a valid UUID"),O=c.string().min(1,"workspaceId must be a non-empty string"),Qe=c.object({name:c.string().min(1,"name is required"),projectId:L.optional(),workspaceId:O.optional(),env:I.default("production"),authentications:c.record(c.string()).optional().default({}),config:c.record(c.unknown()).optional().default({})}),fe=c.object({name:c.string().min(1,"name is required"),workspaceId:O.optional(),env:I,authentications:c.record(c.string()),config:c.record(c.unknown())}),Xe=c.discriminatedUnion("kind",[fe.extend({kind:c.literal("provisioned"),projectId:L}),fe.extend({kind:c.literal("unprovisioned")})]),le=["helix","tray"],pe=["ts","mts","cts","js","mjs","cjs"],j="HELIX_CONFIG";function C(e){let t=e?.trim();return t||process.env[j]?.trim()||void 0}function b(e,t){let r=C(t?.configFile);if(r!==void 0){let n=m(e,r);return _(n)?n:void 0}for(let n of le)for(let o of pe){let i=m(e,`${n}.config.${o}`);if(_(i))return i}}function Ye(e){for(let t of le)if(pe.some(r=>_(m(e,`${t}.config.${r}`))))return t;return"helix"}function et(e,t){return m(e,t).replace(/\.(?:m|c)?[jt]s$/,"")}function D(e,t){let r=C(t?.configFile);return{name:Ye(e),cwd:e,...r!==void 0?{configFile:et(e,r)}:{},rcFile:!1,globalRc:!1,packageJson:!1,dotenv:!1,extend:!1}}function H(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=Qe.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,...i}=r.data,d=o!==void 0?{workspaceId:o}:{},s=q(),a=s!==void 0?{...i,env:s}:i;return n!==void 0?{kind:"provisioned",projectId:n,...a,...d}:{kind:"unprovisioned",...a,...d}}async function k(e,t){let r=e??process.cwd(),n=C(t?.configFile),o=n!==void 0?m(r,n):void 0;if(o!==void 0&&!_(o)){let s=t?.configFile?.trim()?"":` (from ${j})`;throw g.configLoadFailed(`Config file not found: ${o}${s}`)}let i;try{i=await Je(D(r,t))}catch(s){throw g.configLoadFailed(`Failed to load helix.config.ts: ${s instanceof Error?s.message:String(s)}`)}let d=i._configFile??b(r,t);if(!d)throw g.configLoadFailed(`helix.config.ts not found at ${m(r,"helix.config.ts")}`);if(o!==void 0&&i._configFile!==void 0){let s=ue(o);if(ue(m(i._configFile))!==s)throw g.configLoadFailed(`Config file conflict: requested ${o} but ${m(i._configFile)} takes extension-resolution precedence \u2014 remove or rename the sibling file`)}return H(i.config,d)}var ge="WORKSPACE_ID_REQUIRED";function tt(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:ge});return e.workspaceId}import{existsSync as rt,watch as nt,writeSync as ot}from"fs";import{basename as it,dirname as st,join as at,resolve as T}from"path";import{watchConfig as dt}from"c12";function ct(e,t){let r=e instanceof Error?e.message:String(e);ot(2,`[helix-watch] ${t.phase} error: ${r}
2
+ `)}function A(){return{kind:"unprovisioned",name:"",authentications:{},config:{},env:"production"}}var ut=/^(?:helix|tray)\.config\.(?:m|c)?[jt]s$/,me=new Set;function he(e,t){return b(e,{configFile:t})!==void 0}async function ft(e,t,r){if(he(e.cwd,t))try{return{current:await k(e.cwd,{configFile:t}),initialLoadSucceeded:!0}}catch(n){return r(n,{phase:"reload"}),{current:A(),initialLoadSucceeded:!1}}if(e.watch){let n=t!==void 0?T(e.cwd,t):T(e.cwd);me.has(n)||(process.stderr.write(t!==void 0?`warning: config file ${T(e.cwd,t)} not found; tray running with default config
3
+ `:`warning: no helix.config.ts found at ${e.cwd}; tray running with default config \u2014 create one via \`helix init\`
4
+ `),me.add(n))}return{current:A(),initialLoadSucceeded:!1}}var W=class{constructor(t,r,n,o){this.cwd=t;this.configFile=r;this.onError=n;this.emit=o}cwd;configFile;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=dt({...D(this.cwd,{configFile:this.configFile}),onUpdate:({newConfig:t})=>{this.stopped||(this.updateChain=this.updateChain.then(async()=>{if(this.stopped)return;let r;try{r=H(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,i)=>{n.once("ready",o),n.once("error",i)})}finally{await n.close()}}).catch(t=>{this.onError(t,{phase:"attach"})}))}attachCreationWatcher(){let t=this.configFile!==void 0?T(this.cwd,this.configFile):void 0,r=t!==void 0?st(t):this.cwd;try{this.directoryWatcher=nt(r,{persistent:!1},(n,o)=>{if(this.stopped||!o)return;let i=o.toString();if(t!==void 0){if(i!==it(t))return}else if(!ut.test(i))return;rt(at(r,i))&&(this.updateChain=this.updateChain.then(async()=>{if(this.stopped)return;let d;try{d=await k(this.cwd,{configFile:this.configFile})}catch(a){this.onError(a,{phase:"reload"});return}if(this.stopped)return;this.emit(d);let s=this.directoryWatcher;s&&(this.directoryWatcher=null,s.close()),this.attachC12Watcher()}))}),this.directoryWatcher.on("error",n=>{this.onError(n,{phase:"attach"})})}catch(n){this.onError(n,{phase:"attach"})}}};async function lt(e){let t=e.onError??ct,r=new Set,n=C(e.configFile),o=await ft(e,n,t),i=o.current;function d(a){i=a;for(let f of[...r])try{f(a)}catch(p){t(p,{phase:"reload"})}}let s=new W(e.cwd,n,t,d);return e.watch&&s.start(o.initialLoadSucceeded),{get(){return i},async reload(){if(!he(e.cwd,n)){let a=A();return d(a),a}try{let a=await k(e.cwd,{configFile:n});return d(a),a}catch(a){return t(a,{phase:"reload"}),i}},subscribe(a){return r.add(a),()=>{r.delete(a)}},async stop(){r.clear(),await s.stop()},ready(){return s.watcherReady()}}}export{Ge as ENVIRONMENT_VALUES,j as HELIX_CONFIG_ENV,Ee as SCHEDULE_DEFINITION_KIND,we as ScheduleConfigError,ge as WORKSPACE_ID_REQUIRED_CODE,lt as createConfigService,Ve as defineConfig,Ae as defineFunction,ke as defineSchedule,I as environmentSchema,ve as getContext,R as getSdkConfig,Se as isScheduleDefinition,k as loadConfig,Xe as loadedTrayConfigSchema,L as projectIdSchema,tt as requireWorkspaceId,b as resolveConfigFile,C as resolveConfigOverride,Ce as runWithinAppSpan,O as workspaceIdSchema};
@@ -1,5 +1,5 @@
1
- import { L as LoadedTrayConfig } from '../sdk-config-_xIe9XvP.js';
2
- export { e as SdkConfigInput, s as setSdkConfig } from '../sdk-config-_xIe9XvP.js';
1
+ import { L as LoadedTrayConfig } from '../sdk-config-vY_-Reh3.js';
2
+ export { f as SdkConfigInput, s as setSdkConfig } from '../sdk-config-vY_-Reh3.js';
3
3
  import 'zod';
4
4
 
5
5
  /** BroadcastChannel name for parent→worker config updates. */
@@ -101,16 +101,42 @@ type ProvisionedTrayConfig = Extract<LoadedTrayConfig, {
101
101
  type UnprovisionedTrayConfig = Extract<LoadedTrayConfig, {
102
102
  kind: "unprovisioned";
103
103
  }>;
104
+ /**
105
+ * Env var naming an alternate config file (relative paths resolve against cwd).
106
+ * Sits between the explicit `configFile` option (the CLI's `--config` flag) and
107
+ * the `helix.config.ts` default in resolution precedence.
108
+ */
109
+ declare const HELIX_CONFIG_ENV = "HELIX_CONFIG";
110
+ /** Options accepted by every config entry point that resolves the config file. */
111
+ interface ConfigFileOptions {
112
+ /**
113
+ * Explicit config file path (the CLI's `--config` flag). Resolution
114
+ * precedence: this option → `HELIX_CONFIG` env var → `helix.config.ts`
115
+ * (then legacy `tray.config.*`) in cwd.
116
+ */
117
+ configFile?: string;
118
+ }
119
+ /**
120
+ * The effective config-file override, if any: the explicit `configFile` option
121
+ * (CLI `--config` flag) wins over the `HELIX_CONFIG` env var. Returns the raw
122
+ * (possibly relative) path — callers resolve it against their cwd.
123
+ */
124
+ declare function resolveConfigOverride(configFile?: string): string | undefined;
104
125
  /**
105
126
  * Absolute path of the project config file in `cwd`, preferring `helix.config.*`
106
127
  * and falling back to a legacy `tray.config.*`. Returns undefined when neither exists.
128
+ * A config-file override (option or HELIX_CONFIG) replaces the default search
129
+ * entirely: only the named file is considered.
107
130
  */
108
- declare function resolveConfigFile(cwd: string): string | undefined;
131
+ declare function resolveConfigFile(cwd: string, options?: ConfigFileOptions): string | undefined;
109
132
  /**
110
- * One-shot stateless read of `helix.config.ts`. NO caching — every call hits disk.
111
- * For hot-reload or stable snapshot, use {@link createConfigService} instead.
133
+ * One-shot stateless read of the project config. NO caching — every call hits disk.
134
+ * Reads `helix.config.ts` (legacy `tray.config.*` fallback) unless an override
135
+ * names another file — `options.configFile` (CLI `--config` flag) first, then
136
+ * the `HELIX_CONFIG` env var. For hot-reload or stable snapshot, use
137
+ * {@link createConfigService} instead.
112
138
  */
113
- declare function loadConfig(cwd?: string): Promise<LoadedTrayConfig>;
139
+ declare function loadConfig(cwd?: string, options?: ConfigFileOptions): Promise<LoadedTrayConfig>;
114
140
  /** Phase that surfaced an error inside ConfigService watcher. */
115
141
  type WatchErrorPhase = "attach" | "reload" | "validation";
116
142
  /** Stable code on errors thrown by {@link requireWorkspaceId}. Narrow by this, not instanceof. */
@@ -181,4 +207,4 @@ interface SdkConfigInput extends Partial<SdkRuntimeConfig>, BrokerSecrets {
181
207
  declare function setSdkConfig(options: SdkConfigInput): void;
182
208
  declare function getSdkConfig(): Readonly<SdkRuntimeConfig>;
183
209
 
184
- export { type LoadedTrayConfig as L, type ProvisionedTrayConfig as P, type SdkRuntimeConfig as S, type TrayConfig as T, type UnprovisionedTrayConfig as U, type WatchErrorPhase as W, WORKSPACE_ID_REQUIRED_CODE as a, type WorkspaceIdRequiredError as b, loadedTrayConfigSchema as c, resolveConfigFile as d, type SdkConfigInput as e, getSdkConfig as g, loadConfig as l, projectIdSchema as p, requireWorkspaceId as r, setSdkConfig as s, workspaceIdSchema as w };
210
+ export { type ConfigFileOptions as C, HELIX_CONFIG_ENV as H, type LoadedTrayConfig as L, type ProvisionedTrayConfig as P, type SdkRuntimeConfig as S, type TrayConfig as T, type UnprovisionedTrayConfig as U, type WatchErrorPhase as W, WORKSPACE_ID_REQUIRED_CODE as a, type WorkspaceIdRequiredError as b, loadedTrayConfigSchema as c, resolveConfigFile as d, resolveConfigOverride as e, type SdkConfigInput as f, getSdkConfig as g, loadConfig as l, projectIdSchema as p, requireWorkspaceId as r, setSdkConfig as s, workspaceIdSchema as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trayai/helix-sdk",
3
- "version": "0.96.0",
3
+ "version": "0.97.0",
4
4
  "description": "Helix SDK runtime — defineFunction, ctx, defineConfig, authed HTTP",
5
5
  "license": "Apache-2.0",
6
6
  "author": "trayio",