@repogo/plugins 0.1.42 → 0.1.44
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 +213 -75
- package/dist/context.d.ts +20 -14
- package/dist/define.d.ts +54 -24
- package/dist/generated.d.ts +4 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +66 -20
- package/dist/remote.d.ts +7 -0
- package/dist/runtime.d.ts +15 -10
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +76 -60
- package/dist/workflow/contracts.d.ts +3 -1
- package/dist/workflow/definition.d.ts +1 -1
- package/dist/workflow/events.d.ts +1 -0
- package/dist/workflow-executor.js +139 -83
- package/package.json +2 -2
package/dist/testing.js
CHANGED
|
@@ -91,6 +91,7 @@ function carryable(value) {
|
|
|
91
91
|
|
|
92
92
|
// src/runtime.ts
|
|
93
93
|
import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
|
|
94
|
+
import { existsSync } from "fs";
|
|
94
95
|
import { mkdir as mkdir3, readdir as readdir3 } from "fs/promises";
|
|
95
96
|
import { join as join3 } from "path";
|
|
96
97
|
import { z as z5 } from "zod";
|
|
@@ -155,7 +156,7 @@ function recordRead(tag) {
|
|
|
155
156
|
}
|
|
156
157
|
function assertWritable(tag) {
|
|
157
158
|
if (current.getStore()?.readOnly) {
|
|
158
|
-
throw new MethodError("FORBIDDEN", `A
|
|
159
|
+
throw new MethodError("FORBIDDEN", `A read can't change data (it tried to write ${tag}).`);
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
function recordWrite(tag) {
|
|
@@ -953,32 +954,32 @@ function kvMethods(name, kv, schema) {
|
|
|
953
954
|
const entry = z.object({ key: z.string(), value: schema, expiresAt: z.string().nullable(), createdAt: z.string(), updatedAt: z.string() });
|
|
954
955
|
return {
|
|
955
956
|
get: {
|
|
956
|
-
|
|
957
|
+
effect: "read",
|
|
957
958
|
description: `Get one ${name} entry by key, or null`,
|
|
958
959
|
input: z.object({ key }),
|
|
959
960
|
output: entry.nullable(),
|
|
960
961
|
run: ({ key: key2 }) => kv.entry(key2)
|
|
961
962
|
},
|
|
962
963
|
list: {
|
|
963
|
-
|
|
964
|
+
effect: "read",
|
|
964
965
|
description: `List ${name} entries in key order, optionally by prefix`,
|
|
965
966
|
input: z.object({ prefix: z.string().optional(), limit }),
|
|
966
967
|
output: z.array(entry),
|
|
967
968
|
run: (input) => kv.list(input)
|
|
968
969
|
},
|
|
969
970
|
set: {
|
|
970
|
-
|
|
971
|
+
effect: "write",
|
|
971
972
|
description: `Set a ${name} key; ttl ("5m", "1h", "7d" or ms) makes it expire`,
|
|
972
973
|
input: z.object({ key, value: schema, ttl }),
|
|
973
974
|
output: entry,
|
|
974
975
|
run: ({ key: key2, value, ttl: ttl2 }) => kv.set(key2, value, { ttl: ttl2 })
|
|
975
976
|
},
|
|
976
977
|
delete: {
|
|
977
|
-
|
|
978
|
+
effect: "destructive",
|
|
978
979
|
description: `Delete a ${name} key`,
|
|
979
980
|
input: z.object({ key }),
|
|
980
981
|
output: z.null(),
|
|
981
|
-
|
|
982
|
+
approve: "Delete this key?",
|
|
982
983
|
run: async ({ key: key2 }) => (await kv.delete(key2), null)
|
|
983
984
|
}
|
|
984
985
|
};
|
|
@@ -988,45 +989,46 @@ function streamMethods(name, stream, schema) {
|
|
|
988
989
|
const line = z.object({ id: z.string(), key: z.string(), offset: z.number(), at: z.string(), data: schema });
|
|
989
990
|
return {
|
|
990
991
|
tail: {
|
|
991
|
-
|
|
992
|
+
effect: "read",
|
|
993
|
+
stream: true,
|
|
992
994
|
description: `Follow a ${name} key live: its last lines (100 unless last says), then each new one, until it ends`,
|
|
993
995
|
input: z.object({ key, from: z.number().int().min(0).optional(), last: z.number().int().positive().max(500).optional() }),
|
|
994
996
|
run: ({ key: key2, from, last }, signal) => stream.follow(key2, { from, last, signal })
|
|
995
997
|
},
|
|
996
998
|
read: {
|
|
997
|
-
|
|
999
|
+
effect: "read",
|
|
998
1000
|
description: `Read a ${name} key's lines from an offset`,
|
|
999
1001
|
input: z.object({ key, from: z.number().int().min(0).optional(), limit }),
|
|
1000
1002
|
output: z.array(line),
|
|
1001
1003
|
run: ({ key: key2, ...options }) => stream.read(key2, options)
|
|
1002
1004
|
},
|
|
1003
1005
|
keys: {
|
|
1004
|
-
|
|
1006
|
+
effect: "read",
|
|
1005
1007
|
description: `List ${name} keys, most recently written first`,
|
|
1006
1008
|
input: z.object({ limit }),
|
|
1007
1009
|
output: z.array(z.object({ key: z.string(), lines: z.number(), bytes: z.number(), endedAt: z.string().nullable(), updatedAt: z.string() })),
|
|
1008
1010
|
run: (input) => stream.keys(input)
|
|
1009
1011
|
},
|
|
1010
1012
|
append: {
|
|
1011
|
-
|
|
1013
|
+
effect: "write",
|
|
1012
1014
|
description: `Append a line to a ${name} key`,
|
|
1013
1015
|
input: z.object({ key, data: schema }),
|
|
1014
1016
|
output: line,
|
|
1015
1017
|
run: ({ key: key2, data }) => stream.append(key2, data)
|
|
1016
1018
|
},
|
|
1017
1019
|
end: {
|
|
1018
|
-
|
|
1020
|
+
effect: "write",
|
|
1019
1021
|
description: `End a ${name} key: followers stop and appends fail`,
|
|
1020
1022
|
input: z.object({ key }),
|
|
1021
1023
|
output: z.null(),
|
|
1022
1024
|
run: async ({ key: key2 }) => (await stream.end(key2), null)
|
|
1023
1025
|
},
|
|
1024
1026
|
delete: {
|
|
1025
|
-
|
|
1027
|
+
effect: "destructive",
|
|
1026
1028
|
description: `Delete a ${name} key and its lines`,
|
|
1027
1029
|
input: z.object({ key }),
|
|
1028
1030
|
output: z.null(),
|
|
1029
|
-
|
|
1031
|
+
approve: "Delete this stream and its lines?",
|
|
1030
1032
|
run: async ({ key: key2 }) => (await stream.delete(key2), null)
|
|
1031
1033
|
}
|
|
1032
1034
|
};
|
|
@@ -1051,28 +1053,28 @@ function bucketMethods(name, bucket, schema) {
|
|
|
1051
1053
|
const revision = z.number().int().optional();
|
|
1052
1054
|
return {
|
|
1053
1055
|
list: {
|
|
1054
|
-
|
|
1056
|
+
effect: "read",
|
|
1055
1057
|
description: `List ${name} files, newest first, optionally in one folder or with one status`,
|
|
1056
1058
|
input: z.object({ folder: z.string().optional(), status: status.optional(), limit }),
|
|
1057
1059
|
output: z.array(file),
|
|
1058
1060
|
run: (input) => bucket.list(input)
|
|
1059
1061
|
},
|
|
1060
1062
|
search: {
|
|
1061
|
-
|
|
1063
|
+
effect: "read",
|
|
1062
1064
|
description: `Search ${name} files by name, folder and meta`,
|
|
1063
1065
|
input: z.object({ query: z.string(), limit }),
|
|
1064
1066
|
output: z.array(file),
|
|
1065
1067
|
run: ({ query, limit: limit2 }) => bucket.search(query, { limit: limit2 })
|
|
1066
1068
|
},
|
|
1067
1069
|
get: {
|
|
1068
|
-
|
|
1070
|
+
effect: "read",
|
|
1069
1071
|
description: `Get one ${name} file by id, or null`,
|
|
1070
1072
|
input: z.object({ id }),
|
|
1071
1073
|
output: file.nullable(),
|
|
1072
1074
|
run: ({ id: id2 }) => bucket.get(id2)
|
|
1073
1075
|
},
|
|
1074
1076
|
put: {
|
|
1075
|
-
|
|
1077
|
+
effect: "write",
|
|
1076
1078
|
description: `Add a file to ${name}: its name, and text or an absolute path to copy`,
|
|
1077
1079
|
input: z.object({
|
|
1078
1080
|
name: z.string(),
|
|
@@ -1089,25 +1091,25 @@ function bucketMethods(name, bucket, schema) {
|
|
|
1089
1091
|
}
|
|
1090
1092
|
},
|
|
1091
1093
|
update: {
|
|
1092
|
-
|
|
1094
|
+
effect: "write",
|
|
1093
1095
|
description: `Change fields of a ${name} file's meta; null removes one`,
|
|
1094
1096
|
input: z.object({ id, meta: z.record(z.string(), z.unknown()), revision }),
|
|
1095
1097
|
output: file,
|
|
1096
1098
|
run: ({ id: id2, meta, revision: revision2 }) => bucket.update(id2, meta, { revision: revision2 })
|
|
1097
1099
|
},
|
|
1098
1100
|
move: {
|
|
1099
|
-
|
|
1101
|
+
effect: "write",
|
|
1100
1102
|
description: `Rename a ${name} file or move it to another folder`,
|
|
1101
1103
|
input: z.object({ id, name: z.string().optional(), folder: z.string().optional(), revision }),
|
|
1102
1104
|
output: file,
|
|
1103
1105
|
run: ({ id: id2, name: name2, folder, revision: revision2 }) => bucket.move(id2, { name: name2, folder }, { revision: revision2 })
|
|
1104
1106
|
},
|
|
1105
1107
|
delete: {
|
|
1106
|
-
|
|
1108
|
+
effect: "destructive",
|
|
1107
1109
|
description: `Delete a ${name} file and its meta`,
|
|
1108
1110
|
input: z.object({ id }),
|
|
1109
1111
|
output: z.null(),
|
|
1110
|
-
|
|
1112
|
+
approve: "Delete this file?",
|
|
1111
1113
|
run: async ({ id: id2 }) => (await bucket.delete(id2), null)
|
|
1112
1114
|
}
|
|
1113
1115
|
};
|
|
@@ -1501,7 +1503,7 @@ var RunMetadataSchema = z3.object({
|
|
|
1501
1503
|
root: z3.string().refine(isAbsolute2),
|
|
1502
1504
|
namespace: z3.string().regex(/^[a-z0-9][a-z0-9.-]*$/),
|
|
1503
1505
|
plugin: z3.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
1504
|
-
caller: z3.enum(["card", "agent", "cron", "code", "http"]),
|
|
1506
|
+
caller: z3.enum(["card", "agent", "cron", "code", "http", "device"]),
|
|
1505
1507
|
workflow: z3.string().regex(/^[a-z][a-zA-Z0-9_-]*$/),
|
|
1506
1508
|
input: z3.json(),
|
|
1507
1509
|
key: z3.string().nullable(),
|
|
@@ -1524,29 +1526,29 @@ function workflowMethods(plugin, namespace, name, definition, ctx, bridge, direc
|
|
|
1524
1526
|
return bridge({ operation, dir, plugin, namespace, workflow: name, root: ctx.root, caller: ctx.caller, input, settings: workflowSettings(definition) });
|
|
1525
1527
|
};
|
|
1526
1528
|
const id = z4.object({ id: z4.string().uuid() }).strict();
|
|
1527
|
-
const spec = (method,
|
|
1529
|
+
const spec = (method, effect, input, description, approve, output) => ({
|
|
1528
1530
|
name: `${plugin}.${name}.${method}`,
|
|
1529
1531
|
plugin,
|
|
1530
|
-
|
|
1532
|
+
effect,
|
|
1531
1533
|
input,
|
|
1532
1534
|
description,
|
|
1533
|
-
|
|
1535
|
+
approve,
|
|
1534
1536
|
output,
|
|
1535
1537
|
run: async (input2, raw) => {
|
|
1536
|
-
if (
|
|
1538
|
+
if (effect === "read")
|
|
1537
1539
|
recordRead(workflowTag(namespace));
|
|
1538
1540
|
const request = method === "start" ? { ...input2, input: raw.input } : input2;
|
|
1539
1541
|
return invoke(method, request);
|
|
1540
1542
|
}
|
|
1541
1543
|
});
|
|
1542
1544
|
return [
|
|
1543
|
-
spec("list", "
|
|
1544
|
-
spec("get", "
|
|
1545
|
-
spec("start", "
|
|
1546
|
-
spec("retry", "
|
|
1547
|
-
spec("restart", "
|
|
1548
|
-
spec("cancel", "
|
|
1549
|
-
spec("cancelAll", "
|
|
1545
|
+
spec("list", "read", z4.object({ limit: z4.number().int().min(1).max(100).default(20), status: z4.string().optional(), cursor: z4.string().optional() }).strict(), "List workflow runs", undefined, z4.object({ items: z4.array(RunSummarySchema), cursor: z4.string().nullable() })),
|
|
1546
|
+
spec("get", "read", id, "Get one workflow run"),
|
|
1547
|
+
spec("start", "write", z4.object({ input: definition.input, key: z4.string().min(1).max(200).optional() }).strict(), definition.description ?? "Start a durable workflow", definition.approve, RunSummarySchema),
|
|
1548
|
+
spec("retry", "write", id, "Retry a safely failed query step", undefined, RunSummarySchema),
|
|
1549
|
+
spec("restart", "write", id, "Start a run that needs attention over from the beginning, on the current code", undefined, RunSummarySchema),
|
|
1550
|
+
spec("cancel", "write", id, "Request cancellation of a workflow", undefined, RunSummarySchema),
|
|
1551
|
+
spec("cancelAll", "write", z4.object({ status: z4.string().optional() }).strict(), "Cancel every unfinished run, or only those in one status", undefined, z4.object({ cancelled: z4.number().int() }))
|
|
1550
1552
|
];
|
|
1551
1553
|
}
|
|
1552
1554
|
function workflowSettings(definition) {
|
|
@@ -1606,6 +1608,8 @@ class Runtime {
|
|
|
1606
1608
|
continue;
|
|
1607
1609
|
if (only && !only.includes(entry.name))
|
|
1608
1610
|
continue;
|
|
1611
|
+
if (!existsSync(join3(pluginsDir, entry.name, "app.ts")))
|
|
1612
|
+
continue;
|
|
1609
1613
|
const module = await logContext.run({ plugin: entry.name }, () => import(join3(pluginsDir, entry.name, "app.ts")));
|
|
1610
1614
|
await this.add(entry.name, module.default);
|
|
1611
1615
|
}
|
|
@@ -1731,9 +1735,11 @@ class Runtime {
|
|
|
1731
1735
|
this.register({
|
|
1732
1736
|
name: fullName,
|
|
1733
1737
|
plugin: name,
|
|
1734
|
-
|
|
1738
|
+
effect: method.effect,
|
|
1739
|
+
stream: method.stream,
|
|
1735
1740
|
description: method.description,
|
|
1736
|
-
|
|
1741
|
+
approve: method.approve,
|
|
1742
|
+
approveUpTo: method.approveUpTo,
|
|
1737
1743
|
http: method.http,
|
|
1738
1744
|
input: method.input,
|
|
1739
1745
|
output: method.output,
|
|
@@ -1764,6 +1770,10 @@ class Runtime {
|
|
|
1764
1770
|
})))));
|
|
1765
1771
|
}
|
|
1766
1772
|
async call(name, input = {}, options = {}) {
|
|
1773
|
+
if (options.signal) {
|
|
1774
|
+
const { signal, ...rest } = options;
|
|
1775
|
+
return this.callSignal.run(signal, () => this.call(name, input, rest));
|
|
1776
|
+
}
|
|
1767
1777
|
if (options.files) {
|
|
1768
1778
|
const { files, ...rest } = options;
|
|
1769
1779
|
const fields = input ?? {};
|
|
@@ -1787,17 +1797,17 @@ class Runtime {
|
|
|
1787
1797
|
return runWith(where, () => this.call(name, input, options));
|
|
1788
1798
|
if (where !== logContext.getStore())
|
|
1789
1799
|
return logContext.run(where, () => this.call(name, input, options));
|
|
1790
|
-
if ((options.expect === "
|
|
1800
|
+
if ((options.expect === "read" || currentAccess()?.readOnly) && method.effect !== "read") {
|
|
1791
1801
|
throw new MethodError("FORBIDDEN", `${name} changes data, so it only runs when the user acts.`);
|
|
1792
1802
|
}
|
|
1793
1803
|
const raw = input ?? {};
|
|
1794
1804
|
const parsed = method.input.parse(raw);
|
|
1795
|
-
if (method.
|
|
1796
|
-
throw new MethodError("CONFIRM",
|
|
1797
|
-
if (method.
|
|
1805
|
+
if (method.approve && (options.confirmed === false || workflowScope.getStore()))
|
|
1806
|
+
throw new MethodError("CONFIRM", fillQuestion(method.approve, parsed));
|
|
1807
|
+
if (method.stream)
|
|
1798
1808
|
return { result: await this.sample(name, input), reads: [], invalidates: [] };
|
|
1799
1809
|
const confirmed = this.callConfirmed.getStore() ?? false;
|
|
1800
|
-
const run = await tracked(method.
|
|
1810
|
+
const run = await tracked(method.effect === "read", (tags) => this.notifyInvalidated(tags), () => this.callConfirmed.run(confirmed || method.approve !== undefined, async () => {
|
|
1801
1811
|
const result = await method.run(parsed, raw);
|
|
1802
1812
|
await this.events.emit(name, { input: parsed, result });
|
|
1803
1813
|
return result;
|
|
@@ -1816,8 +1826,8 @@ class Runtime {
|
|
|
1816
1826
|
const method = this.methods.get(name);
|
|
1817
1827
|
if (!method)
|
|
1818
1828
|
throw new MethodError("NOT_FOUND", `There is no method ${name}.`);
|
|
1819
|
-
if (method.
|
|
1820
|
-
throw new MethodError("VALIDATION", `${name} is
|
|
1829
|
+
if (!method.stream)
|
|
1830
|
+
throw new MethodError("VALIDATION", `${name} is not a stream.`);
|
|
1821
1831
|
const raw = input ?? {};
|
|
1822
1832
|
const parsed = method.input.parse(raw);
|
|
1823
1833
|
const root = options.root ?? this.callRoot.getStore() ?? this.root;
|
|
@@ -1872,7 +1882,7 @@ class Runtime {
|
|
|
1872
1882
|
const statuses = [];
|
|
1873
1883
|
for (const entry of entries) {
|
|
1874
1884
|
const timezone = entry.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1875
|
-
const signature = `${entry.plugin}.${entry.method} ${entry.schedule} ${timezone}`;
|
|
1885
|
+
const signature = `${entry.plugin}.${entry.method} ${entry.schedule} ${timezone} ${entry.root ?? ""}`;
|
|
1876
1886
|
try {
|
|
1877
1887
|
const job = kept.get(signature) ?? this.cronJob(entry, timezone, signature);
|
|
1878
1888
|
jobs.push(job);
|
|
@@ -1904,9 +1914,9 @@ class Runtime {
|
|
|
1904
1914
|
const method = this.methods.get(name);
|
|
1905
1915
|
if (!method || method.plugin !== entry.plugin)
|
|
1906
1916
|
throw new Error(`${entry.method} isn't one of ${entry.plugin}'s methods`);
|
|
1907
|
-
if (method.
|
|
1908
|
-
throw new Error(`${name} is a stream; a cron runs a
|
|
1909
|
-
if (method.
|
|
1917
|
+
if (method.stream)
|
|
1918
|
+
throw new Error(`${name} is a stream; a cron runs a read or a write`);
|
|
1919
|
+
if (method.approve)
|
|
1910
1920
|
throw new Error(`${name} asks before it runs, so nothing may run it on a schedule`);
|
|
1911
1921
|
if (!method.input.safeParse({}).success)
|
|
1912
1922
|
throw new Error(`${name} needs input; a cron calls it with {}`);
|
|
@@ -1922,6 +1932,7 @@ class Runtime {
|
|
|
1922
1932
|
return {
|
|
1923
1933
|
signature,
|
|
1924
1934
|
plugin: entry.plugin,
|
|
1935
|
+
root: entry.root,
|
|
1925
1936
|
method: name,
|
|
1926
1937
|
schedule: entry.schedule,
|
|
1927
1938
|
timezone,
|
|
@@ -1946,7 +1957,7 @@ class Runtime {
|
|
|
1946
1957
|
job.running = true;
|
|
1947
1958
|
job.lastRunAt = now;
|
|
1948
1959
|
const startedAt = Date.now();
|
|
1949
|
-
started.push(this.call(job.method, {}, { caller: "cron" }).then(() => {
|
|
1960
|
+
started.push(this.call(job.method, {}, { caller: "cron", root: job.root }).then(() => {
|
|
1950
1961
|
job.lastError = null;
|
|
1951
1962
|
}, (error) => {
|
|
1952
1963
|
job.lastError = toCallError(error).message;
|
|
@@ -2007,9 +2018,11 @@ class Runtime {
|
|
|
2007
2018
|
description: plugin.description ?? null,
|
|
2008
2019
|
methods: [...this.methods.values()].filter((method) => method.plugin === name).map((method) => ({
|
|
2009
2020
|
name: method.name,
|
|
2010
|
-
|
|
2021
|
+
effect: method.effect,
|
|
2022
|
+
stream: method.stream ?? false,
|
|
2011
2023
|
description: method.description ?? null,
|
|
2012
|
-
|
|
2024
|
+
approve: method.approve ?? null,
|
|
2025
|
+
approveUpTo: method.approve ? method.approveUpTo ?? "full-access" : null,
|
|
2013
2026
|
http: method.http ?? null,
|
|
2014
2027
|
input: z5.toJSONSchema(method.input, { io: "input", unrepresentable: "any" }),
|
|
2015
2028
|
output: method.output ? z5.toJSONSchema(method.output, { io: "output", unrepresentable: "any" }) : null
|
|
@@ -2083,6 +2096,9 @@ class Runtime {
|
|
|
2083
2096
|
register(method) {
|
|
2084
2097
|
if (this.methods.has(method.name))
|
|
2085
2098
|
throw new Error(`${method.name} is defined twice; rename the method or the data`);
|
|
2099
|
+
if (method.effect === "destructive" && !method.approve) {
|
|
2100
|
+
throw new Error(`${method.name} is destructive, so it needs an approve question, such as { approve: "Delete {{id}}?" }`);
|
|
2101
|
+
}
|
|
2086
2102
|
this.methods.set(method.name, method);
|
|
2087
2103
|
}
|
|
2088
2104
|
registerGenerated(plugin, name, methods) {
|
|
@@ -2099,32 +2115,32 @@ class Runtime {
|
|
|
2099
2115
|
const prefix = `${plugin}.${name}`;
|
|
2100
2116
|
const id = z5.object({ id: z5.string() });
|
|
2101
2117
|
const record2 = schema.extend({ id: z5.string(), createdAt: z5.string(), updatedAt: z5.string() });
|
|
2102
|
-
const crud = (method,
|
|
2118
|
+
const crud = (method, effect, input, output, description, run, approve) => this.register({
|
|
2103
2119
|
name: `${prefix}.${method}`,
|
|
2104
2120
|
plugin,
|
|
2105
|
-
|
|
2121
|
+
effect,
|
|
2106
2122
|
description,
|
|
2107
|
-
|
|
2123
|
+
approve,
|
|
2108
2124
|
input,
|
|
2109
2125
|
output,
|
|
2110
2126
|
run: async (value, raw) => run(value, raw)
|
|
2111
2127
|
});
|
|
2112
|
-
crud("list", "
|
|
2128
|
+
crud("list", "read", z5.object({
|
|
2113
2129
|
limit: z5.number().int().positive().max(500).optional(),
|
|
2114
2130
|
offset: z5.number().int().min(0).optional(),
|
|
2115
2131
|
orderBy: z5.string().optional(),
|
|
2116
2132
|
order: z5.enum(["asc", "desc"]).optional()
|
|
2117
2133
|
}), z5.array(record2), `List ${name} records, newest first unless orderBy says`, (input) => collection.list(input));
|
|
2118
|
-
crud("get", "
|
|
2119
|
-
crud("create", "
|
|
2120
|
-
crud("update", "
|
|
2121
|
-
crud("delete", "
|
|
2134
|
+
crud("get", "read", id, record2.nullable(), `Get one ${name} record by id, or null`, (input) => collection.get(input.id));
|
|
2135
|
+
crud("create", "write", schema, record2, `Create a ${name} record`, (input) => collection.create(input));
|
|
2136
|
+
crud("update", "write", id.extend(schema.partial().shape), record2, `Update fields on a ${name} record`, ({ id: id2, ...fields }, raw) => collection.update(id2, Object.fromEntries(Object.entries(fields).filter(([key]) => Object.hasOwn(raw ?? {}, key)))));
|
|
2137
|
+
crud("delete", "destructive", id, z5.null(), `Delete a ${name} record by id`, async (input) => {
|
|
2122
2138
|
await collection.delete(input.id);
|
|
2123
2139
|
return null;
|
|
2124
2140
|
}, "Delete this record?");
|
|
2125
2141
|
}
|
|
2126
2142
|
}
|
|
2127
|
-
function
|
|
2143
|
+
function fillQuestion(question, input) {
|
|
2128
2144
|
const fields = input ?? {};
|
|
2129
2145
|
return question.replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g, (_, field2) => {
|
|
2130
2146
|
const value = Object.hasOwn(fields, field2) ? fields[field2] : undefined;
|
|
@@ -2147,9 +2163,9 @@ function cronState(job) {
|
|
|
2147
2163
|
function mcpFailure(server, tool, error) {
|
|
2148
2164
|
switch (error.code) {
|
|
2149
2165
|
case "NOT_READ_ONLY":
|
|
2150
|
-
return new MethodError("FORBIDDEN", `${server}.${tool} changes data, so it runs from a
|
|
2166
|
+
return new MethodError("FORBIDDEN", `${server}.${tool} changes data, so it runs from a write, not a read.`);
|
|
2151
2167
|
case "CONFIRM":
|
|
2152
|
-
return new MethodError("FORBIDDEN", `${server}.${tool} may destroy data, so it runs only from a method with
|
|
2168
|
+
return new MethodError("FORBIDDEN", `${server}.${tool} may destroy data, so it runs only from a method with an approve.`);
|
|
2153
2169
|
}
|
|
2154
2170
|
return new MethodError("INTERNAL", error.message);
|
|
2155
2171
|
}
|
|
@@ -35,7 +35,7 @@ export type WorkflowTarget = {
|
|
|
35
35
|
export type WorkflowRequest = WorkflowTarget & {
|
|
36
36
|
operation: "start" | "list" | "get" | "cancel" | "cancelAll" | "retry" | "restart";
|
|
37
37
|
input: Record<string, unknown>;
|
|
38
|
-
caller?: "card" | "agent" | "cron" | "code" | "http";
|
|
38
|
+
caller?: "card" | "agent" | "cron" | "code" | "http" | "device";
|
|
39
39
|
settings?: {
|
|
40
40
|
timeoutMs: number;
|
|
41
41
|
maxOfflineMs: number;
|
|
@@ -55,6 +55,7 @@ export declare const RunMetadataSchema: z.ZodObject<{
|
|
|
55
55
|
cron: "cron";
|
|
56
56
|
code: "code";
|
|
57
57
|
http: "http";
|
|
58
|
+
device: "device";
|
|
58
59
|
}>;
|
|
59
60
|
workflow: z.ZodString;
|
|
60
61
|
input: z.ZodJSONSchema;
|
|
@@ -79,6 +80,7 @@ export declare const RunLocationSchema: z.ZodObject<{
|
|
|
79
80
|
cron: "cron";
|
|
80
81
|
code: "code";
|
|
81
82
|
http: "http";
|
|
83
|
+
device: "device";
|
|
82
84
|
}>;
|
|
83
85
|
}, z.core.$strict>;
|
|
84
86
|
export type RunMetadata = z.infer<typeof RunMetadataSchema>;
|
|
@@ -23,7 +23,7 @@ export declare function workflowContext(base: Context, run: RunContext & {
|
|
|
23
23
|
export type WorkflowOptions<O extends z.ZodType = z.ZodType> = {
|
|
24
24
|
description?: string;
|
|
25
25
|
output?: O;
|
|
26
|
-
|
|
26
|
+
approve?: string;
|
|
27
27
|
timeout?: Duration;
|
|
28
28
|
maxOffline?: Duration;
|
|
29
29
|
retention?: Duration;
|