@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.
Files changed (56) hide show
  1. package/README.md +1 -1
  2. package/dist/alerting/alert-manager.d.ts +2 -0
  3. package/dist/alerting/alert-manager.js +3 -0
  4. package/dist/cli/api.js +5 -19
  5. package/dist/cli/console.d.ts +12 -0
  6. package/dist/cli/console.js +62 -0
  7. package/dist/cli/generated/operation-catalog.snapshot.json +71 -0
  8. package/dist/cli/generated/operation-client.generated.d.ts +15 -2
  9. package/dist/cli/generated/operation-client.generated.js +2 -0
  10. package/dist/cli/help-text.d.ts +1 -1
  11. package/dist/cli/help-text.js +1 -0
  12. package/dist/cli/index.d.ts +1 -0
  13. package/dist/cli/index.js +1 -0
  14. package/dist/cli/json-input.d.ts +20 -0
  15. package/dist/cli/json-input.js +33 -0
  16. package/dist/cli/parse-arguments.js +2 -0
  17. package/dist/cli/schedule.js +7 -9
  18. package/dist/cli/types.d.ts +1 -0
  19. package/dist/cli/workflow-commands.js +5 -25
  20. package/dist/cli-main.js +4 -2
  21. package/dist/core/engine/index.d.ts +3 -0
  22. package/dist/core/engine/index.js +3 -0
  23. package/dist/diagnostics/recommendations.js +1 -9
  24. package/dist/http.js +2 -2
  25. package/dist/indexeddb.js +1 -1
  26. package/dist/server/authorization-scope.d.ts +1 -1
  27. package/dist/server/authorization-scope.js +0 -2
  28. package/dist/server/dashboard-assets.d.ts +26 -0
  29. package/dist/server/dashboard-assets.js +95 -0
  30. package/dist/server/index.d.ts +10 -1
  31. package/dist/server/index.js +11 -2
  32. package/dist/server/interactive-operations.js +1 -0
  33. package/dist/server/operations/create-schedule.js +19 -46
  34. package/dist/server/operations/list-alerts.d.ts +26 -0
  35. package/dist/server/operations/list-alerts.js +54 -0
  36. package/dist/server/operations/schedule-faults.d.ts +9 -1
  37. package/dist/server/operations/schedule-faults.js +51 -0
  38. package/dist/server/operations/update-schedule.js +7 -54
  39. package/dist/server/rest-bindings.js +3 -0
  40. package/dist/service-worker/index.d.ts +4 -1
  41. package/dist/service-worker/index.js +2 -2
  42. package/dist/service-worker/setup.d.ts +20 -0
  43. package/dist/service-worker/setup.js +5 -5
  44. package/dist/storage/bun-sql.js +13 -31
  45. package/dist/storage/lmdb.js +1 -1
  46. package/dist/storage/memory.js +1 -1
  47. package/dist/storage/neon.js +2 -2
  48. package/dist/storage/postgres.js +2 -2
  49. package/dist/storage/resolve.js +1 -1
  50. package/dist/storage/scoped-storage.js +1 -1
  51. package/dist/storage/testing.js +1 -1
  52. package/dist/storage/turso.js +2 -2
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/dist/web-extension.js +1 -1
  56. package/package.json +5 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Weft
2
2
 
3
- A Bun-native durable execution engine. Current release: `0.15.0`.
3
+ A Bun-native durable execution engine. Current release: `0.16.0`.
4
4
 
5
5
  Install the library from npm as `@lostgradient/weft`:
6
6
 
@@ -40,5 +40,7 @@ export declare class AlertManager implements Disposable {
40
40
  tick(): void;
41
41
  /** Get current state of all alert rules (for debugging/testing). */
42
42
  get states(): readonly AlertState[];
43
+ /** Get the alert rules that are currently firing. */
44
+ get activeStates(): readonly AlertState[];
43
45
  [Symbol.dispose](): void;
44
46
  }
