# Task Server

Timer-based task scheduler that runs Lua functions on recurring schedules defined with Later.js English phrases.

## Architecture

```mermaid
graph LR
    A[task-server.lua<br>port 5957] -->|loads rules from| B[task DB table]
    A -->|calculates next fire| C[timer.lua<br>QuickJS + Later.js]
    C -->|returns unix timestamps| A
    A -->|executes at fire time| D[allowed_function<br>→ Lua function]
    D -->|saves result to| E[task_work DB table]
```

## How It Works

1. `task-server.lua` starts and reads active timer rules from the `task` database table
2. `timer.lua` sends all rules to Later.js (QuickJS engine), which returns the next N fire times for each rule
3. The server sleeps until the earliest fire time, then calls the registered Lua function
4. Results are saved to `task_work` table (optional, controlled by `save_result`)

## Task Record Format

Task records are stored in the `task` database table. Template file: `form/nc/erp-sync/cxml/task/cxml-download-task.json`

```json
{
    "name": "form/nc/erp-sync/cxml/task/cxml-download-task.json",
    "task": [
        {
            "task_type": "timer",
            "task_state": "active",
            "description": "cXML download from Ariba server",
            "timer_rule": "every 10 min on the 0 min on the 0 sec",
            "json_data": {
                "function": "cxml-worker.download",
                "save_result": 10
            }
        }
    ]
}
```

### Fields

| Field | Type | Description |
|-------|------|-------------|
| `task_type` | string | `"timer"` for scheduled tasks |
| `task_state` | string | `"active"` to enable, anything else to disable |
| `description` | string | Human-readable task name, shown in logs |
| `timer_rule` | string | Later.js English phrase (see below) |
| `json_data.function` | string | Function path registered in `allowed_function.json` |
| `json_data.save_result` | number | How many task_work records to keep (oldest are deleted) |
| `json_data.parameter` | array | Parameters passed to the function (optional) |

## Timer Rules (Later.js English Phrases)

