Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| iqxwebnext:technical:web_jobs [2026/10/05 18:08] – Gareth Johnstone | iqxwebnext:technical:web_jobs [2026/10/05 20:02] (current) – Gareth Johnstone | ||
|---|---|---|---|
| Line 6: | Line 6: | ||
| Jobs are written and managed from the **Web Jobs** pages in iqxWEB (Owner → Web Jobs → *Manage* and *Scheduler*) and served from `/ | Jobs are written and managed from the **Web Jobs** pages in iqxWEB (Owner → Web Jobs → *Manage* and *Scheduler*) and served from `/ | ||
| - | ## How these differ | + | This page is about the JavaScript jobs served |
| - | + | ||
| - | IQX has had **JobRunner** | + | |
| - | + | ||
| - | | | JobRunner XML job | Web Job | | + | |
| - | |---|---|---| | + | |
| - | | Language | XML with a command vocabulary | Plain JavaScript | + | |
| - | | Where it runs | Inside the IQX application / WPK | On WebHub, in its own worker thread, over HTTP | | + | |
| - | | Called from | Screens, buttons, the desktop scheduler | Browsers, iqxWEB, webhooks, curl, the Web Jobs scheduler | | + | |
| - | | Answers with | Screen actions, database effects | JSON, files, or a complete web page that can post back to itself | | + | |
| - | | Reaches out | Through IQX's own integrations | `db.exec` SQL, `http()` to the public internet, approved modules | | + | |
| - | | Written by | Consultants / IQX | Power users and IQX, from the Owner page | | + | |
| - | + | ||
| - | They do not share code or a file format, and one does not run the other. If a task is "press a button in IQX and do something to this record", | + | |
| ## Writing a job | ## Writing a job | ||
| Line 47: | Line 34: | ||
| The rules that matter: | The rules that matter: | ||
| - | - **The return value is the result.** A plain value or object is sent as JSON; a `response.*` value is sent as that response. `finish ( result )` still works (jobs written before October 2026 use it) and whichever of the two happens first wins. | + | - **The return value is the result.** A plain value or object is sent as JSON; a `response.*` value is sent as that response. |
| - **An uncaught error fails the run immediately** with `JOB-001` and the message — including a rejected promise nobody caught and a throw inside a timer. You no longer need a `try/catch` just to make failures prompt; catch only where you want to turn an error into a particular answer. | - **An uncaught error fails the run immediately** with `JOB-001` and the message — including a rejected promise nobody caught and a throw inside a timer. You no longer need a `try/catch` just to make failures prompt; catch only where you want to turn an error into a particular answer. | ||
| - **`success: false` in a JSON result marks the run as failed.** The caller gets a `JOB-001` error *instead of* your payload, the run is counted as a failure, and a scheduled run is recorded as failed. Use it to mean "this did not work". For "no rows matched" | - **`success: false` in a JSON result marks the run as failed.** The caller gets a `JOB-001` error *instead of* your payload, the run is counted as a failure, and a scheduled run is recorded as failed. Use it to mean "this did not work". For "no rows matched" | ||
| Line 78: | Line 65: | ||
| | `request` | `{ method, url, path, query, headers, ip, caller }`. `method` tells a page job a landing `GET` from a submitted `POST`; a scheduled run sees `" | | `request` | `{ method, url, path, query, headers, ip, caller }`. `method` tells a page job a landing `GET` from a submitted `POST`; a scheduled run sees `" | ||
| | `userID`, `isAuthenticated` | The caller' | | `userID`, `isAuthenticated` | The caller' | ||
| - | | `db.exec ( sql, params? )` | Runs the SQL as the pears (business-table) user with `?` placeholders bound from the array, and resolves to an **array of row objects** for a `SELECT` (`{ affectedRows }` for insert/ | + | | `db.exec ( sql, params? )` | Runs the SQL as the pears (business-table) user with `?` placeholders bound from the array, and resolves to an **array of row objects** for a `SELECT` (`{ affectedRows }` for insert/ |
| | `http ( url, options? )` | Outbound HTTP. `options`: `method`, `headers`, `body` (a string, bytes, or an object which is sent as JSON), `username`/ | | `http ( url, options? )` | Outbound HTTP. `options`: `method`, `headers`, `body` (a string, bytes, or an object which is sent as JSON), `username`/ | ||
| | `require ( name )` | Loads an **approved** module synchronously: | | `require ( name )` | Loads an **approved** module synchronously: | ||
| | `console` | `log`/ | | `console` | `log`/ | ||
| - | | `response` | Builders for non-JSON answers — see *Responses*. `rawResponse ( body, headers )` is the older form of `response.raw`. | | + | | `response` | Builders for non-JSON answers — see *Responses*. | |
| | `sign ( payload, ttlSeconds? )`, `verify ( token )` | A signed, expiring token (HMAC, keyed from the server secret). `verify` returns the payload or `null`. This is how a multi-step page proves that an earlier step passed — see *Serving pages*. Default TTL 15 minutes. | | | `sign ( payload, ttlSeconds? )`, `verify ( token )` | A signed, expiring token (HMAC, keyed from the server secret). `verify` returns the payload or `null`. This is how a multi-step page proves that an earlier step passed — see *Serving pages*. Default TTL 15 minutes. | | ||
| | `breakpoint ( )` | Pauses the job when it is being run under the debugger; a no-op otherwise (so is a plain `debugger` statement). See *Debugging*. | | | `breakpoint ( )` | Pauses the job when it is being run under the debugger; a no-op otherwise (so is a plain `debugger` statement). See *Debugging*. | | ||
| | `limits` | The resolved `{ runtimeMs, memoryMb, cpuMs }` for this run. | | | `limits` | The resolved `{ runtimeMs, memoryMb, cpuMs }` for this run. | | ||
| | `Buffer`, `URL`, `URLSearchParams`, | | `Buffer`, `URL`, `URLSearchParams`, | ||
| - | | `extendLimits ( )` | Deprecated — declare limits in the header instead. Still extends the runtime for old jobs and logs a warning. | | ||
| ## Who may call a job | ## Who may call a job | ||
| Line 262: | Line 248: | ||
| | `JOB-002` | `require ( )` of a module that is not approved or not installed. | | | `JOB-002` | `require ( )` of a module that is not approved or not installed. | | ||
| | `JOB-003` | A limit was hit: runtime, memory or CPU. The message says which. | | | `JOB-003` | A limit was hit: runtime, memory or CPU. The message says which. | | ||
| - | | `JOB-004` | The job finished without a result (`finish ( )` with nothing, or nothing returned | + | | `JOB-004` | The job finished without a result (nothing |
| | `AUTH-SESSION-001`, | | `AUTH-SESSION-001`, | ||
| | `SYS-429` | Too many: the rate limit for callers without credentials, | | `SYS-429` | Too many: the rate limit for callers without credentials, | ||
| Line 270: | Line 256: | ||
| ## Trying it | ## Trying it | ||
| - | The repo ships a small test pack; run each and compare with its header. `_test-limits` returns the resolved limits, and `? | + | The repo ships a small test pack; run each and compare with its header. `_test-limits` returns the resolved limits, and `? |
| - | + | ||
| - | ## Jobs written before October 2026 | + | |
| - | + | ||
| - | The earlier engine ran the file as a synchronous function body, so every job wrapped itself in `(async function (params, userID, finish) { … })(params, userID, finish)` and used `await require ( )`, `getDB ( )` and `finish ( )`. All of that still works unchanged inside the new wrapper; the only behavioural differences are that an uncaught error now fails at once instead of waiting for the timeout, that `extendLimits ( )` logs a deprecation pointing at the header, and that files must be named `< | + | |
| ## For developers | ## For developers | ||