@xano/xanoscript-language-server 11.8.0 → 11.8.2

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/table.js CHANGED
@@ -19,7 +19,18 @@ export const ItemsToken = createTokenByName("items", {
19
19
  categories: [Identifier],
20
20
  });
21
21
 
22
- export const TableTokens = [TableToken, AutoCompleteToken, ItemsToken];
22
+ // "visibility"
23
+ export const VisibilityToken = createTokenByName("visibility", {
24
+ longer_alt: Identifier,
25
+ categories: [Identifier],
26
+ });
27
+
28
+ export const TableTokens = [
29
+ TableToken,
30
+ AutoCompleteToken,
31
+ ItemsToken,
32
+ VisibilityToken,
33
+ ];
23
34
 
24
35
  /**
25
36
  * Maps a token name to a type
@@ -31,6 +42,7 @@ export function mapTokenToType(token) {
31
42
  return "keyword";
32
43
  case AutoCompleteToken.name:
33
44
  case ItemsToken.name:
45
+ case VisibilityToken.name:
34
46
  return "identifier";
35
47
  default:
36
48
  return null;
package/lexer/tokens.js CHANGED
@@ -480,6 +480,7 @@ function buildTokenTypeMap() {
480
480
  [FiltersToken.name]: "keyword",
481
481
 
482
482
  [DbLinkToken.name]: "function",
483
+ [TriggerToken.name]: "function",
483
484
 
484
485
  // Variable-related tokens
485
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.8.0",
3
+ "version": "11.8.2",
4
4
  "description": "Language Server Protocol implementation for XanoScript",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -1,12 +1,13 @@
1
1
  import { columnDefaultValueAttribute } from "./columnDefaultValueAttribute.js";
2
2
  import { descriptionFieldAttribute } from "./descriptionFieldAttribute.js";
3
3
  import { disabledFieldAttribute } from "./disabledFieldAttribute.js";
4
- import { docsFieldAttribute } from "./docsFieldAttribute.js"
4
+ import { docsFieldAttribute } from "./docsFieldAttribute.js";
5
5
  import { guidFieldAttribute } from "./guidFieldAttribute.js";
6
6
  import { inputFilterFn } from "./inputFilterFn.js";
7
7
  import { sensitiveFieldAttribute } from "./sensitiveFieldAttribute.js";
8
8
  import { valueFieldAttribute } from "./valueFieldAttribute.js";
9
9
  import { valuesFieldAttribute } from "./valuesFieldAttribute.js";
10
+ import { visibilityFieldAttribute } from "./visibilityFieldAttribute.js";
10
11
 
11
12
  /**
12
13
  * Registers all the parsers in this folder
@@ -15,32 +16,30 @@ import { valuesFieldAttribute } from "./valuesFieldAttribute.js";
15
16
  export const register = ($) => {
16
17
  $.columnDefaultValueAttribute = $.RULE(
17
18
  "columnDefaultValueAttribute",
18
- columnDefaultValueAttribute($)
19
+ columnDefaultValueAttribute($),
19
20
  );
20
21
  $.descriptionFieldAttribute = $.RULE(
21
22
  "descriptionFieldAttribute",
22
- descriptionFieldAttribute($)
23
+ descriptionFieldAttribute($),
23
24
  );
24
25
  $.disabledFieldAttribute = $.RULE(
25
26
  "disabledFieldAttribute",
26
- disabledFieldAttribute($)
27
- );
28
- $.docsFieldAttribute = $.RULE(
29
- "docsFieldAttribute",
30
- docsFieldAttribute($)
31
- );
32
- $.guidFieldAttribute = $.RULE(
33
- "guidFieldAttribute",
34
- guidFieldAttribute($)
27
+ disabledFieldAttribute($),
35
28
  );
29
+ $.docsFieldAttribute = $.RULE("docsFieldAttribute", docsFieldAttribute($));
30
+ $.guidFieldAttribute = $.RULE("guidFieldAttribute", guidFieldAttribute($));
36
31
  $.inputFilterFn = $.RULE("inputFilterFn", inputFilterFn($));
37
32
  $.sensitiveFieldAttribute = $.RULE(
38
33
  "sensitiveFieldAttribute",
39
- sensitiveFieldAttribute($)
34
+ sensitiveFieldAttribute($),
40
35
  );
41
36
  $.valueFieldAttribute = $.RULE("valueFieldAttribute", valueFieldAttribute($));
42
37
  $.valuesFieldAttribute = $.RULE(
43
38
  "valuesFieldAttribute",
44
- valuesFieldAttribute($)
39
+ valuesFieldAttribute($),
40
+ );
41
+ $.visibilityFieldAttribute = $.RULE(
42
+ "visibilityFieldAttribute",
43
+ visibilityFieldAttribute($),
45
44
  );
46
45
  };
@@ -0,0 +1,17 @@
1
+ import { EqualToken } from "../../lexer/control.js";
2
+ import { VisibilityToken } from "../../lexer/table.js";
3
+
4
+ /**
5
+ * Visibility Field Attribute Parser on table metadata definitions
6
+ * @param {import('../base_parser.js').XanoBaseParser} $
7
+ * @returns
8
+ */
9
+ export const visibilityFieldAttribute = ($) => () => {
10
+ $.sectionStack.push("visibilityFieldAttribute");
11
+ const parent = $.CONSUME(VisibilityToken); // "visibility"
12
+ $.CONSUME(EqualToken); // "="
13
+ $.SUBRULE($.schemaParseEnumFn, {
14
+ ARGS: [parent, ["private", "internal", "public"]],
15
+ });
16
+ $.sectionStack.pop();
17
+ };
@@ -0,0 +1,44 @@
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) {
7
+ parser.reset();
8
+ const lexResult = lexDocument(inputText);
9
+ parser.input = lexResult.tokens;
10
+ parser.visibilityFieldAttribute();
11
+ return parser;
12
+ }
13
+
14
+ describe("visibilityFieldAttribute", () => {
15
+ it("visibilityFieldAttribute accepts a private value", () => {
16
+ const parser = parse(`visibility = "private"`);
17
+ expect(parser.errors).to.be.empty;
18
+ });
19
+
20
+ it("visibilityFieldAttribute accepts an internal value", () => {
21
+ const parser = parse(`visibility = "internal"`);
22
+ expect(parser.errors).to.be.empty;
23
+ });
24
+
25
+ it("visibilityFieldAttribute accepts a public value", () => {
26
+ const parser = parse(`visibility = "public"`);
27
+ expect(parser.errors).to.be.empty;
28
+ });
29
+
30
+ it("visibilityFieldAttribute rejects anything else", () => {
31
+ const parser = parse(`visibility = "something"`);
32
+ expect(parser.errors).to.not.be.empty;
33
+ });
34
+
35
+ it("visibilityFieldAttribute rejects digit", () => {
36
+ const parser = parse("visibility = 123");
37
+ expect(parser.errors).to.not.be.empty;
38
+ });
39
+
40
+ it("visibilityFieldAttribute can be compact", () => {
41
+ const parser = parse(`visibility="private"`);
42
+ expect(parser.errors).to.be.empty;
43
+ });
44
+ });
@@ -3,10 +3,19 @@ import { describe, it } from "mocha";
3
3
  import { lexDocument } from "../../lexer/lexer.js";
