@lostgradient/weft 0.15.0 → 0.17.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.
Files changed (147) hide show
  1. package/README.md +1 -1
  2. package/dist/alerting/alert-manager.d.ts +5 -3
  3. package/dist/alerting/alert-manager.js +26 -5
  4. package/dist/alerting/index.d.ts +1 -1
  5. package/dist/alerting/types.d.ts +17 -13
  6. package/dist/alerting/types.js +5 -0
  7. package/dist/cli/api.js +5 -19
  8. package/dist/cli/console.d.ts +12 -0
  9. package/dist/cli/console.js +62 -0
  10. package/dist/cli/generated/operation-catalog.snapshot.json +71 -0
  11. package/dist/cli/generated/operation-client.generated.d.ts +15 -2
  12. package/dist/cli/generated/operation-client.generated.js +2 -0
  13. package/dist/cli/help-text.d.ts +1 -1
  14. package/dist/cli/help-text.js +1 -0
  15. package/dist/cli/index.d.ts +1 -0
  16. package/dist/cli/index.js +1 -0
  17. package/dist/cli/json-input.d.ts +20 -0
  18. package/dist/cli/json-input.js +33 -0
  19. package/dist/cli/parse-arguments.js +2 -0
  20. package/dist/cli/schedule.js +7 -9
  21. package/dist/cli/types.d.ts +1 -0
  22. package/dist/cli/workflow-commands.js +5 -25
  23. package/dist/cli-main.js +4 -2
  24. package/dist/core/engine/disposal.js +7 -0
  25. package/dist/core/engine/engine-runtime-helpers.d.ts +3 -1
  26. package/dist/core/engine/engine-runtime-helpers.js +2 -2
  27. package/dist/core/engine/errors.d.ts +20 -0
  28. package/dist/core/engine/errors.js +8 -0
  29. package/dist/core/engine/index.d.ts +39 -14
  30. package/dist/core/engine/index.js +84 -9
  31. package/dist/core/engine/inline-launch-queue.d.ts +6 -2
  32. package/dist/core/engine/inline-launch-queue.js +41 -6
  33. package/dist/core/engine/internals.d.ts +2 -0
  34. package/dist/core/engine/lease-manager.d.ts +3 -2
  35. package/dist/core/engine/lease-manager.js +7 -3
  36. package/dist/core/engine/operations-time.js +6 -0
  37. package/dist/core/execution-strategy.d.ts +8 -0
  38. package/dist/core/inline-execution-strategy.d.ts +1 -0
  39. package/dist/core/inline-execution-strategy.js +33 -20
  40. package/dist/core/weft-error.d.ts +1 -1
  41. package/dist/core/weft-error.js +1 -0
  42. package/dist/core/worker-execution-strategy.d.ts +2 -0
  43. package/dist/core/worker-execution-strategy.js +7 -2
  44. package/dist/core/worker-turn-watchdog.d.ts +4 -1
  45. package/dist/core/worker-turn-watchdog.js +9 -4
  46. package/dist/diagnostics/recommendations.js +1 -9
  47. package/dist/http.js +2 -2
  48. package/dist/index.d.ts +2 -2
  49. package/dist/index.js +1 -0
  50. package/dist/indexeddb.js +1 -1
  51. package/dist/json-schema.js +1 -1
  52. package/dist/mcp/tools.js +1 -3
  53. package/dist/server/authorization-scope.d.ts +1 -1
  54. package/dist/server/authorization-scope.js +0 -2
  55. package/dist/server/dashboard-assets.d.ts +37 -0
  56. package/dist/server/dashboard-assets.js +209 -0
  57. package/dist/server/index.d.ts +10 -1
  58. package/dist/server/index.js +11 -2
  59. package/dist/server/interactive-operations.js +1 -0
  60. package/dist/server/operations/aggregate-workflows.js +0 -7
  61. package/dist/server/operations/async-activity.js +5 -24
  62. package/dist/server/operations/bulk-cancel-workflows.js +3 -8
  63. package/dist/server/operations/bulk-delete-workflows.js +3 -8
  64. package/dist/server/operations/bulk-mutate-workflow-tags.js +3 -8
  65. package/dist/server/operations/bulk-operation-helpers.d.ts +2 -10
  66. package/dist/server/operations/bulk-operation-helpers.js +0 -6
  67. package/dist/server/operations/bulk-retry-failed-workflows.js +3 -8
  68. package/dist/server/operations/bulk-signal-workflows.js +3 -8
  69. package/dist/server/operations/cancel-schedule.js +1 -3
  70. package/dist/server/operations/cancel-workflow.js +1 -3
  71. package/dist/server/operations/create-schedule.js +21 -49
  72. package/dist/server/operations/fork-workflow.js +2 -3
  73. package/dist/server/operations/get-checkpoint-at.js +1 -6
  74. package/dist/server/operations/get-registry.js +1 -7
  75. package/dist/server/operations/get-retention-overview.js +2 -15
  76. package/dist/server/operations/get-review.js +2 -15
  77. package/dist/server/operations/get-schedule.js +2 -8
  78. package/dist/server/operations/get-stream-chunks.js +2 -6
  79. package/dist/server/operations/get-system-lease.js +1 -7
  80. package/dist/server/operations/get-system-metrics.js +0 -4
  81. package/dist/server/operations/get-task-diagnostics.js +2 -13
  82. package/dist/server/operations/get-update-result.js +1 -6
  83. package/dist/server/operations/get-workflow-attributes.js +2 -15
  84. package/dist/server/operations/get-workflow-events.js +2 -15
  85. package/dist/server/operations/get-workflow-observability.js +2 -5
  86. package/dist/server/operations/get-workflow-result.js +0 -7
  87. package/dist/server/operations/get-workflow-timeline.js +1 -6
  88. package/dist/server/operations/get-workflow.d.ts +3 -6
  89. package/dist/server/operations/get-workflow.js +2 -12
  90. package/dist/server/operations/list-alerts.d.ts +29 -0
  91. package/dist/server/operations/list-alerts.js +47 -0
  92. package/dist/server/operations/list-checkpoints.js +1 -6
  93. package/dist/server/operations/list-reviews.js +0 -7
  94. package/dist/server/operations/list-schedules.js +3 -8
  95. package/dist/server/operations/list-task-queues.js +0 -4
  96. package/dist/server/operations/list-workers.js +0 -4
  97. package/dist/server/operations/list-workflows.js +0 -7
  98. package/dist/server/operations/pause-schedule.js +1 -3
  99. package/dist/server/operations/purge-workflows.js +2 -5
  100. package/dist/server/operations/query-workflow.js +4 -15
  101. package/dist/server/operations/replay-workflow.js +2 -6
  102. package/dist/server/operations/resume-schedule.js +1 -3
  103. package/dist/server/operations/resume-workflow.js +1 -3
  104. package/dist/server/operations/schedule-faults.d.ts +9 -1
  105. package/dist/server/operations/schedule-faults.js +51 -0
  106. package/dist/server/operations/set-workflow-attributes.js +3 -15
  107. package/dist/server/operations/signal-workflow.js +3 -12
  108. package/dist/server/operations/single-workflow-tag-mutation.js +0 -7
  109. package/dist/server/operations/start-or-signal-workflow.js +2 -3
  110. package/dist/server/operations/start-workflow.js +2 -3
  111. package/dist/server/operations/storage-capabilities.js +1 -3
  112. package/dist/server/operations/storage.js +7 -13
  113. package/dist/server/operations/submit-review-decision.js +3 -12
  114. package/dist/server/operations/suspend-workflow.js +1 -3
  115. package/dist/server/operations/timeout-workflow.js +1 -3
  116. package/dist/server/operations/update-schedule.js +7 -56
  117. package/dist/server/operations/update-workflow.js +2 -3
  118. package/dist/server/operations/worker-drain.js +0 -8
  119. package/dist/server/rest-binding.d.ts +3 -2
  120. package/dist/server/rest-bindings.js +3 -0
  121. package/dist/server/route-model.d.ts +2 -0
  122. package/dist/server/route-model.js +1 -1
  123. package/dist/server/runtime/task-reconciliation.d.ts +10 -1
  124. package/dist/server/runtime/task-reconciliation.js +26 -2
  125. package/dist/server/serve-internals.js +12 -6
  126. package/dist/service-worker/index.d.ts +4 -1
  127. package/dist/service-worker/index.js +2 -2
  128. package/dist/service-worker/setup.d.ts +20 -0
  129. package/dist/service-worker/setup.js +5 -5
  130. package/dist/storage/bun-sql.js +13 -31
  131. package/dist/storage/indexeddb.js +28 -48
  132. package/dist/storage/lmdb.js +1 -1
  133. package/dist/storage/memory.js +1 -1
  134. package/dist/storage/neon.js +2 -2
  135. package/dist/storage/postgres.js +2 -2
  136. package/dist/storage/resolve.js +1 -1
  137. package/dist/storage/scoped-storage.js +1 -1
  138. package/dist/storage/testing.d.ts +6 -3
  139. package/dist/storage/testing.js +1 -1
  140. package/dist/storage/turso.js +2 -2
  141. package/dist/version.d.ts +1 -1
  142. package/dist/version.js +1 -1
  143. package/dist/web-extension.js +1 -1
  144. package/dist/worker/protocol-schemas.d.ts +2 -2
  145. package/dist/worker/protocol-schemas.js +2 -2
  146. package/dist/worker/protocol.js +1 -1
  147. package/package.json +5 -1
