Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,49 @@
|
||||
# async-task
|
||||
|
||||
The long-running-task pattern. Plugin handlers should never do heavy
|
||||
work inline — the sandbox has a fuel ceiling and the hook fan-out is
|
||||
held while the handler runs. This example records an audit event
|
||||
representing the work-request and returns immediately; a host-side
|
||||
worker consumes the event and runs the actual work outside the
|
||||
sandbox.
|
||||
|
||||
## Why not just block in the handler
|
||||
|
||||
- Sandbox fuel ceiling: handlers that exceed the budget trap and the
|
||||
invocation is logged as a failure.
|
||||
- Hook fan-out: each plugin subscriber serialises the host event loop.
|
||||
A slow plugin slows every later subscriber.
|
||||
- Audit clarity: a fast "work-requested" row in the trail is more
|
||||
useful than a 30-second-blocking row that may or may not have
|
||||
completed by the time the page loads.
|
||||
|
||||
## Pattern
|
||||
|
||||
```
|
||||
Host emits entity.updated
|
||||
│
|
||||
▼
|
||||
Plugin handler runs in sandbox
|
||||
│
|
||||
├── ctx.audit.record({ action: "async-task.work-requested", ... })
|
||||
│ (writes one PluginCapabilityUse audit row + an audit event)
|
||||
│
|
||||
└── returns { enqueued: true } immediately
|
||||
│
|
||||
▼
|
||||
Outbox dispatcher reads audit event
|
||||
│
|
||||
▼
|
||||
worker subscribes to action="async-task.work-requested",
|
||||
runs the long work outside the sandbox, emits a new hook
|
||||
event on completion if a follow-up is needed.
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
- The audit body is not a payload queue — it has a small size cap and
|
||||
the runtime sanitises it. Pass IDs through it, fetch the full record
|
||||
from the worker side via `db.read` if needed.
|
||||
- The audit row arrives *eventually* (transactional outbox), not
|
||||
synchronously. If you need synchronous behaviour, plugins are the
|
||||
wrong tool — call out to a regular host RPC instead.
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Async-task example — Phase 06 #23.
|
||||
*
|
||||
* Long-running side effects should not run inside the plugin invocation
|
||||
* (the sandbox has a fuel ceiling and the hook fan-out is blocked
|
||||
* while a handler runs). Instead, the plugin records an audit event
|
||||
* and returns immediately; a host worker picks up the event from the
|
||||
* outbox and does the heavy lifting outside the sandbox.
|
||||
*
|
||||
* The plugin never blocks waiting on the worker — that is the whole
|
||||
* point of the pattern. If the host wants a callback, it emits a new
|
||||
* hook event when the worker is done; the plugin can subscribe to
|
||||
* that too.
|
||||
*/
|
||||
import { defineHook, type PluginHookEntityPayload } from "../../src/index.js";
|
||||
|
||||
export const handleEntityUpdated = defineHook<PluginHookEntityPayload, { enqueued: boolean }>(
|
||||
async (input, ctx) => {
|
||||
ctx.log.info(
|
||||
{
|
||||
tenantId: input.tenantId,
|
||||
entity: input.entityType,
|
||||
changedFieldCount: input.changedFields?.length ?? 0,
|
||||
},
|
||||
"async-task.handleEntityUpdated received update"
|
||||
);
|
||||
|
||||
// Record one structured audit event — the host worker subscribes
|
||||
// to plugin-emitted audit events and runs the actual work. Body
|
||||
// is bounded so we don't accidentally turn the audit log into a
|
||||
// queue (the audit sink rejects payloads above its cap).
|
||||
await ctx.audit.record({
|
||||
action: "async-task.work-requested",
|
||||
target: `${input.entityType}:${input.entityId}`,
|
||||
meta: {
|
||||
tenantId: input.tenantId,
|
||||
changedFieldCount: input.changedFields?.length ?? 0,
|
||||
},
|
||||
});
|
||||
|
||||
ctx.log.info({}, "async-task enqueued work-request, returning fast");
|
||||
return { enqueued: true };
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,32 @@
|
||||
# Phase 06 #23 example — async-task plugin.
|
||||
#
|
||||
# Demonstrates the long-running-task pattern: instead of doing the work
|
||||
# inline (which blocks the hook fan-out and risks fuel exhaustion), the
|
||||
# plugin enqueues the work through `audit` (the capability for emitting
|
||||
# host events) and returns immediately. The host outbox picks up the
|
||||
# event and a worker handles the actual work outside the sandbox.
|
||||
#
|
||||
# The benefit: the plugin invocation stays fast and traceable while the
|
||||
# heavy lifting happens in regular BullMQ. The plugin still has clear
|
||||
# audit rows for the trigger; the worker handles its own observability.
|
||||
|
||||
schemaVersion = 1
|
||||
id = "de.toolctrl.examples.async-task"
|
||||
version = "0.1.0"
|
||||
name = "Async Task Demo"
|
||||
description = "Enqueues a long-running task via the audit capability, returns immediately."
|
||||
entry = "plugin.wasm"
|
||||
wasmSha256 = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
signature = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
signedBy = "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
allowedHosts = []
|
||||
|
||||
[[capabilities]]
|
||||
name = "log"
|
||||
|
||||
[[capabilities]]
|
||||
name = "audit"
|
||||
|
||||
[[hooks]]
|
||||
name = "entity.updated"
|
||||
export = "handleEntityUpdated"
|
||||
Reference in new issue
Block a user