4
4
  import { parser } from "../test_parser.js";
5
5
 
6
- function parse(inputText) {
6
+ function parse(inputText, context = null) {
7
+ parser.reset();
7
8
  const lexResult = lexDocument(inputText);
8
9
  parser.input = lexResult.tokens;
9
- parser.schemaClause();
10
+
11
+ if (context) {
12
+ parser.sectionStack.push(context);
13
+ parser.schemaClause();
14
+ parser.sectionStack.pop();
15
+ } else {
16
+ parser.schemaClause();
17
+ }
18
+
10
19
  return parser;
11
20
  }
12
21
 
@@ -39,6 +48,33 @@ describe("schemaClause", () => {
39
48
  expect(parser.errors).to.be.empty;
40
49
  });
41
50
 
51
+ it("schemaClause accepts a visibility when in a table definition", () => {
52
+ const parser = parse(
53
+ `schema {
54
+ int rank {
55
+ visibility = "private"
56
+ }
57
+ text label {
58
+ visibility = "internal"
59
+ }
60
+ }`,
61
+ "tableDeclaration",
62
+ );
63
+ expect(parser.errors).to.be.empty;
64
+ });
65
+
66
+ it("schemaClause rejects a visibility when not in a table definition", () => {
67
+ const parser = parse(`schema {
68
+ int rank {
69
+ visibility = "private"
70
+ }
71
+ text label {
72
+ visibility = "internal"
73
+ }
74
+ }`);
75
+ expect(parser.errors).to.not.be.empty;
76
+ });
77
+
42
78
  it("schemaClause accepts a description", () => {
43
79
  const parser = parse(`schema {
44
80
  text label {
@@ -107,6 +107,7 @@ export function columnDefinition($) {
107
107
  "table?": "[string]",
108
108
  "size?": "[number]",
109
109
  "sensitive?": "[boolean]",
110
+ "visibility?": ["public", "private", "internal"],
110
111
  },
111
112
  captured,
112
113
  ],
@@ -115,7 +116,15 @@ export function columnDefinition($) {
115
116
  if (inputTypeToken.image == "vector" && !captured.size) {
116
117
  $.addWarning(
117
118
  'Column named "vector" should have a size attribute defining its length.',
118
- nameToken
119
+ nameToken,
120
+ );
121
+ }
122
+
123
+ const inTable = $.sectionStack.includes("tableDeclaration");
124
+ if (captured.visibility && !inTable) {
125
+ $.addInvalidValueError(
126
+ nameToken,
127
+ 'The "visibility" attribute is only valid within a table schema definition.',
119
128
  );
120
129
  }
121
130
 
@@ -131,7 +140,7 @@ export function columnDefinition($) {
131
140
  inputTypeToken.image,
132
141
  iterable,
133
142
  nullable,
134
- optional
143
+ optional,
135
144
  );
136
145
 
137
146
  $.sectionStack.pop();
@@ -17,6 +17,7 @@ export function redisPopFn($) {
17
17
  key: "[expression]",
18
18
  "description?": "[string]",
19
19
  "disabled?": "[boolean]",
20
+ "count?": "[expression]",
20
21
  },
21
22
  ],
22
23
  });
@@ -64,4 +64,12 @@ describe("redis.pop", () => {
64
64
  } as $x7`);
65
65
  expect(parser.errors).to.be.empty;
66
66
  });
67
+
68
+ it("pop accepts an optional count attribute", () => {
69
+ const parser = parse(`pop {
70
+ key = "list"
71
+ count = 5
72
+ } as $x7`);
73
+ expect(parser.errors).to.be.empty;
74
+ });
67
75
  });
