Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
#
# SPDX-License-Identifier: MIT

spark_locals_without_parens = [expose?: 1, name: 1]
spark_locals_without_parens = [
action: 3,
action: 4,
argument_names: 1,
expose?: 1,
field_names: 1,
name: 1,
namespace: 1,
namespace: 2
]

[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
Expand Down
2 changes: 1 addition & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ import Config
config :ash, policies: [show_policy_breakdowns?: true]

config :ash_lua,
ash_domains: [AshLua.Test.Posts]
ash_domains: [AshLua.Test.Posts, AshLua.Test.Surface]
84 changes: 84 additions & 0 deletions documentation/dsls/DSL-AshLua.Domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Extension that exposes an Ash domain's resources to Lua scripts evaluated throug
Domain-level configuration for AshLua.


### Nested DSLs
* [namespace](#lua-namespace)
* action


### Examples
Expand All @@ -31,6 +34,87 @@ end



### lua.namespace
```elixir
namespace name
```


Defines a public Lua namespace for actions.

### Nested DSLs
* [action](#lua-namespace-action)


### Examples
```
namespace "pages" do
action :list, MyApp.StorefrontPage, :list_for_storefront
end

```

```
namespace "storefronts.pages" do
action :list, MyApp.StorefrontPage, :list_for_storefront
end

```



### Arguments

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`name`](#lua-namespace-name){: #lua-namespace-name .spark-required} | `String.t \| list(String.t)` | | The public Lua namespace. Dotted strings are split into nested Lua tables, so "storefronts.pages" exposes `storefronts.pages.*`. |



### lua.namespace.action
```elixir
action name, resource, action
```


Expose an Ash action at a public Lua function name inside a namespace.



### Examples
```
namespace "pages" do
action :list, MyApp.StorefrontPage, :list_for_storefront
end

```



### Arguments

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`name`](#lua-namespace-action-name){: #lua-namespace-action-name .spark-required} | `atom` | | The Lua function name inside the namespace. |
| [`resource`](#lua-namespace-action-resource){: #lua-namespace-action-resource .spark-required} | `module` | | The Ash resource that owns the action. |
| [`action`](#lua-namespace-action-action){: #lua-namespace-action-action .spark-required} | `atom` | | The internal Ash action to call. |






### Introspection

Target: `AshLua.Domain.Action`




### Introspection

Target: `AshLua.Domain.Namespace`




Expand Down
2 changes: 2 additions & 0 deletions documentation/dsls/DSL-AshLua.Resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ end
|------|------|---------|------|
| [`name`](#lua-name){: #lua-name } | `String.t` | | The Lua key (under the domain table) to expose this resource as. Defaults to snake_case of the resource module's last segment. |
| [`expose?`](#lua-expose?){: #lua-expose? } | `boolean` | `true` | Whether to expose this resource and its public actions to Lua. |
| [`field_names`](#lua-field_names){: #lua-field_names } | `keyword` | `[]` | A keyword list mapping internal Ash field names to exact Lua-facing field names. |
| [`argument_names`](#lua-argument_names){: #lua-argument_names } | `keyword` | `[]` | A keyword list mapping internal Ash argument names to exact Lua-facing argument names per action. |



Expand Down
30 changes: 17 additions & 13 deletions documentation/tutorials/getting-started-with-ash-lua.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,14 @@ callable as `accounts.user.<action>(input)` from Lua.
```elixir
{[user_id], _lua} =
AshLua.eval!(
"""
local user, err = accounts.user.create({
name = "Zach",
email = "z@example.com",
fields = { "id" }
})
"""
local user, err = accounts.user.create({
input = {
name = "Zach",
email = "z@example.com"
},
fields = { "id" }
})
assert(err == nil)
return user.id
""",
Expand All @@ -123,7 +125,7 @@ two values: a result and an error. A successful call returns `(result, nil)`;
a failed call returns `(nil, err_table)`.

```lua
local user, err = accounts.user.create({ name = "Zach" })
local user, err = accounts.user.create({ input = { name = "Zach" } })

if err then
-- err is a table: { message = "...", errors = { { code = "...", fields = {...}, ... }, ... } }
Expand All @@ -136,7 +138,7 @@ end
If you'd rather have errors raise, wrap the call in Lua's built-in `assert`:

```lua
local user = assert(accounts.user.create({ name = "Zach" }))
local user = assert(accounts.user.create({ input = { name = "Zach" } }))
```

`assert` returns the first value when the second is `nil`, and raises with the
Expand Down Expand Up @@ -212,19 +214,21 @@ Supported operations: `"count"`, `"exists"`, and `{ "sum" | "avg" | "min" |

## 7. Mutations

Create / update / delete behave like read, except update and delete take the
primary key inline in the input:
Create / update / delete behave like read, except action fields and arguments go
under the `input` key. Update and delete take the primary key there too:

```lua
local post = assert(posts.post.create({
title = "Hello", body = "World", fields = { "id", "title" }
input = { title = "Hello", body = "World" },
fields = { "id", "title" }
}))

local updated = assert(posts.post.update({
id = post.id, title = "Hello again", fields = { "title" }
input = { id = post.id, title = "Hello again" },
fields = { "title" }
}))

assert(posts.post.destroy({ id = post.id }))
assert(posts.post.destroy({ input = { id = post.id } }))
```

Generic actions (defined with `action :name, type do ... end`) take their
Expand Down
11 changes: 6 additions & 5 deletions lib/ash_lua.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ defmodule AshLua do
AshLua exposes Ash actions to Lua scripts evaluated through the [`lua`](https://hex.pm/packages/lua)
Elixir package, ensuring a consistent actor / tenant / context are propagated into every Ash call.

The Lua surface is derived from `Ash.Info.Manifest.generate/1` — every public action becomes a
callable at `<domain>.<resource>.<action>` (names overridable via the `AshLua.Domain` and
`AshLua.Resource` DSL extensions).
The Lua surface is resolved from `Ash.Info.Manifest.generate/1` plus AshLua DSL. Domains with no
explicit `lua do namespace ... end` config keep the legacy
`<domain>.<resource>.<action>` callable shape; domains with explicit namespaces expose only their
configured public action paths.

## Example

Expand All @@ -30,15 +31,15 @@ defmodule AshLua do
end

AshLua.eval!(\"""
local user, err = accounts.user.create({ name = "Zach" })
local user, err = accounts.user.create({ input = { name = "Zach" } })
assert(err == nil)
return user.id
\""", otp_app: :my_app, actor: current_user)

Action callables always return `(result, nil)` on success and `(nil, err_table)` on failure.
Wrap a call in Lua's built-in `assert()` for raise semantics:

local user = assert(accounts.user.create({ name = "Zach" }))
local user = assert(accounts.user.create({ input = { name = "Zach" } }))

## Actor / tenant / context

Expand Down
Loading