@blokjs/lsp-server 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js ADDED
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
5
+ const node_1 = require("vscode-languageserver/node");
6
+ const completion_1 = require("./completion");
7
+ const diagnostics_1 = require("./diagnostics");
8
+ const hover_1 = require("./hover");
9
+ /**
10
+ * Blok Workflow Language Server
11
+ *
12
+ * Provides workflow intelligence for any LSP-compatible editor:
13
+ * - Diagnostics: Real-time validation of workflow JSON files
14
+ * - Completion: Contextual auto-completion for triggers, steps, runtimes, etc.
15
+ * - Hover: Rich documentation on hover for workflow fields and values
16
+ *
17
+ * Communication: stdio (default) or TCP
18
+ * File types: JSON files matching `**​/workflows/**​/*.json` or `blok.workflow.json`
19
+ */
20
+ // Create connection and document manager
21
+ const connection = (0, node_1.createConnection)(node_1.ProposedFeatures.all);
22
+ const documents = new node_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument);
23
+ let hasConfigurationCapability = false;
24
+ let hasWorkspaceFolderCapability = false;
25
+ const defaultSettings = {
26
+ workflowGlob: "**/workflows/**/*.json",
27
+ maxDiagnostics: 100,
28
+ };
29
+ let globalSettings = defaultSettings;
30
+ const documentSettings = new Map();
31
+ connection.onInitialize((params) => {
32
+ const capabilities = params.capabilities;
33
+ hasConfigurationCapability = !!(capabilities.workspace && capabilities.workspace.configuration);
34
+ hasWorkspaceFolderCapability = !!(capabilities.workspace && capabilities.workspace.workspaceFolders);
35
+ const result = {
36
+ capabilities: {
37
+ textDocumentSync: node_1.TextDocumentSyncKind.Incremental,
38
+ completionProvider: {
39
+ resolveProvider: false,
40
+ triggerCharacters: ['"', ":"],
41
+ },
42
+ hoverProvider: true,
43
+ },
44
+ };
45
+ if (hasWorkspaceFolderCapability) {
46
+ result.capabilities.workspace = {
47
+ workspaceFolders: {
48
+ supported: true,
49
+ },
50
+ };
51
+ }
52
+ return result;
53
+ });
54
+ connection.onInitialized(() => {
55
+ if (hasConfigurationCapability) {
56
+ connection.client.register(node_1.DidChangeConfigurationNotification.type, undefined);
57
+ }
58
+ });
59
+ // Configuration handling
60
+ connection.onDidChangeConfiguration((change) => {
61
+ if (hasConfigurationCapability) {
62
+ documentSettings.clear();
63
+ }
64
+ else {
65
+ globalSettings = change.settings?.blok || defaultSettings;
66
+ }
67
+ // Revalidate all open documents
68
+ for (const doc of documents.all()) {
69
+ validateDocument(doc);
70
+ }
71
+ });
72
+ function getDocumentSettings(resource) {
73
+ if (!hasConfigurationCapability) {
74
+ return globalSettings;
75
+ }
76
+ let result = documentSettings.get(resource);
77
+ if (!result) {
78
+ result = globalSettings;
79
+ documentSettings.set(resource, result);
80
+ }
81
+ return result;
82
+ }
83
+ // Document validation
84
+ function isWorkflowFile(uri) {
85
+ // Match workflow files by path pattern
86
+ return /workflows?[/\\].*\.json$/i.test(uri) || /\.workflow\.json$/i.test(uri) || /blok\.json$/i.test(uri);
87
+ }
88
+ function validateDocument(document) {
89
+ if (!isWorkflowFile(document.uri))
90
+ return;
91
+ const text = document.getText();
92
+ const diagnostics = (0, diagnostics_1.validateWorkflow)(text);
93
+ const settings = getDocumentSettings(document.uri);
94
+ // Limit diagnostics if configured
95
+ const limited = diagnostics.slice(0, settings.maxDiagnostics);
96
+ connection.sendDiagnostics({ uri: document.uri, diagnostics: limited });
97
+ }
98
+ // Validate on open and change
99
+ documents.onDidChangeContent((change) => {
100
+ validateDocument(change.document);
101
+ });
102
+ documents.onDidClose((event) => {
103
+ documentSettings.delete(event.document.uri);
104
+ connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
105
+ });
106
+ // Completion
107
+ connection.onCompletion((params) => {
108
+ const document = documents.get(params.textDocument.uri);
109
+ if (!document)
110
+ return [];
111
+ if (!isWorkflowFile(document.uri))
112
+ return [];
113
+ const text = document.getText();
114
+ const offset = document.offsetAt(params.position);
115
+ return (0, completion_1.getCompletions)(text, offset);
116
+ });
117
+ // Hover
118
+ connection.onHover((params) => {
119
+ const document = documents.get(params.textDocument.uri);
120
+ if (!document)
121
+ return null;
122
+ if (!isWorkflowFile(document.uri))
123
+ return null;
124
+ const text = document.getText();
125
+ return (0, hover_1.getHover)(text, params.position.line, params.position.character);
126
+ });
127
+ // Start listening
128
+ documents.listen(connection);
129
+ connection.listen();
130
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ ;;; blok-lsp.el --- Blok Workflow LSP integration for Emacs
2
+ ;;
3
+ ;; Add this to your Emacs config (init.el or .emacs)
4
+ ;; Requires: lsp-mode (https://emacs-lsp.github.io/lsp-mode/)
5
+ ;; Install: npm install -g @blok/lsp-server
6
+
7
+ (with-eval-after-load 'lsp-mode
8
+ ;; Register the Blok LSP server
9
+ (lsp-register-client
10
+ (make-lsp-client
11
+ :new-connection (lsp-stdio-connection '("blok-lsp" "--stdio"))
12
+ :activation-fn (lsp-activate-on "json")
13
+ :server-id 'blok-lsp
14
+ :priority -1
15
+ :initialization-options
16
+ '((blok
17
+ (workflowGlob . "**/workflows/**/*.json")
18
+ (maxDiagnostics . 100)))))
19
+
20
+ ;; Add to the auto-start list
21
+ (add-to-list 'lsp-language-id-configuration '(json-mode . "json")))
@@ -0,0 +1,12 @@
1
+ # Blok Workflow LSP - Helix Editor Configuration
2
+ #
3
+ # Add this to your Helix config: ~/.config/helix/languages.toml
4
+ # Requires: npm install -g @blok/lsp-server
5
+
6
+ [[language]]
7
+ name = "json"
8
+ language-servers = ["blok-lsp"]
9
+
10
+ [language-server.blok-lsp]
11
+ command = "blok-lsp"
12
+ args = ["--stdio"]
@@ -0,0 +1,59 @@
1
+ -- Blok Workflow LSP - Neovim Configuration
2
+ --
3
+ -- Add this to your Neovim config (init.lua or lua/plugins/blok.lua)
4
+ -- Requires: nvim-lspconfig (https://github.com/neovim/nvim-lspconfig)
5
+ --
6
+ -- Option 1: Install globally
7
+ -- npm install -g @blok/lsp-server
8
+ --
9
+ -- Option 2: Use from project
10
+ -- npx blok-lsp (runs from node_modules)
11
+
12
+ local lspconfig = require("lspconfig")
13
+ local configs = require("lspconfig.configs")
14
+
15
+ -- Register the Blok LSP server if not already registered
16
+ if not configs.blok_lsp then
17
+ configs.blok_lsp = {
18
+ default_config = {
19
+ -- Use global install or npx
20
+ cmd = { "blok-lsp", "--stdio" },
21
+ -- Alternative: use npx (slower startup, no global install needed)
22
+ -- cmd = { "npx", "blok-lsp", "--stdio" },
23
+ filetypes = { "json" },
24
+ root_dir = lspconfig.util.root_pattern(
25
+ "blok.json",
26
+ "blokctl.config.ts",
27
+ "blokctl.config.js",
28
+ "package.json"
29
+ ),
30
+ settings = {
31
+ blok = {
32
+ workflowGlob = "**/workflows/**/*.json",
33
+ maxDiagnostics = 100,
34
+ },
35
+ },
36
+ -- Only activate for workflow JSON files
37
+ on_new_config = function(new_config, new_root_dir)
38
+ -- The LSP server itself filters by file path pattern
39
+ end,
40
+ },
41
+ }
42
+ end
43
+
44
+ -- Setup the LSP server
45
+ lspconfig.blok_lsp.setup({
46
+ on_attach = function(client, bufnr)
47
+ -- Enable completion triggered by <c-x><c-o>
48
+ vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc"
49
+
50
+ -- Keybindings for LSP features
51
+ local opts = { buffer = bufnr, noremap = true, silent = true }
52
+ vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
53
+ vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts)
54
+ vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, opts)
55
+ vim.keymap.set("n", "]d", vim.diagnostic.goto_next, opts)
56
+ vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, opts)
57
+ end,
58
+ capabilities = vim.lsp.protocol.make_client_capabilities(),
59
+ })
@@ -0,0 +1,16 @@
1
+ {
2
+ "clients": {
3
+ "blok-lsp": {
4
+ "enabled": true,
5
+ "command": ["blok-lsp", "--stdio"],
6
+ "selector": "source.json",
7
+ "initializationOptions": {
8
+ "blok": {
9
+ "workflowGlob": "**/workflows/**/*.json",
10
+ "maxDiagnostics": 100
11
+ }
12
+ },
13
+ "settings": {}
14
+ }
15
+ }
16
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@blokjs/lsp-server",
3
+ "version": "0.2.0",
4
+ "description": "Language Server Protocol server for Blok workflow files - provides diagnostics, completion, and hover documentation for any LSP-compatible editor",
5
+ "license": "Apache-2.0",
6
+ "main": "./dist/server.js",
7
+ "bin": {
8
+ "blok-lsp": "./dist/server.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "watch": "tsc --watch",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest"
15
+ },
16
+ "dependencies": {
17
+ "vscode-languageserver": "^9.0.1",
18
+ "vscode-languageserver-textdocument": "^1.0.12"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.15.21",
22
+ "typescript": "^5.8.3",
23
+ "vitest": "^4.0.18"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
28
+ "keywords": [
29
+ "blok",
30
+ "lsp",
31
+ "language-server",
32
+ "workflow",
33
+ "json",
34
+ "blok"
35
+ ],
36
+ "private": false,
37
+ "publishConfig": {
38
+ "access": "public"
39
+ }
40
+ }
@@ -0,0 +1,184 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { CompletionItemKind } from "vscode-languageserver";
3
+ import { getCompletions } from "../completion";
4
+
5
+ function getOffset(text: string): number {
6
+ // Returns the offset at the end of the text
7
+ return text.length;
8
+ }
9
+
10
+ describe("WorkflowCompletionProvider (LSP)", () => {
11
+ describe("trigger type completions", () => {
12
+ it("should provide trigger type completions inside trigger object", () => {
13
+ const content = '{\n "trigger": {\n "';
14
+ const items = getCompletions(content, getOffset(content));
15
+ const labels = items.map((i) => i.label);
16
+ expect(labels).toContain("http");
17
+ expect(labels).toContain("grpc");
18
+ expect(labels).toContain("cron");
19
+ expect(labels).toContain("queue");
20
+ expect(labels).toContain("pubsub");
21
+ expect(labels).toContain("worker");
22
+ expect(labels).toContain("webhook");
23
+ expect(labels).toContain("websocket");
24
+ expect(labels).toContain("sse");
25
+ });
26
+ });
27
+
28
+ describe("HTTP method completions", () => {
29
+ it("should provide HTTP method completions for method key in trigger", () => {
30
+ const content = '{\n "trigger": {\n "http": {\n "method": "';
31
+ const items = getCompletions(content, getOffset(content));
32
+ const labels = items.map((i) => i.label);
33
+ expect(labels).toContain("GET");
34
+ expect(labels).toContain("POST");
35
+ expect(labels).toContain("PUT");
36
+ expect(labels).toContain("DELETE");
37
+ expect(labels).toContain("PATCH");
38
+ expect(labels).toContain("ANY");
39
+ });
40
+ });
41
+
42
+ describe("step type completions", () => {
43
+ it("should provide step type completions inside steps", () => {
44
+ const content = '{\n "steps": [\n {\n "type": "';
45
+ const items = getCompletions(content, getOffset(content));
46
+ const labels = items.map((i) => i.label);
47
+ expect(labels).toContain("module");
48
+ expect(labels).toContain("local");
49
+ expect(labels).toContain("runtime.nodejs");
50
+ expect(labels).toContain("runtime.python3");
51
+ expect(labels).toContain("runtime.go");
52
+ expect(labels).toContain("runtime.java");
53
+ expect(labels).toContain("runtime.rust");
54
+ });
55
+ });
56
+
57
+ describe("runtime completions", () => {
58
+ it("should provide runtime completions", () => {
59
+ const content = '{\n "steps": [\n {\n "runtime": "';
60
+ const items = getCompletions(content, getOffset(content));
61
+ const labels = items.map((i) => i.label);
62
+ expect(labels).toContain("nodejs");
63
+ expect(labels).toContain("python3");
64
+ expect(labels).toContain("go");
65
+ expect(labels).toContain("java");
66
+ expect(labels).toContain("rust");
67
+ expect(labels).toContain("docker");
68
+ expect(labels).toContain("wasm");
69
+ });
70
+ });
71
+
72
+ describe("node package completions", () => {
73
+ it("should provide node completions for node key", () => {
74
+ const content = '{\n "steps": [\n {\n "node": "';
75
+ const items = getCompletions(content, getOffset(content));
76
+ const labels = items.map((i) => i.label);
77
+ expect(labels).toContain("@blokjs/api-call");
78
+ expect(labels).toContain("@blokjs/if-else");
79
+ expect(labels).toContain("@blokjs/react");
80
+ });
81
+
82
+ it("should use Module kind for node packages", () => {
83
+ const content = '{\n "steps": [\n {\n "node": "';
84
+ const items = getCompletions(content, getOffset(content));
85
+ const apiCall = items.find((i) => i.label === "@blokjs/api-call");
86
+ expect(apiCall?.kind).toBe(CompletionItemKind.Module);
87
+ });
88
+ });
89
+
90
+ describe("queue provider completions", () => {
91
+ it("should provide queue provider completions", () => {
92
+ const content = '{\n "trigger": {\n "queue": {\n "provider": "';
93
+ const items = getCompletions(content, getOffset(content));
94
+ const labels = items.map((i) => i.label);
95
+ expect(labels).toContain("kafka");
96
+ expect(labels).toContain("rabbitmq");
97
+ expect(labels).toContain("sqs");
98
+ expect(labels).toContain("redis");
99
+ });
100
+ });
101
+
102
+ describe("pubsub provider completions", () => {
103
+ it("should provide pubsub provider completions", () => {
104
+ const content = '{\n "trigger": {\n "pubsub": {\n "provider": "';
105
+ const items = getCompletions(content, getOffset(content));
106
+ const labels = items.map((i) => i.label);
107
+ expect(labels).toContain("gcp");
108
+ expect(labels).toContain("aws");
109
+ expect(labels).toContain("azure");
110
+ expect(labels).toContain("nats");
111
+ });
112
+ });
113
+
114
+ describe("webhook source completions", () => {
115
+ it("should provide webhook source completions", () => {
116
+ const content = '{\n "trigger": {\n "webhook": {\n "source": "';
117
+ const items = getCompletions(content, getOffset(content));
118
+ const labels = items.map((i) => i.label);
119
+ expect(labels).toContain("github");
120
+ expect(labels).toContain("stripe");
121
+ expect(labels).toContain("shopify");
122
+ expect(labels).toContain("custom");
123
+ });
124
+ });
125
+
126
+ describe("condition type completions", () => {
127
+ it("should provide condition type completions", () => {
128
+ const content = '{\n "nodes": {\n "filter": {\n "conditions": [\n {\n "type": "';
129
+ const items = getCompletions(content, getOffset(content));
130
+ const labels = items.map((i) => i.label);
131
+ expect(labels).toContain("if");
132
+ expect(labels).toContain("else");
133
+ });
134
+
135
+ it("should use Keyword kind for condition types", () => {
136
+ const content = '{\n "nodes": {\n "filter": {\n "conditions": [\n {\n "type": "';
137
+ const items = getCompletions(content, getOffset(content));
138
+ const ifItem = items.find((i) => i.label === "if");
139
+ expect(ifItem?.kind).toBe(CompletionItemKind.Keyword);
140
+ });
141
+ });
142
+
143
+ describe("top-level key completions", () => {
144
+ it("should provide top-level key completions at root", () => {
145
+ const content = '{\n "';
146
+ const items = getCompletions(content, getOffset(content));
147
+ const labels = items.map((i) => i.label);
148
+ expect(labels).toContain("name");
149
+ expect(labels).toContain("version");
150
+ expect(labels).toContain("trigger");
151
+ expect(labels).toContain("steps");
152
+ expect(labels).toContain("nodes");
153
+ });
154
+
155
+ it("should provide HTTP-specific keys inside http object", () => {
156
+ const content = '{\n "trigger": {\n "http": {\n "';
157
+ const items = getCompletions(content, getOffset(content));
158
+ const labels = items.map((i) => i.label);
159
+ expect(labels).toContain("method");
160
+ expect(labels).toContain("path");
161
+ });
162
+
163
+ it("should provide step-specific keys inside steps array", () => {
164
+ const content = '{\n "steps": [\n {\n "';
165
+ const items = getCompletions(content, getOffset(content));
166
+ const labels = items.map((i) => i.label);
167
+ expect(labels).toContain("name");
168
+ expect(labels).toContain("node");
169
+ expect(labels).toContain("type");
170
+ expect(labels).toContain("runtime");
171
+ });
172
+ });
173
+
174
+ describe("empty results", () => {
175
+ it("should return empty for unknown context", () => {
176
+ const content = '{\n "unknown": {\n "provider": "';
177
+ const items = getCompletions(content, getOffset(content));
178
+ // Should not provide queue/pubsub providers in unknown context
179
+ const labels = items.map((i) => i.label);
180
+ expect(labels).not.toContain("kafka");
181
+ expect(labels).not.toContain("gcp");
182
+ });
183
+ });
184
+ });
@@ -0,0 +1,142 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ FIELD_DOCS,
4
+ NODE_PACKAGES,
5
+ PUBSUB_PROVIDERS,
6
+ QUEUE_PROVIDERS,
7
+ STEP_FIELD_DOCS,
8
+ TRIGGER_DOCS,
9
+ VALID_HTTP_METHODS,
10
+ VALID_RUNTIMES,
11
+ VALID_STEP_TYPES,
12
+ VALID_TRIGGERS,
13
+ WEBHOOK_SOURCES,
14
+ } from "../constants";
15
+
16
+ describe("LSP Constants", () => {
17
+ describe("VALID_TRIGGERS", () => {
18
+ it("should have all 10 trigger types", () => {
19
+ expect(VALID_TRIGGERS).toHaveLength(10);
20
+ expect(VALID_TRIGGERS).toContain("http");
21
+ expect(VALID_TRIGGERS).toContain("grpc");
22
+ expect(VALID_TRIGGERS).toContain("manual");
23
+ expect(VALID_TRIGGERS).toContain("cron");
24
+ expect(VALID_TRIGGERS).toContain("queue");
25
+ expect(VALID_TRIGGERS).toContain("pubsub");
26
+ expect(VALID_TRIGGERS).toContain("worker");
27
+ expect(VALID_TRIGGERS).toContain("webhook");
28
+ expect(VALID_TRIGGERS).toContain("websocket");
29
+ expect(VALID_TRIGGERS).toContain("sse");
30
+ });
31
+ });
32
+
33
+ describe("VALID_HTTP_METHODS", () => {
34
+ it("should include standard HTTP methods and ANY", () => {
35
+ expect(VALID_HTTP_METHODS).toContain("GET");
36
+ expect(VALID_HTTP_METHODS).toContain("POST");
37
+ expect(VALID_HTTP_METHODS).toContain("PUT");
38
+ expect(VALID_HTTP_METHODS).toContain("DELETE");
39
+ expect(VALID_HTTP_METHODS).toContain("PATCH");
40
+ expect(VALID_HTTP_METHODS).toContain("ANY");
41
+ });
42
+ });
43
+
44
+ describe("VALID_STEP_TYPES", () => {
45
+ it("should include local, module, and runtime types", () => {
46
+ expect(VALID_STEP_TYPES).toContain("local");
47
+ expect(VALID_STEP_TYPES).toContain("module");
48
+ expect(VALID_STEP_TYPES).toContain("runtime.nodejs");
49
+ expect(VALID_STEP_TYPES).toContain("runtime.python3");
50
+ expect(VALID_STEP_TYPES).toContain("runtime.go");
51
+ expect(VALID_STEP_TYPES).toContain("runtime.java");
52
+ expect(VALID_STEP_TYPES).toContain("runtime.rust");
53
+ });
54
+ });
55
+
56
+ describe("VALID_RUNTIMES", () => {
57
+ it("should include all 11 runtime kinds", () => {
58
+ expect(VALID_RUNTIMES).toHaveLength(11);
59
+ expect(VALID_RUNTIMES).toContain("nodejs");
60
+ expect(VALID_RUNTIMES).toContain("bun");
61
+ expect(VALID_RUNTIMES).toContain("python3");
62
+ expect(VALID_RUNTIMES).toContain("go");
63
+ expect(VALID_RUNTIMES).toContain("java");
64
+ expect(VALID_RUNTIMES).toContain("rust");
65
+ expect(VALID_RUNTIMES).toContain("docker");
66
+ expect(VALID_RUNTIMES).toContain("wasm");
67
+ });
68
+ });
69
+
70
+ describe("TRIGGER_DOCS", () => {
71
+ it("should have documentation for all triggers", () => {
72
+ for (const trigger of VALID_TRIGGERS) {
73
+ expect(TRIGGER_DOCS).toHaveProperty(trigger);
74
+ expect(TRIGGER_DOCS[trigger].title).toBeTruthy();
75
+ expect(TRIGGER_DOCS[trigger].description).toBeTruthy();
76
+ }
77
+ });
78
+
79
+ it("should include examples for all trigger docs", () => {
80
+ for (const trigger of VALID_TRIGGERS) {
81
+ expect(TRIGGER_DOCS[trigger].example).toBeTruthy();
82
+ }
83
+ });
84
+ });
85
+
86
+ describe("FIELD_DOCS", () => {
87
+ it("should have documentation for core workflow fields", () => {
88
+ expect(FIELD_DOCS).toHaveProperty("name");
89
+ expect(FIELD_DOCS).toHaveProperty("version");
90
+ expect(FIELD_DOCS).toHaveProperty("trigger");
91
+ expect(FIELD_DOCS).toHaveProperty("steps");
92
+ expect(FIELD_DOCS).toHaveProperty("nodes");
93
+ expect(FIELD_DOCS).toHaveProperty("inputs");
94
+ expect(FIELD_DOCS).toHaveProperty("conditions");
95
+ expect(FIELD_DOCS).toHaveProperty("set_var");
96
+ });
97
+ });
98
+
99
+ describe("STEP_FIELD_DOCS", () => {
100
+ it("should have documentation for step fields", () => {
101
+ expect(STEP_FIELD_DOCS).toHaveProperty("node");
102
+ expect(STEP_FIELD_DOCS).toHaveProperty("type");
103
+ expect(STEP_FIELD_DOCS).toHaveProperty("runtime");
104
+ });
105
+ });
106
+
107
+ describe("NODE_PACKAGES", () => {
108
+ it("should include core node packages", () => {
109
+ const names = NODE_PACKAGES.map((n) => n.name);
110
+ expect(names).toContain("@blokjs/api-call");
111
+ expect(names).toContain("@blokjs/if-else");
112
+ expect(names).toContain("@blokjs/react");
113
+ });
114
+ });
115
+
116
+ describe("QUEUE_PROVIDERS", () => {
117
+ it("should include all queue providers", () => {
118
+ expect(QUEUE_PROVIDERS).toContain("kafka");
119
+ expect(QUEUE_PROVIDERS).toContain("rabbitmq");
120
+ expect(QUEUE_PROVIDERS).toContain("sqs");
121
+ expect(QUEUE_PROVIDERS).toContain("redis");
122
+ });
123
+ });
124
+
125
+ describe("PUBSUB_PROVIDERS", () => {
126
+ it("should include all pubsub providers", () => {
127
+ expect(PUBSUB_PROVIDERS).toContain("gcp");
128
+ expect(PUBSUB_PROVIDERS).toContain("aws");
129
+ expect(PUBSUB_PROVIDERS).toContain("azure");
130
+ expect(PUBSUB_PROVIDERS).toContain("nats");
131
+ });
132
+ });
133
+
134
+ describe("WEBHOOK_SOURCES", () => {
135
+ it("should include all webhook sources", () => {
136
+ expect(WEBHOOK_SOURCES).toContain("github");
137
+ expect(WEBHOOK_SOURCES).toContain("stripe");
138
+ expect(WEBHOOK_SOURCES).toContain("shopify");
139
+ expect(WEBHOOK_SOURCES).toContain("custom");
140
+ });
141
+ });
142
+ });