@@ -17,6 +17,7 @@ export function redisShiftFn($) {
17
17
  key: "[expression]",
18
18
  "description?": "[string]",
19
19
  "disabled?": "[boolean]",
20
+ "count?": "[expression]",
20
21
  },
21
22
  ],
22
23
  });
@@ -64,4 +64,12 @@ describe("redis.shift", () => {
64
64
  } as $x8`);
65
65
  expect(parser.errors).to.be.empty;
66
66
  });
67
+
68
+ it("shift accepts an optional count attribute", () => {
69
+ const parser = parse(`shift {
70
+ key = "list"
71
+ count = 5
72
+ } as $x8`);
73
+ expect(parser.errors).to.be.empty;
74
+ });
67
75
  });
@@ -27,7 +27,6 @@ describe("workflowExpectFn", () => {
27
27
  ) {
28
28
  value = $som_value
29
29
  }`);
30
- console.log(parser.errors);
31
30
  expect(parser.errors).to.be.empty;
32
31
  });
33
32
  });
@@ -1,46 +1,33 @@
1
- import { LCurly, RCurly } from "../../lexer/control.js";
2
- import { NewlineToken } from "../../lexer/tokens.js";
3
-
4
1
  /**
5
2
  *
6
3
  * @param {import('../base_parser.js').XanoBaseParser} $
7
4
  */
