@uwdata/mosaic-spec 0.9.0 → 0.11.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.
@@ -12,13 +12,13 @@ export class ASTNode {
12
12
  * @param {import('../ast-to-dom.js').InstantiateContext} ctx The instantiation context.
13
13
  * @returns {*} The instantiated value of this node.
14
14
  */
15
- instantiate(ctx: import('../ast-to-dom.js').InstantiateContext): any;
15
+ instantiate(ctx: import("../ast-to-dom.js").InstantiateContext): any;
16
16
  /**
17
17
  * Generate ESM code for this AST node.
18
18
  * @param {import('../ast-to-esm.js').CodegenContext} ctx The code generator context.
19
19
  * @returns {string|void} The generated ESM code for the node.
20
20
  */
21
- codegen(ctx: import('../ast-to-esm.js').CodegenContext): string | void;
21
+ codegen(ctx: import("../ast-to-esm.js").CodegenContext): string | void;
22
22
  /**
23
23
  * @returns {*} This AST node in JSON specification format.
24
24
  */
@@ -5,7 +5,7 @@
5
5
  * @param {import('../parse-spec.js').ParseContext} ctx The parser context.
6
6
  * @returns {DataNode} a parsed data definition AST node
7
7
  */
8
- export function parseData(name: string, spec: import('../spec/Data.js').DataDefinition, ctx: import('../parse-spec.js').ParseContext): DataNode;
8
+ export function parseData(name: string, spec: import("../spec/Data.js").DataDefinition, ctx: import("../parse-spec.js").ParseContext): DataNode;
9
9
  export const TABLE_DATA: "table";
10
10
  export const PARQUET_DATA: "parquet";
11
11
  export const CSV_DATA: "csv";
@@ -21,13 +21,13 @@ export class QueryDataNode extends DataNode {
21
21
  * @param {import('../ast-to-dom.js').InstantiateContext} ctx The instantiation context.
22
22
  * @returns {string|void} The instantiated query.
23
23
  */
24
- instantiateQuery(ctx: import('../ast-to-dom.js').InstantiateContext): string | void;
24
+ instantiateQuery(ctx: import("../ast-to-dom.js").InstantiateContext): string | void;
25
25
  /**
26
26
  * Code generate a table creation query.
27
27
  * @param {import('../ast-to-esm.js').CodegenContext} ctx The code generator context.
28
28
  * @returns {string|void} The generated query code.
29
29
  */
30
- codegenQuery(ctx: import('../ast-to-esm.js').CodegenContext): string | void;
30
+ codegenQuery(ctx: import("../ast-to-esm.js").CodegenContext): string | void;
31
31
  }
32
32
  export class TableDataNode extends QueryDataNode {
33
33
  constructor(name: any, query: any, options: any);
@@ -1,9 +1,8 @@
1
- export function parseParam(spec: any, ctx: any): SelectionNode | ParamNode;
1
+ export function parseParam(spec: any, ctx: any): import("./SelectionNode.js").SelectionNode | ParamNode;
2
2
  export class ParamNode extends ASTNode {
3
3
  value: any;
4
4
  date: any;
5
5
  instantiate(ctx: any): any;
6
6
  codegen(ctx: any): string;
7
7
  }
8
- import { SelectionNode } from './SelectionNode.js';
9
8
  import { ASTNode } from './ASTNode.js';
@@ -2,7 +2,7 @@ export class ParamRefNode extends ASTNode {
2
2
  constructor(name: any);
3
3
  name: any;
4
4
  instantiate(ctx: any): any;
5
- codegen(): string;
5
+ codegen(ctx: any): string;
6
6
  toJSON(): string;
7
7
  }
8
8
  import { ASTNode } from './ASTNode.js';
@@ -1,12 +1,24 @@
1
+ export function parseSelection(spec: any, ctx: any): SelectionNode;
1
2
  export class SelectionNode extends ASTNode {
2
- constructor(select: string, cross: any);
3
+ /**
4
+ * Create a Selection AST node.
5
+ * @param {string} select The selection type.
6
+ * @param {OptionsNode} options Selection options.
7
+ */
8
+ constructor(select?: string, options?: OptionsNode);
3
9
  select: string;
4
- cross: any;
10
+ options: OptionsNode;
5
11
  instantiate(ctx: any): any;
6
12
  codegen(ctx: any): string;
7
13
  toJSON(): {
8
14
  select: string;
9
- cross: any;
10
15
  };
11
16
  }
17
+ export class IncludeNode extends ASTNode {
18
+ constructor(refs: any);
19
+ refs: any;
20
+ instantiate(ctx: any): any;
21
+ codegen(ctx: any): string;
22
+ }
12
23
  import { ASTNode } from './ASTNode.js';
24
+ import { OptionsNode } from './OptionsNode.js';
@@ -1,4 +1,4 @@
1
- export function parseTransform(spec: any, ctx: any): TransformNode;
1
+ export function parseTransform(spec: any, ctx: any): BinTransformNode | TransformNode;
2
2
  export class TransformNode extends ASTNode {
3
3
  constructor(name: any, args: any, options: any);
4
4
  name: any;
@@ -10,4 +10,12 @@ export class TransformNode extends ASTNode {
10
10
  [x: number]: any;
11
11
  };
12
12
  }
13
+ export class BinTransformNode extends ASTNode {
14
+ constructor(name: any, arg: any, options: any);
15
+ name: any;
16
+ arg: any;
17
+ options: any;
18
+ instantiate(ctx: any): any;
19
+ codegen(ctx: any): string;
20
+ }
13
21
  import { ASTNode } from './ASTNode.js';
@@ -5,6 +5,7 @@ export const OPTIONS: "options";
5
5
  export const SELECTION: "selection";
6
6
  export const PARAMREF: "paramref";
7
7
  export const PARAM: "param";
8
+ export const INCLUDE: "include";
8
9
  export const SELECT: "select";
9
10
  export const VALUE: "value";
10
11
  export const CROSSFILTER: "crossfilter";
@@ -22,7 +22,7 @@ export { VSpaceNode } from "./ast/VSpaceNode.js";
22
22
  /**
23
23
  * A Mosaic declarative specification.
24
24
  */
25
- export type Spec = import('./spec/Spec.js').Spec;
25
+ export type Spec = import("./spec/Spec.js").Spec;
26
26
  export { astToDOM, InstantiateContext } from "./ast-to-dom.js";
27
27
  export { astToESM, CodegenContext } from "./ast-to-esm.js";
28
28
  export { DataNode, QueryDataNode, TableDataNode, FileDataNode, CSVDataNode, JSONDataNode, ParquetDataNode, SpatialDataNode, LiteralJSONDataNode } from "./ast/DataNode.js";
@@ -20,7 +20,7 @@
20
20
  * dataset definition AST nodes.
21
21
  * @returns {SpecNode} The top-level AST spec node.
22
22
  */
23
- export function parseSpec(spec: import('./spec/Spec.js').Spec, options?: {
23
+ export function parseSpec(spec: import("./spec/Spec.js").Spec, options?: {
24
24
  components?: Map<string, Function>;
25
25
  transforms?: Set<string>;
26
26
  inputs?: Set<string>;
@@ -59,15 +59,40 @@ export class ParseContext {
59
59
  plotDefaults: import("./ast/PlotAttributeNode.js").PlotAttributeNode[];
60
60
  parseComponent(spec: any): any;
61
61
  /**
62
- * Test if a value is param reference, if so, generate a paramter definition
63
- * as needed and return a new ParamRefNode. Otherwise, return a LiteralNode.
62
+ * Test if a value is a param reference, if so, generates a parameter
63
+ * definition as needed and returns a new ParamRefNode. Otherwise,
64
+ * returns a LiteralNode for the given value.
64
65
  * @param {*} value The value to test.
65
- * @param {() => ParamNode | SelectionNode} [makeNode] A Param of Selection AST
66
- * node constructor.
67
66
  * @returns {ParamRefNode|LiteralNode} An AST node for the input value.
68
67
  */
69
- maybeParam(value: any, makeNode?: () => ParamNode | SelectionNode): ParamRefNode | LiteralNode;
70
- maybeSelection(value: any): LiteralNode | ParamRefNode;
68
+ maybeParam(value: any): ParamRefNode | LiteralNode;
69
+ /**
70
+ * Test if a value is a param reference, if so, generates a selection
71
+ * definition as needed and returns a new ParamRefNode. Otherwise,
72
+ * returns a LiteralNode for the given value.
73
+ * @param {*} value The value to test.
74
+ * @returns {ParamRefNode|LiteralNode} An AST node for the input value.
75
+ */
76
+ maybeSelection(value: any): ParamRefNode | LiteralNode;
77
+ /**
78
+ * Create a parameter reference. Generates a parameter definition if needed
79
+ * and returns a new ParamRefNode.
80
+ * @param {string} name The parameter name.
81
+ * @param {() => ParamNode | SelectionNode} [makeNode] A Param or Selection AST
82
+ * node constructor.
83
+ * @returns {ParamRefNode|null} A node referring to the param or selection.
84
+ */
85
+ paramRef(name: string, makeNode?: () => ParamNode | SelectionNode): ParamRefNode | null;
86
+ /**
87
+ * Create a selection reference. Generates a selection definition if needed
88
+ * and returns a new ParamRefNode. Returns null if a non-selection parameter
89
+ * with the same name already exists and *strict* is true.
90
+ * @param {string} name The selection name.
91
+ * @param {boolean} [strict=false] Indicates if this method may return param
92
+ * references (false, default) or only selection references (true).
93
+ * @returns {ParamRefNode|null} A node referring to the param or selection.
94
+ */
95
+ selectionRef(name: string, strict?: boolean): ParamRefNode | null;
71
96
  error(message: any, data: any): void;
72
97
  }
73
98
  /**
@@ -80,7 +105,7 @@ export type PlotNames = {
80
105
  marks: Set<string>;
81
106
  };
82
107
  import { SpecNode } from './ast/SpecNode.js';
83
- import { ParamNode } from './ast/ParamNode.js';
84
- import { SelectionNode } from './ast/SelectionNode.js';
85
108
  import { ParamRefNode } from './ast/ParamRefNode.js';
86
109
  import { LiteralNode } from './ast/LiteralNode.js';
110
+ import { ParamNode } from './ast/ParamNode.js';
111
+ import { SelectionNode } from './ast/SelectionNode.js';
@@ -162,6 +162,11 @@ export interface Table {
162
162
  * A table grid widget.
163
163
  */
164
164
  input: 'table';
165
+ /**
166
+ * The output selection. A selection clause is added for each
167
+ * currently selected table row.
168
+ */
169
+ as?: ParamRef;
165
170
  /**
166
171
  * The name of a database table to use as a data source for this widget.
167
172
  */
@@ -46,6 +46,18 @@ export interface Selection {
46
46
  * but not oneself (default `false`, except for `crossfilter` selections).
47
47
  */
48
48
  cross?: boolean;
49
+ /**
50
+ * A flag for setting an initial empty selection state. If true, a selection
51
+ * with no clauses corresponds to an empty selection with no records. If
52
+ * false, a selection with no clauses selects all values.
53
+ */
54
+ empty?: boolean;
55
+ /**
56
+ * Upstream selections whose clauses should be included as part of this
57
+ * selection. Any clauses or activations published to the upstream
58
+ * selections will be relayed to this selection.
59
+ */
60
+ include?: ParamRef | ParamRef[];
49
61
  }
50
62
  /** A Param or Selection definition. */
51
63
  export type ParamDefinition = ParamValue | Param | ParamDate | Selection;
@@ -1328,6 +1328,12 @@ export interface PlotAttributes {
1328
1328
  * For continuous scales only.
1329
1329
  */
1330
1330
  rNice?: boolean | number | Interval | ParamRef;
1331
+ /**
1332
+ * A textual label to show on the axis or legend; if null, show no label. By
1333
+ * default the scale label is inferred from channel definitions, possibly with
1334
+ * an arrow (↑, →, ↓, or ←) to indicate the direction of increasing value.
1335
+ */
1336
+ rLabel?: string | null | ParamRef;
1331
1337
  /**
1332
1338
  * If true, shorthand for a transform suitable for percentages, mapping
1333
1339
  * proportions in [0, 1] to [0, 100].
@@ -31,20 +31,47 @@ type Arg2Opt = Arg | [Arg, Arg?];
31
31
  * second and third arguments are optional.
32
32
  */
33
33
  type Arg3Opt = Arg | [Arg, Arg?, Arg?];
34
- /** Bin transform options. */
35
- export interface BinOptions {
34
+ /** Binning interval names. */
35
+ export type BinInterval = 'date' | 'number' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year';
36
+ export interface Bin {
37
+ /**
38
+ * Bin a continuous variable into discrete intervals. The bin argument
39
+ * specifies a data column or expression to bin. Both numerical and
40
+ * temporal (date/time) values are supported.
41
+ */
42
+ bin: Arg | [Arg];
43
+ /**
44
+ * The interval bin unit to use, typically used to indicate a date/time
45
+ * unit for binning temporal values, such as `hour`, `day`, or `month`.
46
+ * If `date`, the extent of data values is used to automatically select
47
+ * an interval for temporal data. The value `number` enforces normal
48
+ * numerical binning, even over temporal data. If unspecified, defaults
49
+ * to `number` for numerical data and `date` for temporal data.
50
+ */
51
+ interval?: BinInterval;
52
+ /**
53
+ * The step size to use between bins. When binning numerical values (or
54
+ * interval type `number`), this setting specifies the numerical step size.
55
+ * For data/time intervals, this indicates the number of steps of that unit,
56
+ * such as hours, days, or years.
57
+ */
58
+ step?: number;
36
59
  /**
37
60
  * The target number of binning steps to use. To accommodate human-friendly
38
- * bin boundaries, the actual number of bins may diverge from this exact number.
61
+ * ("nice") bin boundaries, the actual number of bins may diverge from this
62
+ * exact value. This option is ignored when **step** is specified.
39
63
  */
40
64
  steps?: number;
41
65
  /**
42
- * The minimum allowed bin step size (default `0`).
43
- * For example, a setting of `1` will prevent step sizes less than 1.
66
+ * The minimum allowed bin step size (default `0`) when performing numerical
67
+ * binning. For example, a setting of `1` prevents step sizes less than 1.
68
+ * This option is ignored when **step** is specified.
44
69
  */
45
70
  minstep?: number;
46
71
  /**
47
- * A flag requesting "nice" human-friendly step sizes (default `true`).
72
+ * A flag (default `true`) requesting "nice" human-friendly end points and
73
+ * step sizes when performing numerical binning. When **step** is specified,
74
+ * this option affects the binning end points (e.g., origin) only.
48
75
  */
49
76
  nice?: true;
50
77
  /**
@@ -53,13 +80,6 @@ export interface BinOptions {
53
80
  */
54
81
  offset?: number;
55
82
  }
56
- export interface Bin {
57
- /**
58
- * Bin a continuous variable into discrete intervals. This transform accepts
59
- * a data column to bin over as well as an optional bin options object.
60
- */
61
- bin: Arg | [Arg] | [Arg, BinOptions];
62
- }
63
83
  export interface DateMonth {
64
84
  /**
65
85
  * Transform a Date value to a month boundary for cyclic comparison.
@@ -2,11 +2,11 @@ export function paramRef(value: any): any;
2
2
  export function paramStr(value: any): any;
3
3
  export function toParamRef(name: any): string;
4
4
  export function toArray(value: any): any[];
5
- export function isArray(value: any): boolean;
5
+ export function isArray(value: any): value is any[];
6
6
  export function isObject(value: any): boolean;
7
- export function isNumber(value: any): boolean;
7
+ export function isNumber(value: any): value is number;
8
8
  export function isNumberOrString(value: any): boolean;
9
- export function isString(value: any): boolean;
9
+ export function isString(value: any): value is string;
10
10
  export function isFunction(value: any): boolean;
11
11
  export function error(message: any, data: any): void;
12
12
  export function isoparse(string: any, fallback: any): any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uwdata/mosaic-spec",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Declarative specification of Mosaic-powered applications.",
5
5
  "keywords": [
6
6
  "mosaic",
@@ -10,7 +10,7 @@
10
10
  "specification"
11
11
  ],
12
12
  "license": "BSD-3-Clause",
13
- "author": "Jeffrey Heer (http://idl.cs.washington.edu)",
13
+ "author": "Jeffrey Heer (https://idl.uw.edu)",
14
14
  "type": "module",
15
15
  "main": "src/index.js",
16
16
  "module": "src/index.js",
@@ -28,15 +28,15 @@
28
28
  "types": "tsc -p tsconfig.json && npm run schema",
29
29
  "schema": "ts-json-schema-generator -f tsconfig.json -p src/spec/Spec.ts -t Spec --no-type-check --no-ref-encode --functions hide > dist/mosaic-schema.json",
30
30
  "pretest": "npm run prebuild && npm run types",
31
- "test": "mocha 'test/**/*-test.js' && tsc -p jsconfig.json",
31
+ "test": "vitest run && tsc -p jsconfig.json",
32
32
  "version": "cd ../.. && npm run docs:schema",
33
33
  "prepublishOnly": "npm run test && npm run lint && npm run build"
34
34
  },
35
35
  "dependencies": {
36
- "@uwdata/mosaic-core": "^0.9.0",
37
- "@uwdata/mosaic-sql": "^0.9.0",
38
- "@uwdata/vgplot": "^0.9.0",
39
- "ts-json-schema-generator": "^2.2.0"
36
+ "@uwdata/mosaic-core": "^0.11.0",
37
+ "@uwdata/mosaic-sql": "^0.11.0",
38
+ "@uwdata/vgplot": "^0.11.0",
39
+ "ts-json-schema-generator": "^2.3.0"
40
40
  },
41
- "gitHead": "89bb9b0dfa747aed691eaeba35379525a6764c61"
41
+ "gitHead": "861d616f39926a1d2aee83b59dbdd70b0b3caf12"
42
42
  }
@@ -1,19 +1,18 @@
1
1
  import { isArray, isObject, isoparse } from '../util.js';
2
2
  import { ASTNode } from './ASTNode.js';
3
+ import { parseSelection } from './SelectionNode.js';
3
4
  import { CROSSFILTER, INTERSECT, PARAM, SINGLE, UNION, VALUE } from '../constants.js';
4
- import { SelectionNode } from './SelectionNode.js';
5
5
 
6
6
  const paramTypes = new Set([VALUE, SINGLE, CROSSFILTER, INTERSECT, UNION]);
7
7
 
8
8
  export function parseParam(spec, ctx) {
9
9
  const param = isObject(spec) ? spec : { value: spec };
10
- const { select = VALUE, cross, date, value } = param;
10
+ const { select = VALUE, value, date } = param;
11
11
  if (!paramTypes.has(select)) {
12
12
  ctx.error(`Unrecognized param type: ${select}`, param);
13
13
  }
14
-
15
14
  if (select !== VALUE) {
16
- return new SelectionNode(select, cross);
15
+ return parseSelection(spec, ctx);
17
16
  } else if (isArray(value)) {
18
17
  return new ParamNode(value.map(v => ctx.maybeParam(v)));
19
18
  } else {
@@ -12,7 +12,7 @@ export class ParamRefNode extends ASTNode {
12
12
  return ctx.activeParams?.get(this.name);
13
13
  }
14
14
 
15
- codegen() {
15
+ codegen(ctx) { // eslint-disable-line no-unused-vars
16
16
  return toParamRef(this.name);
17
17
  }
18
18
 
@@ -1,26 +1,62 @@
1
1
  import { ASTNode } from './ASTNode.js';
2
- import { INTERSECT, SELECTION } from '../constants.js';
2
+ import { OptionsNode, parseOptions } from './OptionsNode.js';
3
+ import { INCLUDE, INTERSECT, SELECTION } from '../constants.js';
4
+ import { paramRef, toArray } from '../util.js';
5
+
6
+ export function parseSelection(spec, ctx) {
7
+ const { select, include, ...options } = spec;
8
+ const opt = parseOptions(options, ctx);
9
+ if (include) {
10
+ opt.options.include = new IncludeNode(
11
+ toArray(include).map(ref => ctx.selectionRef(paramRef(ref)))
12
+ );
13
+ }
14
+ return new SelectionNode(select, opt);
15
+ }
3
16
 
4
17
  export class SelectionNode extends ASTNode {
5
- constructor(select = INTERSECT, cross) {
18
+ /**
19
+ * Create a Selection AST node.
20
+ * @param {string} select The selection type.
21
+ * @param {OptionsNode} options Selection options.
22
+ */
23
+ constructor(select = INTERSECT, options = new OptionsNode({})) {
6
24
  super(SELECTION);
7
25
  this.select = select;
8
- this.cross = cross;
26
+ this.options = options;
27
+ }
28
+
29
+ instantiate(ctx) {
30
+ const { select, options } = this;
31
+ return ctx.api.Selection[select](options.instantiate(ctx));
32
+ }
33
+
34
+ codegen(ctx) {
35
+ const { select, options } = this;
36
+ return `${ctx.ns()}Selection.${select}(${options.codegen(ctx)})`;
37
+ }
38
+
39
+ toJSON() {
40
+ const { select, options } = this;
41
+ return { select, ...options.toJSON() };
42
+ }
43
+ }
44
+
45
+ export class IncludeNode extends ASTNode {
46
+ constructor(refs) {
47
+ super(INCLUDE);
48
+ this.refs = refs;
9
49
  }
10
50
 
11
51
  instantiate(ctx) {
12
- const { select, cross } = this;
13
- return ctx.api.Selection[select]({ cross });
52
+ return this.refs.map(ref => ref.instantiate(ctx));
14
53
  }
15
54
 
16
55
  codegen(ctx) {
17
- const { select, cross } = this;
18
- const arg = cross != null ? `{ cross: ${cross} }` : '';
19
- return `${ctx.ns()}Selection.${select}(${arg})`;
56
+ return `[${this.refs.map(ref => ref.codegen(ctx)).join(', ')}]`;
20
57
  }
21
58
 
22
59
  toJSON() {
23
- const { select, cross } = this;
24
- return { select, cross };
60
+ return this.refs.map(ref => ref.toJSON());
25
61
  }
26
62
  }
@@ -1,5 +1,6 @@
1
1
  import { ASTNode } from './ASTNode.js';
2
2
  import { TRANSFORM } from '../constants.js';
3
+ import { parseOptions } from './OptionsNode.js';
3
4
 
4
5
  function toArray(value) {
5
6
  return value == null ? [] : [value].flat();
@@ -17,16 +18,21 @@ export function parseTransform(spec, ctx) {
17
18
  return; // return undefined to signal no transform!
18
19
  }
19
20
 
20
- const args = name === 'count' || name == null ? [] : toArray(spec[name]);
21
- const options = {
22
- distinct: spec.distinct,
23
- orderby: toArray(spec.orderby).map(v => ctx.maybeParam(v)),
24
- partitionby: toArray(spec.partitionby).map(v => ctx.maybeParam(v)),
25
- rows: spec.rows ? ctx.maybeParam(spec.rows) : null,
26
- range: spec.range ? ctx.maybeParam(spec.range) : null
27
- };
28
-
29
- return new TransformNode(name, args, options);
21
+ if (name === 'bin') {
22
+ const { bin, ...options } = spec;
23
+ const [arg] = toArray(bin);
24
+ return new BinTransformNode(name, arg, parseOptions(options, ctx));
25
+ } else {
26
+ const args = name === 'count' && !spec[name] ? [] : toArray(spec[name]);
27
+ const options = {
28
+ distinct: spec.distinct,
29
+ orderby: toArray(spec.orderby).map(v => ctx.maybeParam(v)),
30
+ partitionby: toArray(spec.partitionby).map(v => ctx.maybeParam(v)),
31
+ rows: spec.rows ? ctx.maybeParam(spec.rows) : null,
32
+ range: spec.range ? ctx.maybeParam(spec.range) : null
33
+ };
34
+ return new TransformNode(name, args, options);
35
+ }
30
36
  }
31
37
 
32
38
  export class TransformNode extends ASTNode {
@@ -112,6 +118,34 @@ export class TransformNode extends ASTNode {
112
118
  }
113
119
  }
114
120
 
121
+ export class BinTransformNode extends ASTNode {
122
+ constructor(name, arg, options) {
123
+ super(TRANSFORM);
124
+ this.name = name;
125
+ this.arg = arg;
126
+ this.options = options;
127
+ }
128
+
129
+ instantiate(ctx) {
130
+ const { name, arg, options } = this;
131
+ return ctx.api[name](arg, options.instantiate(ctx));
132
+ }
133
+
134
+ codegen(ctx) {
135
+ const { name, arg, options } = this;
136
+ const opt = options.codegen(ctx);
137
+ return `${ctx.ns()}${name}(`
138
+ + JSON.stringify(arg)
139
+ + (opt ? `, ${opt}` : '')
140
+ + ')';
141
+ }
142
+
143
+ toJSON() {
144
+ const { name, arg, options } = this;
145
+ return { [name]: arg, ...options.toJSON() };
146
+ }
147
+ }
148
+
115
149
  function simplify(array) {
116
150
  return array.length === 0 ? '' : array.length === 1 ? array[0] : array;
117
151
  }
package/src/constants.js CHANGED
@@ -14,6 +14,7 @@ export const OPTIONS = 'options';
14
14
  export const SELECTION = 'selection';
15
15
  export const PARAMREF = 'paramref';
16
16
  export const PARAM = 'param';
17
+ export const INCLUDE = 'include';
17
18
 
18
19
  // param and selection types
19
20
  export const SELECT = 'select';
package/src/parse-spec.js CHANGED
@@ -68,7 +68,6 @@ export class ParseContext {
68
68
  }
69
69
 
70
70
  parse(spec) {
71
- // eslint-disable-next-line no-unused-vars
72
71
  const {
73
72
  meta,
74
73
  config,
@@ -113,29 +112,67 @@ export class ParseContext {
113
112
  }
114
113
 
115
114
  /**
116
- * Test if a value is param reference, if so, generate a paramter definition
117
- * as needed and return a new ParamRefNode. Otherwise, return a LiteralNode.
115
+ * Test if a value is a param reference, if so, generates a parameter
116
+ * definition as needed and returns a new ParamRefNode. Otherwise,
117
+ * returns a LiteralNode for the given value.
118
118
  * @param {*} value The value to test.
119
- * @param {() => ParamNode | SelectionNode} [makeNode] A Param of Selection AST
120
- * node constructor.
121
119
  * @returns {ParamRefNode|LiteralNode} An AST node for the input value.
122
120
  */
123
- maybeParam(value, makeNode = () => new ParamNode) {
124
- const { params } = this;
121
+ maybeParam(value) {
125
122
  const name = paramRef(value);
123
+ return name
124
+ ? this.paramRef(name)
125
+ : new LiteralNode(value);
126
+ }
126
127
 
127
- if (name) {
128
- if (!params.has(name)) {
129
- const p = makeNode();
130
- params.set(name, p);
131
- }
132
- return new ParamRefNode(name);
128
+ /**
129
+ * Test if a value is a param reference, if so, generates a selection
130
+ * definition as needed and returns a new ParamRefNode. Otherwise,
131
+ * returns a LiteralNode for the given value.
132
+ * @param {*} value The value to test.
133
+ * @returns {ParamRefNode|LiteralNode} An AST node for the input value.
134
+ */
135
+ maybeSelection(value) {
136
+ const name = paramRef(value);
137
+ return name
138
+ ? this.selectionRef(name)
139
+ : new LiteralNode(value);
140
+ }
141
+
142
+ /**
143
+ * Create a parameter reference. Generates a parameter definition if needed
144
+ * and returns a new ParamRefNode.
145
+ * @param {string} name The parameter name.
146
+ * @param {() => ParamNode | SelectionNode} [makeNode] A Param or Selection AST
147
+ * node constructor.
148
+ * @returns {ParamRefNode|null} A node referring to the param or selection.
149
+ */
150
+ paramRef(name, makeNode = () => new ParamNode) {
151
+ const { params } = this;
152
+ if (!name) return null;
153
+ let p = params.get(name);
154
+ if (!p) {
155
+ p = makeNode();
156
+ params.set(name, p);
133
157
  }
134
- return new LiteralNode(value);
158
+ return new ParamRefNode(name);
135
159
  }
136
160
 
137
- maybeSelection(value) {
138
- return this.maybeParam(value, () => new SelectionNode);
161
+ /**
162
+ * Create a selection reference. Generates a selection definition if needed
163
+ * and returns a new ParamRefNode. Returns null if a non-selection parameter
164
+ * with the same name already exists and *strict* is true.
165
+ * @param {string} name The selection name.
166
+ * @param {boolean} [strict=false] Indicates if this method may return param
167
+ * references (false, default) or only selection references (true).
168
+ * @returns {ParamRefNode|null} A node referring to the param or selection.
169
+ */
170
+ selectionRef(name, strict = false) {
171
+ const p = this.params.get(name);
172
+ if (strict && p && !(p instanceof SelectionNode)) {
173
+ return null;
174
+ }
175
+ return this.paramRef(name, () => new SelectionNode);
139
176
  }
140
177
 
141
178
  error(message, data) {
package/src/spec/Input.ts CHANGED
@@ -163,6 +163,11 @@ export interface Table {
163
163
  * A table grid widget.
164
164
  */
165
165
  input: 'table';
166
+ /**
167
+ * The output selection. A selection clause is added for each
168
+ * currently selected table row.
169
+ */
170
+ as?: ParamRef;
166
171
  /**
167
172
  * The name of a database table to use as a data source for this widget.
168
173
  */