@@ -1448,8 +1448,8 @@ config :ash_typescript,
14481448 validate_endpoint: "/rpc/validate",
14491449
14501450 # Dynamic endpoints (for separate frontend projects)
1451- # run_endpoint: {:imported_ts_func , "CustomTypes.getRunEndpoint"},
1452- # validate_endpoint: {:imported_ts_func , "CustomTypes.getValidateEndpoint "},
1451+ # run_endpoint: {:runtime_expr , "CustomTypes.getRunEndpoint() "},
1452+ # validate_endpoint: {:runtime_expr , "process.env.RPC_VALIDATE_ENDPOINT "},
14531453
14541454 # Custom error response handling
14551455 # rpc_error_response_handler: "MyAppConfig.handleRpcResponseError",
@@ -1542,7 +1542,7 @@ Customize how field names are formatted in generated TypeScript:
15421542
15431543### Dynamic RPC Endpoints
15441544
1545- For separate frontend projects or different deployment environments, AshTypescript supports dynamic endpoint configuration through imported TypeScript functions .
1545+ For separate frontend projects or different deployment environments, AshTypescript supports dynamic endpoint configuration through runtime TypeScript expressions .
15461546
15471547#### Why Use Dynamic Endpoints?
15481548
@@ -1551,29 +1551,74 @@ When building a separate frontend project (not embedded in your Phoenix app), yo
15511551- **Staging**: `https://staging-api.myapp.com/rpc/run`
15521552- **Production**: `https://api.myapp.com/rpc/run`
15531553
1554- Instead of hardcoding the endpoint in your Elixir config, you can reference a TypeScript function that returns the appropriate endpoint based on your frontend ' s environment .
1554+ Instead of hardcoding the endpoint in your Elixir config, you can use runtime expressions that will be evaluated at runtime in your TypeScript code .
15551555
1556- # ### Configuration
1556+ #### Configuration Options
15571557
1558- Configure endpoints to use imported TypeScript functions instead of string literals :
1558+ You can use various runtime expressions depending on your needs :
15591559
15601560```elixir
15611561# config/config.exs
15621562config :ash_typescript,
1563- # Use function references instead of string literals
1564- run_endpoint: {:imported_ts_func , " MyAppConfig.getRunEndpoint " },
1565- validate_endpoint: {:imported_ts_func , " MyAppConfig.getValidateEndpoint " },
1563+ # Option 1: Use environment variables directly (Node.js)
1564+ run_endpoint: {:runtime_expr , "process.env.RPC_RUN_ENDPOINT || ' /rpc/run ' "},
1565+ validate_endpoint: {:runtime_expr , "process.env.RPC_VALIDATE_ENDPOINT || ' /rpc/validate ' "},
15661566
1567- # Import the module containing your endpoint functions
1568- import_into_generated: [
1569- %{
1570- import_name: " MyAppConfig" ,
1571- file: " ./myAppConfig"
1572- }
1573- ]
1567+ # Option 2: Use Vite environment variables
1568+ # run_endpoint: {:runtime_expr, "import.meta.env.VITE_RPC_RUN_ENDPOINT || ' /rpc/run' "},
1569+ # validate_endpoint: {:runtime_expr, "import.meta.env.VITE_RPC_VALIDATE_ENDPOINT || ' /rpc/validate' "},
1570+
1571+ # Option 3: Use custom functions from imported modules
1572+ # run_endpoint: {:runtime_expr, "MyAppConfig.getRunEndpoint()"},
1573+ # validate_endpoint: {:runtime_expr, "MyAppConfig.getValidateEndpoint()"},
1574+
1575+ # Option 4: Use complex expressions with conditionals
1576+ # run_endpoint: {:runtime_expr, "window.location.hostname === ' localhost' ? ' http://localhost:4000/rpc/run' : ' /rpc/run' "},
1577+
1578+ # Import modules if needed for custom functions (Option 3)
1579+ # import_into_generated: [
1580+ # %{
1581+ # import_name: "MyAppConfig",
1582+ # file: "./myAppConfig"
1583+ # }
1584+ # ]
15741585```
15751586
1576- # ### TypeScript Implementation
1587+ #### Usage Examples
1588+
1589+ **Option 1: Environment Variables (Node.js/Next.js)**
1590+
1591+ ```bash
1592+ # .env.local
1593+ RPC_RUN_ENDPOINT=http://localhost:4000/rpc/run
1594+ RPC_VALIDATE_ENDPOINT=http://localhost:4000/rpc/validate
1595+
1596+ # .env.production
1597+ RPC_RUN_ENDPOINT=https://api.myapp.com/rpc/run
1598+ RPC_VALIDATE_ENDPOINT=https://api.myapp.com/rpc/validate
1599+ ```
1600+
1601+ Generated TypeScript will use the environment variables directly:
1602+ ```typescript
1603+ const response = await fetchFunction(process.env.RPC_RUN_ENDPOINT || ' /rpc/run' , fetchOptions);
1604+ ```
1605+
1606+ **Option 2: Vite Environment Variables**
1607+
1608+ ```bash
1609+ # .env.development
1610+ VITE_RPC_RUN_ENDPOINT=http://localhost:4000/rpc/run
1611+
1612+ # .env.production
1613+ VITE_RPC_RUN_ENDPOINT=https://api.myapp.com/rpc/run
1614+ ```
1615+
1616+ Generated TypeScript:
1617+ ```typescript
1618+ const response = await fetchFunction(import.meta.env.VITE_RPC_RUN_ENDPOINT || ' /rpc/run' , fetchOptions);
1619+ ```
1620+
1621+ **Option 3: Custom Functions**
15771622
15781623Create a TypeScript file with functions that return the appropriate endpoints:
15791624
@@ -1596,33 +1641,55 @@ export function getValidateEndpoint(): string {
15961641// Production: VITE_API_URL=https://api.myapp.com
15971642```
15981643
1644+ **Option 4: Complex Conditional Expressions**
1645+
1646+ For browser-based applications that need different endpoints based on hostname:
1647+
1648+ ```elixir
1649+ config :ash_typescript,
1650+ run_endpoint: {:runtime_expr, """
1651+ (window.location.hostname === ' localhost'
1652+ ? ' http://localhost:4000/rpc/run'
1653+ : `https://${window.location.hostname}/rpc/run`)
1654+ """}
1655+ ```
1656+
1657+ This allows dynamic endpoint resolution based on the current page' s hostname.
1658+
15991659# ### Generated Code
16001660
1601- The generated RPC functions will call your endpoint functions instead of using hardcoded strings :
1661+ The generated RPC functions will use your runtime expressions directly in the code :
16021662
16031663` ` ` typescript
1604- // Generated in ash_rpc.ts
1605- import * as CustomTypes from " ./customTypes " ;
1664+ // Example 1: With environment variables
1665+ // config: run_endpoint: {:runtime_expr, " process.env.RPC_RUN_ENDPOINT || '/rpc/run' " }
16061666
16071667export async function createTodo<Fields extends CreateTodoFields>(
16081668 config: CreateTodoConfig< Fields>
16091669) : Promise< CreateTodoResult< Fields>> {
1610- const headers: Record< string, string> = {
1611- " Content-Type" : " application/json" ,
1612- ...config.headers,
1613- };
1670+ // Runtime expression is embedded directly
1671+ const response = await fetchFunction(
1672+ process.env.RPC_RUN_ENDPOINT || ' /rpc/run' ,
1673+ fetchOptions
1674+ );
1675+ // ... rest of implementation
1676+ }
1677+ ` ` `
16141678
1615- const fetchFunction = config.customFetch || fetch;
1616- const fetchOptions: RequestInit = {
1617- ...config.fetchOptions,
1618- method: " POST" ,
1619- headers,
1620- body: JSON.stringify(payload),
1621- };
1679+ ` ` ` typescript
1680+ // Example 2: With custom function
1681+ // config: run_endpoint: {:runtime_expr, " MyAppConfig.getRunEndpoint()" }
16221682
1623- // Calls your function instead of using a hardcoded string
1624- const response = await fetchFunction(CustomTypes.MyAppConfig (), fetchOptions);
1683+ import * as MyAppConfig from " ./myAppConfig" ;
16251684
1685+ export async function createTodo<Fields extends CreateTodoFields>(
1686+ config: CreateTodoConfig< Fields>
1687+ ) : Promise< CreateTodoResult< Fields>> {
1688+ // Custom function is called at runtime
1689+ const response = await fetchFunction(
1690+ MyAppConfig.getRunEndpoint (),
1691+ fetchOptions
1692+ );
16261693 // ... rest of implementation
16271694}
16281695` ` `
0 commit comments