@@ -155,6 +155,9 @@ export class AlertManager {
155
155
  get states() {
156
156
  return this.#states;
157
157
  }
158
+ get activeStates() {
159
+ return this.#states.filter((state) => state.status === "firing");
160
+ }
158
161
  [Symbol.dispose]() {
159
162
  if (this.#tickInterval !== null) {
160
163
  clearInterval(this.#tickInterval);
package/dist/cli/api.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createLiveOperationRegistry } from "../server/rest-bindings.js";
2
2
  import { findNearestCandidate } from "./command-suggestions.js";
3
3
  import snapshotData from "./generated/operation-catalog.snapshot.json";
4
+ import { loadJsonInput } from "./json-input.js";
4
5
  import { sendJsonRpcRequest } from "./json-rpc-client.js";
5
6
  const snapshot = snapshotData, operationByName = new Map(snapshot.operations.map((operation) => [operation.name, operation]));
6
7
  export async function executeApi(command) {
@@ -107,25 +108,10 @@ function formatOperationDescription(operation) {
107
108
  `);
108
109
  }
109
110
  async function readInput(command) {
110
- if (command.input !== void 0)
111
- return parseInput(command.input);
112
- if (command.inputFile !== void 0) {
113
- if (command.inputFile === "-")
114
- return parseInput(await Bun.stdin.text());
115
- const file = Bun.file(command.inputFile);
116
- if (!await file.exists())
117
- return { ok: !1, output: usageError(`api: input file not found: ${command.inputFile}`) };
118
- return parseInput(await file.text());
119
- }
120
- return { ok: !0, value: {} };
121
- }
122
- function parseInput(source) {
123
- try {
124
- return { ok: !0, value: JSON.parse(source) };
125
- } catch (error) {
126
- const message = error instanceof Error ? error.message : String(error);
127
- return { ok: !1, output: usageError(`api: invalid JSON input: ${message}`) };
128
- }
111
+ const result = await loadJsonInput(command.input, command.inputFile);
112
+ if (!result.ok)
113
+ return { ok: !1, output: usageError(`api: ${result.error.message}`) };
114
+ return { ok: !0, value: result.value === void 0 ? {} : result.value };
129
115
  }
130
116
  function validateInput(operationName, input) {
131
117
  const operation = createLiveOperationRegistry().get(operationName);
@@ -0,0 +1,12 @@
1
+ import type { DashboardAssets, DashboardRouteTarget } from '../server/index.ts';
2
+ export interface ConsoleMount {
3
+ dashboard: DashboardRouteTarget;
4
+ dashboardAssets: DashboardAssets;
5
+ }
6
+ export interface ConsoleModuleLoaderOptions {
7
+ cwd?: string;
8
+ resolveModule?: (specifier: string, cwd: string) => string;
9
+ importModule?: (url: string) => Promise<unknown>;
10
+ }
11
+ /** Resolve and load the optional console from the CLI user's environment. */
12
+ export declare function loadConsoleMount(options?: ConsoleModuleLoaderOptions): Promise<ConsoleMount>;
@@ -0,0 +1,62 @@
1
+ import { statSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ const CONSOLE_PACKAGE_NAME = "@lostgradient/weft-console";
5
+ function defaultImportModule(url) {
6
+ return import(url);
7
+ }
8
+ function isDashboardRouteTarget(value) {
9
+ return typeof value === "function" || typeof value === "string" || value instanceof Response;
10
+ }
11
+ function isConsoleModule(value) {
12
+ return typeof value === "object" && value !== null;
13
+ }
14
+ function consoleError(message) {
15
+ return Error(`--console ${message}`);
16
+ }
17
+ function resolveConsoleEntry(cwd, resolveModule) {
18
+ try {
19
+ return resolveModule(CONSOLE_PACKAGE_NAME, cwd);
20
+ } catch {
21
+ throw consoleError(`requires ${CONSOLE_PACKAGE_NAME}. Install it in the CLI project: bun add ${CONSOLE_PACKAGE_NAME}`);
22
+ }
23
+ }
24
+ async function importConsoleModule(resolvedEntry, importModule) {
25
+ try {
26
+ const loaded = await importModule(pathToFileURL(resolvedEntry).href);
27
+ if (!isConsoleModule(loaded))
28
+ throw Error("module is not an object");
29
+ return loaded;
30
+ } catch {
31
+ throw consoleError(`could not load ${CONSOLE_PACKAGE_NAME}; reinstall or update it, then retry, or remove --console`);
32
+ }
33
+ }
34
+ function loadDashboard(module) {
35
+ if (typeof module.weftConsole !== "function")
36
+ throw consoleError(`${CONSOLE_PACKAGE_NAME} must export a weftConsole() function; update the package or remove --console`);
37
+ try {
38
+ const dashboard = module.weftConsole();
39
+ if (!isDashboardRouteTarget(dashboard))
40
+ throw Error("invalid dashboard route target");
41
+ return dashboard;
42
+ } catch {
43
+ throw consoleError(`${CONSOLE_PACKAGE_NAME} weftConsole() failed; reinstall or update it, then retry, or remove --console`);
44
+ }
45
+ }
46
+ function resolveConsoleAssets(resolvedEntry) {
47
+ const directory = join(dirname(fileURLToPath(pathToFileURL(resolvedEntry))), "assets");
48
+ try {
49
+ if (!statSync(directory).isDirectory())
50
+ throw Error("not a directory");
51
+ } catch {
52
+ throw consoleError(`${CONSOLE_PACKAGE_NAME} is missing its built assets; reinstall or update it, then retry, or remove --console`);
53
+ }
54
+ return { prefix: "/assets", directory };
55
+ }
56
+ export async function loadConsoleMount(options = {}) {
57
+ const cwd = options.cwd ?? process.cwd(), resolveModule = options.resolveModule ?? Bun.resolveSync, importModule = options.importModule ?? defaultImportModule, resolvedEntry = resolveConsoleEntry(cwd, resolveModule), module = await importConsoleModule(resolvedEntry, importModule);
58
+ return {
59
+ dashboard: loadDashboard(module),
60
+ dashboardAssets: resolveConsoleAssets(resolvedEntry)
61
+ };
62
+ }
@@ -98,6 +98,77 @@
98
98
  "type": "object"
99
99
  }
100
100
  },
101
+ {
102
+ "name": "weft.alerts.list",
103
+ "kind": "unary",
104
+ "summary": "List currently firing alerts",
105
+ "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.",
106
+ "tags": ["Observability"],
107
+ "destructive": false,
108
+ "access": {
109
+ "kind": "scoped",
110
+ "scopes": ["system:read"]
111
+ },
112
+ "transports": {
113
+ "http": true,
114
+ "jsonRpcHttp": true,
115
+ "jsonRpcWebSocket": true,
116
+ "jsonRpcStdio": true
117
+ },
118
+ "producibleFaults": [],
119
+ "inputSchema": {
120
+ "additionalProperties": false,
121
+ "properties": {},
122
+ "type": "object"
123
+ },
124
+ "outputSchema": {
125
+ "additionalProperties": false,
126
+ "properties": {
127
+ "items": {
128
+ "items": {
129
+ "additionalProperties": false,
130
+ "properties": {
131
+ "currentValue": {
132
+ "type": "number"
133
+ },
134
+ "firedAt": {
135
+ "anyOf": [
136
+ {
137
+ "type": "number"
138
+ },
139
+ {
140
+ "type": "null"
141
+ }
142
+ ]
143
+ },
144
+ "metric": {
145
+ "enum": ["workflow.failure_rate", "activity.p99_duration", "storage.size"],
146
+ "type": "string"
147
+ },
148
+ "threshold": {
149
+ "type": "number"
150
+ },
151
+ "window": {
152
+ "anyOf": [
153
+ {
154
+ "type": "string"
155
+ },
156
+ {
157
+ "type": "null"
158
+ }
159
+ ]
160
+ }
161
+ },
162
+ "required": ["metric", "threshold", "currentValue", "window", "firedAt"],
163
+ "type": "object"
164
+ },
165
+ "type": "array"
166
+ }
167
+ },
168
+ "required": ["items"],
169
+ "type": "object"
170
+ }
171
+ },
101
172
  {
102
173
  "name": "weft.events.sse",
103
174
  "kind": "stream",
@@ -1,7 +1,7 @@
1
1
  import { type CatalogWeftClient, type WeftClientConnection } from '../operation-client-runtime.ts';
2
- export declare const CATALOG_OPERATION_NAMES: readonly ["weft.activities.complete", "weft.activities.fail", "weft.recover.all", "weft.retention.get", "weft.reviews.decision.submit", "weft.reviews.get", "weft.reviews.list", "weft.schedules.cancel", "weft.schedules.create", "weft.schedules.get", "weft.schedules.list", "weft.schedules.pause", "weft.schedules.resume", "weft.schedules.update", "weft.storage.capabilities", "weft.system.lease", "weft.system.metrics", "weft.system.registry", "weft.task.queues.list", "weft.tasks.diagnostics", "weft.updates.result.get", "weft.worker.deployments.drain", "weft.worker.deployments.resume", "weft.workers.drain", "weft.workers.list", "weft.workers.resume", "weft.workflows.activities.pending.list", "weft.workflows.aggregate", "weft.workflows.attributes.get", "weft.workflows.attributes.set", "weft.workflows.bulk.cancel", "weft.workflows.bulk.delete", "weft.workflows.bulk.retryfailed", "weft.workflows.bulk.signal", "weft.workflows.bulk.tags", "weft.workflows.cancel", "weft.workflows.checkpoints.get", "weft.workflows.checkpoints.list", "weft.workflows.events.list", "weft.workflows.finalizer.get", "weft.workflows.fork", "weft.workflows.get", "weft.workflows.list", "weft.workflows.purge", "weft.workflows.query", "weft.workflows.replay", "weft.workflows.result.get", "weft.workflows.resume", "weft.workflows.scheduleprovenance.get", "weft.workflows.signal", "weft.workflows.start", "weft.workflows.startorsignal", "weft.workflows.streams.chunks", "weft.workflows.suspend", "weft.workflows.tags.add", "weft.workflows.tags.remove", "weft.workflows.timeline.get", "weft.workflows.timeout", "weft.workflows.update"];
2
+ export declare const CATALOG_OPERATION_NAMES: readonly ["weft.activities.complete", "weft.activities.fail", "weft.alerts.list", "weft.recover.all", "weft.retention.get", "weft.reviews.decision.submit", "weft.reviews.get", "weft.reviews.list", "weft.schedules.cancel", "weft.schedules.create", "weft.schedules.get", "weft.schedules.list", "weft.schedules.pause", "weft.schedules.resume", "weft.schedules.update", "weft.storage.capabilities", "weft.system.lease", "weft.system.metrics", "weft.system.registry", "weft.task.queues.list", "weft.tasks.diagnostics", "weft.updates.result.get", "weft.worker.deployments.drain", "weft.worker.deployments.resume", "weft.workers.drain", "weft.workers.list", "weft.workers.resume", "weft.workflows.activities.pending.list", "weft.workflows.aggregate", "weft.workflows.attributes.get", "weft.workflows.attributes.set", "weft.workflows.bulk.cancel", "weft.workflows.bulk.delete", "weft.workflows.bulk.retryfailed", "weft.workflows.bulk.signal", "weft.workflows.bulk.tags", "weft.workflows.cancel", "weft.workflows.checkpoints.get", "weft.workflows.checkpoints.list", "weft.workflows.events.list", "weft.workflows.finalizer.get", "weft.workflows.fork", "weft.workflows.get", "weft.workflows.list", "weft.workflows.purge", "weft.workflows.query", "weft.workflows.replay", "weft.workflows.result.get", "weft.workflows.resume", "weft.workflows.scheduleprovenance.get", "weft.workflows.signal", "weft.workflows.start", "weft.workflows.startorsignal", "weft.workflows.streams.chunks", "weft.workflows.suspend", "weft.workflows.tags.add", "weft.workflows.tags.remove", "weft.workflows.timeline.get", "weft.workflows.timeout", "weft.workflows.update"];
3
3
  export type CatalogOperationName = (typeof CATALOG_OPERATION_NAMES)[number];
4
- export declare const CLIENT_OPERATION_NAMES: readonly ["weft.activities.complete", "weft.activities.fail", "weft.recover.all", "weft.retention.get", "weft.reviews.decision.submit", "weft.reviews.get", "weft.reviews.list", "weft.schedules.cancel", "weft.schedules.create", "weft.schedules.get", "weft.schedules.list", "weft.schedules.pause", "weft.schedules.resume", "weft.schedules.update", "weft.storage.capabilities", "weft.system.lease", "weft.system.metrics", "weft.system.registry", "weft.task.queues.list", "weft.tasks.diagnostics", "weft.tasks.diagnostics.deadletters.clear", "weft.updates.result.get", "weft.worker.deployments.drain", "weft.worker.deployments.resume", "weft.workers.drain", "weft.workers.list", "weft.workers.resume", "weft.workflows.activities.pending.list", "weft.workflows.aggregate", "weft.workflows.attributes.get", "weft.workflows.attributes.set", "weft.workflows.bulk.cancel", "weft.workflows.bulk.delete", "weft.workflows.bulk.retryfailed", "weft.workflows.bulk.signal", "weft.workflows.bulk.tags", "weft.workflows.cancel", "weft.workflows.checkpoints.get", "weft.workflows.checkpoints.list", "weft.workflows.events.list", "weft.workflows.finalizer.get", "weft.workflows.fork", "weft.workflows.get", "weft.workflows.list", "weft.workflows.purge", "weft.workflows.query", "weft.workflows.replay", "weft.workflows.result.get", "weft.workflows.resume", "weft.workflows.scheduleprovenance.get", "weft.workflows.signal", "weft.workflows.start", "weft.workflows.startorsignal", "weft.workflows.streams.chunks", "weft.workflows.suspend", "weft.workflows.tags.add", "weft.workflows.tags.remove", "weft.workflows.timeline.get", "weft.workflows.timeout", "weft.workflows.update"];
4
+ export declare const CLIENT_OPERATION_NAMES: readonly ["weft.activities.complete", "weft.activities.fail", "weft.alerts.list", "weft.recover.all", "weft.retention.get", "weft.reviews.decision.submit", "weft.reviews.get", "weft.reviews.list", "weft.schedules.cancel", "weft.schedules.create", "weft.schedules.get", "weft.schedules.list", "weft.schedules.pause", "weft.schedules.resume", "weft.schedules.update", "weft.storage.capabilities", "weft.system.lease", "weft.system.metrics", "weft.system.registry", "weft.task.queues.list", "weft.tasks.diagnostics", "weft.tasks.diagnostics.deadletters.clear", "weft.updates.result.get", "weft.worker.deployments.drain", "weft.worker.deployments.resume", "weft.workers.drain", "weft.workers.list", "weft.workers.resume", "weft.workflows.activities.pending.list", "weft.workflows.aggregate", "weft.workflows.attributes.get", "weft.workflows.attributes.set", "weft.workflows.bulk.cancel", "weft.workflows.bulk.delete", "weft.workflows.bulk.retryfailed", "weft.workflows.bulk.signal", "weft.workflows.bulk.tags", "weft.workflows.cancel", "weft.workflows.checkpoints.get", "weft.workflows.checkpoints.list", "weft.workflows.events.list", "weft.workflows.finalizer.get", "weft.workflows.fork", "weft.workflows.get", "weft.workflows.list", "weft.workflows.purge", "weft.workflows.query", "weft.workflows.replay", "weft.workflows.result.get", "weft.workflows.resume", "weft.workflows.scheduleprovenance.get", "weft.workflows.signal", "weft.workflows.start", "weft.workflows.startorsignal", "weft.workflows.streams.chunks", "weft.workflows.suspend", "weft.workflows.tags.add", "weft.workflows.tags.remove", "weft.workflows.timeline.get", "weft.workflows.timeout", "weft.workflows.update"];
5
5
  export type ClientOperationName = (typeof CLIENT_OPERATION_NAMES)[number];
6
6
  export declare const CLIENT_REST_OPERATION_BINDINGS: {
7
7
  readonly 'weft.tasks.diagnostics.deadletters.clear': {
@@ -93,6 +93,19 @@ export type ClientOperationTypes = {
93
93
  };
94
94
  readonly faults: 'InvalidParams' | 'NotFound';
95
95
  };
96
+ 'weft.alerts.list': {
97
+ readonly input: {};
98
+ readonly output: {
99
+ readonly items: ReadonlyArray<{
100
+ readonly currentValue: number;
101
+ readonly firedAt: unknown;
102
+ readonly metric: 'workflow.failure_rate' | 'activity.p99_duration' | 'storage.size';
103
+ readonly threshold: number;
104
+ readonly window: unknown;
105
+ }>;
106
+ };
107
+ readonly faults: never;
108
+ };
96
109
  'weft.recover.all': {
97
110
  readonly input: {};
98
111
  readonly output: {
@@ -5,6 +5,7 @@ import {
5
5
  export const CATALOG_OPERATION_NAMES = [
6
6
  "weft.activities.complete",
7
7
  "weft.activities.fail",
8
+ "weft.alerts.list",
8
9
  "weft.recover.all",
9
10
  "weft.retention.get",
10
11
  "weft.reviews.decision.submit",
@@ -65,6 +66,7 @@ export const CATALOG_OPERATION_NAMES = [
65
66
  ], CLIENT_OPERATION_NAMES = [
66
67
  "weft.activities.complete",
67
68
  "weft.activities.fail",
69
+ "weft.alerts.list",
68
70
  "weft.recover.all",
69
71
  "weft.retention.get",
70
72
  "weft.reviews.decision.submit",
@@ -1,4 +1,4 @@
1
- export declare const HELP_TEXT = "\nweft - Bun-native durable execution engine\n\nUsage: weft [command] [options]\n\nCommands:\n serve Start the Weft server (default)\n doctor Run diagnostics on the Weft database\n conformance Run RemoteWorker protocol conformance checks\n schedule Manage recurring schedules\n timeline Inspect workflow timeline and replay history\n version:check Check workflow version compatibility\n validate Lint workflow definitions for design-time anti-patterns\n codegen Generate TypeScript declarations from a registry snapshot\n api Inspect and invoke catalog operations on a running server\n server Inspect a running server's health and operation surface\n workflow List, inspect, start, signal, and cancel workflows on a server\n tail Stream a workflow's events as Server-Sent Events\n completions Generate or install shell completion scripts\n version Print the installed Weft version\n\nVersion (leading token only; a subcommand owns its own option line):\n weft version | weft --version | weft -v Print the installed Weft version\n\nServe Options:\n -p, --port <port> Server port (default: 7233)\n -d, --database <path> Database file path (default: ./weft.db)\n -s, --storage <backend> Storage backend: sqlite, lmdb, memory (default: sqlite)\n -w, --workflows <path> Path to workflow module to register on startup\n -h, --help Show this help message\n";
1
+ export declare const HELP_TEXT = "\nweft - Bun-native durable execution engine\n\nUsage: weft [command] [options]\n\nCommands:\n serve Start the Weft server (default)\n doctor Run diagnostics on the Weft database\n conformance Run RemoteWorker protocol conformance checks\n schedule Manage recurring schedules\n timeline Inspect workflow timeline and replay history\n version:check Check workflow version compatibility\n validate Lint workflow definitions for design-time anti-patterns\n codegen Generate TypeScript declarations from a registry snapshot\n api Inspect and invoke catalog operations on a running server\n server Inspect a running server's health and operation surface\n workflow List, inspect, start, signal, and cancel workflows on a server\n tail Stream a workflow's events as Server-Sent Events\n completions Generate or install shell completion scripts\n version Print the installed Weft version\n\nVersion (leading token only; a subcommand owns its own option line):\n weft version | weft --version | weft -v Print the installed Weft version\n\nServe Options:\n -p, --port <port> Server port (default: 7233)\n -d, --database <path> Database file path (default: ./weft.db)\n -s, --storage <backend> Storage backend: sqlite, lmdb, memory (default: sqlite)\n -w, --workflows <path> Path to workflow module to register on startup\n --console Mount @lostgradient/weft-console\n -h, --help Show this help message\n";
2
2
  export declare const API_HELP_TEXT = "\nweft api - Inspect and invoke catalog operations on a running server\n\nUsage:\n weft api --list [options]\n weft api --describe <operation-name> [options]\n weft api <operation-name> [--input <json> | --input-file <path|->] [options]\n\nOptions:\n --server <url> Server URL (default: WEFT_ADDR, profile, run lockfile, or localhost)\n --token <token> Bearer token (default: WEFT_TOKEN)\n --profile <name> Profile from ~/.weft/config (default: WEFT_PROFILE or default_profile)\n --input <json> JSON object input for the operation\n --input-file <path> Read JSON object input from a file, or '-' for stdin\n --list List catalog operations\n --describe <name> Print one operation's schema and metadata\n --yes Confirm destructive operations without prompting\n -j, --json Emit machine-readable JSON output\n -h, --help Show this help message\n\nExit codes:\n 0 Success\n 1 Operation failed or destructive operation was not confirmed\n 2 Connection error\n 3 Usage or input validation error\n";
3
3
  export declare const CONFORMANCE_HELP_TEXT = "\nweft conformance - Run RemoteWorker protocol conformance checks\n\nUsage: weft conformance [options] -- <worker-command> [args...]\n\nThe worker command receives:\n WEFT_WORKER_URL\n WEFT_WORKER_QUEUE\n WEFT_WORKER_ACTIVITIES\n WEFT_WORKER_PROTOCOL_VERSION\n\nOptions:\n --timeout <ms> Per-check timeout in milliseconds (default: 15000)\n -j, --json Output results as JSON\n -h, --help Show this help message\n";
4
4
  export declare const DOCTOR_HELP_TEXT = "\nweft doctor - Run diagnostics on the Weft database\n\nUsage: weft doctor [options]\n\nOptions:\n -d, --database <path> Database file path (default: ./weft.db)\n -j, --json Output results as JSON\n -h, --help Show this help message\n";
@@ -27,6 +27,7 @@ Serve Options:
27
27
  -d, --database <path> Database file path (default: ./weft.db)
28
28
  -s, --storage <backend> Storage backend: sqlite, lmdb, memory (default: sqlite)
29
29
  -w, --workflows <path> Path to workflow module to register on startup
30
+ --console Mount @lostgradient/weft-console
30
31
  -h, --help Show this help message
31
32
  `, API_HELP_TEXT = `
32
33
  weft api - Inspect and invoke catalog operations on a running server
@@ -4,6 +4,7 @@ export { executeApi } from './api.ts';
4
4
  export { executeCodegen } from './codegen.ts';
5
5
  export { executeCompletions } from './completions.ts';
6
6
  export { executeConformance } from './conformance.ts';
7
+ export { loadConsoleMount } from './console.ts';
7
8
  export { executeDoctor } from './doctor.ts';
8
9
  export { createWeftClient } from './generated/operation-client.generated.ts';
9
10
  export type { CatalogOperationName, WeftClient } from './generated/operation-client.generated.ts';
package/dist/cli/index.js CHANGED
@@ -4,6 +4,7 @@ export { executeApi } from "./api.js";
4
4
  export { executeCodegen } from "./codegen.js";
5
5
  export { executeCompletions } from "./completions.js";
6
6
  export { executeConformance } from "./conformance.js";
7
+ export { loadConsoleMount } from "./console.js";
7
8
  export { executeDoctor } from "./doctor.js";
8
9
  export { createWeftClient } from "./generated/operation-client.generated.js";
9
10
  export {
@@ -0,0 +1,20 @@
1
+ export type JsonInputFailure = {
2
+ readonly kind: 'invalid-json';
3
+ readonly detail: string;
4
+ readonly message: string;
5
+ } | {
6
+ readonly kind: 'missing-file';
7
+ readonly message: string;
8
+ readonly path: string;
9
+ };
10
+ export type JsonInputResult = {
11
+ readonly ok: true;
12
+ readonly value: unknown;
13
+ } | {
14
+ readonly ok: false;
15
+ readonly error: JsonInputFailure;
16
+ };
17
+ /** Parse one inline JSON source without applying a command-specific error format. */
18
+ export declare function parseJsonInput(source: string): JsonInputResult;
19
+ /** Load JSON from inline input, a file, or stdin without applying caller policy. */
20
+ export declare function loadJsonInput(input: string | undefined, inputFile: string | undefined, readStdin?: () => Promise<string>): Promise<JsonInputResult>;
@@ -0,0 +1,33 @@
1
+ function errorMessage(error) {
2
+ return error instanceof Error ? error.message : String(error);
3
+ }
4
+ export function parseJsonInput(source) {
5
+ try {
6
+ return { ok: !0, value: JSON.parse(source) };
7
+ } catch (error) {
8
+ const detail = errorMessage(error);
9
+ return {
10
+ ok: !1,
11
+ error: { kind: "invalid-json", detail, message: `invalid JSON input: ${detail}` }
12
+ };
13
+ }
14
+ }
15
+ export async function loadJsonInput(input, inputFile, readStdin = () => Bun.stdin.text()) {
16
+ if (input !== void 0)
17
+ return parseJsonInput(input);
18
+ if (inputFile === void 0)
19
+ return { ok: !0, value: void 0 };
20
+ if (inputFile === "-")
21
+ return parseJsonInput(await readStdin());
22
+ const file = Bun.file(inputFile);
23
+ if (!await file.exists())
24
+ return {
25
+ ok: !1,
26
+ error: {
27
+ kind: "missing-file",
28
+ message: `input file not found: ${inputFile}`,
29
+ path: inputFile
30
+ }
31
+ };
32
+ return parseJsonInput(await file.text());
33
+ }
@@ -75,6 +75,7 @@ function parseServeArguments(args) {
75
75
  database: { type: "string", short: "d", default: "./weft.db" },
76
76
  storage: { type: "string", short: "s", default: "sqlite" },
77
77
  help: { type: "boolean", short: "h", default: !1 },
78
+ console: { type: "boolean", default: !1 },
78
79
  workflows: { type: "string", short: "w" }
79
80
  },
80
81
  strict: !0,
@@ -89,6 +90,7 @@ function parseServeArguments(args) {
89
90
  database: values.database ?? "./weft.db",
90
91
  storage: parseStorageBackend(values.storage),
91
92
  help: values.help ?? !1,
93
+ console: values.console ?? !1,
92
94
  ...values.workflows ? { workflows: values.workflows } : {}
93
95
  };
94
96
  }
@@ -1,4 +1,5 @@
1
1
  import { registerOnRuntimeEngine, runtimeWorkflowEngine } from "../core/runtime-workflow-engine.js";
2
+ import { parseJsonInput } from "./json-input.js";
2
3
  import { createStorage } from "./storage-factory.js";
3
4
  function formatScheduleCadence(schedule) {
4
5
  if (schedule.intervalMs !== void 0)
@@ -42,13 +43,6 @@ function getScheduleCreateValidationError(options) {
42
43
  return "Error: provide a <cronExpression> argument or an --every <duration> flag for schedule create";
43
44
  return null;
44
45
  }
45
- function parseScheduleInput(input) {
46
- try {
47
- return { ok: !0, value: JSON.parse(input) };
48
- } catch (error) {
49
- return { ok: !1, message: `Error: could not parse --input JSON: ${error instanceof Error ? error.message : String(error)}` };
50
- }
51
- }
52
46
  async function registerScheduleWorkflows(engine, workflowsPath, loadRegistrationsFromModule) {
53
47
  const loaded = await loadRegistrationsFromModule(workflowsPath);
54
48
  for (const definition of Object.values(loaded.registrations))
@@ -59,9 +53,13 @@ async function executeScheduleCreate(options, engine, loadRegistrationsFromModul
59
53
  if (validationError !== null)
60
54
  return { stdout: "", stderr: validationError, exitCode: 1 };
61
55
  await registerScheduleWorkflows(engine, options.workflows, loadRegistrationsFromModule);
62
- const parsedInput = parseScheduleInput(options.input);
56
+ const parsedInput = parseJsonInput(options.input);
63
57
  if (!parsedInput.ok)
64
- return { stdout: "", stderr: parsedInput.message, exitCode: 1 };
58
+ return {
59
+ stdout: "",
60
+ stderr: parsedInput.error.kind === "invalid-json" ? `Error: could not parse --input JSON: ${parsedInput.error.detail}` : `Error: ${parsedInput.error.message}`,
61
+ exitCode: 1
62
+ };
65
63
  const spec = options.every !== void 0 ? { every: options.every } : options.cronExpression, handle = await engine.schedule(options.workflowType, parsedInput.value, spec, {
66
64
  ...options.id !== void 0 ? { id: options.id } : {},
67
65
  ...options.overlap !== void 0 ? { overlap: options.overlap } : {},
@@ -10,6 +10,7 @@ export type CliCommand = {
10
10
  database: string;
11
11
  storage: StorageBackend;
12
12
  help: boolean;
13
+ console: boolean;
13
14
  workflows?: string;
14
15
  } | {
15
16
  command: 'version';
@@ -1,7 +1,7 @@
1
+ import { loadJsonInput } from "./json-input.js";
1
2
  import {
2
3
  confirmDestructive,
3
4
  formatTimestamp,
4
- messageOf,
5
5
  ndjson,
6
6
  prettyJson,
7
7
  truncateToWidth
@@ -105,30 +105,10 @@ async function executeWorkflowEvents(command, connection) {
105
105
  }).join(`
106
106
  `), exitCode: 0 };
107
107
  }
108
- async function readInlineInput(input, inputFile) {
109
- if (input !== void 0)
110
- return parseJson(input);
111
- if (inputFile !== void 0) {
112
- if (inputFile === "-")
113
- return parseJson(await Bun.stdin.text());
114
- const file = Bun.file(inputFile);
115
- if (!await file.exists())
116
- return { ok: !1, message: `input file not found: ${inputFile}` };
117
- return parseJson(await file.text());
118
- }
119
- return { ok: !0, value: void 0 };
120
- }
121
- function parseJson(source) {
122
- try {
123
- return { ok: !0, value: JSON.parse(source) };
124
- } catch (error) {
125
- return { ok: !1, message: `invalid JSON input: ${messageOf(error)}` };
126
- }
127
- }
128
108
  async function executeWorkflowStart(command, connection) {
129
- const parsed = await readInlineInput(command.input, command.inputFile);
109
+ const parsed = await loadJsonInput(command.input, command.inputFile);
130
110
  if (!parsed.ok)
131
- return { stdout: "", stderr: `workflow start: ${parsed.message}`, exitCode: 3 };
111
+ return { stdout: "", stderr: `workflow start: ${parsed.error.message}`, exitCode: 3 };
132
112
  const result = await callCatalogOperation(connection, "weft.workflows.start", {
133
113
  type: command.workflowType,
134
114
  ...parsed.value === void 0 ? {} : { input: parsed.value },
@@ -169,9 +149,9 @@ async function executeWorkflowCancel(command, connection) {
169
149
  };
170
150
  }
171
151
  async function executeWorkflowSignal(command, connection) {
172
- const parsed = await readInlineInput(command.input, command.inputFile);
152
+ const parsed = await loadJsonInput(command.input, command.inputFile);
173
153
  if (!parsed.ok)
174
- return { stdout: "", stderr: `workflow signal: ${parsed.message}`, exitCode: 3 };
154
+ return { stdout: "", stderr: `workflow signal: ${parsed.error.message}`, exitCode: 3 };
175
155
  const result = await callCatalogOperation(connection, "weft.workflows.signal", {
176
156
  workflowId: command.workflowId,
177
157
  signalName: command.signalName,
package/dist/cli-main.js CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  executeWorkflow,
22
22
  findCliSubcommandName,
23
23
  HELP_TEXT,
24
+ loadConsoleMount,
24
25
  parseCliArguments,
25
26
  removeRunLockfile,
26
27
  SCHEDULE_HELP_TEXT,
@@ -53,7 +54,7 @@ if (parsedArguments.command === "version") {
53
54
  console.log(HELP_TEXT);
54
55
  process.exit(0);
55
56
  }
56
- const storage = await createStorage(parsedArguments.storage, parsedArguments.database), engine = new Engine({ storage });
57
+ const consoleMount = parsedArguments.console ? await loadConsoleMount() : void 0, storage = await createStorage(parsedArguments.storage, parsedArguments.database), engine = new Engine({ storage });
57
58
  if (parsedArguments.workflows) {
58
59
  const { loadRegistrationsFromModule } = await import("./diagnostics/validate.js"), { registerModuleExports } = await import("./cli/serve-registrations.js"), loaded = await loadRegistrationsFromModule(parsedArguments.workflows);
59
60
  registerModuleExports(engine, loaded.registrations, loaded.activities);
@@ -61,7 +62,8 @@ if (parsedArguments.command === "version") {
61
62
  console.log("No --workflows module provided; starting in inspect-only mode.");
62
63
  const server = serve({
63
64
  engine,
64
- port: Number(parsedArguments.port)
65
+ port: Number(parsedArguments.port),
66
+ ...consoleMount
65
67
  });
66
68
  await writeRunLockfile(server.url);
67
69
  console.log(`Weft API running at ${new URL("/api/v1", server.url).href}`);
@@ -1,3 +1,4 @@
1
+ import type { AlertState } from '../../alerting/types.ts';
1
2
  import { type Storage as WeftStorage } from '../../storage/interface.ts';
2
3
  import { type ActivityMetadata, type ActivityRegistrationOptions, type RegisteredActivityFunction } from '../activity-registry.ts';
3
4
  import type { StoredStreamChunk } from '../context.ts';
@@ -145,6 +146,8 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
145
146
  removeEventListener<K extends Extract<keyof WeftEventMap, string>>(type: K, listener: (event: WeftEventMap[K]) => void, options?: boolean | EventListenerOptions): void;
146
147
  removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
147
148
  get state(): EngineStateNamespace;
149
+ /** Return the alert rules that are currently firing. */
150
+ getActiveAlerts(): readonly AlertState[];
148
151
  /**
149
152
  * Register a workflow by name or definition, or register an activity
150
153
  * definition. Definition overloads return this same engine with the
@@ -522,6 +522,9 @@ export class Engine extends EventTarget {
522
522
  workflow: (workflowType, key, options) => new AtomicState(storage, KEYS.stateWorkflow(workflowType, key), options)
523
523
  };
524
524
  }
525
+ getActiveAlerts() {
526
+ return getInternals(this).alertManager?.activeStates ?? [];
527
+ }
525
528
  async#handleStrategyMessage(message) {
526
529
  return handleStrategyMessageFromInternals(getInternals(this), message, this.#createInlineParkingCallbacks());
527
530
  }
@@ -1,3 +1,4 @@
1
+ import { formatBytes } from "./format.js";
1
2
  import { THRESHOLDS } from "./types.js";
2
3
  function integrityFailureRule({ database }) {
3
4
  if (database.integrityOk)
@@ -95,15 +96,6 @@ export function generateRecommendations(report, thresholds) {
95
96
  };
96
97
  return RECOMMENDATION_RULES.flatMap((rule) => rule(context));
97
98
  }
98
- function formatBytes(bytes) {
99
- if (bytes < 1024)
100
- return `${bytes} B`;
101
- if (bytes < 1048576)
102
- return `${(bytes / 1024).toFixed(1)} KB`;
103
- if (bytes < 1073741824)
104
- return `${(bytes / 1048576).toFixed(1)} MB`;
105
- return `${(bytes / 1073741824).toFixed(1)} GB`;
106
- }
107
99
  function formatDuration(milliseconds) {
108
100
  const seconds = Math.floor(milliseconds / 1000), minutes = Math.floor(seconds / 60), hours = Math.floor(minutes / 60), days = Math.floor(hours / 24);
109
101
  if (days > 0)
package/dist/http.js CHANGED
@@ -1,2 +1,2 @@
1
- async function*R(F,G){if(F.body===null)return;let J=F.body.getReader(),Q=new TextDecoder,Z="",$=0,j=!1;try{while(!0){let{done:y,value:z}=await J.read();if(y){j=!0;break}if($+=z.byteLength,$>G.maximumBytes)throw G.sizeLimitError();Z+=Q.decode(z,{stream:!0});let A=Z.split(`
2
- `);Z=A.pop()??"";for(let g of A)yield g}if(Z+=Q.decode(),Z.length>0)yield Z}finally{if(!j)try{await J.cancel()}catch{}J.releaseLock()}}function N(F){let G=[];for(let J=0;J<F.length;J+=512)G.push(String.fromCharCode(...F.subarray(J,J+512)));return btoa(G.join(""))}function B(F){let G=atob(F),J=new Uint8Array(G.length);for(let Q=0;Q<G.length;Q+=1)J[Q]=G.charCodeAt(Q);return J}function q(F){return typeof F==="object"&&F!==null&&!Array.isArray(F)}async function _(F,G){return await F.get(G)!==null}async function*X(F,G,J){for await(let[Q]of F.scan(G,J))yield Q}async function H(F,G){let J=0;for await(let Q of X(F,G))J++;return J}async function L(F,G){let J=[];for await(let Q of X(F,G))J.push({type:"delete",key:Q});if(J.length===0)return 0;return await F.batch(J),J.length}async function U(F,G,J){let Q=[];for await(let Z of X(F,G,J))Q.push({type:"delete",key:Z});if(Q.length===0)return 0;return await F.batch(Q),Q.length}function C(F,G,J){if(!F.capabilities()[G])throw Error(`Feature "${J}" requires storage capability "${G}", but this storage backend does not provide it.`)}var D=1e4;class T extends Error{code="StorageBatchOperationLimitExceededError";cap=D;count;target;constructor(F,G){super(`${F} count ${G} exceeds MAX_BATCH_OPERATIONS (${D}).`);this.name="StorageBatchOperationLimitExceededError",this.target=F,this.count=G}}function M(F,G){if(G>D)throw new T(F,G)}function K(F){return F.length>0?F.slice(0,-1)+String.fromCharCode(F.charCodeAt(F.length-1)+1):"ÿ"}function e(F,G={}){if(G.gt!==void 0&&F<=G.gt)return!1;if(G.gte!==void 0&&F<G.gte)return!1;if(G.lt!==void 0&&F>=G.lt)return!1;if(G.lte!==void 0&&F>G.lte)return!1;return!0}function FF(F,G){if(F===null||G===null)return F===G;if(F.byteLength!==G.byteLength)return!1;for(let J=0;J<F.byteLength;J++)if(F[J]!==G[J])return!1;return!0}async function S(F,G){if(F.has)return F.has(G);return _(F,G)}function P(F,G,J){if(F.keys)return F.keys(G,J);return X(F,G,J)}async function O(F,G){if(F.count)return F.count(G);return H(F,G)}async function b(F,G){if(F.deletePrefix)return F.deletePrefix(G);return L(F,G)}async function v(F,G,J){if(M("conditionalBatch conditions",G.length),M("conditionalBatch operations",J.length),C(F,"conditionalBatch","storageConditionalBatch"),!F.conditionalBatch)throw Error("This storage backend reports conditionalBatch capability but does not implement the conditionalBatch() method.");return F.conditionalBatch(G,J)}function c(F){if(F===void 0)return;if(typeof F!=="number"||!Number.isInteger(F)||F<0)throw Error("deleteRange limit must be a finite non-negative integer");return F===0?0:F}function V(F){let G={},J=!1;for(let Z of["gt","gte","lt","lte"]){let $=F[Z];if($===void 0)continue;if(typeof $!=="string")throw Error("deleteRange bounds must be strings");G[Z]=$,J=!0}if(!J)throw Error("deleteRange requires at least one of gt/gte/lt/lte; use deletePrefix to delete a whole prefix");let Q=c(F.limit);if(Q!==void 0)G.limit=Q;return G}async function h(F,G,J){let Q=V(J);if(F.deleteRange)return F.deleteRange(G,Q);return U(F,G,Q)}function I(F,G){let J={key:F,open:!1};if(G.gte!==void 0&&G.gte>J.key)J.key=G.gte,J.open=!1;if(G.gt!==void 0&&G.gt>=J.key)J.key=G.gt,J.open=!0;return J}function u(F,G){let J={key:K(F),open:!0};if(G.lt!==void 0&&G.lt<=J.key)J.key=G.lt,J.open=!0;if(G.lte!==void 0&&G.lte<J.key)J.key=G.lte,J.open=!1;return J}function ZF(F,G){let J=I(F,G),Q=u(F,G);if(J.key>Q.key||J.key===Q.key&&(J.open||Q.open))return null;return{lower:J,upper:Q}}function E(F){return F.replaceAll(/:+$/g,"")}function k(F,G){let J=E(F),Q=E(G);if(J.length===0)return Q;if(Q.length===0)return J;return`${J}:${Q}`}class W{#G;#J;constructor(F,G){this.#G=F,this.#J=E(G)}#F(F){if(this.#J.length===0)return F;return F.length===0?`${this.#J}:`:`${this.#J}:${F}`}#Z(F){return F.map((G)=>{if(G.type==="put")return{type:"put",key:this.#F(G.key),value:G.value};return{type:"delete",key:this.#F(G.key)}})}#$(F){if(this.#J.length===0)return F;return F.slice(this.#J.length+1)}#M(F={}){let G={};if(F.limit!==void 0)G.limit=F.limit;if(F.reverse!==void 0)G.reverse=F.reverse;if(F.gt!==void 0)G.gt=this.#F(F.gt);if(F.gte!==void 0)G.gte=this.#F(F.gte);if(F.lt!==void 0)G.lt=this.#F(F.lt);if(F.lte!==void 0)G.lte=this.#F(F.lte);return G}#Q(F){let G={};if(F.limit!==void 0)G.limit=F.limit;if(F.gt!==void 0)G.gt=this.#F(F.gt);if(F.gte!==void 0)G.gte=this.#F(F.gte);if(F.lt!==void 0)G.lt=this.#F(F.lt);if(F.lte!==void 0)G.lte=this.#F(F.lte);return G}capabilities(){return this.#G.capabilities()}scoped(F){return new W(this.#G,k(this.#J,F))}async get(F){return this.#G.get(this.#F(F))}async put(F,G){await this.#G.put(this.#F(F),G)}async delete(F){await this.#G.delete(this.#F(F))}async*scan(F,G){for await(let[J,Q]of this.#G.scan(this.#F(F),this.#M(G)))yield[this.#$(J),Q]}async batch(F){M("batch operations",F.length),await this.#G.batch(this.#Z(F))}async conditionalBatch(F,G){return v(this.#G,F.map((J)=>({key:this.#F(J.key),expectedValue:J.expectedValue})),this.#Z(G))}async has(F){return S(this.#G,this.#F(F))}async deletePrefix(F){return b(this.#G,this.#F(F))}async deleteRange(F,G){let J=this.#Q(V(G));return h(this.#G,this.#F(F),J)}async*keys(F,G){for await(let J of P(this.#G,this.#F(F),this.#M(G)))yield this.#$(J)}async count(F){return O(this.#G,this.#F(F))}[Symbol.dispose](){this.#G[Symbol.dispose]()}}function m(F,G){return new W(F,G)}var w=67108864;function x(F){if(F.type==="put")return{type:"put",key:F.key,value:N(F.value)};return{type:"delete",key:F.key}}function f(F){return{key:F.key,expectedValue:F.expectedValue===null?null:N(F.expectedValue)}}function d(F){if(!q(F)||typeof F.key!=="string"||typeof F.value!=="string")throw Error("HTTPStorage scan response contained an invalid NDJSON entry.");return{key:F.key,value:F.value}}function l(F){if(!q(F)||typeof F.applied!=="boolean")throw Error('HTTPStorage conditional batch response must include a boolean "applied" field.');return F.applied}function Y(F,G,J){if(J!==void 0)F.searchParams.set(G,String(J))}function p(F){if(F.trim().length===0)return null;let G=d(JSON.parse(F));return[G.key,B(G.value)]}class a{#G;#J;#F;constructor(F){this.#G=F.baseUrl instanceof URL?F.baseUrl:new URL(F.baseUrl),this.#J={...F.headers},this.#F=F.remoteConditionalBatch??!1}capabilities(){return{persistence:"remote",readAfterWrite:"eventual",scanConsistency:"best-effort",atomicBatch:!0,conditionalBatch:this.#F,boundedRangeDelete:!1}}#Z(F){let G=this.#G.href.endsWith("/")?this.#G.href:`${this.#G.href}/`;return new URL(F.replace(/^\/+/,""),G)}#$(F){return this.#Z(`/v1/storage/${encodeURIComponent(F)}`)}#M(F,G){let J=this.#Z("/v1/storage");return J.searchParams.set("prefix",F),Y(J,"limit",G.limit),Y(J,"reverse",G.reverse),Y(J,"gt",G.gt),Y(J,"gte",G.gte),Y(J,"lt",G.lt),Y(J,"lte",G.lte),J}async#Q(F,G={},J=[]){let Q=new Headers(this.#J);for(let[$,j]of new Headers(G.headers).entries())Q.set($,j);let Z=await fetch(F,{...G,headers:Q});if(!Z.ok&&!J.includes(Z.status))throw Error(`HTTPStorage request failed: ${G.method??"GET"} ${F.pathname} returned ${String(Z.status)}.`);return Z}async get(F){let G=await this.#Q(this.#$(F),{method:"GET"},[404]);if(G.status===404)return null;return new Uint8Array(await G.arrayBuffer())}async put(F,G){await this.#Q(this.#$(F),{method:"PUT",headers:{"content-type":"application/octet-stream"},body:new Blob([G])})}async delete(F){await this.#Q(this.#$(F),{method:"DELETE"})}async*scan(F,G={}){let J=await this.#Q(this.#M(F,G),{method:"GET",headers:{accept:"application/x-ndjson"}});for await(let Q of R(J,{maximumBytes:w,sizeLimitError:()=>Error("HTTPStorage scan response exceeded the maximum allowed size.")})){let Z=p(Q);if(Z!==null)yield Z}}async batch(F){M("batch operations",F.length),await this.#Q(this.#Z("/v1/storage/-/batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({operations:F.map(x)})})}async conditionalBatch(F,G){M("conditionalBatch conditions",F.length),M("conditionalBatch operations",G.length);let J=await this.#Q(this.#Z("/v1/storage/-/conditional-batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({conditions:F.map(f),operations:G.map(x)})});return l(await J.json())}has(F){return _(this,F)}async*keys(F,G){yield*X(this,F,G)}count(F){return H(this,F)}deletePrefix(F){return L(this,F)}deleteRange(F,G){return U(this,F,V(G))}scoped(F){return m(this,F)}[Symbol.dispose](){}}export{a as HTTPStorage};
1
+ async function*B(F,G){if(F.body===null)return;let J=F.body.getReader(),Q=new TextDecoder,Z="",$=0,j=!1;try{while(!0){let{done:y,value:z}=await J.read();if(y){j=!0;break}if($+=z.byteLength,$>G.maximumBytes)throw G.sizeLimitError();Z+=Q.decode(z,{stream:!0});let A=Z.split(`
2
+ `);Z=A.pop()??"";for(let g of A)yield g}if(Z+=Q.decode(),Z.length>0)yield Z}finally{if(!j)try{await J.cancel()}catch{}J.releaseLock()}}function N(F){let G=[];for(let J=0;J<F.length;J+=512)G.push(String.fromCharCode(...F.subarray(J,J+512)));return btoa(G.join(""))}function R(F){let G=atob(F),J=new Uint8Array(G.length);for(let Q=0;Q<G.length;Q+=1)J[Q]=G.charCodeAt(Q);return J}function q(F){return typeof F==="object"&&F!==null&&!Array.isArray(F)}async function _(F,G){return await F.get(G)!==null}async function*X(F,G,J){for await(let[Q]of F.scan(G,J))yield Q}async function H(F,G){let J=0;for await(let Q of X(F,G))J++;return J}async function L(F,G){let J=[];for await(let Q of X(F,G))J.push({type:"delete",key:Q});if(J.length===0)return 0;return await F.batch(J),J.length}async function U(F,G,J){let Q=[];for await(let Z of X(F,G,J))Q.push({type:"delete",key:Z});if(Q.length===0)return 0;return await F.batch(Q),Q.length}function C(F,G,J){if(!F.capabilities()[G])throw Error(`Feature "${J}" requires storage capability "${G}", but this storage backend does not provide it.`)}var D=1e4;class T extends Error{code="StorageBatchOperationLimitExceededError";cap=D;count;target;constructor(F,G){super(`${F} count ${G} exceeds MAX_BATCH_OPERATIONS (${D}).`);this.name="StorageBatchOperationLimitExceededError",this.target=F,this.count=G}}function M(F,G){if(G>D)throw new T(F,G)}function K(F){return F.length>0?F.slice(0,-1)+String.fromCharCode(F.charCodeAt(F.length-1)+1):"ÿ"}function e(F,G={}){if(G.gt!==void 0&&F<=G.gt)return!1;if(G.gte!==void 0&&F<G.gte)return!1;if(G.lt!==void 0&&F>=G.lt)return!1;if(G.lte!==void 0&&F>G.lte)return!1;return!0}function FF(F,G){if(F===null||G===null)return F===G;if(F.byteLength!==G.byteLength)return!1;for(let J=0;J<F.byteLength;J++)if(F[J]!==G[J])return!1;return!0}async function S(F,G){if(F.has)return F.has(G);return _(F,G)}function O(F,G,J){if(F.keys)return F.keys(G,J);return X(F,G,J)}async function P(F,G){if(F.count)return F.count(G);return H(F,G)}async function b(F,G){if(F.deletePrefix)return F.deletePrefix(G);return L(F,G)}async function v(F,G,J){if(M("conditionalBatch conditions",G.length),M("conditionalBatch operations",J.length),C(F,"conditionalBatch","storageConditionalBatch"),!F.conditionalBatch)throw Error("This storage backend reports conditionalBatch capability but does not implement the conditionalBatch() method.");return F.conditionalBatch(G,J)}function c(F){if(F===void 0)return;if(typeof F!=="number"||!Number.isInteger(F)||F<0)throw Error("deleteRange limit must be a finite non-negative integer");return F===0?0:F}function V(F){let G={},J=!1;for(let Z of["gt","gte","lt","lte"]){let $=F[Z];if($===void 0)continue;if(typeof $!=="string")throw Error("deleteRange bounds must be strings");G[Z]=$,J=!0}if(!J)throw Error("deleteRange requires at least one of gt/gte/lt/lte; use deletePrefix to delete a whole prefix");let Q=c(F.limit);if(Q!==void 0)G.limit=Q;return G}async function h(F,G,J){let Q=V(J);if(F.deleteRange)return F.deleteRange(G,Q);return U(F,G,Q)}function I(F,G){let J={key:F,open:!1};if(G.gte!==void 0&&G.gte>J.key)J.key=G.gte,J.open=!1;if(G.gt!==void 0&&G.gt>=J.key)J.key=G.gt,J.open=!0;return J}function u(F,G){let J={key:K(F),open:!0};if(G.lt!==void 0&&G.lt<=J.key)J.key=G.lt,J.open=!0;if(G.lte!==void 0&&G.lte<J.key)J.key=G.lte,J.open=!1;return J}function ZF(F,G){let J=I(F,G),Q=u(F,G);if(J.key>Q.key||J.key===Q.key&&(J.open||Q.open))return null;return{lower:J,upper:Q}}function E(F){return F.replaceAll(/:+$/g,"")}function k(F,G){let J=E(F),Q=E(G);if(J.length===0)return Q;if(Q.length===0)return J;return`${J}:${Q}`}class W{#F;#J;constructor(F,G){this.#F=F,this.#J=E(G)}#G(F){if(this.#J.length===0)return F;return F.length===0?`${this.#J}:`:`${this.#J}:${F}`}#Z(F){return F.map((G)=>{if(G.type==="put")return{type:"put",key:this.#G(G.key),value:G.value};return{type:"delete",key:this.#G(G.key)}})}#$(F){if(this.#J.length===0)return F;return F.slice(this.#J.length+1)}#M(F){let G={};if(F.limit!==void 0)G.limit=F.limit;if(F.gt!==void 0)G.gt=this.#G(F.gt);if(F.gte!==void 0)G.gte=this.#G(F.gte);if(F.lt!==void 0)G.lt=this.#G(F.lt);if(F.lte!==void 0)G.lte=this.#G(F.lte);return G}#Q(F={}){let G=this.#M(F);if(F.reverse!==void 0)G.reverse=F.reverse;return G}#X(F){return this.#M(F)}capabilities(){return this.#F.capabilities()}scoped(F){return new W(this.#F,k(this.#J,F))}async get(F){return this.#F.get(this.#G(F))}async put(F,G){await this.#F.put(this.#G(F),G)}async delete(F){await this.#F.delete(this.#G(F))}async*scan(F,G){for await(let[J,Q]of this.#F.scan(this.#G(F),this.#Q(G)))yield[this.#$(J),Q]}async batch(F){M("batch operations",F.length),await this.#F.batch(this.#Z(F))}async conditionalBatch(F,G){return v(this.#F,F.map((J)=>({key:this.#G(J.key),expectedValue:J.expectedValue})),this.#Z(G))}async has(F){return S(this.#F,this.#G(F))}async deletePrefix(F){return b(this.#F,this.#G(F))}async deleteRange(F,G){let J=this.#X(V(G));return h(this.#F,this.#G(F),J)}async*keys(F,G){for await(let J of O(this.#F,this.#G(F),this.#Q(G)))yield this.#$(J)}async count(F){return P(this.#F,this.#G(F))}[Symbol.dispose](){this.#F[Symbol.dispose]()}}function m(F,G){return new W(F,G)}var w=67108864;function x(F){if(F.type==="put")return{type:"put",key:F.key,value:N(F.value)};return{type:"delete",key:F.key}}function f(F){return{key:F.key,expectedValue:F.expectedValue===null?null:N(F.expectedValue)}}function d(F){if(!q(F)||typeof F.key!=="string"||typeof F.value!=="string")throw Error("HTTPStorage scan response contained an invalid NDJSON entry.");return{key:F.key,value:F.value}}function l(F){if(!q(F)||typeof F.applied!=="boolean")throw Error('HTTPStorage conditional batch response must include a boolean "applied" field.');return F.applied}function Y(F,G,J){if(J!==void 0)F.searchParams.set(G,String(J))}function p(F){if(F.trim().length===0)return null;let G=d(JSON.parse(F));return[G.key,R(G.value)]}class s{#F;#J;#G;constructor(F){this.#F=F.baseUrl instanceof URL?F.baseUrl:new URL(F.baseUrl),this.#J={...F.headers},this.#G=F.remoteConditionalBatch??!1}capabilities(){return{persistence:"remote",readAfterWrite:"eventual",scanConsistency:"best-effort",atomicBatch:!0,conditionalBatch:this.#G,boundedRangeDelete:!1}}#Z(F){let G=this.#F.href.endsWith("/")?this.#F.href:`${this.#F.href}/`;return new URL(F.replace(/^\/+/,""),G)}#$(F){return this.#Z(`/v1/storage/${encodeURIComponent(F)}`)}#M(F,G){let J=this.#Z("/v1/storage");return J.searchParams.set("prefix",F),Y(J,"limit",G.limit),Y(J,"reverse",G.reverse),Y(J,"gt",G.gt),Y(J,"gte",G.gte),Y(J,"lt",G.lt),Y(J,"lte",G.lte),J}async#Q(F,G={},J=[]){let Q=new Headers(this.#J);for(let[$,j]of new Headers(G.headers).entries())Q.set($,j);let Z=await fetch(F,{...G,headers:Q});if(!Z.ok&&!J.includes(Z.status))throw Error(`HTTPStorage request failed: ${G.method??"GET"} ${F.pathname} returned ${String(Z.status)}.`);return Z}async get(F){let G=await this.#Q(this.#$(F),{method:"GET"},[404]);if(G.status===404)return null;return new Uint8Array(await G.arrayBuffer())}async put(F,G){await this.#Q(this.#$(F),{method:"PUT",headers:{"content-type":"application/octet-stream"},body:new Blob([G])})}async delete(F){await this.#Q(this.#$(F),{method:"DELETE"})}async*scan(F,G={}){let J=await this.#Q(this.#M(F,G),{method:"GET",headers:{accept:"application/x-ndjson"}});for await(let Q of B(J,{maximumBytes:w,sizeLimitError:()=>Error("HTTPStorage scan response exceeded the maximum allowed size.")})){let Z=p(Q);if(Z!==null)yield Z}}async batch(F){M("batch operations",F.length),await this.#Q(this.#Z("/v1/storage/-/batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({operations:F.map(x)})})}async conditionalBatch(F,G){M("conditionalBatch conditions",F.length),M("conditionalBatch operations",G.length);let J=await this.#Q(this.#Z("/v1/storage/-/conditional-batch"),{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({conditions:F.map(f),operations:G.map(x)})});return l(await J.json())}has(F){return _(this,F)}async*keys(F,G){yield*X(this,F,G)}count(F){return H(this,F)}deletePrefix(F){return L(this,F)}deleteRange(F,G){return U(this,F,V(G))}scoped(F){return m(this,F)}[Symbol.dispose](){}}export{s as HTTPStorage};