8
5
  export function enumColumnMetadataDefinition($) {
9
- return (parent) => {
6
+ return (parent, captured = {}) => {
10
7
  $.sectionStack.push("enumColumnMetadataDefinition");
11
- $.CONSUME(LCurly); // "{"
12
- // values = [...] is a required field here
13
- let hasValues = false;
14
- let hasDescription = false;
15
- let hasSensitive = false;
16
8
 
17
- $.MANY(() => {
18
- $.AT_LEAST_ONE1(() => $.CONSUME1(NewlineToken)); // Require at least one new line
19
- $.OR([
20
- {
21
- GATE: () => !hasValues,
22
- ALT: () => {
23
- hasValues = true;
24
- $.SUBRULE($.valuesFieldAttribute);
25
- },
26
- },
27
- {
28
- GATE: () => !hasDescription,
29
- ALT: () => $.SUBRULE($.sensitiveFieldAttribute),
30
- },
9
+ $.SUBRULE($.schemaParseAttributeFn, {
10
+ ARGS: [
11
+ parent,
31
12
  {
32
- GATE: () => !hasSensitive,
33
- ALT: () => $.SUBRULE($.descriptionFieldAttribute),
13
+ "description?": "[string]",
14
+ values: ["[string]"],
15
+ "sensitive?": "[boolean]",
16
+ "visibility?": ["public", "private", "internal"],
34
17
  },
35
- ]);
18
+ captured,
19
+ ],
36
20
  });
37
21
 
38
- if (!hasValues) {
39
- $.addMissingError(parent, "{} is missing the values attribute");
22
+ // you can only provide a visibility attribute if you're in a table definition
23
+ const inTable = $.sectionStack.includes("tableDeclaration");
24
+ if (!inTable && "visibility" in captured) {
25
+ $.addInvalidValueError(
26
+ parent,
27
+ 'The "visibility" attribute is only valid within a table schema definition.',
28
+ );
40
29
  }
41
30
 
42
- $.AT_LEAST_ONE2(() => $.CONSUME2(NewlineToken)); // Require at least one new line after
43
- $.CONSUME(RCurly); // "}"
44
31
  $.sectionStack.pop();
45
32
  };
46
33
  }
@@ -3,9 +3,12 @@ import { describe, it } from "mocha";
3
3
  import { lexDocument } from "../../lexer/lexer.js";
4
4
  import { parser } from "../test_parser.js";
5
5
 
6
- function parse(inputText) {
6
+ function parse(inputText, context = null) {
7
7
  const lexResult = lexDocument(inputText);
8
8
  parser.input = lexResult.tokens;
9
+ if (context) {
10
+ parser.sectionStack.push(context);
11
+ }
9
12
  parser.enumColumnMetadataDefinition();
10
13
  return parser;
11
14
  }
@@ -27,6 +30,25 @@ describe("enumColumnMetadataDefinition", () => {
27
30
  expect(parser.errors).to.be.empty;
28
31
  });
29
32
 
33
+ it("enumColumnMetadataDefinition accepts a visibility value when in a table declaration", () => {
34
+ const parser = parse(
35
+ `{
36
+ values = ["active", "inactive", "unknown"]
37
+ visibility = "private"
38
+ }`,
39
+ "tableDeclaration",
40
+ );
41
+ expect(parser.errors).to.be.empty;
42
+ });
43
+
44
+ it("enumColumnMetadataDefinition rejects a visibility value when not in a table declaration", () => {
45
+ const parser = parse(`{
46
+ values = ["active", "inactive", "unknown"]
47
+ visibility = "private"
48
+ }`);
49
+ expect(parser.errors).to.not.be.empty;
50
+ });
51
+
30
52
  it("enumColumnMetadataDefinition accepts a description value", () => {
31
53
  const parser = parse(`{
32
54
  values = ["active", "inactive", "unknown"]
@@ -7,20 +7,57 @@ import { NewlineToken } from "../../lexer/tokens.js";
7
7
  export function objectColumnMetadataDefinition($) {
8
8
  return () => {
9
9
  $.sectionStack.push("objectColumnMetadataDefinition");
10
+
11
+ let hasSensitiveField = false;
12
+ let hasVisibilityField = false;
13
+ let hasDescriptionField = false;
14
+ let hasSchemaClause = false;
15
+
10
16
  $.CONSUME(LCurly); // "{"
11
17
  $.MANY(() => {
12
18
  $.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // Require at least one new line after {
13
19
  $.OR([
14
- { ALT: () => $.SUBRULE($.sensitiveFieldAttribute) },
15
- { ALT: () => $.SUBRULE($.descriptionFieldAttribute) },
16
20
  {
17
- ALT: () =>
18
- $.SUBRULE($.schemaClause, { ARGS: [{ include_file: false }] }),
21
+ GATE: () => !hasSensitiveField, // Only allow the sensitive attribute if it hasn't been used yet
22
+ ALT: () => {
23
+ $.SUBRULE($.sensitiveFieldAttribute);
24
+ hasSensitiveField = true;
25
+ },
26
+ },
27
+ {
28
+ GATE: () => !hasDescriptionField, // Only allow the description attribute if it hasn't been used yet
29
+ ALT: () => {
30
+ $.SUBRULE($.descriptionFieldAttribute);
31
+ hasDescriptionField = true;
32
+ },
33
+ },
34
+ {
35
+ GATE: () => !hasVisibilityField, // Only allow the visibility attribute if it hasn't been used yet
36
+ ALT: () => {
37
+ $.SUBRULE($.visibilityFieldAttribute);
38
+ hasVisibilityField = true;
39
+ },
40
+ },
41
+ {
42
+ GATE: () => !hasSchemaClause, // Only allow the schema clause if it hasn't been used yet
43
+ ALT: () => {
44
+ $.SUBRULE($.schemaClause, { ARGS: [{ include_file: false }] });
45
+ hasSchemaClause = true;
46
+ },
19
47
  },
20
48
  ]);
21
49
  });
22
50
  $.AT_LEAST_ONE1(() => $.CONSUME1(NewlineToken)); // Require at least one new line after {
23
51
  $.CONSUME(RCurly); // "}"
52
+
53
+ const inTable = $.sectionStack.includes("tableDeclaration");
54
+ if (hasVisibilityField && !inTable) {
55
+ $.addInvalidValueError(
56
+ null,
57
+ 'The "visibility" attribute is only valid within a table schema definition.',
58
+ );
59
+ }
60
+
24
61
  $.sectionStack.pop();
25
62
  };
26
63
  }
@@ -69,6 +69,24 @@ describe("table_parser", () => {
69
69
  expect(parser.errors).to.be.empty;
70
70
  });
71
71
 
72
+ it("should should accept a private and internal visibility on input", () => {
73
+ const parser = xanoscriptParser(`table foo {
74
+ auth = false
75
+
76
+ schema {
77
+ int id {
78
+ visibility = "private"
79
+ }
80
+ text cd {
81
+ visibility = "internal"
82
+ }
83
+ }
84
+
85
+ autocomplete = [{ name: "id" }, { name: "cd" }]
86
+ }`);
87
+ expect(parser.errors).to.be.empty;
88
+ });
89
+
72
90
  // autocomplete = [{ name: "id" }, { name: "cd" }];
73
91
  it("should requires a new line between statements", () => {
74
92
  const parser = xanoscriptParser(`table foo {
@@ -3,11 +3,14 @@ table array_columns {
3
3
 
4
4
  schema {
5
5
  int id
6
- timestamp created_at?=now
6
+ timestamp created_at?=now {
7
+ visibility = "internal"
8
+ }
7
9
  text[] many_text? filters=trim
8
10
  email[] many_email_required_sensitive filters=trim|lower {
9
11
  description = "with a description"
10
12
  sensitive = true
13
+ visibility = "private"
11
14
  }
12
15
 
13
16
  enum[] many_enum_required_private {
@@ -131,7 +131,6 @@ describe("workflow_test_parser", () => {
131
131
  }
132
132
  }
133
133
  }`);
134
- console.log(parser.errors);
135
134
  expect(parser.errors).to.be.empty;
136
135
  });
137
136
  });