diff --git a/docs/concepts/javascript2oaf.md b/docs/concepts/javascript2oaf.md index bc2fb20..5d9d770 100644 --- a/docs/concepts/javascript2oaf.md +++ b/docs/concepts/javascript2oaf.md @@ -173,7 +173,7 @@ For function-based type checking there are a set of functions prefixed with "is" | tprintErr | Outputs a string based on a provided template using data from an object to stderr with a new-line | | tprintErrnl| Outputs a string based on a provided template using data from an object to stderr without a new-line | -## Logging +## Logging | Function | Description | |----------|-------------| @@ -189,6 +189,10 @@ For function-based type checking there are a set of functions prefixed with "is" | startLog | Starts logging to a default or specified [OpenAF channel](OpenAF-Channels.md) with automatic housekeeping | | stopLog | Stops logging to a default of specified [OpenAF channel](OpenAF-Channels.md) after a startLog function has been previously executed. | +## Error handling helpers + +OpenAF provides utility helpers to improve diagnostics and recoverability when exceptions are raised. The most notable helper added for the v20250721 release is `$err(exception, rethrow, returnStr, code)`. It formats JavaScript and Java exceptions consistently, prints the stack trace (respecting the `OAF_ERRSTACK` flag) and, when source information is available, highlights the code lines around the failure so troubleshooting is easier. By default `$err` writes the formatted message to the console, but you can set `returnStr=true` to obtain the formatted text programmatically or `rethrow=true` to propagate the original exception after logging it. + ## Navigating arrays and maps There are a set of libraries includes to help to "navigate" through javascript arrays and maps: @@ -201,11 +205,23 @@ There are a set of libraries includes to help to "navigate" through javascript a ## Utility -_tbc_ +The `af` namespace continues to aggregate convenience helpers for day-to-day scripting. Two recent additions are: + +| Function | Description | +|----------|-------------| +| `af.nvl(value, default)` | Returns `value` when it is defined and non-null, or `default` otherwise. It is a concise alternative to common null/undefined coalescing patterns when working with optional data sources. | +| `af.swap(array, index1, index2)` | Returns a new array where the elements at `index1` and `index2` have been exchanged. The original array is validated and left untouched, making it safer to reorganise data that is being shared between functions or promises. | ## Parallel execution -_tbc_ +Asynchronous utilities received multiple improvements that embrace Java virtual threads while keeping the familiar OpenAF APIs: + +* `$do(fn)` continues to run `fn` on the managed worker pool and immediately returns an `oPromise` chain. +* `$doV(fn)` mirrors `$do`, but executes the promise callbacks on Java virtual threads when they are available. This is ideal for IO-bound workloads that benefit from lightweight concurrency without exhausting classic thread pools. +* `$sync()` returns an object exposing `run(fn)` which serialises access to the provided function by using a `ReentrantLock`. It is a simple way to protect shared resources that are updated from multiple promises or channel callbacks without manually instantiating locks. +* `$queue(initialItems)` creates a thread-safe FIFO queue backed by `ConcurrentLinkedQueue`, exposing helpers such as `add`, `poll`, `peek`, `has`, and `size`. Passing an initial array enqueues it immediately, offering an easy coordination primitive for producer/consumer flows. + +These utilities can be combined: for example, producers can push work with `$queue().add(...)`, consumers can fetch it with `$doV` to process in virtual threads, and any critical sections can be guarded with `$sync().run(...)`. ## OPack specific diff --git a/docs/concepts/oJob.md b/docs/concepts/oJob.md index 0a01a7d..606279d 100644 --- a/docs/concepts/oJob.md +++ b/docs/concepts/oJob.md @@ -38,6 +38,14 @@ To run it you would just execute: ojob myrecipe.yaml ```` +## Embeeded documentation for an oJob + +Starting with OpenAF v20250721 the `ojob` CLI can render the oJob documentation directly. Two new options are available: + +* `ojob -reference` renders a human-friendly overview of jobs, arguments and dependencies using the standard console output (with colours when supported). +* `ojob -mdreference` emits the same reference in Markdown. + + In the next chapters will go through how you can add arguments to each job (e.g. how many eggs, how much sugar, …), how you can have jobs chains (e.g. job “Get ingredients” → “Make cake” → “Serve cake” for just a to do “Make cake”), how you can make a production line of cakes with array arguments and more. # Jobs diff --git a/docs/guides/advanced/executing-code-asynchronously.md b/docs/guides/advanced/executing-code-asynchronously.md index 4f8c66e..1c9593d 100644 --- a/docs/guides/advanced/executing-code-asynchronously.md +++ b/docs/guides/advanced/executing-code-asynchronously.md @@ -81,5 +81,57 @@ var allPromises = $doAll(arrayOfPromises) $doWait(allPromises); ```` -Ok. But won't that just lunch a ton of threads "burning" down my machine? No. You don't need to worry about that. Underneath it will create a set of threads giving the number of identified compute cores and reuse them. So some promises will actually be waiting for others to finish and to have a thread available for them to execute. -In OpenAF there is also the function parallel4array and others to make it easy (and smarter) to asynchronously process an array since a promise for each might not actually give the best performance. But that will be the topic of another post. \ No newline at end of file +Ok. But won't that just lunch a ton of threads "burning" down my machine? No. You don't need to worry about that. Underneath it will create a set of threads giving the number of identified compute cores and reuse them. So some promises will actually be waiting for others to finish and to have a thread available for them to execute. +In OpenAF there is also the function parallel4array and others to make it easy (and smarter) to asynchronously process an array since a promise for each might not actually give the best performance. But that will be the topic of another post. + +## Virtual threads with `$doV` + +OpenAF v20250721 introduces `$doV`, a drop-in replacement for `$do` that runs promise executors on Java virtual threads whenever they are available. Virtual threads are extremely lightweight and are ideal when you need to spawn hundreds or thousands of concurrent IO tasks without overwhelming the classic worker pools. Using `$doV` is as simple as swapping the function call: + +````javascript +var promise = $doV(() => { + var body = io.readUrl("https://example.org/service"); + return process(body); +}).then(saveResult) + .catch($err); + +$doWait(promise); +```` + +From an API perspective `$do` and `$doV` share the same chaining semantics, so you can pick the appropriate implementation per workload without refactoring your orchestration. + +## Coordinating concurrent work with `$sync` and `$queue` + +More helpers were added to simplify coordination between asynchronous tasks: + +* `$sync()` creates a small wrapper that serialises access to a critical section. It is perfect to guard code that updates shared state from multiple promises. +* `$queue(initialItems)` yields a thread-safe queue that supports `add`, `poll`, `peek`, `has`, and other utility methods backed by `ConcurrentLinkedQueue`. + +Combining both helpers allows you to build producer/consumer flows without leaving JavaScript: + +````javascript +var work = $queue(); +var syncStats = $sync(); +var stats = []; + +// Producer +$doV(() => { + files.forEach(file => work.add(file)); +}); + +// Consumers +var workers = []; +for (var i = 0; i < 4; i++) { + workers.push($doV(() => { + var next; + while ((next = work.poll()) !== null) { + var report = analyse(next); + syncStats.run(() => stats.push(report)); + } + })); +} + +$doWait($doAll(workers)); +```` + +In the previous snippet producers and consumers use virtual threads to keep latency low, while the `$sync` block ensures the shared `stats` collection stays consistent. diff --git a/docs/howto/Use-files.md b/docs/howto/Use-files.md index 5407532..485839d 100644 --- a/docs/howto/Use-files.md +++ b/docs/howto/Use-files.md @@ -107,4 +107,38 @@ io.readLinesNDJSON("abc123.json", (line) => { sprint(line); }); ````javascript var logline = { date: now(), level: "INFO", message: "a log message" }; io.writeLineNDJSON("abc123.json", logline); -```` \ No newline at end of file +```` + +## Work with LZ4 compressed files + +OpenAF v20250721 adds native helpers for LZ4 compression, making it easy to exchange compressed payloads without external tools: + +* `io.lz4(data)` compresses a map, array, string, byte array or stream using the LZ4 frame format and returns the compressed bytes. +* `io.unlz4(bytes)` performs the inverse operation, returning the original map/string when the input was compressed with `io.lz4`. +* `io.writeFileLZ4Stream(path)` opens an output stream that writes LZ4-compressed data directly to disk. It is ideal when you want to compress large files on the fly. +* `io.readFileLZ4Stream(path)` yields an input stream that decompresses LZ4 content while you read it, avoiding the need to load the entire file in memory. + +Example usage: + +````javascript +// Compress an object and persist the bytes +var payload = { id: 1, values: [1, 2, 3] }; +var compressed = io.lz4(payload); +io.writeFileBytes("payload.lz4", compressed); + +// Later, decompress it back +var restored = io.unlz4(io.readFileBytes("payload.lz4")); + +// Write compressed content incrementally +var outStream = io.writeFileLZ4Stream("huge.log.lz4"); +try { + logs.forEach(line => outStream.write((line + "\n").getBytes("UTF-8"))); +} finally { + outStream.close(); +} + +// Read compressed content as a stream +var inStream = io.readFileLZ4Stream("huge.log.lz4"); +// ...use the Java InputStream as needed... +inStream.close(); +````