130 lines
4.6 KiB
Plaintext
130 lines
4.6 KiB
Plaintext
Applet Guide: Sandboxed Execution, Typed CSV Output
|
|
|
|
# Overview
|
|
An applet is a flat, reusable `.applet.js` script stored in the VFS that queries Dashback REST endpoints and computes tabular data.
|
|
Applets return structured, semicolon-separated (`;`) typed CSV conforming to output metadata headers.
|
|
|
|
Applets can be executed by:
|
|
1. `javascript_execute` tool using `{ "appletvfsfile": "<name>.applet.js", "applet_input": { ... } }`.
|
|
2. REST API endpoint `POST /vfs/<name>.applet.js` with `{ "input": { ... } }`.
|
|
|
|
---
|
|
|
|
# Every `.applet.js` file MUST start with three metadata lines:
|
|
|
|
```
|
|
// @applet-input-schema {"type":"object","properties":{...},"required":[...]}
|
|
// @applet-output-schema name:type;name:type
|
|
// @applet-description "Lorem ipsum"
|
|
```
|
|
|
|
`@applet-input-schema`
|
|
- JSON Schema object (`"type": "object"`).
|
|
- Parameter descriptions in the schema are optional
|
|
|
|
`@applet-output-schema`
|
|
- Semicolon-separated (`col1:type;col2:type;...`) column declarations.
|
|
- Bare column names without `:type` default to `string`.
|
|
- Allowed data types:
|
|
- `string`: plain text or serialized object.
|
|
- `integer`: truncated integer number.
|
|
- `number`: floating-point number.
|
|
- `boolean`: boolean value (`true`, `false`, `1`, `0`, `"true"`, `"false"`).
|
|
- `date`: Date object or ISO string.
|
|
|
|
`@applet-description`
|
|
- Short human-readable summary of what the applet computes (e.g. `"Article catalogue with net prices"`).
|
|
|
|
---
|
|
|
|
# Execution Environment & Globals
|
|
|
|
Applets run in an isolated worker thread pool sandbox.
|
|
|
|
- **Entry point**: Export an async function taking `applet_input`:
|
|
```javascript
|
|
module.exports = async function (applet_input) { ... };
|
|
```
|
|
- **No `require()` or `import`**: Attempting to import modules throws an error.
|
|
- **Pre-injected Globals**:
|
|
- `fetch(path, options)`: Authenticated `fetch` pre-configured with a JWT scoped to the current user and selected company. Use root-relative API routes, e.g. `fetch('/article/42000009')`.
|
|
- `fs`: Sandboxed filesystem interface for reading/writing local company files.
|
|
- `ExcelJS`: Pre-injected spreadsheet library.
|
|
- `PDFDocument`: Pre-injected PDFKit library.
|
|
- `sharp`: Pre-injected image manipulation library.
|
|
- `crypto`: Node / WebCrypto API.
|
|
- `console.log(...)`: returned in the tool response
|
|
|
|
---
|
|
|
|
# Return Value & Typed CSV Mapping
|
|
|
|
The exported function must return:
|
|
- An **Array of row objects**: `[ { col1: 'val1', col2: 10 }, { col1: 'val2', col2: 20 } ]`
|
|
- Or a **Single row object**: `{ col1: 'val1', col2: 10 }` (treated as a 1-row table)
|
|
|
|
---
|
|
|
|
# Standard Applet Template
|
|
|
|
```javascript
|
|
// @applet-input-schema {"type":"object","properties":{"sku":{"type":"string","title":"Article SKU","description":"SKU to lookup"}},"required":["sku"]}
|
|
// @applet-output-schema sku:string;name:string;netPrice:number;tracked:boolean;brand:string
|
|
// @applet-description "Lorem ipsum"
|
|
|
|
module.exports = async function (applet_input) {
|
|
const sku = String(applet_input?.sku || '').trim();
|
|
if (!sku) {
|
|
throw new Error('Missing required parameter: sku');
|
|
}
|
|
|
|
const res = await fetch(`/article/${encodeURIComponent(sku)}`);
|
|
if (res.status === 404) throw new Error(`Article "${sku}" not found.`);
|
|
if (!res.ok) throw new Error(`GET /article/${sku} failed with status ${res.status}`);
|
|
|
|
const { article } = await res.json();
|
|
const price = (article.prices || [])[0];
|
|
|
|
console.log(`Found article ${article.sku}: ${article.name}`);
|
|
|
|
return [{
|
|
sku: article.sku,
|
|
name: article.name || '',
|
|
netPrice: price?.net ?? '',
|
|
tracked: Boolean(article.track_stock),
|
|
brand: article.brand?.name || ''
|
|
}];
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
# Common Implementation Patterns
|
|
|
|
### 1. Paging List Endpoints
|
|
Page using `offset` and `limit` until all items are loaded or `limit` is reached:
|
|
```javascript
|
|
const items = [];
|
|
let offset = 0;
|
|
while (true) {
|
|
const res = await fetch(`/order?offset=${offset}&limit=100`);
|
|
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
|
|
const data = await res.json();
|
|
const page = data.orders || [];
|
|
items.push(...page);
|
|
offset += page.length;
|
|
if (page.length === 0 || offset >= (data.total ?? items.length)) break;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
# Shipped Reference Applets
|
|
|
|
Pre-installed examples in the VFS root provide working reference code:
|
|
- `v1-article-lookup.applet.js`: Single entity lookup and key/value sheet generation.
|
|
- `v1-article-catalogue.applet.js`: Walking category trees and joining category article lists.
|
|
- `v1-category-inventory.applet.js`: Recursive tree flattening and depth calculation.
|
|
- `v1-delivery-register.applet.js`: Paged list fetching with batched detail requests.
|
|
- `v1-order-revenue-by-month.applet.js`: Paged order retrieval with calendar month aggregation.
|