The timer uses [Later.js](https://github.com/breejs/later) text parser. **Simple English only, no cron.**

### Frequency

| Rule | Meaning |
|------|---------|
| `"every 5 min"` | Every 5 minutes |
| `"every 10 min on the 0 min on the 0 sec"` | Every 10 min on the minute boundary |
| `"every 15 min on the 0 min on the 0 sec"` | Every 15 min on the minute boundary |
| `"every 30 min"` | Every 30 minutes |
| `"every 1 hours"` | Every hour |
| `"every 1 day"` | Every day |
| `"every 1 day at 00:00"` | Daily at midnight |

### Time Constraints

| Rule | Meaning |
|------|---------|
| `"at 10:30 am"` | Once daily at 10:30 |
| `"every 1 hour after 08:00 and before 17:00"` | Hourly during business hours |
| `"every 1 hour after 08:00"` | Hourly from 08:00 onwards |

### Day Constraints

| Rule | Meaning |
|------|---------|
| `"every 1 day on Monday, Tuesday, Wednesday, Thursday, Friday"` | Weekdays only |
| `"every 5 min except on Thursday and Friday"` | Skip Thu/Fri |

### Combined Rules

| Rule | Meaning |
|------|---------|
| `"every 1 day at 00:00 except on Saturday and Sunday"` | Weekdays at midnight |
| `"every 5 min on the 0 min on the 12 sec except on Thursday and Friday"` | Every 5 min at :12 sec, skip Thu/Fri |

## Registering a Task Function

### 1. Add to `allowed_function.json`

```json
{
    "allowed_function": {
        "cxml-worker.download": "nc/worker/cxml-worker.download"
    }
}
```

Format: `"displayName": "module.functionName"`

The `execute.getFunction()` call resolves `"module.functionName"` by splitting on `.` — it requires the module, then gets the named property.

### 2. Create the Task Record

Insert into the `task` database table, or use a template JSON file:

```json
{
    "task_type": "timer",
    "task_state": "active",
    "description": "My scheduled task",
    "timer_rule": "every 10 min on the 0 min on the 0 sec",
    "json_data": {
        "function": "my-module.myFunction",
        "save_result": 10
    }
}
```

### 3. The Worker Function

The function must be exported from the module:

```lua
-- nc/worker/my-module.lua
local function myFunction()
    local result = doWork()
    return {count = result.count, error = result.error}
end

return {myFunction = myFunction}
```

## timer.lua API

`lib/timer.lua` wraps Later.js via the QuickJS JavaScript engine.

### `timer.calculate(param, isRestCall)`

Calculates next fire times for one or more rules.

**Parameters:**

- `param` — table or JSON string: `{count = N, rule = {"rule1", "rule2", ...}}`
  - `count` — number of next fire times to calculate per rule (default: 1)
  - `rule` — array of Later.js English phrase strings
- `isRestCall` — boolean, format output for REST API response

**Returns:**

```lua
{
    total_time = 0.005,
    result = {
        {
            timer = "every 10 min on the 0 min on the 0 sec",
            result = "1713283800,1713284400",
            result_text = {"2025-04-16 19:30:00 Thu", "2025-04-16 19:40:00 Thu"},
            time = 0.003
        }
    }
}
```

### `timer.cleanup()`

Frees the QuickJS runtime context.

## Task Server Internals

### Main Loop

```mermaid
sequenceDiagram
    participant TS as task-server
    participant DB as task table
    participant TM as timer.lua
    participant FN as worker function

    TS->>DB: Query active timer rules
    DB-->>TS: rule array
    TS->>TM: calculate(rules)
    TM-->>TS: next fire times
    TS->>TS: sleep until earliest fire time
    Note over TS: Timer fires
    TS->>FN: execute function
    FN-->>TS: result
    TS->>DB: save result to task_work
    TS->>TM: calculate(rules) again
    TM-->>TS: updated next fire times
```

### Rule Loading (`getRule()`)

Queries `task` table where:

- `task_type = "timer"`
- `task_state = "active"`
- `timer_rule <> ""`

### Schedule Calculation (`nextRule()`)

Calls `timer.calculate()` with `count = 2` (returns current + next fire time). Parses the minimum time across all rules to determine sleep duration.

### Rule Execution (`runRule()`)

When fire time arrives:

1. Calls `execute.getFunction(ruleRec)` to resolve the function
2. If `ruleRec.parameter` is set: `pcall(func, unpack(parameter))`
3. Otherwise: `pcall(func)`
4. Saves result to `task_work` if `save_result` is set
5. Deletes old `task_work` records beyond the retention count

## Configuration

| Preference | Path | Description |
|-----------|------|-------------|
| System options | `system/option.json` | `task_server_timeout` (GC interval), `collect_garbage_timeout` |
| Allowed functions | `auth/allowed_function.json` | Function registry for task execution |
| Task records | `task` DB table | Active timer rules |

## Testing

```bash
# Timer/Later.js tests (14 tests, English phrase examples)
lj test/lib/timer-test.lua

# Task server integration tests (4 tests)
lj test/nc/task-server-test.lua
```

## Key Files

| File | Purpose |
|------|---------|
| `nc/task-server.lua` | Main server loop, port 5957 |
| `lib/timer.lua` | Later.js schedule calculation via QuickJS |
| `lib/javascript/javascript.lua` | JS engine wrapper (SpiderMonkey + QuickJS) |
| `nc-plugin/plugin/javascript/later.v8.js` | Later.js library + `schedule()` function |
| `nc-plugin/plugin/javascript/later_nc_extend.js` | `schedule()` wrapper with timezone handling |
| `lib/execute.lua` | `getFunction()` resolves allowed_function names |
| `preference/auth/allowed_function.json` | Function registry |

## See Also

- [Later.js](https://github.com/breejs/later) — schedule phrase reference
- [JavaScript Engine Guide](javascript-guide.md)
- [PEG Guide](peg.md)
