Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
iqxwebnext:technical:web_jobs [2026/10/05 18:23] – Gareth Johnstoneiqxwebnext:technical:web_jobs [2026/10/05 20:02] (current) – Gareth Johnstone
Line 34: 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. A job that returns nothing fails with `JOB-004`.
 - **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" or "not found", return normally, or `response.json ( body, 404 )`. - **`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" or "not found", return normally, or `response.json ( body, 404 )`.
Line 65: 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 `"SCHEDULE"`. `caller` is who is calling: `{ kind, id, name, userClass }` where `kind` is `anonymous`, `user`, `system`, `schedule` or `apikey`. The `Authorization` header is withheld. | | `request` | `{ method, url, path, query, headers, ip, caller }`. `method` tells a page job a landing `GET` from a submitted `POST`; a scheduled run sees `"SCHEDULE"`. `caller` is who is calling: `{ kind, id, name, userClass }` where `kind` is `anonymous`, `user`, `system`, `schedule` or `apikey`. The `Authorization` header is withheld. |
 | `userID`, `isAuthenticated` | The caller's `IQXNetUserID` and whether there is one — a signed-in user opening a public page is recognised. Scheduled runs get the system user. | | `userID`, `isAuthenticated` | The caller's `IQXNetUserID` and whether there is one — a signed-in user opening a public page is recognised. Scheduled runs get the system user. |
-| `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/update/delete). The connection carries the caller's `userstaffid`, so `ExternalCredentialGet ( )` and division-aware procedures work. A failing statement is logged with the job's prefix and rethrown with its SQL code. `getDB ( )` returns the same object. |+| `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/update/delete). The connection carries the caller's `userstaffid`, so `ExternalCredentialGet ( )` and division-aware procedures work. A failing statement is logged with the job's prefix and rethrown with its SQL code. |
 | `http ( url, options? )` | Outbound HTTP. `options`: `method`, `headers`, `body` (a string, bytes, or an object which is sent as JSON), `username`/`password` (basic auth), `timeoutMs`, `followRedirects`. Returns `{ status, ok, statusText, headers, text, bytes, json ( ) }`. Only `http`/`https` to the **public internet**: loopback, private ranges, link-local, the hub's own database server and anything on `WEBJOB_HTTP_DENY` are refused, every redirect hop is re-checked, and a site can narrow it to named hosts with `WEBJOB_HTTP_ALLOW`. 10 s timeout and 10 MB response by default, at most 50 calls per run. | | `http ( url, options? )` | Outbound HTTP. `options`: `method`, `headers`, `body` (a string, bytes, or an object which is sent as JSON), `username`/`password` (basic auth), `timeoutMs`, `followRedirects`. Returns `{ status, ok, statusText, headers, text, bytes, json ( ) }`. Only `http`/`https` to the **public internet**: loopback, private ranges, link-local, the hub's own database server and anything on `WEBJOB_HTTP_DENY` are refused, every redirect hop is re-checked, and a site can narrow it to named hosts with `WEBJOB_HTTP_ALLOW`. 10 s timeout and 10 MB response by default, at most 50 calls per run. |
 | `require ( name )` | Loads an **approved** module synchronously: `const { format } = require ( "date-fns" )`. Approved today: `date-fns`, `@iqx-limited/quick-pdf`. Anything else throws (`JOB-002`). `await require ( … )` from older jobs still works. | | `require ( name )` | Loads an **approved** module synchronously: `const { format } = require ( "date-fns" )`. Approved today: `date-fns`, `@iqx-limited/quick-pdf`. Anything else throws (`JOB-002`). `await require ( … )` from older jobs still works. |
 | `console` | `log`/`info`/`debug`/`warn`/`error`. Lines go to the server log prefixed `[WebJob:<name>]`, are kept with the run in history (see *Run history*), stream live to the run dialog, and can be shown under a page with `_console=1`. | | `console` | `log`/`info`/`debug`/`warn`/`error`. Lines go to the server log prefixed `[WebJob:<name>]`, are kept with the run in history (see *Run history*), stream live to the run dialog, and can be shown under a page with `_console=1`. |
-| `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`, `TextEncoder`/`TextDecoder`, `atob`/`btoa`, `structuredClone`, `AbortController`, timers | Passed through. | | `Buffer`, `URL`, `URLSearchParams`, `TextEncoder`/`TextDecoder`, `atob`/`btoa`, `structuredClone`, `AbortController`, timers | Passed through. |
-| `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 249: 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 and `finish` never called). |+| `JOB-004` | The job finished without a result (nothing was returned). |
 | `AUTH-SESSION-001`, `SYS-403` | The caller may not run this job (nobody signed in / not enough) — see *Who may call a job*. | | `AUTH-SESSION-001`, `SYS-403` | The caller may not run this job (nobody signed in / not enough) — see *Who may call a job*. |
 | `SYS-429` | Too many: the rate limit for callers without credentials, or the node's worker queue is full. | | `SYS-429` | Too many: the rate limit for callers without credentials, or the node's worker queue is full. |
Line 257: 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 `?what=runtime`, `?what=cpu` or `?what=memory` shows each limit ending a run with `JOB-003`. `_test-failures` shows how runs fail and how they don't: `?mode=throw` (default), `async` and `timer` all end at once with `JOB-001`, `?mode=false` is a `success: false` failure, `?mode=404` is a 404 that is *not* a failure, `?mode=ok` succeeds and echoes the caller. `_test-sandbox` lists what the sandbox exposes — every probe should read `undefined` or refused. `_test-http` exercises the outbound network policy. `_test-legacy` proves the pre-October-2026 IIFE shape still runs. `public_test-page` is a page job to open in a browser (inline script, a form that posts back, a console link), and `public_ping` is the smallest possible public job. The engine's unit tests are `npm run test:unit` (`src/routes/webJobEngine.test.ts`). +The repo ships a small test pack; run each and compare with its header. `_test-limits` returns the resolved limits, and `?what=runtime`, `?what=cpu` or `?what=memory` shows each limit ending a run with `JOB-003`. `_test-failures` shows how runs fail and how they don't: `?mode=throw` (default), `async` and `timer` all end at once with `JOB-001`, `?mode=false` is a `success: false` failure, `?mode=404` is a 404 that is *not* a failure, `?mode=ok` succeeds and echoes the caller. `_test-sandbox` lists what the sandbox exposes — every probe should read `undefined` or refused. `_test-http` exercises the outbound network policy. `public_test-page` is a page job to open in a browser (inline script, a form that posts back, a console link), and `public_ping` is the smallest possible public job. The engine's unit tests are `npm run test:unit` (`src/routes/webJobEngine.test.ts`).
- +
-## 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 `<name>.job.js`.+
  
 ## For developers ## For developers
  • iqxwebnext/technical/web_jobs.txt
  • Last modified: 2026/10/05 20:02
  • by Gareth Johnstone