|
1 | | -# Action Definitions (RPC) |
| 1 | +# Action Definitions (Server Functions) |
2 | 2 |
|
3 | | -Custom business logic can be defined in the `actions` section. Actions act as Remote Procedure Calls (RPC) scoped to the object. |
| 3 | +Actions are custom Server-Side Functions attached to an object. They allow you to encapsulate complex business logic that goes beyond standard CRUD operations. |
| 4 | + |
| 5 | +Unlike Hooks (which trigger automatically), Actions are explicitly invoked by the client (API, Button, Scheduled Task). |
| 6 | + |
| 7 | +## 1. Concepts |
| 8 | + |
| 9 | +### 1.1 Scope (`type`) |
| 10 | +* **Global Actions**: Operate on the collection level. |
| 11 | + * *Examples:* "Import CSV", "Generate Monthly Report", "Sync with External API". |
| 12 | + * *Context:* No `id`. |
| 13 | +* **Record Actions**: Operate on a specific record instance. |
| 14 | + * *Examples:* "Approve", "Reject", "Send Email", "Clone". |
| 15 | + * *Context:* Has `id`. |
| 16 | + |
| 17 | +### 1.2 Schema-First Inputs |
| 18 | +Input parameters (`params`) are defined using the same `FieldConfig` schema as object fields. This gives you free validation, type coercion, and UI generation. |
| 19 | + |
| 20 | +## 2. Configuration (YAML) |
| 21 | + |
| 22 | +Actions are declared in `*.object.yml` or JSON. |
4 | 23 |
|
5 | 24 | ```yaml |
6 | 25 | actions: |
7 | | - approve: |
8 | | - label: Approve Request |
9 | | - description: Approves the current record. |
| 26 | + # 1. A Record Action (Button on a row) |
| 27 | + approve_order: |
| 28 | + type: record |
| 29 | + label: Approve Order |
| 30 | + icon: standard:approval |
| 31 | + confirm_text: "Are you sure you want to approve this order? This cannot be undone." |
10 | 32 | params: |
11 | 33 | comment: |
12 | 34 | type: textarea |
13 | | - label: Approval Comments |
14 | | - result: |
15 | | - type: boolean |
| 35 | + required: true |
| 36 | + label: Approval Reason |
| 37 | + |
| 38 | + # 2. A Global Action (Button on list view) |
| 39 | + sync_jira: |
| 40 | + type: global |
| 41 | + label: Sync from Jira |
| 42 | + internal: true # Not exposed to public API |
| 43 | + params: |
| 44 | + project_key: |
| 45 | + type: text |
| 46 | + required: true |
| 47 | +``` |
| 48 | +
|
| 49 | +## 3. Implementation (TypeScript) |
| 50 | +
|
| 51 | +Implement the logic in a companion `*.action.ts` file. |
| 52 | + |
| 53 | +```typescript |
| 54 | +// src/objects/order.action.ts |
| 55 | +import { ActionDefinition } from '@objectql/types'; |
| 56 | +import { Order } from './types'; |
| 57 | +
|
| 58 | +// Input Type Definition |
| 59 | +interface ApproveInput { |
| 60 | + comment: string; |
| 61 | +} |
| 62 | +
|
| 63 | +export const approve_order: ActionDefinition<Order, ApproveInput> = { |
| 64 | + type: 'record', |
| 65 | + |
| 66 | + // Logic |
| 67 | + handler: async ({ id, input, api, user }) => { |
| 68 | + // 1. Fetch current state |
| 69 | + const order = await api.findOne('order', id); |
| 70 | + |
| 71 | + if (order.status !== 'Draft') { |
| 72 | + throw new Error("Only draft orders can be approved"); |
| 73 | + } |
| 74 | +
|
| 75 | + // 2. Perform updates using Atomic Operations or Transactions |
| 76 | + await api.update('order', id, { |
| 77 | + status: 'Approved', |
| 78 | + approved_by: user.id, |
| 79 | + approval_comment: input.comment, |
| 80 | + approved_at: new Date() |
| 81 | + }); |
| 82 | +
|
| 83 | + // 3. Return result to client |
| 84 | + return { success: true, new_status: 'Approved' }; |
| 85 | + } |
| 86 | +} |
16 | 87 | ``` |
17 | 88 |
|
18 | | -## 1. Properties |
| 89 | +## 4. Why this design is "Optimal"? |
| 90 | + |
| 91 | +1. **Unified Schema**: Inputs use the same definitions as Database fields. If you know how to define a table, you know how to define an API argument. |
| 92 | +2. **UI Ready**: The metadata (`label`, `icon`, `confirm_text`, `params`) contains everything a frontend framework (like React Admin or Salesforce Lightning) needs to render a button and a modal form **automatically**. |
| 93 | +3. **Type Safety**: The `ActionDefinition<Entity, Input, Output>` generic ensures your handler code respects the contract. |
| 94 | + |
| 95 | +## 5. Loading & Registration (Standard) |
| 96 | + |
| 97 | +To ensure the Metadata Loader can automatically bind your actions to the correct object, you must follow the file naming convention: |
| 98 | + |
| 99 | +* **Object Definition**: `mypackage/objects/invoice.object.yml` |
| 100 | +* **Action Implementation**: `mypackage/objects/invoice.action.ts` (or `.js`) |
| 101 | + |
| 102 | +The loader extracts the `objectName` from the filename (everything before `.action.`). |
| 103 | + |
| 104 | +```typescript |
| 105 | +// mypackage/objects/invoice.action.ts |
| 106 | +export const approve_invoice: ActionDefinition<Invoice> = { ... }; |
| 107 | +export const reject_invoice: ActionDefinition<Invoice> = { ... }; |
| 108 | +``` |
19 | 109 |
|
20 | | -| Property | Type | Description | |
21 | | -| :--- | :--- | :--- | |
22 | | -| `label` | `string` | Display label (e.g., for buttons). | |
23 | | -| `icon` | `string` | Icon name. | |
24 | | -| `description` | `string` | Help text. | |
25 | | -| `confirmText` | `string` | Confirmation message before execution. | |
26 | | -| `params` | `Map<string, FieldConfig>` | Input parameters schema. Same structure as Object fields. | |
27 | | -| `result` | `FieldConfig` | The shape of the return value. | |
| 110 | +The loader will register `approve_invoice` and `reject_invoice` as actions for the `invoice` object. |
0 commit comments