@lostgradient/weft 0.15.0 → 0.16.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 +1 -1
- package/dist/alerting/alert-manager.d.ts +2 -0
- package/dist/alerting/alert-manager.js +3 -0
- package/dist/cli/api.js +5 -19
- package/dist/cli/console.d.ts +12 -0
- package/dist/cli/console.js +62 -0
- package/dist/cli/generated/operation-catalog.snapshot.json +71 -0
- package/dist/cli/generated/operation-client.generated.d.ts +15 -2
- package/dist/cli/generated/operation-client.generated.js +2 -0
- package/dist/cli/help-text.d.ts +1 -1
- package/dist/cli/help-text.js +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-input.d.ts +20 -0
- package/dist/cli/json-input.js +33 -0
- package/dist/cli/parse-arguments.js +2 -0
- package/dist/cli/schedule.js +7 -9
- package/dist/cli/types.d.ts +1 -0
- package/dist/cli/workflow-commands.js +5 -25
- package/dist/cli-main.js +4 -2
- package/dist/core/engine/index.d.ts +3 -0
- package/dist/core/engine/index.js +3 -0
- package/dist/diagnostics/recommendations.js +1 -9
- package/dist/http.js +2 -2
- package/dist/indexeddb.js +1 -1
- package/dist/server/authorization-scope.d.ts +1 -1
- package/dist/server/authorization-scope.js +0 -2
- package/dist/server/dashboard-assets.d.ts +26 -0
- package/dist/server/dashboard-assets.js +95 -0
- package/dist/server/index.d.ts +10 -1
- package/dist/server/index.js +11 -2
- package/dist/server/interactive-operations.js +1 -0
- package/dist/server/operations/create-schedule.js +19 -46
- package/dist/server/operations/list-alerts.d.ts +26 -0
- package/dist/server/operations/list-alerts.js +54 -0
- package/dist/server/operations/schedule-faults.d.ts +9 -1
- package/dist/server/operations/schedule-faults.js +51 -0
- package/dist/server/operations/update-schedule.js +7 -54
- package/dist/server/rest-bindings.js +3 -0
- package/dist/service-worker/index.d.ts +4 -1
- package/dist/service-worker/index.js +2 -2
- package/dist/service-worker/setup.d.ts +20 -0
- package/dist/service-worker/setup.js +5 -5
- package/dist/storage/bun-sql.js +13 -31
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +2 -2
- package/dist/storage/postgres.js +2 -2
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web-extension.js +1 -1
- package/package.json +5 -1
package/dist/indexeddb.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
async function q(G,J){return await G.get(J)!==null}async function*N(G,J,Q){for await(let[X]of G.scan(J,Q))yield X}async function O(G,J){let Q=0;for await(let X of N(G,J))Q++;return Q}async function R(G,J){let Q=[];for await(let X of N(G,J))Q.push({type:"delete",key:X});if(Q.length===0)return 0;return await G.batch(Q),Q.length}async function h(G,J,Q){let X=[];for await(let Y of N(G,J,Q))X.push({type:"delete",key:Y});if(X.length===0)return 0;return await G.batch(X),X.length}function v(G,J,Q){if(!G.capabilities()[J])throw Error(`Feature "${Q}" requires storage capability "${J}", but this storage backend does not provide it.`)}var A=1e4;class E extends Error{code="StorageBatchOperationLimitExceededError";cap=A;count;target;constructor(G,J){super(`${G} count ${J} exceeds MAX_BATCH_OPERATIONS (${A}).`);this.name="StorageBatchOperationLimitExceededError",this.target=G,this.count=J}}function
|
|
1
|
+
async function q(G,J){return await G.get(J)!==null}async function*N(G,J,Q){for await(let[X]of G.scan(J,Q))yield X}async function O(G,J){let Q=0;for await(let X of N(G,J))Q++;return Q}async function R(G,J){let Q=[];for await(let X of N(G,J))Q.push({type:"delete",key:X});if(Q.length===0)return 0;return await G.batch(Q),Q.length}async function h(G,J,Q){let X=[];for await(let Y of N(G,J,Q))X.push({type:"delete",key:Y});if(X.length===0)return 0;return await G.batch(X),X.length}function v(G,J,Q){if(!G.capabilities()[J])throw Error(`Feature "${Q}" requires storage capability "${J}", but this storage backend does not provide it.`)}var A=1e4;class E extends Error{code="StorageBatchOperationLimitExceededError";cap=A;count;target;constructor(G,J){super(`${G} count ${J} exceeds MAX_BATCH_OPERATIONS (${A}).`);this.name="StorageBatchOperationLimitExceededError",this.target=G,this.count=J}}function K(G,J){if(J>A)throw new E(G,J)}function M(G){return G.length>0?G.slice(0,-1)+String.fromCharCode(G.charCodeAt(G.length-1)+1):"ÿ"}function S(G,J={}){if(J.gt!==void 0&&G<=J.gt)return!1;if(J.gte!==void 0&&G<J.gte)return!1;if(J.lt!==void 0&&G>=J.lt)return!1;if(J.lte!==void 0&&G>J.lte)return!1;return!0}function b(G,J){if(G===null||J===null)return G===J;if(G.byteLength!==J.byteLength)return!1;for(let Q=0;Q<G.byteLength;Q++)if(G[Q]!==J[Q])return!1;return!0}async function m(G,J){if(G.has)return G.has(J);return q(G,J)}function x(G,J,Q){if(G.keys)return G.keys(J,Q);return N(G,J,Q)}async function y(G,J){if(G.count)return G.count(J);return O(G,J)}async function I(G,J){if(G.deletePrefix)return G.deletePrefix(J);return R(G,J)}async function g(G,J,Q){if(K("conditionalBatch conditions",J.length),K("conditionalBatch operations",Q.length),v(G,"conditionalBatch","storageConditionalBatch"),!G.conditionalBatch)throw Error("This storage backend reports conditionalBatch capability but does not implement the conditionalBatch() method.");return G.conditionalBatch(J,Q)}function l(G){if(G===void 0)return;if(typeof G!=="number"||!Number.isInteger(G)||G<0)throw Error("deleteRange limit must be a finite non-negative integer");return G===0?0:G}function T(G){let J={},Q=!1;for(let Y of["gt","gte","lt","lte"]){let $=G[Y];if($===void 0)continue;if(typeof $!=="string")throw Error("deleteRange bounds must be strings");J[Y]=$,Q=!0}if(!Q)throw Error("deleteRange requires at least one of gt/gte/lt/lte; use deletePrefix to delete a whole prefix");let X=l(G.limit);if(X!==void 0)J.limit=X;return J}async function k(G,J,Q){let X=T(Q);if(G.deleteRange)return G.deleteRange(J,X);return h(G,J,X)}function p(G,J){let Q={key:G,open:!1};if(J.gte!==void 0&&J.gte>Q.key)Q.key=J.gte,Q.open=!1;if(J.gt!==void 0&&J.gt>=Q.key)Q.key=J.gt,Q.open=!0;return Q}function d(G,J){let Q={key:M(G),open:!0};if(J.lt!==void 0&&J.lt<=Q.key)Q.key=J.lt,Q.open=!0;if(J.lte!==void 0&&J.lte<Q.key)Q.key=J.lte,Q.open=!1;return Q}function c(G,J){let Q=p(G,J),X=d(G,J);if(Q.key>X.key||Q.key===X.key&&(Q.open||X.open))return null;return{lower:Q,upper:X}}function P(G){return G.replaceAll(/:+$/g,"")}function a(G,J){let Q=P(G),X=P(J);if(Q.length===0)return X;if(X.length===0)return Q;return`${Q}:${X}`}class C{#J;#Q;constructor(G,J){this.#J=G,this.#Q=P(J)}#G(G){if(this.#Q.length===0)return G;return G.length===0?`${this.#Q}:`:`${this.#Q}:${G}`}#X(G){return G.map((J)=>{if(J.type==="put")return{type:"put",key:this.#G(J.key),value:J.value};return{type:"delete",key:this.#G(J.key)}})}#Y(G){if(this.#Q.length===0)return G;return G.slice(this.#Q.length+1)}#Z(G){let J={};if(G.limit!==void 0)J.limit=G.limit;if(G.gt!==void 0)J.gt=this.#G(G.gt);if(G.gte!==void 0)J.gte=this.#G(G.gte);if(G.lt!==void 0)J.lt=this.#G(G.lt);if(G.lte!==void 0)J.lte=this.#G(G.lte);return J}#$(G={}){let J=this.#Z(G);if(G.reverse!==void 0)J.reverse=G.reverse;return J}#U(G){return this.#Z(G)}capabilities(){return this.#J.capabilities()}scoped(G){return new C(this.#J,a(this.#Q,G))}async get(G){return this.#J.get(this.#G(G))}async put(G,J){await this.#J.put(this.#G(G),J)}async delete(G){await this.#J.delete(this.#G(G))}async*scan(G,J){for await(let[Q,X]of this.#J.scan(this.#G(G),this.#$(J)))yield[this.#Y(Q),X]}async batch(G){K("batch operations",G.length),await this.#J.batch(this.#X(G))}async conditionalBatch(G,J){return g(this.#J,G.map((Q)=>({key:this.#G(Q.key),expectedValue:Q.expectedValue})),this.#X(J))}async has(G){return m(this.#J,this.#G(G))}async deletePrefix(G){return I(this.#J,this.#G(G))}async deleteRange(G,J){let Q=this.#U(T(J));return k(this.#J,this.#G(G),Q)}async*keys(G,J){for await(let Q of x(this.#J,this.#G(G),this.#$(J)))yield this.#Y(Q)}async count(G){return y(this.#J,this.#G(G))}[Symbol.dispose](){this.#J[Symbol.dispose]()}}function u(G,J){return new C(G,J)}var U="kv";function s(G=globalThis){let{indexedDB:J,IDBKeyRange:Q}=G;if(J===void 0||Q===void 0)throw Error("IndexedDBStorage requires both indexedDB and IDBKeyRange runtime globals.");return{indexedDB:J,IDBKeyRange:Q}}function j(G){return new Promise((J,Q)=>{G.onsuccess=()=>J(G.result),G.onerror=()=>Q(G.error)})}function w(G,J){let Q=null,X=null,Y=null,$=(Z,V)=>{let H=Z??Error(V);if(X){let B=X;Q=null,X=null,B(H);return}Y=H};return G.onsuccess=()=>{if(!Q)return;let Z=Q;Q=null,X=null,Z(G.result)},G.onerror=()=>{$(G.error,"IndexedDB cursor request failed.")},J.onerror=()=>{$(J.error,"IndexedDB transaction failed.")},J.onabort=()=>{$(J.error,"IndexedDB transaction aborted.")},()=>{return new Promise((Z,V)=>{if(Y){let H=Y;Y=null,V(H);return}if(Q=Z,X=V,G.readyState==="done"){let H=Q;Q=null,X=null,H?.(G.result)}})}}class n{#J;#Q=null;#G;#X;constructor(G="weft",J=s()){this.#J=G,this.#X=J,this.#G=this.#Y()}capabilities(){return{persistence:"local",readAfterWrite:"linearizable",scanConsistency:"best-effort",atomicBatch:!0,conditionalBatch:!0,boundedRangeDelete:!0}}#Y(){let G=this.#X.indexedDB.open(this.#J,1);return G.onupgradeneeded=()=>{let J=G.result;if(!J.objectStoreNames.contains(U))J.createObjectStore(U)},j(G).then((J)=>{return this.#Q=J,J})}async get(G){let X=(await this.#G).transaction(U,"readonly").objectStore(U),Y=await j(X.get(G));return Y===void 0?null:new Uint8Array(Y)}async put(G,J){let Y=(await this.#G).transaction(U,"readwrite").objectStore(U);await j(Y.put(J,G))}async delete(G){let X=(await this.#G).transaction(U,"readwrite").objectStore(U);await j(X.delete(G))}async has(G){let X=(await this.#G).transaction(U,"readonly").objectStore(U);return await j(X.count(G))>0}async deletePrefix(G){let J=await this.#G,Q=M(G),X=this.#X.IDBKeyRange.bound(G,Q,!1,!0);return new Promise((Y,$)=>{let Z=J.transaction(U,"readwrite"),V=Z.objectStore(U),H=0,B=V.count(X);B.onsuccess=()=>{H=B.result,V.delete(X)},Z.oncomplete=()=>Y(H),Z.onerror=()=>$(Z.error)})}async deleteRange(G,J){let Q=T(J),X=c(G,Q);if(X===null)return 0;let Y=this.#X.IDBKeyRange.bound(X.lower.key,X.upper.key,X.lower.open,X.upper.open),$=await this.#G,{limit:Z}=Q;return new Promise((V,H)=>{let B=$.transaction(U,"readwrite"),z=B.objectStore(U),D=0;if(Z===void 0){let F=z.count(Y);F.onsuccess=()=>{D=F.result,z.delete(Y)}}else{let F=z.openCursor(Y,"next");F.onsuccess=()=>{let W=F.result;if(W===null||D>=Z)return;if(W.delete(),D++,D<Z)W.continue()}}B.oncomplete=()=>V(D),B.onerror=()=>H(B.error)})}async*scan(G,J={}){let{limit:Q,reverse:X}=J,Y=await this.#G,$=M(G),Z=this.#X.IDBKeyRange.bound(G,$,!1,!0),V=X?"prev":"next",H=Y.transaction(U,"readonly"),z=H.objectStore(U).openCursor(Z,V),D=0,F=w(z,H),W=!1;try{let L=await F();while(L){if(Q!==void 0&&D>=Q)break;let _=L.key;if(S(_,J))yield[_,new Uint8Array(L.value)],D++;L.continue(),L=await F()}W=!0}finally{if(!W)try{H.abort()}catch{}}}async batch(G){if(K("batch operations",G.length),G.length===0)return;let J=await this.#G;return new Promise((Q,X)=>{let Y=J.transaction(U,"readwrite"),$=Y.objectStore(U);for(let Z of G)if(Z.type==="put")$.put(Z.value,Z.key);else $.delete(Z.key);Y.oncomplete=()=>Q(),Y.onerror=()=>X(Y.error)})}async conditionalBatch(G,J){K("conditionalBatch conditions",G.length),K("conditionalBatch operations",J.length);let Q=await this.#G;return new Promise((X,Y)=>{let $=Q.transaction(U,"readwrite"),Z=$.objectStore(U),V=!1,H=!1,B=(F,W)=>{if(V)return;V=!0,Y(F??Error(W))};$.oncomplete=()=>{if(V)return;V=!0,X(!0)},$.onerror=()=>{B($.error,"IndexedDB conditionalBatch transaction failed.")},$.onabort=()=>{if(V)return;if(V=!0,H){X(!1);return}Y($.error??Error("IndexedDB conditionalBatch transaction aborted."))};let z=()=>{for(let F of J)if(F.type==="put")Z.put(F.value,F.key);else Z.delete(F.key)},D=(F)=>{if(F>=G.length){z();return}let W=G[F],L=Z.get(W.key);L.onsuccess=()=>{let _=L.result,f=_===void 0?null:new Uint8Array(_);if(!b(f,W.expectedValue)){H=!0,$.abort();return}D(F+1)},L.onerror=()=>{B(L.error,"IndexedDB conditionalBatch condition check failed.")}};D(0)})}async*keys(G,J={}){let{limit:Q,reverse:X}=J,Y=await this.#G,$=M(G),Z=this.#X.IDBKeyRange.bound(G,$,!1,!0),V=X?"prev":"next",H=Y.transaction(U,"readonly"),z=H.objectStore(U).openKeyCursor(Z,V),D=0,F=w(z,H),W=!1;try{let L=await F();while(L){if(Q!==void 0&&D>=Q)break;let _=L.key;if(S(_,J))yield _,D++;L.continue(),L=await F()}W=!0}finally{if(!W)try{H.abort()}catch{}}}async count(G){let J=await this.#G,Q=M(G),Y=J.transaction(U,"readonly").objectStore(U);return j(Y.count(this.#X.IDBKeyRange.bound(G,Q,!1,!0)))}scoped(G){return u(this,G)}[Symbol.dispose](){if(this.#Q)this.#Q.close(),this.#Q=null}}export{n as IndexedDBStorage};
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* the exact scope they require. Scope-implication semantics (if any) are a
|
|
17
17
|
* future track.
|
|
18
18
|
*/
|
|
19
|
-
export declare const AUTHORIZATION_SCOPES: readonly ["workflows:read", "workflows:write", "workflows:admin", "schedules:read", "schedules:write", "signals:write", "updates:write", "queries:read", "reviews:read", "reviews:write", "attributes:read", "attributes:write", "tags:write", "streams:read", "events:read", "
|
|
19
|
+
export declare const AUTHORIZATION_SCOPES: readonly ["workflows:read", "workflows:write", "workflows:admin", "schedules:read", "schedules:write", "signals:write", "updates:write", "queries:read", "reviews:read", "reviews:write", "attributes:read", "attributes:write", "tags:write", "streams:read", "events:read", "storage:read", "storage:write", "storage:admin", "workers:write", "system:read", "system:admin"];
|
|
20
20
|
/** String-literal union of every authorization scope. */
|
|
21
21
|
export type AuthorizationScope = (typeof AUTHORIZATION_SCOPES)[number];
|
|
22
22
|
/** Runtime type guard for the `AuthorizationScope` union. */
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static files belonging to an externally supplied dashboard.
|
|
3
|
+
*
|
|
4
|
+
* `prefix` is mounted as `${prefix}/*`; `directory` must exist before
|
|
5
|
+
* `serve()` is called.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import type { DashboardAssets } from '@lostgradient/weft/server';
|
|
10
|
+
*
|
|
11
|
+
* const dashboardAssets: DashboardAssets = {
|
|
12
|
+
* prefix: '/assets',
|
|
13
|
+
* directory: './dist/assets',
|
|
14
|
+
* };
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export interface DashboardAssets {
|
|
18
|
+
prefix: string;
|
|
19
|
+
directory: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ResolvedDashboardAssets {
|
|
22
|
+
prefix: string;
|
|
23
|
+
directory: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function resolveDashboardAssets(value: unknown, pageRoutes: readonly string[]): ResolvedDashboardAssets;
|
|
26
|
+
export declare function createDashboardAssetRoute(assets: ResolvedDashboardAssets): Partial<Record<'GET' | 'HEAD', (request: Request) => Response>>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
const ROOT_API_PREFIXES = ["/api", "/v1"], DISCOVERY_DOCUMENTS = [
|
|
4
|
+
"/openapi.json",
|
|
5
|
+
"/openrpc.json",
|
|
6
|
+
"/asyncapi.json",
|
|
7
|
+
"/.well-known/mcp.json"
|
|
8
|
+
];
|
|
9
|
+
function hasPathPrefix(path, prefix) {
|
|
10
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
11
|
+
}
|
|
12
|
+
function overlapsDashboardPageRoute(prefix, route) {
|
|
13
|
+
if (route.endsWith("/*")) {
|
|
14
|
+
const base = route.slice(0, -2);
|
|
15
|
+
return prefix === base || prefix.startsWith(`${base}/`);
|
|
16
|
+
}
|
|
17
|
+
return prefix === route;
|
|
18
|
+
}
|
|
19
|
+
function hasInvalidPrefixShape(prefix) {
|
|
20
|
+
return prefix.length === 0 || prefix === "/" || !prefix.startsWith("/") || prefix.endsWith("/") || ["?", "#", "%", "*", ":", "\\"].some((character) => prefix.includes(character));
|
|
21
|
+
}
|
|
22
|
+
function validateAssetPrefixShape(prefix) {
|
|
23
|
+
if (hasInvalidPrefixShape(prefix))
|
|
24
|
+
throw Error("dashboardAssets.prefix must be an absolute path prefix without a trailing slash, wildcard, parameter, query, fragment, percent escape, or backslash");
|
|
25
|
+
if (prefix.split("/").slice(1).some((segment) => segment.length === 0 || segment === "." || segment === ".."))
|
|
26
|
+
throw Error("dashboardAssets.prefix must contain only concrete, non-empty path segments");
|
|
27
|
+
}
|
|
28
|
+
function validateAssetPrefixReservations(prefix, pageRoutes) {
|
|
29
|
+
if (ROOT_API_PREFIXES.some((reserved) => hasPathPrefix(prefix, reserved)))
|
|
30
|
+
throw Error("dashboardAssets.prefix must not overlap the /api or /v1 route spaces");
|
|
31
|
+
if (pageRoutes.some((route) => overlapsDashboardPageRoute(prefix, route)))
|
|
32
|
+
throw Error("dashboardAssets.prefix must not overlap dashboard page routes");
|
|
33
|
+
if (DISCOVERY_DOCUMENTS.some((document) => document === prefix))
|
|
34
|
+
throw Error("dashboardAssets.prefix must not overlap a discovery document route");
|
|
35
|
+
}
|
|
36
|
+
function validateAssetPrefix(prefix, pageRoutes) {
|
|
37
|
+
validateAssetPrefixShape(prefix);
|
|
38
|
+
validateAssetPrefixReservations(prefix, pageRoutes);
|
|
39
|
+
}
|
|
40
|
+
export function resolveDashboardAssets(value, pageRoutes) {
|
|
41
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
42
|
+
throw Error("dashboardAssets must be an object with prefix and directory strings");
|
|
43
|
+
if (!("prefix" in value) || !("directory" in value))
|
|
44
|
+
throw Error("dashboardAssets.prefix and dashboardAssets.directory must be strings");
|
|
45
|
+
const { prefix, directory } = value;
|
|
46
|
+
if (typeof prefix !== "string" || typeof directory !== "string")
|
|
47
|
+
throw Error("dashboardAssets.prefix and dashboardAssets.directory must be strings");
|
|
48
|
+
validateAssetPrefix(prefix, pageRoutes);
|
|
49
|
+
const resolvedDirectory = resolve(directory);
|
|
50
|
+
let directoryStats;
|
|
51
|
+
try {
|
|
52
|
+
directoryStats = statSync(resolvedDirectory);
|
|
53
|
+
} catch {
|
|
54
|
+
throw Error(`dashboardAssets.directory does not exist: ${directory}`);
|
|
55
|
+
}
|
|
56
|
+
if (!directoryStats.isDirectory())
|
|
57
|
+
throw Error(`dashboardAssets.directory must be a directory: ${directory}`);
|
|
58
|
+
return { prefix, directory: realpathSync(resolvedDirectory) };
|
|
59
|
+
}
|
|
60
|
+
function isWithinDirectory(directory, path) {
|
|
61
|
+
const pathRelativeToDirectory = relative(directory, path);
|
|
62
|
+
return pathRelativeToDirectory.length > 0 && pathRelativeToDirectory !== ".." && !pathRelativeToDirectory.startsWith(`..${sep}`) && !isAbsolute(pathRelativeToDirectory);
|
|
63
|
+
}
|
|
64
|
+
function assetResponse(directory, prefix, request) {
|
|
65
|
+
const rawWildcardPath = new URL(request.url).pathname.slice(prefix.length + 1);
|
|
66
|
+
let wildcardPath;
|
|
67
|
+
try {
|
|
68
|
+
wildcardPath = decodeURIComponent(rawWildcardPath);
|
|
69
|
+
} catch {
|
|
70
|
+
return new Response("Not Found", { status: 404 });
|
|
71
|
+
}
|
|
72
|
+
if (wildcardPath.length === 0)
|
|
73
|
+
return new Response("Not Found", { status: 404 });
|
|
74
|
+
const pathSegments = wildcardPath.split(/[\\/]/);
|
|
75
|
+
if (pathSegments.some((segment) => segment === ".." || segment.length === 0))
|
|
76
|
+
return new Response("Not Found", { status: 404 });
|
|
77
|
+
const assetPath = resolve(join(directory, ...pathSegments));
|
|
78
|
+
if (!isWithinDirectory(directory, assetPath))
|
|
79
|
+
return new Response("Not Found", { status: 404 });
|
|
80
|
+
let realAssetPath;
|
|
81
|
+
try {
|
|
82
|
+
realAssetPath = realpathSync(assetPath);
|
|
83
|
+
if (!statSync(realAssetPath).isFile())
|
|
84
|
+
return new Response("Not Found", { status: 404 });
|
|
85
|
+
} catch {
|
|
86
|
+
return new Response("Not Found", { status: 404 });
|
|
87
|
+
}
|
|
88
|
+
if (!isWithinDirectory(directory, realAssetPath))
|
|
89
|
+
return new Response("Not Found", { status: 404 });
|
|
90
|
+
return new Response(Bun.file(realAssetPath));
|
|
91
|
+
}
|
|
92
|
+
export function createDashboardAssetRoute(assets) {
|
|
93
|
+
const handler = (request) => assetResponse(assets.directory, assets.prefix, request);
|
|
94
|
+
return { GET: handler, HEAD: handler };
|
|
95
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -10,11 +10,13 @@ import type { PrometheusExporter } from '../observability/metrics.ts';
|
|
|
10
10
|
import type { RoutingPolicy } from '../worker/registry.ts';
|
|
11
11
|
import { WorkerRegistry } from '../worker/registry.ts';
|
|
12
12
|
import type { AuthConfig, RateLimitConfig } from './authentication.ts';
|
|
13
|
+
import { type DashboardAssets } from './dashboard-assets.ts';
|
|
13
14
|
import type { DiscoveryInfo } from './discovery-info.ts';
|
|
14
15
|
import type { CorsOptions } from './runtime/cors.ts';
|
|
15
16
|
import type { SchedulingPolicy } from './task-queue-types.ts';
|
|
16
17
|
import { TaskQueue } from './task-queue.ts';
|
|
17
18
|
export { wireEventBroadcasting, type EventBroadcastingHandle, } from './runtime/event-broadcasting.ts';
|
|
19
|
+
export type { DashboardAssets } from './dashboard-assets.ts';
|
|
18
20
|
export type { CorsOptions } from './runtime/cors.ts';
|
|
19
21
|
export { createRateLimiter, createRotatingApiKeyStore, defaultAuthAuditSink, isSensitiveHeader, redactCredential, redactHeaders, validateRateLimitConfig, type ApiKeyRegistration, type AuthAuditEvent, type AuthAuditSink, type AuthConfig, type RateLimitConfig, type RateLimitDecision, type RateLimiter, type RotatingApiKeyStore, } from './authentication.ts';
|
|
20
22
|
export type { RetryPolicy } from '../core/types.ts';
|
|
@@ -32,6 +34,8 @@ export { TaskQueue } from './task-queue.ts';
|
|
|
32
34
|
* They are intentionally specific (no blanket `/*`) so they cannot shadow the
|
|
33
35
|
* API served under the `/api` prefix or the root-stable discovery endpoints —
|
|
34
36
|
* those fall through to the `fetch` handler.
|
|
37
|
+
* The supported page routes are `/`, `/workflows`, `/workflows/*`, `/reviews`,
|
|
38
|
+
* `/workers`, `/schedules`, `/storage`, and `/system`.
|
|
35
39
|
*
|
|
36
40
|
* @example
|
|
37
41
|
* ```ts
|
|
@@ -42,7 +46,7 @@ export { TaskQueue } from './task-queue.ts';
|
|
|
42
46
|
* console.log(DASHBOARD_PAGE_ROUTES.includes('/workflows')); // true
|
|
43
47
|
* ```
|
|
44
48
|
*/
|
|
45
|
-
export declare const DASHBOARD_PAGE_ROUTES: readonly ["/", "/workflows", "/workflows/*", "/reviews", "/workers"];
|
|
49
|
+
export declare const DASHBOARD_PAGE_ROUTES: readonly ["/", "/workflows", "/workflows/*", "/reviews", "/workers", "/schedules", "/storage", "/system"];
|
|
46
50
|
/**
|
|
47
51
|
* Route pattern owned by an externally supplied dashboard shell.
|
|
48
52
|
*
|
|
@@ -129,6 +133,11 @@ export interface ServeOptions {
|
|
|
129
133
|
development?: boolean;
|
|
130
134
|
/** Optional external dashboard shell served at {@link DASHBOARD_PAGE_ROUTES}. */
|
|
131
135
|
dashboard?: DashboardRouteTarget;
|
|
136
|
+
/**
|
|
137
|
+
* Static files served below an explicit prefix for the supplied dashboard.
|
|
138
|
+
* The directory and prefix are validated synchronously before the port binds.
|
|
139
|
+
*/
|
|
140
|
+
dashboardAssets?: DashboardAssets;
|
|
132
141
|
/** Authentication configuration. When provided, all non-public endpoints require valid credentials. */
|
|
133
142
|
auth?: AuthConfig;
|
|
134
143
|
/**
|
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { WorkerRegistry } from "../worker/registry.js";
|
|
2
|
+
import {
|
|
3
|
+
createDashboardAssetRoute,
|
|
4
|
+
resolveDashboardAssets
|
|
5
|
+
} from "./dashboard-assets.js";
|
|
2
6
|
import { createServerWebSocketHandlers } from "./runtime/authentication-bridge.js";
|
|
3
7
|
import {
|
|
4
8
|
wireEventBroadcasting
|
|
@@ -39,13 +43,18 @@ export const DASHBOARD_PAGE_ROUTES = [
|
|
|
39
43
|
"/workflows",
|
|
40
44
|
"/workflows/*",
|
|
41
45
|
"/reviews",
|
|
42
|
-
"/workers"
|
|
46
|
+
"/workers",
|
|
47
|
+
"/schedules",
|
|
48
|
+
"/storage",
|
|
49
|
+
"/system"
|
|
43
50
|
];
|
|
44
51
|
export function serve(options) {
|
|
45
|
-
const { port, hostname, development, tlsOptions, serverOptions, serverMetricsCollector } = resolveNetworkConfig(options), context = buildServerContext(serverOptions, serverMetricsCollector), boundCleanup = (operationId) => cleanupWorkflowIndex(context, operationId), routes = {};
|
|
52
|
+
const dashboardAssets = options.dashboardAssets === void 0 ? void 0 : resolveDashboardAssets(options.dashboardAssets, DASHBOARD_PAGE_ROUTES), { port, hostname, development, tlsOptions, serverOptions, serverMetricsCollector } = resolveNetworkConfig(options), context = buildServerContext(serverOptions, serverMetricsCollector), boundCleanup = (operationId) => cleanupWorkflowIndex(context, operationId), routes = {};
|
|
46
53
|
if (options.dashboard != null)
|
|
47
54
|
for (const path of DASHBOARD_PAGE_ROUTES)
|
|
48
55
|
routes[path] = options.dashboard;
|
|
56
|
+
if (dashboardAssets !== void 0)
|
|
57
|
+
routes[`${dashboardAssets.prefix}/*`] = createDashboardAssetRoute(dashboardAssets);
|
|
49
58
|
const serverHolder = { current: null }, server = Bun.serve(buildBunServeConfig(port, hostname, development, routes, tlsOptions, buildFetchHandler(serverHolder, context, serverOptions), createServerWebSocketHandlers(context, serverOptions, boundCleanup)));
|
|
50
59
|
serverHolder.current = server;
|
|
51
60
|
const stack = new AsyncDisposableStack;
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { defineOperation } from "../operation-registry.js";
|
|
3
3
|
import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
mapScheduleErrorToFault,
|
|
6
|
+
validateScheduleInputCadence,
|
|
7
|
+
validateScheduleMutableOptions
|
|
8
|
+
} from "./schedule-faults.js";
|
|
5
9
|
import {
|
|
6
10
|
extractSharedScheduleRestFields,
|
|
7
11
|
parseScheduleRestBodyRequestRecord
|
|
8
12
|
} from "./schedule-rest-body.js";
|
|
9
|
-
const
|
|
10
|
-
"skip",
|
|
11
|
-
"queue",
|
|
12
|
-
"cancel-running",
|
|
13
|
-
"allow"
|
|
14
|
-
]), createScheduleInput = z.object({
|
|
13
|
+
const createScheduleInput = z.object({
|
|
15
14
|
type: z.unknown().describe("Workflow type name. Runtime validation requires a non-empty string."),
|
|
16
15
|
cronExpression: z.unknown().optional().describe("Cron expression. Supply exactly one of cronExpression or every; runtime validation requires a non-empty string."),
|
|
17
16
|
every: z.unknown().optional().describe("Interval period (duration string or milliseconds). Supply exactly one of cronExpression or every."),
|
|
@@ -29,43 +28,21 @@ function validateRequiredScheduleFields(input) {
|
|
|
29
28
|
throw invalidParamsFault("Missing required field: type");
|
|
30
29
|
return { type: input.type, spec: validateScheduleInputCadence(input) };
|
|
31
30
|
}
|
|
31
|
+
function validateScheduleId(value) {
|
|
32
|
+
if (value === void 0)
|
|
33
|
+
return;
|
|
34
|
+
if (typeof value !== "string" || value.length === 0)
|
|
35
|
+
throw invalidParamsFault('Field "id" must be a non-empty string');
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
32
38
|
function validateOptionalScheduleFields(input) {
|
|
33
|
-
|
|
34
|
-
if (input.id !== void 0) {
|
|
35
|
-
if (typeof input.id !== "string" || input.id.length === 0)
|
|
36
|
-
throw invalidParamsFault('Field "id" must be a non-empty string');
|
|
37
|
-
validatedId = input.id;
|
|
38
|
-
}
|
|
39
|
-
let validatedDescription;
|
|
40
|
-
if (input.description !== void 0) {
|
|
41
|
-
if (typeof input.description !== "string")
|
|
42
|
-
throw invalidParamsFault('Field "description" must be a string');
|
|
43
|
-
validatedDescription = input.description;
|
|
44
|
-
}
|
|
45
|
-
let validatedOverlap;
|
|
46
|
-
if (input.overlap !== void 0) {
|
|
47
|
-
if (typeof input.overlap !== "string" || !isScheduleOverlapPolicy(input.overlap))
|
|
48
|
-
throw invalidParamsFault('Field "overlap" must be one of skip, queue, cancel-running, allow');
|
|
49
|
-
validatedOverlap = input.overlap;
|
|
50
|
-
}
|
|
51
|
-
let validatedBackfill;
|
|
52
|
-
if (input.backfill !== void 0) {
|
|
53
|
-
if (typeof input.backfill !== "boolean")
|
|
54
|
-
throw invalidParamsFault('Field "backfill" must be a boolean');
|
|
55
|
-
validatedBackfill = input.backfill;
|
|
56
|
-
}
|
|
57
|
-
let validatedJitter;
|
|
58
|
-
if (input.jitter !== void 0) {
|
|
59
|
-
if (typeof input.jitter !== "string" && typeof input.jitter !== "number")
|
|
60
|
-
throw invalidParamsFault('Field "jitter" must be a duration string or a number of milliseconds');
|
|
61
|
-
validatedJitter = input.jitter;
|
|
62
|
-
}
|
|
39
|
+
const validatedId = validateScheduleId(input.id), validatedOptions = validateScheduleMutableOptions(input);
|
|
63
40
|
return {
|
|
64
41
|
id: validatedId,
|
|
65
|
-
description:
|
|
66
|
-
overlap:
|
|
67
|
-
backfill:
|
|
68
|
-
jitter:
|
|
42
|
+
description: validatedOptions.description,
|
|
43
|
+
overlap: validatedOptions.overlap,
|
|
44
|
+
backfill: validatedOptions.backfill,
|
|
45
|
+
jitter: validatedOptions.jitter
|
|
69
46
|
};
|
|
70
47
|
}
|
|
71
48
|
function validateCreateScheduleInput(input) {
|
|
@@ -99,11 +76,7 @@ export const createScheduleOperation = defineOperation({
|
|
|
99
76
|
throw mapScheduleErrorToFault(validated.id ?? "", error);
|
|
100
77
|
}
|
|
101
78
|
}
|
|
102
|
-
})
|
|
103
|
-
function isScheduleOverlapPolicy(value) {
|
|
104
|
-
return VALID_SCHEDULE_OVERLAP_POLICIES.has(value);
|
|
105
|
-
}
|
|
106
|
-
export const createScheduleRestBinding = {
|
|
79
|
+
}), createScheduleRestBinding = {
|
|
107
80
|
method: "POST",
|
|
108
81
|
path: "/v1/schedules",
|
|
109
82
|
pathParamNames: [],
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `weft.alerts.list` operation + REST binding.
|
|
3
|
+
*
|
|
4
|
+
* Reports the alert rules that are currently firing. The response deliberately
|
|
5
|
+
* omits rule actions and webhook destinations: this is an operator diagnostic
|
|
6
|
+
* surface, not a configuration export.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { AlertMetric } from '../../alerting/types.ts';
|
|
10
|
+
import type { UnknownRestBinding } from '../rest-bindings.ts';
|
|
11
|
+
declare const listAlertsInput: z.ZodObject<{}, z.core.$strip>;
|
|
12
|
+
declare const activeAlertSchema: z.ZodObject<{
|
|
13
|
+
metric: z.ZodType<AlertMetric, unknown, z.core.$ZodTypeInternals<AlertMetric, unknown>>;
|
|
14
|
+
threshold: z.ZodNumber;
|
|
15
|
+
currentValue: z.ZodNumber;
|
|
16
|
+
window: z.ZodNullable<z.ZodString>;
|
|
17
|
+
firedAt: z.ZodNullable<z.ZodNumber>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export type ListAlertsInput = z.infer<typeof listAlertsInput>;
|
|
20
|
+
export type ActiveAlert = z.infer<typeof activeAlertSchema>;
|
|
21
|
+
export type ListAlertsOutput = {
|
|
22
|
+
items: ActiveAlert[];
|
|
23
|
+
};
|
|
24
|
+
export declare const listAlertsOperation: import("../operation-catalog.ts").OperationDefinition<Record<string, never>, ListAlertsOutput, unknown>;
|
|
25
|
+
export declare const listAlertsRestBinding: UnknownRestBinding;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { shapeOperationFaultAsJson } from "../operation-fault.js";
|
|
3
|
+
import { defineOperation } from "../operation-registry.js";
|
|
4
|
+
const alertMetricSchema = z.enum([
|
|
5
|
+
"workflow.failure_rate",
|
|
6
|
+
"activity.p99_duration",
|
|
7
|
+
"storage.size"
|
|
8
|
+
]), listAlertsInput = z.object({}), activeAlertSchema = z.object({
|
|
9
|
+
metric: alertMetricSchema,
|
|
10
|
+
threshold: z.number(),
|
|
11
|
+
currentValue: z.number(),
|
|
12
|
+
window: z.string().nullable(),
|
|
13
|
+
firedAt: z.number().nullable()
|
|
14
|
+
}), listAlertsOutput = z.object({
|
|
15
|
+
items: z.array(activeAlertSchema)
|
|
16
|
+
});
|
|
17
|
+
export const listAlertsOperation = defineOperation({
|
|
18
|
+
name: "weft.alerts.list",
|
|
19
|
+
mcpExposable: !1,
|
|
20
|
+
summary: "List currently firing alerts",
|
|
21
|
+
description: "List the alert rules that are currently firing, including their metric, threshold, current value, configured window, and firing timestamp. Read-only and limited to active in-memory alert state; resolved alerts are not returned.",
|
|
22
|
+
destructive: !1,
|
|
23
|
+
tags: ["Observability"],
|
|
24
|
+
inputSchema: listAlertsInput,
|
|
25
|
+
outputSchema: listAlertsOutput,
|
|
26
|
+
access: { kind: "scoped", scopes: { kind: "anyOf", scopes: ["system:read"] } },
|
|
27
|
+
discoverable: !0,
|
|
28
|
+
transports: { http: !0, jsonRpcHttp: !0, jsonRpcWebSocket: !0, jsonRpcStdio: !0 },
|
|
29
|
+
unknownKeyPolicy: { http: "strip", jsonRpc: "reject" },
|
|
30
|
+
invoke: async ({ engine }) => {
|
|
31
|
+
return {
|
|
32
|
+
items: engine.getActiveAlerts().map((state) => ({
|
|
33
|
+
metric: state.rule.metric,
|
|
34
|
+
threshold: state.rule.threshold,
|
|
35
|
+
currentValue: state.currentValue,
|
|
36
|
+
window: state.rule.window ?? null,
|
|
37
|
+
firedAt: state.lastFiredAt ?? null
|
|
38
|
+
}))
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}), listAlertsRestBinding = {
|
|
42
|
+
method: "GET",
|
|
43
|
+
path: "/v1/alerts",
|
|
44
|
+
pathParamNames: [],
|
|
45
|
+
operationName: "weft.alerts.list",
|
|
46
|
+
inputSources: {},
|
|
47
|
+
extractInput: async () => ({}),
|
|
48
|
+
success: { kind: "json", status: 200 },
|
|
49
|
+
shapeSuccess: (output) => new Response(JSON.stringify(output), {
|
|
50
|
+
status: 200,
|
|
51
|
+
headers: { "Content-Type": "application/json" }
|
|
52
|
+
}),
|
|
53
|
+
shapeFault: shapeOperationFaultAsJson
|
|
54
|
+
};
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import type { ScheduleSpec } from '../../core/types.ts';
|
|
1
|
+
import type { ScheduleSpec, ScheduleUpdateOptions } from '../../core/types.ts';
|
|
2
2
|
import type { OperationFault } from '../operation-fault.ts';
|
|
3
3
|
export { isOperationFault } from './operation-helpers.ts';
|
|
4
|
+
export type ScheduleMutableOptionsInput = {
|
|
5
|
+
description?: unknown;
|
|
6
|
+
overlap?: unknown;
|
|
7
|
+
backfill?: unknown;
|
|
8
|
+
jitter?: unknown;
|
|
9
|
+
};
|
|
10
|
+
/** Validate the mutable schedule options shared by create and update. */
|
|
11
|
+
export declare function validateScheduleMutableOptions(input: ScheduleMutableOptionsInput): ScheduleUpdateOptions;
|
|
4
12
|
export declare function mapScheduleErrorToFault(scheduleId: string, error: unknown): OperationFault;
|
|
5
13
|
/**
|
|
6
14
|
* Validate the mutually exclusive schedule cadence fields (`cronExpression` or
|
|
@@ -1,5 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isValidScheduleOverlapPolicy,
|
|
3
|
+
normalizeScheduleUpdateOptions
|
|
4
|
+
} from "../../core/engine/validation/schedule.js";
|
|
1
5
|
import { invalidParamsFault } from "./operation-helpers.js";
|
|
2
6
|
export { isOperationFault } from "./operation-helpers.js";
|
|
7
|
+
export function validateScheduleMutableOptions(input) {
|
|
8
|
+
const description = validateScheduleDescription(input.description), overlap = validateScheduleOverlap(input.overlap), backfill = validateScheduleBackfill(input.backfill), jitter = validateScheduleJitter(input.jitter);
|
|
9
|
+
return {
|
|
10
|
+
...description !== void 0 ? { description } : {},
|
|
11
|
+
...overlap !== void 0 ? { overlap } : {},
|
|
12
|
+
...backfill !== void 0 ? { backfill } : {},
|
|
13
|
+
...jitter !== void 0 ? { jitter } : {}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function validateScheduleDescription(value) {
|
|
17
|
+
if (value === void 0)
|
|
18
|
+
return;
|
|
19
|
+
if (typeof value !== "string")
|
|
20
|
+
throw invalidParamsFault('Field "description" must be a string');
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function validateScheduleOverlap(value) {
|
|
24
|
+
if (value === void 0)
|
|
25
|
+
return;
|
|
26
|
+
if (!isValidScheduleOverlapPolicy(value))
|
|
27
|
+
throw invalidParamsFault('Field "overlap" must be one of skip, queue, cancel-running, allow');
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function validateScheduleBackfill(value) {
|
|
31
|
+
if (value === void 0)
|
|
32
|
+
return;
|
|
33
|
+
if (typeof value !== "boolean")
|
|
34
|
+
throw invalidParamsFault('Field "backfill" must be a boolean');
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function validateScheduleJitter(value) {
|
|
38
|
+
if (value === void 0)
|
|
39
|
+
return;
|
|
40
|
+
if (typeof value !== "string" && typeof value !== "number")
|
|
41
|
+
throw invalidParamsFault('Field "jitter" must be a duration string or a number of milliseconds');
|
|
42
|
+
try {
|
|
43
|
+
normalizeScheduleUpdateOptions({ jitter: value });
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
46
|
+
throw invalidParamsFault(formatJitterValidationMessage(message));
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
function formatJitterValidationMessage(message) {
|
|
51
|
+
const enginePrefix = "Invalid options.jitter: ", hasEnginePrefix = message.startsWith(enginePrefix), wireDetail = (hasEnginePrefix ? message.slice(enginePrefix.length) : message).replaceAll("options.jitter", 'Field "jitter"');
|
|
52
|
+
return hasEnginePrefix ? `Field "jitter" is invalid: ${wireDetail}` : wireDetail;
|
|
53
|
+
}
|
|
3
54
|
export function mapScheduleErrorToFault(scheduleId, error) {
|
|
4
55
|
const message = error instanceof Error ? error.message : String(error), normalizedMessage = message.toLowerCase();
|
|
5
56
|
if (normalizedMessage.includes("not found"))
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { normalizeScheduleUpdateOptions } from "../../core/engine/validation/schedule.js";
|
|
3
2
|
import { defineOperation } from "../operation-registry.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { shapeRestFault } from "./operation-helpers.js";
|
|
4
|
+
import {
|
|
5
|
+
mapScheduleErrorToFault,
|
|
6
|
+
validateScheduleInputCadence,
|
|
7
|
+
validateScheduleMutableOptions
|
|
8
|
+
} from "./schedule-faults.js";
|
|
6
9
|
import {
|
|
7
10
|
extractSharedScheduleRestFields,
|
|
8
11
|
parseScheduleRestBodyRequestRecord
|
|
@@ -16,56 +19,6 @@ const updateScheduleInput = z.object({
|
|
|
16
19
|
backfill: z.unknown().optional(),
|
|
17
20
|
jitter: z.unknown().optional()
|
|
18
21
|
});
|
|
19
|
-
function isScheduleOverlapPolicy(value) {
|
|
20
|
-
return value === "skip" || value === "queue" || value === "cancel-running" || value === "allow";
|
|
21
|
-
}
|
|
22
|
-
function validateDescription(value) {
|
|
23
|
-
if (value === void 0)
|
|
24
|
-
return;
|
|
25
|
-
if (typeof value !== "string")
|
|
26
|
-
throw invalidParamsFault('Field "description" must be a string');
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
function validateOverlap(value) {
|
|
30
|
-
if (value === void 0)
|
|
31
|
-
return;
|
|
32
|
-
if (typeof value !== "string" || !isScheduleOverlapPolicy(value))
|
|
33
|
-
throw invalidParamsFault('Field "overlap" must be one of skip, queue, cancel-running, allow');
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
function validateBackfill(value) {
|
|
37
|
-
if (value === void 0)
|
|
38
|
-
return;
|
|
39
|
-
if (typeof value !== "boolean")
|
|
40
|
-
throw invalidParamsFault('Field "backfill" must be a boolean');
|
|
41
|
-
return value;
|
|
42
|
-
}
|
|
43
|
-
function formatJitterValidationMessage(message) {
|
|
44
|
-
const enginePrefix = "Invalid options.jitter: ", hasEnginePrefix = message.startsWith(enginePrefix), wireDetail = (hasEnginePrefix ? message.slice(enginePrefix.length) : message).replaceAll("options.jitter", 'Field "jitter"');
|
|
45
|
-
return hasEnginePrefix ? `Field "jitter" is invalid: ${wireDetail}` : wireDetail;
|
|
46
|
-
}
|
|
47
|
-
function validateJitter(value) {
|
|
48
|
-
if (value === void 0)
|
|
49
|
-
return;
|
|
50
|
-
if (typeof value !== "string" && typeof value !== "number")
|
|
51
|
-
throw invalidParamsFault('Field "jitter" must be a duration string or a number of milliseconds');
|
|
52
|
-
try {
|
|
53
|
-
normalizeScheduleUpdateOptions({ jitter: value });
|
|
54
|
-
} catch (error) {
|
|
55
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
-
throw invalidParamsFault(formatJitterValidationMessage(message));
|
|
57
|
-
}
|
|
58
|
-
return value;
|
|
59
|
-
}
|
|
60
|
-
function validateScheduleUpdateOptions(input) {
|
|
61
|
-
const description = validateDescription(input.description), overlap = validateOverlap(input.overlap), backfill = validateBackfill(input.backfill), jitter = validateJitter(input.jitter);
|
|
62
|
-
return {
|
|
63
|
-
...description !== void 0 ? { description } : {},
|
|
64
|
-
...overlap !== void 0 ? { overlap } : {},
|
|
65
|
-
...backfill !== void 0 ? { backfill } : {},
|
|
66
|
-
...jitter !== void 0 ? { jitter } : {}
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
22
|
export const updateScheduleOperation = defineOperation({
|
|
70
23
|
name: "weft.schedules.update",
|
|
71
24
|
mcpExposable: !1,
|
|
@@ -80,7 +33,7 @@ export const updateScheduleOperation = defineOperation({
|
|
|
80
33
|
transports: { http: !0, jsonRpcHttp: !0, jsonRpcWebSocket: !0, jsonRpcStdio: !0 },
|
|
81
34
|
unknownKeyPolicy: { http: "strip", jsonRpc: "reject" },
|
|
82
35
|
invoke: async ({ input, engine }) => {
|
|
83
|
-
const typedEngine = engine, spec = validateScheduleInputCadence(input), options =
|
|
36
|
+
const typedEngine = engine, spec = validateScheduleInputCadence(input), options = validateScheduleMutableOptions(input);
|
|
84
37
|
try {
|
|
85
38
|
await typedEngine.updateSchedule(input.scheduleId, spec, options);
|
|
86
39
|
return null;
|
|
@@ -98,6 +98,7 @@ import {
|
|
|
98
98
|
getWorkflowTimelineRestBinding
|
|
99
99
|
} from "./operations/get-workflow-timeline.js";
|
|
100
100
|
import { getWorkflowOperation, getWorkflowRestBinding } from "./operations/get-workflow.js";
|
|
101
|
+
import { listAlertsOperation, listAlertsRestBinding } from "./operations/list-alerts.js";
|
|
101
102
|
import {
|
|
102
103
|
listCheckpointsOperation,
|
|
103
104
|
listCheckpointsRestBinding
|
|
@@ -214,6 +215,7 @@ import {
|
|
|
214
215
|
} from "./operations/workflow-events-sse.js";
|
|
215
216
|
import { workflowEventsSubscriptionOperation } from "./operations/workflow-events-subscription.js";
|
|
216
217
|
export const REST_BINDINGS = [
|
|
218
|
+
listAlertsRestBinding,
|
|
217
219
|
startWorkflowRestBinding,
|
|
218
220
|
startOrSignalWorkflowRestBinding,
|
|
219
221
|
recoverAllRestBinding,
|
|
@@ -401,6 +403,7 @@ export function createLiveOperationRegistry(options) {
|
|
|
401
403
|
buildTaskDiagnosticsOperationForRegistry(resolved),
|
|
402
404
|
clearTaskDeadLetterOperation,
|
|
403
405
|
listSchedulesOperation,
|
|
406
|
+
listAlertsOperation,
|
|
404
407
|
getScheduleOperation,
|
|
405
408
|
replayWorkflowOperation,
|
|
406
409
|
storageCapabilities.storageCapabilitiesOperation,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { RegistryAgnosticEngine } from '../core/engine';
|
|
10
10
|
import type { ServiceWorkerScheduler } from './scheduler.ts';
|
|
11
|
+
import type { ServiceWorkerHandlerOptions } from './setup.ts';
|
|
11
12
|
import type { MinimalExtendableEvent, MinimalFetchEvent, MinimalPeriodicSyncEvent } from './shared.ts';
|
|
12
13
|
export { ServiceWorkerScheduler } from './scheduler.ts';
|
|
13
14
|
export type { ServiceWorkerSchedulerOptions } from './scheduler.ts';
|
|
@@ -44,6 +45,8 @@ export interface ServiceWorkerOptions {
|
|
|
44
45
|
*/
|
|
45
46
|
engine: RegistryAgnosticEngine;
|
|
46
47
|
pathPrefix?: string;
|
|
48
|
+
/** Handler options supported by the Service Worker runtime. */
|
|
49
|
+
handlerOptions?: ServiceWorkerHandlerOptions;
|
|
47
50
|
}
|
|
48
51
|
/**
|
|
49
52
|
* Create a fetch event handler that intercepts requests matching the given
|
|
@@ -127,4 +130,4 @@ export declare function createLifecycleHandlers(): {
|
|
|
127
130
|
install: (event: MinimalExtendableEvent) => void;
|
|
128
131
|
activate: (event: MinimalExtendableEvent) => void;
|
|
129
132
|
};
|
|
130
|
-
export { setupServiceWorker, type SetupServiceWorkerOptions, type SetupServiceWorkerResult, } from './setup.ts';
|
|
133
|
+
export { setupServiceWorker, type ServiceWorkerHandlerOptions, type SetupServiceWorkerOptions, type SetupServiceWorkerResult, } from './setup.ts';
|