@xano/xanoscript-language-server 11.7.2 → 11.8.1

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/lexer/tokens.js CHANGED
@@ -284,6 +284,12 @@ export const SensitiveToken = createTokenByName("sensitive", {
284
284
  categories: [Identifier], // could also be an identifier
285
285
  });
286
286
 
287
+ // define the sensitivity of a column
288
+ export const TriggerToken = createTokenByName("trigger", {
289
+ longer_alt: Identifier,
290
+ categories: [Identifier], // could also be an identifier
291
+ });
292
+
287
293
  export const DescriptionToken = createTokenByName("description", {
288
294
  longer_alt: Identifier,
289
295
  categories: [Identifier], // could also be an identifier
@@ -350,6 +356,7 @@ export const allTokens = uniq([
350
356
  AuthToken,
351
357
  GuidToken,
352
358
  SensitiveToken,
359
+ TriggerToken,
353
360
  TagsToken,
354
361
  DescriptionToken,
355
362
  DocsToken,
@@ -473,6 +480,7 @@ function buildTokenTypeMap() {
473
480
  [FiltersToken.name]: "keyword",
474
481
 
475
482
  [DbLinkToken.name]: "function",
483
+ [TriggerToken.name]: "function",
476
484
 
477
485
  // Variable-related tokens
478
486
  [AuthToken.name]: "variable",
@@ -400,12 +400,17 @@ api.call "account/{account_id}" verb=POST {
400
400
 
401
401
  Calls an internal Xano API endpoint from within a `workflow_test` or `test` clause. This allows you to invoke internal APIs as part of your end-to-end workflow tests or unit tests within queries, functions, and tasks.
402
402
 
403
- **Note:** `api.call` is only available within `workflow_test`, `test` (in queries/functions/tasks/middleware), and `tool` contexts, including within control structures like `group`, `conditional`, loops, etc.
403
+ # trigger.call
404
404
 
405
- - `api.call` signature is similar to the `query` definition it's invoking (e.g., `query <URL> verb=<METHOD> {...}`).
406
- - `api_group`: The API group to which the endpoint belongs.
407
- - `headers`: Optional custom headers to include in the request.
408
- - `input`: The request payload containing parameters for the API call.
405
+ ```xs
406
+ trigger.call "onNewOrder" {
407
+ input = {new: {}, old: {}, action: "", datasource: ""}
408
+ } as $new_order_response
409
+ ```
410
+
411
+ **Note:** `trigger.call` is only available within `workflow_test`.
412
+
413
+ - `trigger.call` can call any trigger defined within your workspace, table, realtime, agentic or API triggers.
409
414
 
410
415
  # tool.call
411
416
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xano/xanoscript-language-server",
3
- "version": "11.7.2",
3
+ "version": "11.8.1",
4
4
  "description": "Language Server Protocol implementation for XanoScript",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -21,6 +21,7 @@ export function apiGroupDeclaration($) {
21
21
  let hasHistory = false;
22
22
  let hasSwagger = false;
23
23
  let hasTags = false;
24
+ let hasMiddleware = false;
24
25
 
25
26
  $.sectionStack.push("apiGroupDeclaration");
26
27
  // Allow leading comments and newlines before the api_group declaration
@@ -44,6 +45,13 @@ export function apiGroupDeclaration($) {
44
45
  $.SUBRULE($.booleanValue);
45
46
  },
46
47
  },
48
+ {
49
+ GATE: () => !hasMiddleware,
50
+ ALT: () => {
51
+ hasMiddleware = true;
52
+ $.SUBRULE($.middlewareClause);
53
+ },
54
+ },
47
55
  {
48
56
  GATE: () => !hasCanonical,
49
57
  ALT: () => {
@@ -39,6 +39,16 @@ describe("api_group", () => {
39
39
  expect(parser.errors).to.be.empty;
40
40
  });
41
41
 
42
+ it("should accept a middleware", () => {
43
+ const parser = xanoscriptParser(`api_group "some name" {
44
+ description = "Some description"
45
+ active = false
46
+ canonical = "HZ4jLtdc"
47
+ middleware = {pre: [{name: "M1"}], post: [{name: "M1"}]}
48
+ }`);
49
+ expect(parser.errors).to.be.empty;
50
+ });
51
+
42
52
  it("should parse a disable swagger api_group", () => {
43
53
  const parser = xanoscriptParser(`api_group Authentication {
44
54
  active = false
@@ -55,6 +55,10 @@ export function nakedStackFn($) {
55
55
  GATE: () => options.allowCallStatements,
56
56
  ALT: () => $.SUBRULE($.middlewareCallFn),
57
57
  },
58
+ {
59
+ GATE: () => options.allowCallStatements,
60
+ ALT: () => $.SUBRULE($.triggerCallFn),
61
+ },
58
62
  { ALT: () => $.SUBRULE($.workflowExpectFn) },
59
63
  ]);
60
64
  });
@@ -36,10 +36,12 @@ export function viewClause($) {
36
36
 
37
37
  // each key is the view name, ensure either search or hide is present
38
38
  for (const viewName in captured) {
39
- if (!captured[viewName].search && !captured[viewName].hide) {
39
+ const hasSearch = !!captured[viewName].search;
40
+ const hasSort = !!captured[viewName].sort;
41
+ if (!hasSearch && !hasSort && !captured[viewName].hide) {
40
42
  $.addMissingError(
41
43
  parent,
42
- `view ${viewName} must have either a search or hide criteria`
44
+ `view ${viewName} must have either a search, sort or hide criteria`,
43
45
  );
44
46
  }
45
47
  }
@@ -109,13 +109,22 @@ describe("viewClause", () => {
109
109
  expect(parser.errors).to.be.empty;
110
110
  });
111
111
 
112
- it("viewClause requires either a search or hide criteria", () => {
112
+ it("viewClause accept either a sort criteria", () => {
113
113
  const parser = parse(`view = {
114
114
  "no search criteria": {
115
115
  sort : {id: "asc"}
116
116
  id : "9ed7daad-682f-455e-bf02-ca53444cd429"
117
117
  }
118
118
  }`);
119
+ expect(parser.errors).to.be.empty;
120
+ });
121
+
122
+ it("viewClause requires either a search, sort or hide criteria", () => {
123
+ const parser = parse(`view = {
124
+ "no criteria": {
125
+ id : "9ed7daad-682f-455e-bf02-ca53444cd429"
126
+ }
127
+ }`);
119
128
  expect(parser.errors).to.not.be.empty;
120
129
  });
121
130
 
@@ -32,6 +32,7 @@ import { streamFn } from "./streamFn.js";
32
32
  import { register as register_text } from "./text/register.js";
33
33
  import { textFn } from "./textFn.js";
34
34
  import { toolCallFn } from "./toolCallFn.js";
35
+ import { triggerCallFn } from "./triggerCallFn.js";
35
36
  import { unitExpectFn } from "./unitExpectFn.js";
36
37
  import { register as register_util } from "./util/register.js";
37
38
  import { utilFn } from "./utilFn.js";
@@ -70,6 +71,7 @@ export const register = ($) => {
70
71
  $.unitExpectFn = $.RULE("unitExpectFn", unitExpectFn($));
71
72
  $.workflowExpectFn = $.RULE("workflowExpectFn", workflowExpectFn($));
72
73
  $.toolCallFn = $.RULE("toolCallFn", toolCallFn($));
74
+ $.triggerCallFn = $.RULE("triggerCallFn", triggerCallFn($));
73
75
  register_ai($);
74
76
  register_schema($);
75
77
  register_api($);
@@ -0,0 +1,48 @@
1
+ import { CallToken } from "../../lexer/function.js";
2
+ import { StringLiteral } from "../../lexer/literal.js";
3
+ import { Identifier } from "../../lexer/tokens.js";
4
+ import { DotToken, TriggerToken } from "../../lexer/tokens.js";
5
+
6
+ /**
7
+ * @param {import('../../base_parser.js').XanoBaseParser} $
8
+ */
9
+ export function triggerCallFn($) {
10
+ return () => {
11
+ $.sectionStack.push("triggerCallFn");
12
+
13
+ $.CONSUME(TriggerToken); // "trigger"
14
+ $.CONSUME(DotToken); // "."
15
+ const fnToken = $.CONSUME(CallToken); // "call"
16
+
17
+ // Validate that trigger.call is only used in allowed contexts
18
+ const validContexts = ["workflowTestDeclaration"];
19
+ const isInValidContext = validContexts.some((context) =>
20
+ $.sectionStack.includes(context),
21
+ );
22
+
23
+ if (!isInValidContext) {
24
+ $.addInvalidValueError(
25
+ fnToken,
26
+ "trigger.call can only be used within workflow_test contexts",
27
+ );
28
+ }
29
+
30
+ $.OR([
31
+ { ALT: () => $.CONSUME(StringLiteral) }, // "foo/bar"
32
+ { ALT: () => $.CONSUME(Identifier) }, // foo
33
+ ]);
34
+
35
+ $.SUBRULE($.schemaParseAttributeFn, {
36
+ ARGS: [
37
+ fnToken,
38
+ {
39
+ "description?": "[string]",
40
+ "disabled?": "[boolean]",
41
+ "input?": { "[string]": "[expression]" },
42
+ },
43
+ ],
44
+ });
45
+ $.SUBRULE($.asVariable, { ARGS: [fnToken] });
46
+ $.sectionStack.pop();
47
+ };
48
+ }
@@ -0,0 +1,51 @@
1
+ import { expect } from "chai";
2
+ import { describe, it } from "mocha";
3
+ import { lexDocument } from "../../lexer/lexer.js";
4
+ import { parser } from "../test_parser.js";
5
+
6
+ function parse(inputText, context = null) {
7
+ parser.reset();
8
+ const lexResult = lexDocument(inputText);
9
+ parser.input = lexResult.tokens;
10
+
11
+ if (context) {
12
+ parser.sectionStack.push(context);
13
+ parser.triggerCallFn();
14
+ parser.sectionStack.pop();
15
+ } else {
16
+ parser.triggerCallFn();
17
+ }
18
+
19
+ return parser;
20
+ }
21
+
22
+ describe("triggerCallFn", () => {
23
+ it("should parse trigger.call statement", () => {
24
+ const parser = parse(
25
+ `trigger.call my_agent {
26
+ input = {
27
+ "key": "value"
28
+ }
29
+ } as $result`,
30
+ "workflowTestDeclaration",
31
+ );
32
+ expect(parser.errors).to.be.empty;
33
+ });
34
+
35
+ it("trigger.call can be one liner without a body", () => {
36
+ const parser = parse(
37
+ `trigger.call my_agent as $result`,
38
+ "workflowTestDeclaration",
39
+ );
40
+ expect(parser.errors).to.be.empty;
41
+ });
42
+
43
+ it("should not allow trigger.call statement outside of workflowTestDeclaration", () => {
44
+ const parser = parse(`trigger.call my_agent {
45
+ input = {
46
+ "key": "value"
47
+ }
48
+ } as $result`);
49
+ expect(parser.errors).to.not.be.empty;
50
+ });
51
+ });
@@ -114,6 +114,22 @@ describe("query_parser", () => {
114
114
  expect(parser.errors).to.be.empty;
115
115
  });
116
116
 
117
+ it("should accept a middleware clause", () => {
118
+ const parser = xanoscriptParser(`query foo verb=GET {
119
+ input {
120
+ text user_id filters=trim
121
+ }
122
+
123
+ stack {
124
+ }
125
+
126
+ response = null
127
+
128
+ middleware = {pre: [{name: "M1"}], post: [{name: "M1"}]}
129
+ }`);
130
+ expect(parser.errors).to.be.empty;
131
+ });
132
+
117
133
  it("should accept a test", () => {
118
134
  const parser = xanoscriptParser(`query test_expect verb=GET {
119
135
  input {
@@ -34,6 +34,26 @@ describe("table_parser", () => {
34
34
  expect(parser.errors).to.be.empty;
35
35
  });
36
36
 
37
+ it("should parse a basic table with an ordering view", () => {
38
+ const parser = xanoscriptParser(`table foo {
39
+ auth = false
40
+
41
+ schema {
42
+ // Primary key
43
+ int id
44
+ }
45
+
46
+ view = {
47
+ asc: {
48
+ alias: "testAlias"
49
+ sort : {Country: "asc"}
50
+ id : "b4530953-23df-401a-a2c9-9432b31cd808"
51
+ }
52
+ }
53
+ }`);
54
+ expect(parser.errors).to.be.empty;
55
+ });
56
+
37
57
  // autocomplete = [{ name: "id" }, { name: "cd" }];
38
58
  it("should parse a table with an autocomplete field", () => {
39
59
  const parser = xanoscriptParser(`table foo {