fyflow-plugin-sdk 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -2
- package/dist/index.d.ts +80 -0
- package/dist/index.js +116 -0
- package/package.json +37 -3
- package/src/index.ts +116 -0
package/README.md
CHANGED
|
@@ -1,3 +1,138 @@
|
|
|
1
|
-
#
|
|
1
|
+
# fyflow-plugin-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Types and `definePlugin` for writing **Plugins** for fyflow-app, the visual builder and Host for [fyflow-orchestration](https://www.npmjs.com/package/fyflow-orchestration) flows. A Plugin contributes Node Types (with the Workers that run their Tasks), gate types, Resource Group types and Resource Groups.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install --save-peer fyflow-plugin-sdk
|
|
7
|
+
npm install --save-dev fyflow-orchestration # its types, for type checking; the Host brings its own at run time
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## The contract
|
|
11
|
+
|
|
12
|
+
A Plugin is a module whose default export is a plain object. `definePlugin` returns its argument unchanged; it only gives you type checking. Depend on the SDK as a peer and do not bundle it: the Host provides it at run time (see below), and a copy of your own would only report its own `SDK_VERSION`, not the Host's.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { definePlugin } from "fyflow-plugin-sdk";
|
|
16
|
+
|
|
17
|
+
export default definePlugin({
|
|
18
|
+
id: "acme.signer", // stable and unique across Plugins
|
|
19
|
+
version: "1.0.0",
|
|
20
|
+
base: import.meta.url, // what Worker `url`s resolve against
|
|
21
|
+
workers: {
|
|
22
|
+
sign: {
|
|
23
|
+
url: "./sign.worker.js",
|
|
24
|
+
configSchema: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: { key: { type: "string", writeOnly: true } }, // a Profile secret
|
|
27
|
+
},
|
|
28
|
+
settingsSchema: { type: "object", properties: { field: { type: "string" } } },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
nodeTypes: {
|
|
32
|
+
Sign: {
|
|
33
|
+
worker: "sign",
|
|
34
|
+
inline: true,
|
|
35
|
+
groups: ["acme.signing"],
|
|
36
|
+
icon: "signature", // from the app's icon set, below
|
|
37
|
+
description: "Signs a field with HMAC.",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
groups: { "acme.signing": { type: "ConcurrentLimitGroup", config: { limit: 2 } } },
|
|
41
|
+
gateTypes: {
|
|
42
|
+
threshold: {
|
|
43
|
+
routes: ["below", "at-or-above"],
|
|
44
|
+
defaultRoute: "below",
|
|
45
|
+
fn: ({ key, settings }) => (Number(key) >= Number(settings.at) ? "at-or-above" : "below"),
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Field | What it is |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `workers` | Worker modules by key. A Worker module's default export is a class: its constructor gets the Profile's config, and `run({ settings, input }, context)` does one Task (see [Workers](#workers)). Schemas are JSON Schema in fyflow's subset. A Worker's `outputSchema`, what `run` returns (accepted by every version of the SDK, as fyflow's own Worker entry), is the form a person fills in to mark a failed Task done, and the paths the editor suggests for conditions on its Node's Edges and gates before any Run. |
|
|
54
|
+
| `nodeTypes` | Node Types by name, each naming one of this Plugin's `workers`, with fyflow's declaration fields (`inline`, `pool`, `config`, `settings` defaults, `retryPolicy`, `groups`), and, since 0.2.0, how the app shows it: an optional `icon` and a one-line `description`. |
|
|
55
|
+
| `gateTypes` | Gate types by name: their routes, default route, settings schema, and the decision `fn`. |
|
|
56
|
+
| `groupTypes` | Resource Group types by name: a `create(config, key)` factory for the scheduler's limit, and a `configSchema`. |
|
|
57
|
+
| `groups` | Resource Groups by key, of fyflow's built-in types (`ConcurrentLimitGroup`, `RateLimitGroup`, `KeyedRateLimitGroup`) or this Plugin's own. A Node Type may use its own Plugin's groups, and the built-in Plugin's `disk` group (below). |
|
|
58
|
+
|
|
59
|
+
Do not name a setting like a member of every JavaScript object (`constructor`, `toString`, `valueOf`, `hasOwnProperty`, `__proto__`). The Host hands Agents a Flow Definition's shape as a JSON Schema that includes your `settingsSchema`, and some JSON Schema validators misread such a property: they refuse, or throw, on a Flow that is valid.
|
|
60
|
+
|
|
61
|
+
Every name a Plugin contributes (Node Type, gate type, Resource Group type, Resource Group) must be unique across the Plugins a Host loads: the Plugin the Host saw first keeps a name, and a later one that repeats it is skipped with the reason.
|
|
62
|
+
|
|
63
|
+
## The Plugin directory
|
|
64
|
+
|
|
65
|
+
A Host loads Plugins from its Plugin directory (`<dataDir>/plugins` unless `pluginDir`, `FYFLOW_PLUGIN_DIR` or `--plugin-dir` says otherwise):
|
|
66
|
+
|
|
67
|
+
- a **folder** holding `plugin.ts` or `plugin.js`, or a package (`package.json` and its entry), as the Host lays out a Plugin installed from the app;
|
|
68
|
+
- a **package** named `fyflow-plugin-*` or `@scope/fyflow-plugin-*` under the directory's `node_modules`, installed with npm. A package must ship JavaScript: Node does not strip TypeScript under `node_modules`.
|
|
69
|
+
|
|
70
|
+
A Plugin's own imports resolve from the Plugin directory's `node_modules`, so `npm install` your other dependencies there (or bundle them). The SDK is the Host's to provide: on a server, `npm install fyflow-plugin-sdk` there (npm also installs it as your package's peer); the desktop app puts its own there by itself. Changes take effect when the Host is told to **Reload**.
|
|
71
|
+
|
|
72
|
+
## Peer range
|
|
73
|
+
|
|
74
|
+
Declare the SDK versions your Plugin works with as a peer dependency:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{ "peerDependencies": { "fyflow-plugin-sdk": "^0.2.0" } }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A Host implements its SDK version (`SDK_VERSION`, exported here) and the earlier ones it is compatible with (`SDK_COMPATIBLE`): a 0.3 Host still loads a Plugin declaring `^0.2.0` or `^0.1.0`. It skips, with the reason, a Plugin whose range excludes them all. Raise the range only for what a Host must do at run time: declare `^0.2.0` when you use `icon` or `description`. The Worker types of 0.3.0 vanish at run time (`import type`), so take 0.3 as a devDependency for them and leave the peer range as it was.
|
|
81
|
+
|
|
82
|
+
## Icons
|
|
83
|
+
|
|
84
|
+
A Node Type's `icon` names one of the app's icons, from [Lucide](https://lucide.dev/icons/): `activity`, `archive`, `bell`, `bot`, `box`, `braces`, `brain`, `calculator`, `calendar`, `camera`, `chart-bar`, `check`, `clock`, `cloud`, `code`, `cpu`, `credit-card`, `database`, `download`, `file`, `file-text`, `filter`, `flag`, `folder`, `git-branch`, `globe`, `hash`, `image`, `key`, `link`, `list`, `lock`, `mail`, `map-pin`, `message-square`, `package`, `pen-tool`, `play`, `plug`, `repeat`, `search`, `send`, `server`, `settings`, `shield`, `shopping-cart`, `signature`, `sparkles`, `table`, `tag`, `terminal`, `timer`, `trash`, `upload`, `user`, `users`, `wand-sparkles`, `webhook`, `zap`. A name outside the set is not an error: the Node Type shows its Kind's icon, and the Plugins page shows a warning.
|
|
85
|
+
|
|
86
|
+
## Workers
|
|
87
|
+
|
|
88
|
+
A Worker module's default export is a class. The Host builds each instance (one per worker of the pool: a thread, or on an inline pool an object on the Host's own thread) with the config of the Profile the pool is for, and reuses it across Tasks, so keep per-Task state out of fields. `run` gets the Task's envelope (the Node's `settings` and the Task input) and a context; what it returns is the Task's result, and throwing fails the Task with the error's message. Since 0.3.0 the SDK types all of it:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import type { TaskEnvelope, Worker, WorkerContext } from "fyflow-plugin-sdk";
|
|
92
|
+
|
|
93
|
+
type Config = { key?: string };
|
|
94
|
+
type Settings = { field?: string };
|
|
95
|
+
|
|
96
|
+
export default class Sign implements Worker<Settings> {
|
|
97
|
+
readonly #config: Config;
|
|
98
|
+
|
|
99
|
+
constructor(config: Config) {
|
|
100
|
+
this.#config = config;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async run({ settings, input }: TaskEnvelope<Settings>, context: WorkerContext) {
|
|
104
|
+
if (this.#config.key === undefined) throw new Error("No key: set `key` on this Node's Profile");
|
|
105
|
+
context.sendProgress(0, "signing"); // a fraction from 0 to 1, not a percentage
|
|
106
|
+
context.signal.throwIfAborted(); // aborted when the Task is Cancelled
|
|
107
|
+
const value = settings.field === undefined ? input : (input as Record<string, unknown>)[settings.field];
|
|
108
|
+
return { signed: await hmac(this.#config.key, value) }; // hmac: your own signing function
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`WorkerClass<Config, Settings, Input, Output>` types the class itself, constructor included. Check `context.signal` between steps, or pass it to anything that takes one (`fetch`, streams): a Worker that stops promptly frees its slot sooner, and what it returns after a Cancel is discarded. Use `import type`, so a Worker module needs nothing from the SDK at run time. The context offers what the Host shows (progress) and Cancel; fyflow's scheduler passes more, but the SDK promises only these.
|
|
114
|
+
|
|
115
|
+
## The `disk` group
|
|
116
|
+
|
|
117
|
+
The Host's built-in Plugin declares one Resource Group any Plugin's Node Types may name: `disk` (exported as `DISK_GROUP` since 0.3.0), a concurrency limit on Tasks that read or write the Host's disks. It is 4 unless the Host overrides it on its Resource Groups page. Name it when your Tasks do heavy disk work (copying, transcoding, indexing), so they share one budget with the Host's own file system Node Types instead of thrashing the disk beside them:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { DISK_GROUP, definePlugin } from "fyflow-plugin-sdk";
|
|
121
|
+
|
|
122
|
+
export default definePlugin({
|
|
123
|
+
// …
|
|
124
|
+
nodeTypes: { Transcode: { worker: "transcode", groups: [DISK_GROUP] } },
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Other Plugins' groups stay out of reach, so Disabling one Plugin never breaks another. `DISK_GROUP` is a value, so a Plugin that imports it needs the SDK at run time; writing `"disk"` instead works on any Host that has the group.
|
|
129
|
+
|
|
130
|
+
## Profiles and secrets
|
|
131
|
+
|
|
132
|
+
A Node Type's Worker config lives on the Host, not in Flows: a **Profile** is a named configuration of a Node Type, edited in the Host's UI with a form built from the Worker's `configSchema`, and a task Node chooses one. Every Node Type has a default Profile seeded from its declaration's `config`. A Profile's config is merged over the default's, which is merged over yours.
|
|
133
|
+
|
|
134
|
+
Mark a config property `writeOnly: true` to make it a **secret**: the Host never shows it again, only whether it is set, and redacts its value from errors and logs. Only properties of the config object can be secrets.
|
|
135
|
+
|
|
136
|
+
## Versioning
|
|
137
|
+
|
|
138
|
+
This package follows semver. `SDK_VERSION` always equals the package version.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { GateType, GroupInstance, GroupType, NodeTypeDeclaration, WorkerEntry } from "fyflow-orchestration";
|
|
2
|
+
export type { GateContext, GateType, GroupInstance, GroupType, JsonSchema, NodeTypeDeclaration, RetryPolicy, WorkerEntry, } from "fyflow-orchestration";
|
|
3
|
+
/**
|
|
4
|
+
* The SDK version this package is, and so the one a Host built with it implements. A Plugin whose
|
|
5
|
+
* `fyflow-plugin-sdk` peer range excludes it, and every one in `SDK_COMPATIBLE`, is not loaded. Kept equal to
|
|
6
|
+
* package.json's `version` (tested).
|
|
7
|
+
*/
|
|
8
|
+
export declare const SDK_VERSION = "0.3.0";
|
|
9
|
+
/**
|
|
10
|
+
* The earlier SDK versions this one is a superset of: nothing a Plugin written for them says has changed meaning,
|
|
11
|
+
* so a Host implements them too (ADR-0021). 0.2.0 only added optional fields, and 0.3.0 only types.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SDK_COMPATIBLE: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* The key of the `disk` Resource Group the Host's built-in Plugin declares (exported since 0.3.0): a concurrency
|
|
16
|
+
* limit (4 unless the Host overrides it) on Tasks that read or write the Host's disks. Any Plugin's Node Type may
|
|
17
|
+
* name it in `groups`, so a Plugin doing heavy disk work shares one budget with every other. Other Plugins' groups
|
|
18
|
+
* are out of reach.
|
|
19
|
+
*/
|
|
20
|
+
export declare const DISK_GROUP = "disk";
|
|
21
|
+
/** A Node Type as a Plugin declares it: fyflow's declaration, and how the app shows it (since 0.2.0). */
|
|
22
|
+
export interface PluginNodeType extends NodeTypeDeclaration {
|
|
23
|
+
/** An icon from the app's icon set (Lucide names, listed in the README); an unknown one shows its Kind's icon. */
|
|
24
|
+
icon?: string;
|
|
25
|
+
/** One line on what it does, shown in the palette. */
|
|
26
|
+
description?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface Plugin {
|
|
29
|
+
/** Stable, unique id, e.g. `acme.images`. */
|
|
30
|
+
id: string;
|
|
31
|
+
version: string;
|
|
32
|
+
/** What worker `url`s resolve against: the plugin module's own `import.meta.url`. */
|
|
33
|
+
base: string;
|
|
34
|
+
/**
|
|
35
|
+
* Worker modules by key; `url` is relative to `base`. A property of a `configSchema` marked `writeOnly: true` is
|
|
36
|
+
* a Profile secret: a Host never shows it (ADR-0016).
|
|
37
|
+
*/
|
|
38
|
+
workers?: Readonly<Record<string, WorkerEntry>>;
|
|
39
|
+
/**
|
|
40
|
+
* Node Types by name, each naming one of this plugin's `workers`; `groups` names Resource Groups (from this
|
|
41
|
+
* plugin's `groups`) every Task of the type acquires.
|
|
42
|
+
*/
|
|
43
|
+
nodeTypes?: Readonly<Record<string, PluginNodeType>>;
|
|
44
|
+
/** Gate types by name: the routes a gate of the type takes, and its decision. */
|
|
45
|
+
gateTypes?: Readonly<Record<string, GateType>>;
|
|
46
|
+
/** Resource Group types by name: a factory for the scheduler's limit, from a Resource Group's `config`. */
|
|
47
|
+
groupTypes?: Readonly<Record<string, GroupType>>;
|
|
48
|
+
/** Resource Groups by key: an instance of a group type (fyflow's built-ins or this plugin's), with its config. */
|
|
49
|
+
groups?: Readonly<Record<string, GroupInstance>>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* What a Worker's `run` is handed for one Task: the Node's `settings` (checked by the Worker's `settingsSchema`)
|
|
53
|
+
* and the Task input, after the Node's transform if it has one (since 0.3.0).
|
|
54
|
+
*/
|
|
55
|
+
export interface TaskEnvelope<Settings = Record<string, unknown>, Input = unknown> {
|
|
56
|
+
settings: Settings;
|
|
57
|
+
input: Input;
|
|
58
|
+
}
|
|
59
|
+
/** The second argument to a Worker's `run`: what it may do while its Task runs (since 0.3.0). */
|
|
60
|
+
export interface WorkerContext {
|
|
61
|
+
/** Reports how far the Task is, as a fraction from 0 to 1 (not a percentage), with an optional message. */
|
|
62
|
+
sendProgress(progress: number, message?: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Aborted when the Task is Cancelled. Pass it on to anything that takes one, or check it between steps: a Worker
|
|
65
|
+
* that stops promptly frees its slot sooner. What `run` returns after it aborts is discarded.
|
|
66
|
+
*/
|
|
67
|
+
readonly signal: AbortSignal;
|
|
68
|
+
}
|
|
69
|
+
/** One instance of a Worker, reused across the Tasks of its pool (since 0.3.0). */
|
|
70
|
+
export interface Worker<Settings = Record<string, unknown>, Input = unknown, Output = unknown> {
|
|
71
|
+
/** Does one Task. Throwing fails it; the error's message is what the Host shows. */
|
|
72
|
+
run(envelope: TaskEnvelope<Settings, Input>, context: WorkerContext): Output | Promise<Output>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A Worker module's default export: a class whose constructor gets the config of the Profile its pool was built
|
|
76
|
+
* for (the Worker's `configSchema` checks it), once per instance (since 0.3.0).
|
|
77
|
+
*/
|
|
78
|
+
export type WorkerClass<Config = Record<string, unknown>, Settings = Record<string, unknown>, Input = unknown, Output = unknown> = new (config: Config) => Worker<Settings, Input, Output>;
|
|
79
|
+
/** Identity at runtime; gives plugin authors type checking and a single import. */
|
|
80
|
+
export declare function definePlugin(plugin: Plugin): Plugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// The contract between fyflow-app and the code that extends it (ADR-0007, ADR-0015, ADR-0016). Published as
|
|
2
|
+
// `fyflow-plugin-sdk`: everything here is types but `definePlugin`, `SDK_VERSION`, `SDK_COMPATIBLE` and
|
|
3
|
+
// `DISK_GROUP`.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The SDK version this package is, and so the one a Host built with it implements. A Plugin whose
|
|
25
|
+
* `fyflow-plugin-sdk` peer range excludes it, and every one in `SDK_COMPATIBLE`, is not loaded. Kept equal to
|
|
26
|
+
* package.json's `version` (tested).
|
|
27
|
+
*/
|
|
28
|
+
export const SDK_VERSION = "0.3.0";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The earlier SDK versions this one is a superset of: nothing a Plugin written for them says has changed meaning,
|
|
32
|
+
* so a Host implements them too (ADR-0021). 0.2.0 only added optional fields, and 0.3.0 only types.
|
|
33
|
+
*/
|
|
34
|
+
export const SDK_COMPATIBLE = ["0.2.0", "0.1.0"];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The key of the `disk` Resource Group the Host's built-in Plugin declares (exported since 0.3.0): a concurrency
|
|
38
|
+
* limit (4 unless the Host overrides it) on Tasks that read or write the Host's disks. Any Plugin's Node Type may
|
|
39
|
+
* name it in `groups`, so a Plugin doing heavy disk work shares one budget with every other. Other Plugins' groups
|
|
40
|
+
* are out of reach.
|
|
41
|
+
*/
|
|
42
|
+
export const DISK_GROUP = "disk";
|
|
43
|
+
|
|
44
|
+
/** A Node Type as a Plugin declares it: fyflow's declaration, and how the app shows it (since 0.2.0). */
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* What a Worker's `run` is handed for one Task: the Node's `settings` (checked by the Worker's `settingsSchema`)
|
|
78
|
+
* and the Task input, after the Node's transform if it has one (since 0.3.0).
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
/** The second argument to a Worker's `run`: what it may do while its Task runs (since 0.3.0). */
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
/** One instance of a Worker, reused across the Tasks of its pool (since 0.3.0). */
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A Worker module's default export: a class whose constructor gets the config of the Profile its pool was built
|
|
104
|
+
* for (the Worker's `configSchema` checks it), once per instance (since 0.3.0).
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
/** Identity at runtime; gives plugin authors type checking and a single import. */
|
|
114
|
+
export function definePlugin(plugin ) {
|
|
115
|
+
return plugin;
|
|
116
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fyflow-plugin-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Types and definePlugin for fyflow-app Plugins: Node Types, Workers, gate types and Resource Groups",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"fyflow",
|
|
7
|
+
"fyflow-plugin",
|
|
8
|
+
"plugin",
|
|
9
|
+
"workflow"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"fyflow-orchestration": "^0.12.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"fyflow-orchestration": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"fyflow-orchestration": "0.12.0"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src",
|
|
36
|
+
"!src/**/*.test.ts"
|
|
37
|
+
],
|
|
38
|
+
"main": "./dist/index.js",
|
|
39
|
+
"types": "./dist/index.d.ts"
|
|
6
40
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// The contract between fyflow-app and the code that extends it (ADR-0007, ADR-0015, ADR-0016). Published as
|
|
2
|
+
// `fyflow-plugin-sdk`: everything here is types but `definePlugin`, `SDK_VERSION`, `SDK_COMPATIBLE` and
|
|
3
|
+
// `DISK_GROUP`.
|
|
4
|
+
import type {
|
|
5
|
+
GateType,
|
|
6
|
+
GroupInstance,
|
|
7
|
+
GroupType,
|
|
8
|
+
NodeTypeDeclaration,
|
|
9
|
+
WorkerEntry,
|
|
10
|
+
} from "fyflow-orchestration";
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
GateContext,
|
|
14
|
+
GateType,
|
|
15
|
+
GroupInstance,
|
|
16
|
+
GroupType,
|
|
17
|
+
JsonSchema,
|
|
18
|
+
NodeTypeDeclaration,
|
|
19
|
+
RetryPolicy,
|
|
20
|
+
WorkerEntry,
|
|
21
|
+
} from "fyflow-orchestration";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The SDK version this package is, and so the one a Host built with it implements. A Plugin whose
|
|
25
|
+
* `fyflow-plugin-sdk` peer range excludes it, and every one in `SDK_COMPATIBLE`, is not loaded. Kept equal to
|
|
26
|
+
* package.json's `version` (tested).
|
|
27
|
+
*/
|
|
28
|
+
export const SDK_VERSION = "0.3.0";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The earlier SDK versions this one is a superset of: nothing a Plugin written for them says has changed meaning,
|
|
32
|
+
* so a Host implements them too (ADR-0021). 0.2.0 only added optional fields, and 0.3.0 only types.
|
|
33
|
+
*/
|
|
34
|
+
export const SDK_COMPATIBLE: readonly string[] = ["0.2.0", "0.1.0"];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The key of the `disk` Resource Group the Host's built-in Plugin declares (exported since 0.3.0): a concurrency
|
|
38
|
+
* limit (4 unless the Host overrides it) on Tasks that read or write the Host's disks. Any Plugin's Node Type may
|
|
39
|
+
* name it in `groups`, so a Plugin doing heavy disk work shares one budget with every other. Other Plugins' groups
|
|
40
|
+
* are out of reach.
|
|
41
|
+
*/
|
|
42
|
+
export const DISK_GROUP = "disk";
|
|
43
|
+
|
|
44
|
+
/** A Node Type as a Plugin declares it: fyflow's declaration, and how the app shows it (since 0.2.0). */
|
|
45
|
+
export interface PluginNodeType extends NodeTypeDeclaration {
|
|
46
|
+
/** An icon from the app's icon set (Lucide names, listed in the README); an unknown one shows its Kind's icon. */
|
|
47
|
+
icon?: string;
|
|
48
|
+
/** One line on what it does, shown in the palette. */
|
|
49
|
+
description?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Plugin {
|
|
53
|
+
/** Stable, unique id, e.g. `acme.images`. */
|
|
54
|
+
id: string;
|
|
55
|
+
version: string;
|
|
56
|
+
/** What worker `url`s resolve against: the plugin module's own `import.meta.url`. */
|
|
57
|
+
base: string;
|
|
58
|
+
/**
|
|
59
|
+
* Worker modules by key; `url` is relative to `base`. A property of a `configSchema` marked `writeOnly: true` is
|
|
60
|
+
* a Profile secret: a Host never shows it (ADR-0016).
|
|
61
|
+
*/
|
|
62
|
+
workers?: Readonly<Record<string, WorkerEntry>>;
|
|
63
|
+
/**
|
|
64
|
+
* Node Types by name, each naming one of this plugin's `workers`; `groups` names Resource Groups (from this
|
|
65
|
+
* plugin's `groups`) every Task of the type acquires.
|
|
66
|
+
*/
|
|
67
|
+
nodeTypes?: Readonly<Record<string, PluginNodeType>>;
|
|
68
|
+
/** Gate types by name: the routes a gate of the type takes, and its decision. */
|
|
69
|
+
gateTypes?: Readonly<Record<string, GateType>>;
|
|
70
|
+
/** Resource Group types by name: a factory for the scheduler's limit, from a Resource Group's `config`. */
|
|
71
|
+
groupTypes?: Readonly<Record<string, GroupType>>;
|
|
72
|
+
/** Resource Groups by key: an instance of a group type (fyflow's built-ins or this plugin's), with its config. */
|
|
73
|
+
groups?: Readonly<Record<string, GroupInstance>>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* What a Worker's `run` is handed for one Task: the Node's `settings` (checked by the Worker's `settingsSchema`)
|
|
78
|
+
* and the Task input, after the Node's transform if it has one (since 0.3.0).
|
|
79
|
+
*/
|
|
80
|
+
export interface TaskEnvelope<Settings = Record<string, unknown>, Input = unknown> {
|
|
81
|
+
settings: Settings;
|
|
82
|
+
input: Input;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The second argument to a Worker's `run`: what it may do while its Task runs (since 0.3.0). */
|
|
86
|
+
export interface WorkerContext {
|
|
87
|
+
/** Reports how far the Task is, as a fraction from 0 to 1 (not a percentage), with an optional message. */
|
|
88
|
+
sendProgress(progress: number, message?: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Aborted when the Task is Cancelled. Pass it on to anything that takes one, or check it between steps: a Worker
|
|
91
|
+
* that stops promptly frees its slot sooner. What `run` returns after it aborts is discarded.
|
|
92
|
+
*/
|
|
93
|
+
readonly signal: AbortSignal;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** One instance of a Worker, reused across the Tasks of its pool (since 0.3.0). */
|
|
97
|
+
export interface Worker<Settings = Record<string, unknown>, Input = unknown, Output = unknown> {
|
|
98
|
+
/** Does one Task. Throwing fails it; the error's message is what the Host shows. */
|
|
99
|
+
run(envelope: TaskEnvelope<Settings, Input>, context: WorkerContext): Output | Promise<Output>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A Worker module's default export: a class whose constructor gets the config of the Profile its pool was built
|
|
104
|
+
* for (the Worker's `configSchema` checks it), once per instance (since 0.3.0).
|
|
105
|
+
*/
|
|
106
|
+
export type WorkerClass<
|
|
107
|
+
Config = Record<string, unknown>,
|
|
108
|
+
Settings = Record<string, unknown>,
|
|
109
|
+
Input = unknown,
|
|
110
|
+
Output = unknown,
|
|
111
|
+
> = new (config: Config) => Worker<Settings, Input, Output>;
|
|
112
|
+
|
|
113
|
+
/** Identity at runtime; gives plugin authors type checking and a single import. */
|
|
114
|
+
export function definePlugin(plugin: Plugin): Plugin {
|
|
115
|
+
return plugin;
|
|
116
|
+
}
|