openclaw-brokerkit 0.1.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 +197 -0
- package/dist/index.js +113 -0
- package/dist/src/client.js +152 -0
- package/dist/src/commands.js +101 -0
- package/dist/src/config.js +108 -0
- package/dist/src/errors.js +15 -0
- package/dist/src/generated/operator-v1.js +57 -0
- package/dist/src/http.js +279 -0
- package/dist/src/operator-v1.js +92 -0
- package/dist/src/runtime.js +312 -0
- package/dist/src/store.js +235 -0
- package/dist/src/types.js +1 -0
- package/dist/ui/assets/index-8w0_bcoC.js +143 -0
- package/dist/ui/assets/index-D8Ypbs3j.css +1 -0
- package/dist/ui/index.html +13 -0
- package/openclaw.plugin.json +98 -0
- package/package.json +77 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Generated by scripts/generate-operator-v1.mjs. Do not edit.
|
|
2
|
+
export const OPERATOR_V1_SCHEMA_SHA256 = "a4510c3d0e1ddf32dfb8e8bd684677ade3f3d6541912d42720af8c43ef11c2eb";
|
|
3
|
+
export const operatorV1 = {
|
|
4
|
+
apiVersion: "brokerkit.io/operator/v1",
|
|
5
|
+
statuses: [
|
|
6
|
+
"pending",
|
|
7
|
+
"active",
|
|
8
|
+
"denied",
|
|
9
|
+
"canceled",
|
|
10
|
+
"expired",
|
|
11
|
+
"consumed",
|
|
12
|
+
"revoked",
|
|
13
|
+
],
|
|
14
|
+
actions: ["approve", "deny", "cancel", "revoke"],
|
|
15
|
+
risks: ["unknown", "low", "medium", "high", "critical"],
|
|
16
|
+
eventKinds: [
|
|
17
|
+
"request.created",
|
|
18
|
+
"request.approved",
|
|
19
|
+
"request.denied",
|
|
20
|
+
"request.canceled",
|
|
21
|
+
"request.expired",
|
|
22
|
+
"request.consumed",
|
|
23
|
+
"request.revoked",
|
|
24
|
+
"request.updated",
|
|
25
|
+
],
|
|
26
|
+
errorCodes: [
|
|
27
|
+
"invalid_request",
|
|
28
|
+
"unauthorized",
|
|
29
|
+
"forbidden",
|
|
30
|
+
"not_found",
|
|
31
|
+
"revision_conflict",
|
|
32
|
+
"invalid_transition",
|
|
33
|
+
"constraint_exceeded",
|
|
34
|
+
"idempotency_conflict",
|
|
35
|
+
"cursor_expired",
|
|
36
|
+
"rate_limited",
|
|
37
|
+
"temporarily_unavailable",
|
|
38
|
+
"internal_error",
|
|
39
|
+
],
|
|
40
|
+
limits: {
|
|
41
|
+
id: 128,
|
|
42
|
+
requester: 80,
|
|
43
|
+
operation: 500,
|
|
44
|
+
reason: 2000,
|
|
45
|
+
actor: 200,
|
|
46
|
+
title: 200,
|
|
47
|
+
summary: 2000,
|
|
48
|
+
facts: 20,
|
|
49
|
+
factLabel: 80,
|
|
50
|
+
factValue: 500,
|
|
51
|
+
cursor: 1024,
|
|
52
|
+
page: 100,
|
|
53
|
+
idempotencyKey: 200,
|
|
54
|
+
errorMessage: 500,
|
|
55
|
+
correlationId: 200,
|
|
56
|
+
},
|
|
57
|
+
};
|
package/dist/src/http.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { createReadStream, existsSync, statSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pluginErrorCode } from "./errors.js";
|
|
5
|
+
export function createHttpHandler(runtime, rootDir, capability) {
|
|
6
|
+
const uiDir = path.join(rootDir, "dist", "ui");
|
|
7
|
+
const rateLimit = createRateLimiter(120, 60_000);
|
|
8
|
+
return async (req, res) => {
|
|
9
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
10
|
+
if (url.pathname.startsWith("/plugins/brokerkit/api/v1/"))
|
|
11
|
+
return handleApi(req, res, url, runtime, capability, rateLimit);
|
|
12
|
+
return serveUi(req, res, url, uiDir);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
async function handleApi(req, res, url, runtime, capability, rateLimit) {
|
|
16
|
+
if (!runtime || !capability)
|
|
17
|
+
return json(res, 404, { error: { code: "not_found" } });
|
|
18
|
+
if (!authorized(req.headers.authorization, capability))
|
|
19
|
+
return json(res, 401, { error: { code: "not_authorized" } });
|
|
20
|
+
if (req.headers.origin !== "null")
|
|
21
|
+
return json(res, 403, { error: { code: "not_authorized" } });
|
|
22
|
+
if (url.search)
|
|
23
|
+
return json(res, 400, { error: { code: "invalid_input" } });
|
|
24
|
+
if (!rateLimit(req.socket.remoteAddress ?? "local"))
|
|
25
|
+
return json(res, 429, { error: { code: "rate_limited" } });
|
|
26
|
+
try {
|
|
27
|
+
if (req.method === "GET" &&
|
|
28
|
+
url.pathname === "/plugins/brokerkit/api/v1/snapshot")
|
|
29
|
+
return json(res, 200, runtime().snapshot());
|
|
30
|
+
const detail = url.pathname.match(/^\/plugins\/brokerkit\/api\/v1\/requests\/([^/]+)$/);
|
|
31
|
+
if (req.method === "GET" && detail) {
|
|
32
|
+
const handle = decodeHandle(detail[1]);
|
|
33
|
+
const request = runtime()
|
|
34
|
+
.snapshot()
|
|
35
|
+
.requests.find((value) => value.handle === handle);
|
|
36
|
+
return request
|
|
37
|
+
? json(res, 200, request)
|
|
38
|
+
: json(res, 404, { error: { code: "request_not_found" } });
|
|
39
|
+
}
|
|
40
|
+
const decision = url.pathname.match(/^\/plugins\/brokerkit\/api\/v1\/requests\/([^/]+)\/(approve|deny|cancel|revoke)$/);
|
|
41
|
+
if (req.method === "POST" && decision) {
|
|
42
|
+
if (!isJSON(req.headers["content-type"]))
|
|
43
|
+
throw new Error("invalid_input");
|
|
44
|
+
const input = parseDecisionInput(await readJSON(req), decision[2]);
|
|
45
|
+
const result = await runtime().decide(decodeHandle(decision[1]), decision[2], input.expectedRevision, "openclaw:control-ui", input.options);
|
|
46
|
+
return json(res, 200, result);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const mapped = mapHttpError(error);
|
|
51
|
+
return json(res, mapped.status, { error: { code: mapped.code } });
|
|
52
|
+
}
|
|
53
|
+
return json(res, 404, { error: { code: "not_found" } });
|
|
54
|
+
}
|
|
55
|
+
function serveUi(req, res, url, uiDir) {
|
|
56
|
+
if (url.search || !url.pathname.startsWith("/plugins/brokerkit/ui/"))
|
|
57
|
+
return json(res, 404, { error: { code: "not_found" } });
|
|
58
|
+
if (req.method !== "GET")
|
|
59
|
+
return json(res, 405, { error: { code: "invalid_input" } });
|
|
60
|
+
const relative = url.pathname.replace(/^\/plugins\/brokerkit\/ui\/?/, "") || "index.html";
|
|
61
|
+
if (relative.includes(".."))
|
|
62
|
+
return json(res, 404, {});
|
|
63
|
+
let file = path.join(uiDir, relative);
|
|
64
|
+
if (!existsSync(file) || !statSync(file).isFile())
|
|
65
|
+
file = path.join(uiDir, "index.html");
|
|
66
|
+
res.statusCode = 200;
|
|
67
|
+
res.setHeader("content-type", contentType(file));
|
|
68
|
+
res.setHeader("cache-control", file.endsWith("index.html")
|
|
69
|
+
? "no-store"
|
|
70
|
+
: "public, max-age=31536000, immutable");
|
|
71
|
+
res.setHeader("content-security-policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'self'");
|
|
72
|
+
securityHeaders(res);
|
|
73
|
+
createReadStream(file).pipe(res);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
function authorized(header, capability) {
|
|
77
|
+
const token = header?.startsWith("Bearer ") ? header.slice(7) : "";
|
|
78
|
+
const left = Buffer.from(token);
|
|
79
|
+
const right = Buffer.from(capability);
|
|
80
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
81
|
+
}
|
|
82
|
+
async function readJSON(req) {
|
|
83
|
+
const chunks = [];
|
|
84
|
+
let size = 0;
|
|
85
|
+
for await (const chunk of req) {
|
|
86
|
+
const value = Buffer.from(chunk);
|
|
87
|
+
size += value.length;
|
|
88
|
+
if (size > 16_384)
|
|
89
|
+
throw new Error("invalid_input");
|
|
90
|
+
chunks.push(value);
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks));
|
|
94
|
+
assertUniqueObjectKeys(text);
|
|
95
|
+
const value = JSON.parse(text);
|
|
96
|
+
if (!record(value))
|
|
97
|
+
throw new Error("invalid_input");
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new Error("invalid_input");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function assertUniqueObjectKeys(text) {
|
|
105
|
+
let index = 0;
|
|
106
|
+
const whitespace = () => {
|
|
107
|
+
while (/\s/u.test(text[index] ?? ""))
|
|
108
|
+
index += 1;
|
|
109
|
+
};
|
|
110
|
+
const string = () => {
|
|
111
|
+
const start = index;
|
|
112
|
+
if (text[index] !== '"')
|
|
113
|
+
throw new Error("invalid_input");
|
|
114
|
+
index += 1;
|
|
115
|
+
while (index < text.length) {
|
|
116
|
+
if (text[index] === "\\") {
|
|
117
|
+
index += 2;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (text[index] === '"') {
|
|
121
|
+
index += 1;
|
|
122
|
+
return JSON.parse(text.slice(start, index));
|
|
123
|
+
}
|
|
124
|
+
index += 1;
|
|
125
|
+
}
|
|
126
|
+
throw new Error("invalid_input");
|
|
127
|
+
};
|
|
128
|
+
const value = (depth) => {
|
|
129
|
+
if (depth > 32)
|
|
130
|
+
throw new Error("invalid_input");
|
|
131
|
+
whitespace();
|
|
132
|
+
if (text[index] === '"')
|
|
133
|
+
return void string();
|
|
134
|
+
if (text[index] === "{") {
|
|
135
|
+
index += 1;
|
|
136
|
+
whitespace();
|
|
137
|
+
const keys = new Set();
|
|
138
|
+
if (text[index] === "}")
|
|
139
|
+
return void (index += 1);
|
|
140
|
+
for (;;) {
|
|
141
|
+
whitespace();
|
|
142
|
+
const key = string();
|
|
143
|
+
if (keys.has(key))
|
|
144
|
+
throw new Error("invalid_input");
|
|
145
|
+
keys.add(key);
|
|
146
|
+
whitespace();
|
|
147
|
+
if (text[index] !== ":")
|
|
148
|
+
throw new Error("invalid_input");
|
|
149
|
+
index += 1;
|
|
150
|
+
value(depth + 1);
|
|
151
|
+
whitespace();
|
|
152
|
+
if (text[index] === "}")
|
|
153
|
+
return void (index += 1);
|
|
154
|
+
if (text[index] !== ",")
|
|
155
|
+
throw new Error("invalid_input");
|
|
156
|
+
index += 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (text[index] === "[") {
|
|
160
|
+
index += 1;
|
|
161
|
+
whitespace();
|
|
162
|
+
if (text[index] === "]")
|
|
163
|
+
return void (index += 1);
|
|
164
|
+
for (;;) {
|
|
165
|
+
value(depth + 1);
|
|
166
|
+
whitespace();
|
|
167
|
+
if (text[index] === "]")
|
|
168
|
+
return void (index += 1);
|
|
169
|
+
if (text[index] !== ",")
|
|
170
|
+
throw new Error("invalid_input");
|
|
171
|
+
index += 1;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const start = index;
|
|
175
|
+
while (index < text.length && !/[\s,}\]]/u.test(text[index] ?? ""))
|
|
176
|
+
index += 1;
|
|
177
|
+
if (start === index)
|
|
178
|
+
throw new Error("invalid_input");
|
|
179
|
+
JSON.parse(text.slice(start, index));
|
|
180
|
+
};
|
|
181
|
+
value(0);
|
|
182
|
+
whitespace();
|
|
183
|
+
if (index !== text.length)
|
|
184
|
+
throw new Error("invalid_input");
|
|
185
|
+
}
|
|
186
|
+
function json(res, status, value) {
|
|
187
|
+
res.statusCode = status;
|
|
188
|
+
res.setHeader("content-type", "application/json");
|
|
189
|
+
res.setHeader("cache-control", "no-store");
|
|
190
|
+
res.setHeader("content-security-policy", "default-src 'none'");
|
|
191
|
+
securityHeaders(res);
|
|
192
|
+
res.end(JSON.stringify(value));
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
function securityHeaders(res) {
|
|
196
|
+
res.setHeader("x-content-type-options", "nosniff");
|
|
197
|
+
res.setHeader("referrer-policy", "no-referrer");
|
|
198
|
+
res.setHeader("cross-origin-resource-policy", "same-origin");
|
|
199
|
+
}
|
|
200
|
+
function decodeHandle(value) {
|
|
201
|
+
if (!value || value.length > 256)
|
|
202
|
+
throw new Error("invalid_input");
|
|
203
|
+
const decoded = decodeURIComponent(value);
|
|
204
|
+
if (!/^[A-Za-z0-9_-]{22,256}$/u.test(decoded))
|
|
205
|
+
throw new Error("invalid_input");
|
|
206
|
+
return decoded;
|
|
207
|
+
}
|
|
208
|
+
function isJSON(value) {
|
|
209
|
+
return Boolean(value && /^application\/json(?:\s*;|$)/iu.test(value));
|
|
210
|
+
}
|
|
211
|
+
function mapHttpError(error) {
|
|
212
|
+
const code = pluginErrorCode(error);
|
|
213
|
+
if (code === "invalid_input")
|
|
214
|
+
return { code, status: 400 };
|
|
215
|
+
if (code === "request_not_found")
|
|
216
|
+
return { code, status: 404 };
|
|
217
|
+
if (code === "revision_stale" || code === "request_terminal")
|
|
218
|
+
return { code, status: 409 };
|
|
219
|
+
if (code === "action_not_allowed")
|
|
220
|
+
return { code, status: 422 };
|
|
221
|
+
if (code === "source_unavailable")
|
|
222
|
+
return { code, status: 503 };
|
|
223
|
+
return { code: "internal_error", status: 500 };
|
|
224
|
+
}
|
|
225
|
+
function parseDecisionInput(body, action) {
|
|
226
|
+
if (Object.keys(body).some((key) => key !== "expectedRevision" && key !== "reason" && key !== "constraints") ||
|
|
227
|
+
!positiveSafeInteger(body.expectedRevision) ||
|
|
228
|
+
(body.reason !== undefined && typeof body.reason !== "string"))
|
|
229
|
+
throw new Error("invalid_input");
|
|
230
|
+
const options = {};
|
|
231
|
+
if (typeof body.reason === "string" && body.reason.trim()) {
|
|
232
|
+
const reason = body.reason.trim();
|
|
233
|
+
if (Buffer.byteLength(reason, "utf8") > 2000)
|
|
234
|
+
throw new Error("invalid_input");
|
|
235
|
+
options.reason = reason;
|
|
236
|
+
}
|
|
237
|
+
if (body.constraints !== undefined) {
|
|
238
|
+
if (action !== "approve" || !record(body.constraints))
|
|
239
|
+
throw new Error("invalid_input");
|
|
240
|
+
const constraints = body.constraints;
|
|
241
|
+
if (Object.keys(constraints).some((key) => key !== "durationSeconds" && key !== "maxUses") ||
|
|
242
|
+
!positiveSafeInteger(constraints.durationSeconds) ||
|
|
243
|
+
!positiveSafeInteger(constraints.maxUses))
|
|
244
|
+
throw new Error("invalid_input");
|
|
245
|
+
options.constraints = {
|
|
246
|
+
duration_seconds: constraints.durationSeconds,
|
|
247
|
+
max_uses: constraints.maxUses,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return { expectedRevision: body.expectedRevision, options };
|
|
251
|
+
}
|
|
252
|
+
function record(value) {
|
|
253
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
254
|
+
}
|
|
255
|
+
function positiveSafeInteger(value) {
|
|
256
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
257
|
+
}
|
|
258
|
+
function createRateLimiter(limit, windowMs) {
|
|
259
|
+
const entries = new Map();
|
|
260
|
+
return (key) => {
|
|
261
|
+
const now = Date.now();
|
|
262
|
+
const current = entries.get(key);
|
|
263
|
+
if (!current || current.resetAt <= now) {
|
|
264
|
+
entries.set(key, { count: 1, resetAt: now + windowMs });
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
current.count += 1;
|
|
268
|
+
return current.count <= limit;
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function contentType(file) {
|
|
272
|
+
if (file.endsWith(".js"))
|
|
273
|
+
return "text/javascript";
|
|
274
|
+
if (file.endsWith(".css"))
|
|
275
|
+
return "text/css";
|
|
276
|
+
if (file.endsWith(".svg"))
|
|
277
|
+
return "image/svg+xml";
|
|
278
|
+
return "text/html; charset=utf-8";
|
|
279
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { operatorV1 } from "./generated/operator-v1.js";
|
|
3
|
+
const timestamp = z.iso.datetime({ offset: true });
|
|
4
|
+
const nonNegativeInteger = z.number().int().nonnegative().safe();
|
|
5
|
+
const positiveInteger = z.number().int().positive().safe();
|
|
6
|
+
const status = z.enum(operatorV1.statuses);
|
|
7
|
+
const action = z.enum(operatorV1.actions);
|
|
8
|
+
const fact = z.object({
|
|
9
|
+
label: z.string().min(1).max(operatorV1.limits.factLabel),
|
|
10
|
+
value: z.string().min(1).max(operatorV1.limits.factValue),
|
|
11
|
+
});
|
|
12
|
+
const presentation = z.object({
|
|
13
|
+
risk: z.enum(operatorV1.risks),
|
|
14
|
+
title: z.string().min(1).max(operatorV1.limits.title),
|
|
15
|
+
summary: z.string().max(operatorV1.limits.summary).optional(),
|
|
16
|
+
facts: z.array(fact).max(operatorV1.limits.facts).optional(),
|
|
17
|
+
});
|
|
18
|
+
export const brokerRequestSchema = z.object({
|
|
19
|
+
id: z.string().min(1).max(operatorV1.limits.id),
|
|
20
|
+
revision: positiveInteger,
|
|
21
|
+
requester: z.string().min(1).max(operatorV1.limits.requester),
|
|
22
|
+
operation: z.string().min(1).max(operatorV1.limits.operation),
|
|
23
|
+
status,
|
|
24
|
+
requested_at: timestamp,
|
|
25
|
+
pending_expires_at: timestamp.optional(),
|
|
26
|
+
active_expires_at: timestamp.optional(),
|
|
27
|
+
requested_duration_seconds: positiveInteger,
|
|
28
|
+
requested_max_uses: positiveInteger,
|
|
29
|
+
granted_max_uses: positiveInteger.nullable(),
|
|
30
|
+
used_count: nonNegativeInteger,
|
|
31
|
+
request_reason: z.string().max(operatorV1.limits.reason).optional(),
|
|
32
|
+
decided_at: timestamp.optional(),
|
|
33
|
+
decided_by: z.string().max(operatorV1.limits.actor).optional(),
|
|
34
|
+
decided_on_behalf_of: z.string().max(operatorV1.limits.actor).optional(),
|
|
35
|
+
decision_reason: z.string().max(operatorV1.limits.reason).optional(),
|
|
36
|
+
presentation,
|
|
37
|
+
presentation_unavailable: z.boolean().optional(),
|
|
38
|
+
allowed_actions: z
|
|
39
|
+
.array(action)
|
|
40
|
+
.max(operatorV1.actions.length)
|
|
41
|
+
.refine((value) => new Set(value).size === value.length),
|
|
42
|
+
approval_bounds: z
|
|
43
|
+
.object({
|
|
44
|
+
max_duration_seconds: nonNegativeInteger,
|
|
45
|
+
max_uses: positiveInteger,
|
|
46
|
+
})
|
|
47
|
+
.optional(),
|
|
48
|
+
});
|
|
49
|
+
const requestPageSchema = z.object({
|
|
50
|
+
requests: z.array(brokerRequestSchema).max(operatorV1.limits.page),
|
|
51
|
+
next_cursor: z.string().min(1).max(operatorV1.limits.cursor).optional(),
|
|
52
|
+
event_cursor: z.string().min(1).max(operatorV1.limits.cursor).optional(),
|
|
53
|
+
});
|
|
54
|
+
const brokerEventSchema = z.object({
|
|
55
|
+
cursor: z.string().min(1).max(operatorV1.limits.cursor),
|
|
56
|
+
kind: z.enum(operatorV1.eventKinds),
|
|
57
|
+
request_id: z.string().min(1).max(operatorV1.limits.id),
|
|
58
|
+
revision: positiveInteger,
|
|
59
|
+
status,
|
|
60
|
+
occurred_at: timestamp,
|
|
61
|
+
used_count: nonNegativeInteger,
|
|
62
|
+
});
|
|
63
|
+
const descriptorSchema = z.object({
|
|
64
|
+
api_version: z.literal(operatorV1.apiVersion),
|
|
65
|
+
});
|
|
66
|
+
const healthSchema = z.object({ status: z.string().min(1).max(128) });
|
|
67
|
+
const errorEnvelopeSchema = z.object({
|
|
68
|
+
error: z.object({
|
|
69
|
+
code: z.enum(operatorV1.errorCodes),
|
|
70
|
+
message: z.string().min(1).max(operatorV1.limits.errorMessage),
|
|
71
|
+
correlation_id: z.string().min(1).max(operatorV1.limits.correlationId),
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
export function parseDescriptor(value) {
|
|
75
|
+
return descriptorSchema.parse(value);
|
|
76
|
+
}
|
|
77
|
+
export function parseHealth(value) {
|
|
78
|
+
return healthSchema.parse(value);
|
|
79
|
+
}
|
|
80
|
+
export function parseRequest(value) {
|
|
81
|
+
return brokerRequestSchema.parse(value);
|
|
82
|
+
}
|
|
83
|
+
export function parseRequestPage(value) {
|
|
84
|
+
return requestPageSchema.parse(value);
|
|
85
|
+
}
|
|
86
|
+
export function parseBrokerEvent(value) {
|
|
87
|
+
return brokerEventSchema.parse(value);
|
|
88
|
+
}
|
|
89
|
+
export function parseErrorEnvelope(value) {
|
|
90
|
+
const result = errorEnvelopeSchema.safeParse(value);
|
|
91
|
+
return result.success ? result.data : undefined;
|
|
92
|
+
}
|