Skip to content

Commit 7f17ca6

Browse files
committed
refactor(rpc): rename :imported_ts_func to :runtime_expr for endpoint configuration
1 parent 01abd76 commit 7f17ca6

File tree

3 files changed

+117
-50
lines changed

3 files changed

+117
-50
lines changed

README.md

Lines changed: 100 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -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
15621562
config :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
15781623
Create 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
16071667
export 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
```

lib/ash_typescript/rpc/codegen.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,23 @@ defmodule AshTypescript.Rpc.Codegen do
1919
2020
Accepts either:
2121
- A string: Returns the string as a quoted literal for direct embedding
22-
- A tuple {:imported_ts_func, "FunctionName"}: Returns a function call expression
22+
- A tuple {:runtime_expr, "expression"}: Returns the expression as-is for runtime evaluation
2323
2424
## Examples
2525
2626
iex> format_endpoint_for_typescript("/rpc/run")
2727
"\"/rpc/run\""
2828
29-
iex> format_endpoint_for_typescript({:imported_ts_func, "CustomTypes.getRunEndpoint"})
29+
iex> format_endpoint_for_typescript({:runtime_expr, "CustomTypes.getRunEndpoint()"})
3030
"CustomTypes.getRunEndpoint()"
3131
"""
3232
def format_endpoint_for_typescript(endpoint) when is_binary(endpoint) do
3333
"\"#{endpoint}\""
3434
end
3535

36-
def format_endpoint_for_typescript({:imported_ts_func, function_name})
37-
when is_binary(function_name) do
38-
"#{function_name}()"
36+
def format_endpoint_for_typescript({:runtime_expr, expression})
37+
when is_binary(expression) do
38+
expression
3939
end
4040

4141
@doc """

test/ash_typescript/rpc/endpoint_configuration_test.exs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@ defmodule AshTypescript.Rpc.EndpointConfigurationTest do
1616
"\"http://localhost:4000/api/rpc\""
1717
end
1818

19-
test "formats function reference as function call" do
20-
assert Codegen.format_endpoint_for_typescript({:imported_ts_func, "getRunEndpoint"}) ==
19+
test "formats runtime expression as-is" do
20+
assert Codegen.format_endpoint_for_typescript({:runtime_expr, "getRunEndpoint()"}) ==
2121
"getRunEndpoint()"
2222
end
2323

24-
test "formats namespaced function reference as function call" do
24+
test "formats namespaced runtime expression as-is" do
2525
assert Codegen.format_endpoint_for_typescript(
26-
{:imported_ts_func, "CustomTypes.getRunEndpoint"}
26+
{:runtime_expr, "CustomTypes.getRunEndpoint()"}
2727
) == "CustomTypes.getRunEndpoint()"
2828
end
2929

30-
test "formats deeply namespaced function reference" do
30+
test "formats deeply namespaced runtime expression as-is" do
3131
assert Codegen.format_endpoint_for_typescript(
32-
{:imported_ts_func, "Config.Endpoints.getRunEndpoint"}
32+
{:runtime_expr, "Config.Endpoints.getRunEndpoint()"}
3333
) == "Config.Endpoints.getRunEndpoint()"
3434
end
3535
end
@@ -47,14 +47,14 @@ defmodule AshTypescript.Rpc.EndpointConfigurationTest do
4747
assert String.contains?(generated, ~s[fetchFunction("/rpc/validate"])
4848
end
4949

50-
test "generates correct TypeScript with function reference endpoints" do
50+
test "generates correct TypeScript with runtime expression endpoints" do
5151
{:ok, generated} =
5252
Codegen.generate_typescript_types(:ash_typescript,
53-
run_endpoint: {:imported_ts_func, "CustomTypes.getRunEndpoint"},
54-
validate_endpoint: {:imported_ts_func, "CustomTypes.getValidateEndpoint"}
53+
run_endpoint: {:runtime_expr, "CustomTypes.getRunEndpoint()"},
54+
validate_endpoint: {:runtime_expr, "CustomTypes.getValidateEndpoint()"}
5555
)
5656

57-
# Should contain the function call (without quotes) in fetch call
57+
# Should contain the runtime expression (without quotes) in fetch call
5858
assert String.contains?(generated, "fetchFunction(CustomTypes.getRunEndpoint()")
5959
assert String.contains?(generated, "fetchFunction(CustomTypes.getValidateEndpoint()")
6060

@@ -65,11 +65,11 @@ defmodule AshTypescript.Rpc.EndpointConfigurationTest do
6565
test "generates correct TypeScript with mixed endpoint types" do
6666
{:ok, generated} =
6767
Codegen.generate_typescript_types(:ash_typescript,
68-
run_endpoint: {:imported_ts_func, "CustomTypes.getRunEndpoint"},
68+
run_endpoint: {:runtime_expr, "CustomTypes.getRunEndpoint()"},
6969
validate_endpoint: "/rpc/validate"
7070
)
7171

72-
# run_endpoint should be a function call
72+
# run_endpoint should be a runtime expression
7373
assert String.contains?(generated, "fetchFunction(CustomTypes.getRunEndpoint()")
7474

7575
# validate_endpoint should be a string literal

0 commit comments

Comments
 (0)