@@ -0,0 +1,209 @@
1
+ import * as fileSystem from "node:fs";
2
+ import { isAbsolute, join, relative, resolve as resolvePath, sep } from "node:path";
3
+ import { API_PREFIX, DIRECT_HTTP_ROUTES, ROOT_API_PREFIX } from "./route-model.js";
4
+ function overlapsDashboardPageRoute(prefix, route) {
5
+ if (route.endsWith("/*")) {
6
+ const base = route.slice(0, -2);
7
+ return prefix === base || prefix.startsWith(`${base}/`);
8
+ }
9
+ return prefix === route;
10
+ }
11
+ function overlapsWildcardRoute(prefix, route) {
12
+ return prefix === route || prefix.startsWith(`${route}/`) || route.startsWith(`${prefix}/`);
13
+ }
14
+ function hasInvalidPrefixShape(prefix) {
15
+ return prefix.length === 0 || prefix === "/" || !prefix.startsWith("/") || prefix.endsWith("/") || ["?", "#", "%", "*", ":", "\\"].some((character) => prefix.includes(character));
16
+ }
17
+ function validateAssetPrefixShape(prefix) {
18
+ if (hasInvalidPrefixShape(prefix))
19
+ throw Error("dashboardAssets.prefix must be an absolute path prefix without a trailing slash, wildcard, parameter, query, fragment, percent escape, or backslash");
20
+ if (prefix.split("/").slice(1).some((segment) => segment.length === 0 || segment === "." || segment === ".."))
21
+ throw Error("dashboardAssets.prefix must contain only concrete, non-empty path segments");
22
+ const serializedPathname = new URL(prefix, "http://weft.invalid").pathname;
23
+ if (serializedPathname !== prefix)
24
+ throw Error(`dashboardAssets.prefix must round-trip through URL serialization unchanged; ${prefix} serializes as ${serializedPathname}`);
25
+ }
26
+ function validateAssetPrefixReservations(prefix, pageRoutes) {
27
+ if ([
28
+ API_PREFIX,
29
+ ROOT_API_PREFIX,
30
+ ...DIRECT_HTTP_ROUTES.map(({ path }) => path)
31
+ ].some((reserved) => overlapsWildcardRoute(prefix, reserved)))
32
+ throw Error("dashboardAssets.prefix must not overlap the /api, /v1, or direct HTTP route spaces");
33
+ if (pageRoutes.some((route) => overlapsDashboardPageRoute(prefix, route)))
34
+ throw Error("dashboardAssets.prefix must not overlap dashboard page routes");
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 = resolvePath(directory);
50
+ let directoryStats;
51
+ try {
52
+ directoryStats = fileSystem.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: fileSystem.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 hasSameFileIdentity(left, right) {
65
+ return left.dev === right.dev && left.ino === right.ino;
66
+ }
67
+ function resolveAssetPath(directory, prefix, request) {
68
+ if (request.signal.aborted)
69
+ return;
70
+ const rawWildcardPath = new URL(request.url).pathname.slice(prefix.length + 1);
71
+ let wildcardPath;
72
+ try {
73
+ wildcardPath = decodeURIComponent(rawWildcardPath);
74
+ } catch {
75
+ return;
76
+ }
77
+ if (wildcardPath.length === 0)
78
+ return;
79
+ const pathSegments = wildcardPath.split(/[\\/]/);
80
+ if (pathSegments.some((segment) => segment === ".." || segment.length === 0))
81
+ return;
82
+ const assetPath = resolvePath(join(directory, ...pathSegments));
83
+ if (!isWithinDirectory(directory, assetPath))
84
+ return;
85
+ return assetPath;
86
+ }
87
+ function isVerifiedAssetDescriptor(request, directory, canonicalStats, openedStats, postOpenStats, postOpenAssetPath, openedAssetPath) {
88
+ return !request.signal.aborted && isWithinDirectory(directory, postOpenAssetPath) && (openedAssetPath === void 0 || isWithinDirectory(directory, openedAssetPath)) && canonicalStats.isFile() && openedStats.isFile() && postOpenStats.isFile() && hasSameFileIdentity(openedStats, canonicalStats) && hasSameFileIdentity(openedStats, postOpenStats);
89
+ }
90
+ function resolveOpenedDescriptorPath(descriptor, assetFileSystem) {
91
+ for (const descriptorPath of [`/proc/self/fd/${descriptor}`, `/dev/fd/${descriptor}`])
92
+ try {
93
+ const resolvedPath = assetFileSystem.realpathSync(descriptorPath);
94
+ if (resolvedPath.startsWith("/proc/self/fd/") || resolvedPath.startsWith("/dev/fd/"))
95
+ continue;
96
+ return resolvedPath;
97
+ } catch {}
98
+ return;
99
+ }
100
+ function createAssetStream(descriptor, verifiedSize, assetFileSystem) {
101
+ let closed = !1, cancelled = !1, position = 0, remainingBytes = verifiedSize, inFlightRead;
102
+ const close = () => {
103
+ if (!closed) {
104
+ closed = !0;
105
+ assetFileSystem.closeSync(descriptor);
106
+ }
107
+ };
108
+ return { stream: new ReadableStream({
109
+ async pull(controller) {
110
+ if (cancelled)
111
+ return;
112
+ if (remainingBytes === 0) {
113
+ close();
114
+ controller.close();
115
+ return;
116
+ }
117
+ const buffer = Buffer.allocUnsafe(Math.min(65536, remainingBytes)), read = assetFileSystem.read(descriptor, buffer, 0, buffer.byteLength, position);
118
+ inFlightRead = read;
119
+ try {
120
+ const bytesRead = await read;
121
+ if (cancelled) {
122
+ close();
123
+ return;
124
+ }
125
+ if (bytesRead === 0) {
126
+ close();
127
+ controller.error(Error("Dashboard asset ended before its verified content length was read."));
128
+ return;
129
+ }
130
+ const boundedBytesRead = Math.min(bytesRead, remainingBytes);
131
+ position += boundedBytesRead;
132
+ remainingBytes -= boundedBytesRead;
133
+ controller.enqueue(buffer.subarray(0, boundedBytesRead));
134
+ if (remainingBytes === 0) {
135
+ close();
136
+ controller.close();
137
+ }
138
+ } catch (error) {
139
+ close();
140
+ if (!cancelled)
141
+ controller.error(error);
142
+ } finally {
143
+ if (inFlightRead === read)
144
+ inFlightRead = void 0;
145
+ }
146
+ },
147
+ async cancel() {
148
+ cancelled = !0;
149
+ try {
150
+ await inFlightRead;
151
+ } catch {}
152
+ close();
153
+ }
154
+ }), close };
155
+ }
156
+ function readAssetDescriptor(descriptor, buffer, offset, length, position) {
157
+ return new Promise((_resolve, reject) => {
158
+ fileSystem.read(descriptor, buffer, offset, length, position, (error, bytesRead) => {
159
+ if (error)
160
+ reject(error);
161
+ else
162
+ _resolve(bytesRead);
163
+ });
164
+ });
165
+ }
166
+ function assetResponse(directory, prefix, request, assetFileSystem) {
167
+ const assetPath = resolveAssetPath(directory, prefix, request);
168
+ if (assetPath === void 0)
169
+ return new Response("Not Found", { status: 404 });
170
+ let descriptor;
171
+ try {
172
+ const realAssetPath = assetFileSystem.realpathSync(assetPath);
173
+ if (!isWithinDirectory(directory, realAssetPath))
174
+ return new Response("Not Found", { status: 404 });
175
+ const canonicalStats = assetFileSystem.statSync(realAssetPath);
176
+ descriptor = assetFileSystem.openSync(realAssetPath, assetFileSystem.constants.O_RDONLY | assetFileSystem.constants.O_NOFOLLOW | assetFileSystem.constants.O_NONBLOCK);
177
+ const fileStats = assetFileSystem.fstatSync(descriptor), postOpenAssetPath = assetFileSystem.realpathSync(realAssetPath), postOpenStats = assetFileSystem.statSync(postOpenAssetPath), openedAssetPath = resolveOpenedDescriptorPath(descriptor, assetFileSystem);
178
+ if (!isVerifiedAssetDescriptor(request, directory, canonicalStats, fileStats, postOpenStats, postOpenAssetPath, openedAssetPath))
179
+ return new Response("Not Found", { status: 404 });
180
+ const headers = {
181
+ "content-length": String(fileStats.size),
182
+ "content-type": Bun.file(realAssetPath).type
183
+ };
184
+ if (request.method === "HEAD")
185
+ return new Response(null, { headers });
186
+ const assetStream = createAssetStream(descriptor, fileStats.size, assetFileSystem);
187
+ try {
188
+ const response = new Response(assetStream.stream, { headers });
189
+ descriptor = void 0;
190
+ return response;
191
+ } catch {
192
+ assetStream.close();
193
+ descriptor = void 0;
194
+ return new Response("Not Found", { status: 404 });
195
+ }
196
+ } catch {
197
+ return new Response("Not Found", { status: 404 });
198
+ } finally {
199
+ if (descriptor !== void 0)
200
+ assetFileSystem.closeSync(descriptor);
201
+ }
202
+ }
203
+ export function createDashboardAssetRoute(assets, assetFileSystem) {
204
+ const fileSystemForAsset = assetFileSystem ?? {
205
+ ...fileSystem,
206
+ read: readAssetDescriptor
207
+ }, handler = (request) => assetResponse(assets.directory, assets.prefix, request, fileSystemForAsset);
208
+ return { GET: handler, HEAD: handler };
209
+ }
@@ -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
  /**
@@ -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,4 +1,5 @@
1
1
  export const INTERACTIVE_OPERATION_NAMES = [
2
+ "weft.alerts.list",
2
3
  "weft.workflows.start",
3
4
  "weft.workflows.get",
4
5
  "weft.workflows.scheduleprovenance.get",
@@ -79,12 +79,6 @@ function extractAggregateWorkflowsInput(request) {
79
79
  ...limitValue !== void 0 && Number.isFinite(limitValue) && limitValue >= 1 ? { limit: limitValue } : {}
80
80
  };
81
81
  }
82
- function shapeAggregateWorkflowsSuccess(result) {
83
- return new Response(JSON.stringify(result), {
84
- status: 200,
85
- headers: { "Content-Type": "application/json" }
86
- });
87
- }
88
82
  function shapeAggregateWorkflowsFault(fault) {
89
83
  if (fault.code === "Unprocessable")
90
84
  return shapeRestFault(fault, { status: 400 });
@@ -106,6 +100,5 @@ export const aggregateWorkflowsRestBinding = {
106
100
  },
107
101
  extractInput: async (request) => extractAggregateWorkflowsInput(request),
108
102
  success: { kind: "json", status: 200 },
109
- shapeSuccess: (output) => shapeAggregateWorkflowsSuccess(output),
110
103
  shapeFault: shapeAggregateWorkflowsFault
111
104
  };
@@ -11,7 +11,7 @@ import { PayloadSizeExceededError } from "../../core/payload-size.js";
11
11
  import { raiseFault } from "../operation-catalog.js";
12
12
  import { defineOperation } from "../operation-registry.js";
13
13
  import { readRestJsonBody } from "../rest-body.js";
14
- import { invalidParamsFault, isOperationFault, shapeRestFault } from "./operation-helpers.js";
14
+ import { invalidParamsFault, isOperationFault } from "./operation-helpers.js";
15
15
  const asyncActivityReadAccess = {
16
16
  kind: "scoped",
17
17
  scopes: { kind: "anyOf", scopes: ["workflows:read"] }
@@ -137,20 +137,7 @@ export const completeAsyncActivityOperation = defineOperation({
137
137
  }
138
138
  return { ok: !0 };
139
139
  }
140
- });
141
- function shapeAsyncActivitySuccess(output) {
142
- return new Response(JSON.stringify(output), {
143
- status: 200,
144
- headers: { "Content-Type": "application/json" }
145
- });
146
- }
147
- function shapePendingAsyncActivitiesSuccess(output) {
148
- return new Response(JSON.stringify(output), {
149
- status: 200,
150
- headers: { "Content-Type": "application/json" }
151
- });
152
- }
153
- export const listPendingAsyncActivitiesRestBinding = {
140
+ }), listPendingAsyncActivitiesRestBinding = {
154
141
  method: "GET",
155
142
  path: "/v1/workflows/:id/pending-async-activities",
156
143
  pathParamNames: ["id"],
@@ -168,9 +155,7 @@ export const listPendingAsyncActivitiesRestBinding = {
168
155
  ...cursor === null ? {} : { cursor }
169
156
  };
170
157
  },
171
- success: { kind: "json", status: 200 },
172
- shapeSuccess: (output) => shapePendingAsyncActivitiesSuccess(output),
173
- shapeFault: shapeRestFault
158
+ success: { kind: "json", status: 200 }
174
159
  };
175
160
  async function readJsonObjectBody(request, context) {
176
161
  const body = await readRestJsonBody(request, context).catch((error) => {
@@ -198,9 +183,7 @@ export const completeAsyncActivityRestBinding = {
198
183
  ..."result" in body ? { result: body.result } : {}
199
184
  };
200
185
  },
201
- success: { kind: "json", status: 200 },
202
- shapeSuccess: (output) => shapeAsyncActivitySuccess(output),
203
- shapeFault: shapeRestFault
186
+ success: { kind: "json", status: 200 }
204
187
  }, failAsyncActivityRestBinding = {
205
188
  method: "POST",
206
189
  path: "/v1/activities/fail",
@@ -217,7 +200,5 @@ export const completeAsyncActivityRestBinding = {
217
200
  error: body.error
218
201
  };
219
202
  },
220
- success: { kind: "json", status: 200 },
221
- shapeSuccess: (output) => shapeAsyncActivitySuccess(output),
222
- shapeFault: shapeRestFault
203
+ success: { kind: "json", status: 200 }
223
204
  };
@@ -12,11 +12,8 @@ import {
12
12
  parseBulkOperationControlFromBody,
13
13
  readOptionalJsonBody
14
14
  } from "./bulk-filter-helpers.js";
15
- import {
16
- shapeBulkJsonSuccess,
17
- validatedListFilterFromBulkInput
18
- } from "./bulk-operation-helpers.js";
19
- import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
15
+ import { validatedListFilterFromBulkInput } from "./bulk-operation-helpers.js";
16
+ import { invalidParamsFault } from "./operation-helpers.js";
20
17
  const bulkCancelWorkflowsInput = bulkListFilterInputSchema.merge(bulkOperationControlInputSchema), bulkCancelWorkflowsOutput = z.unknown();
21
18
  export const bulkCancelWorkflowsOperation = defineOperation({
22
19
  name: "weft.workflows.bulk.cancel",
@@ -58,7 +55,5 @@ export const bulkCancelWorkflowsOperation = defineOperation({
58
55
  throw invalidParamsFault(faultMessage(error));
59
56
  }
60
57
  },
61
- success: { kind: "json", status: 200 },
62
- shapeSuccess: (output) => shapeBulkJsonSuccess(output),
63
- shapeFault: shapeRestFault
58
+ success: { kind: "json", status: 200 }
64
59
  };
@@ -16,11 +16,8 @@ import {
16
16
  readOptionalJsonBody,
17
17
  unprocessableFault
18
18
  } from "./bulk-filter-helpers.js";
19
- import {
20
- shapeBulkJsonSuccess,
21
- validatedListFilterFromBulkInput
22
- } from "./bulk-operation-helpers.js";
23
- import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
19
+ import { validatedListFilterFromBulkInput } from "./bulk-operation-helpers.js";
20
+ import { invalidParamsFault } from "./operation-helpers.js";
24
21
  const bulkDeleteWorkflowsInput = bulkListFilterInputSchema.merge(bulkOperationControlInputSchema), bulkDeleteWorkflowsOutput = z.unknown();
25
22
  export const bulkDeleteWorkflowsOperation = defineOperation({
26
23
  name: "weft.workflows.bulk.delete",
@@ -65,7 +62,5 @@ export const bulkDeleteWorkflowsOperation = defineOperation({
65
62
  throw invalidParamsFault(faultMessage(error));
66
63
  }
67
64
  },
68
- success: { kind: "json", status: 200 },
69
- shapeSuccess: (output) => shapeBulkJsonSuccess(output),
70
- shapeFault: shapeRestFault
65
+ success: { kind: "json", status: 200 }
71
66
  };
@@ -13,11 +13,8 @@ import {
13
13
  parseBulkOperationControlFromBody,
14
14
  readOptionalJsonBody
15
15
  } from "./bulk-filter-helpers.js";
16
- import {
17
- shapeBulkJsonSuccess,
18
- validatedListFilterFromBulkInput
19
- } from "./bulk-operation-helpers.js";
20
- import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
16
+ import { validatedListFilterFromBulkInput } from "./bulk-operation-helpers.js";
17
+ import { invalidParamsFault } from "./operation-helpers.js";
21
18
  const bulkMutateWorkflowTagsInput = z.object({
22
19
  filter: bulkListFilterInputSchema.optional(),
23
20
  tags: z.array(z.string()),
@@ -94,7 +91,5 @@ export const bulkMutateWorkflowTagsRestBinding = {
94
91
  ...parseBulkOperationControlFromBody(body)
95
92
  };
96
93
  },
97
- success: { kind: "json", status: 200 },
98
- shapeSuccess: (output) => shapeBulkJsonSuccess(output),
99
- shapeFault: shapeRestFault
94
+ success: { kind: "json", status: 200 }
100
95
  };
@@ -1,4 +1,4 @@
1
- import type { BulkCancelResult, BulkDeleteResult, BulkOperationDryRunResult, BulkRetryFailedResult, BulkSignalResult, BulkTagResult, ListFilter, PurgeResult } from '../../core/types.ts';
1
+ import type { ListFilter } from '../../core/types.ts';
2
2
  import { type BulkListFilterInput } from './bulk-filter-helpers.ts';
3
3
  /**
4
4
  * Coerces filter tags (label `Field "filter.tags"`), builds a `ListFilter`
@@ -13,15 +13,7 @@ import { type BulkListFilterInput } from './bulk-filter-helpers.ts';
13
13
  * runs.
14
14
  *
15
15
  * Not used by `purge-workflows.ts`: purge allows empty/unscoped filters and
16
- * shapes faults through `shapeRestFault` (sanitized) rather than passing
16
+ * uses the canonical REST fault fallback (sanitized) rather than passing
17
17
  * raw engine messages through unmasked.
18
18
  */
19
19
  export declare function validatedListFilterFromBulkInput(input: BulkListFilterInput): ListFilter;
20
- export type BulkOperationSuccessResult = BulkCancelResult | BulkDeleteResult | BulkRetryFailedResult | BulkSignalResult | BulkTagResult | BulkOperationDryRunResult | PurgeResult;
21
- /**
22
- * Standard JSON 200 response shape for bulk-workflow operations. Used by
23
- * every bulk operation's REST `shapeSuccess`; the result types are unioned
24
- * rather than left as `unknown` so the type system catches accidental
25
- * `undefined` / `Response` / `Error` arguments at the call site.
26
- */
27
- export declare function shapeBulkJsonSuccess(result: BulkOperationSuccessResult): Response;
@@ -21,9 +21,3 @@ export function validatedListFilterFromBulkInput(input) {
21
21
  }
22
22
  return filter;
23
23
  }
24
- export function shapeBulkJsonSuccess(result) {
25
- return new Response(JSON.stringify(result), {
26
- status: 200,
27
- headers: { "Content-Type": "application/json" }
28
- });
29
- }
@@ -12,11 +12,8 @@ import {
12
12
  parseBulkOperationControlFromBody,
13
13
  readOptionalJsonBody
14
14
  } from "./bulk-filter-helpers.js";
15
- import {
16
- shapeBulkJsonSuccess,
17
- validatedListFilterFromBulkInput
18
- } from "./bulk-operation-helpers.js";
19
- import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
15
+ import { validatedListFilterFromBulkInput } from "./bulk-operation-helpers.js";
16
+ import { invalidParamsFault } from "./operation-helpers.js";
20
17
  const bulkRetryFailedWorkflowsInput = bulkListFilterInputSchema.merge(bulkOperationControlInputSchema), bulkRetryFailedWorkflowsOutput = z.unknown();
21
18
  export const bulkRetryFailedWorkflowsOperation = defineOperation({
22
19
  name: "weft.workflows.bulk.retryfailed",
@@ -58,7 +55,5 @@ export const bulkRetryFailedWorkflowsOperation = defineOperation({
58
55
  throw invalidParamsFault(faultMessage(error));
59
56
  }
60
57
  },
61
- success: { kind: "json", status: 200 },
62
- shapeSuccess: (output) => shapeBulkJsonSuccess(output),
63
- shapeFault: shapeRestFault
58
+ success: { kind: "json", status: 200 }
64
59
  };
@@ -12,11 +12,8 @@ import {
12
12
  parseBulkOperationControlFromBody,
13
13
  readOptionalJsonBody
14
14
  } from "./bulk-filter-helpers.js";
15
- import {
16
- shapeBulkJsonSuccess,
17
- validatedListFilterFromBulkInput
18
- } from "./bulk-operation-helpers.js";
19
- import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
15
+ import { validatedListFilterFromBulkInput } from "./bulk-operation-helpers.js";
16
+ import { invalidParamsFault } from "./operation-helpers.js";
20
17
  const bulkSignalWorkflowsInput = bulkListFilterInputSchema.extend({
21
18
  name: z.string().min(1),
22
19
  payload: z.unknown().optional()
@@ -74,7 +71,5 @@ export const bulkSignalWorkflowsOperation = defineOperation({
74
71
  ...parseBulkOperationControlFromBody(body)
75
72
  };
76
73
  },
77
- success: { kind: "json", status: 200 },
78
- shapeSuccess: (output) => shapeBulkJsonSuccess(output),
79
- shapeFault: shapeRestFault
74
+ success: { kind: "json", status: 200 }
80
75
  };
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
2
  import { defineOperation } from "../operation-registry.js";
3
- import { shapeRestFault } from "./operation-helpers.js";
4
3
  import { mapScheduleErrorToFault } from "./schedule-faults.js";
5
4
  const cancelScheduleInput = z.object({
6
5
  scheduleId: z.string().min(1)
@@ -38,6 +37,5 @@ export const cancelScheduleOperation = defineOperation({
38
37
  extractInput: async (_request, pathParams) => ({
39
38
  scheduleId: pathParams.id ?? ""
40
39
  }),
41
- success: { kind: "empty", status: 204 },
42
- shapeFault: shapeRestFault
40
+ success: { kind: "empty", status: 204 }
43
41
  };
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
- import { shapeRestFault } from "./operation-helpers.js";
3
2
  import {
4
3
  createSingleWorkflowControlOperation,
5
4
  extractWorkflowIdFromPath
@@ -29,6 +28,5 @@ export const cancelWorkflowOperation = createSingleWorkflowControlOperation({
29
28
  workflowId: { kind: "path", pathParam: "id" }
30
29
  },
31
30
  extractInput: async (_request, pathParams) => extractWorkflowIdFromPath(pathParams),
32
- success: { kind: "empty", status: 204 },
33
- shapeFault: shapeRestFault
31
+ success: { kind: "empty", status: 204 }
34
32
  };
@@ -1,17 +1,16 @@
1
1
  import { z } from "zod";
2
2
  import { defineOperation } from "../operation-registry.js";
3
- import { invalidParamsFault, shapeRestFault } from "./operation-helpers.js";
4
- import { mapScheduleErrorToFault, validateScheduleInputCadence } from "./schedule-faults.js";
3
+ import { invalidParamsFault } from "./operation-helpers.js";
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 VALID_SCHEDULE_OVERLAP_POLICIES = new Set([
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
- let validatedId;
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: validatedDescription,
66
- overlap: validatedOverlap,
67
- backfill: validatedBackfill,
68
- jitter: validatedJitter
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: [],
@@ -128,6 +101,5 @@ export const createScheduleRestBinding = {
128
101
  ...extractSharedScheduleRestFields(record)
129
102
  };
130
103
  },
131
- success: { kind: "json", status: 201 },
132
- shapeFault: shapeRestFault
104
+ success: { kind: "json", status: 201 }
133
105
  };