@dforge-core/metadata 0.0.22 → 0.0.23

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/CHANGELOG.md CHANGED
@@ -5,6 +5,140 @@ All notable changes to `@dforge-core/metadata` are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.0.23] — 2026-09-14
9
+
10
+ ### Added
11
+
12
+ - **`@dforge-core/metadata/dsl` — the action-DSL analyzer**, as a second entry point:
13
+ a lexer, a parser and the static rules for `logic/actions/*.dsl`. It was implemented
14
+ twice — once in the language server (token-based, module-aware, drawing squiggles) and
15
+ once in the module validator (regex-based, module-blind, gating a pack) — with only
16
+ three rules in common, so an editor hint and a pre-pack failure disagreed about the
17
+ same script.
18
+
19
+ The rules take what they know about the module through a small injected `DslContext`
20
+ (module code, the action, and the columns of the entity behind the record context)
21
+ rather than a loader, so the same rule serves a language server and a file-walking
22
+ validator. Every context field is optional and every rule that reads one fails open:
23
+ with an empty context the text-only rules still run and the rest stand down silently.
24
+ A false "unknown column" underlines working code and blocks a pack; a missed one costs
25
+ an install round trip.
26
+
27
+ `checkDsl(text, ctx)` returns `DslIssue[]` carrying both byte offsets and a 1-indexed
28
+ line/column, so a range-based host and a `file.dsl:12` reporter each get what they
29
+ need without re-deriving it. `parseDsl`, `tokenize` and the built-in catalog are
30
+ exported too, for completion, hover and go-to-definition. `sendEmail` in that
31
+ catalog is `sendEmail(to, subjectOrTemplate, dataOrBody?)` — three arguments,
32
+ the second choosing template or raw mode, as `ScriptContext.sendEmail` does;
33
+ the fourth argument it used to advertise never existed. `applyProfile` is
34
+ `applyProfile(json, profile)`, taking the inline `{ map, lines? }` object the
35
+ runtime demands — the profile code it used to advertise throws, profile-by-code
36
+ being a later slice. `ocrExtract` states the return type its mode decides: a
37
+ JSON string to `JSON.parse()` without `opts.mode`, a parsed object with it.
38
+ And `download` sets a URL on the action result rather than fetching anything
39
+ into storage, which is what it used to claim.
40
+
41
+ Deliberately **not** in the root barrel: the root export is registries and types that
42
+ the web app and the VS Code webview pull into a browser bundle, and importing it must
43
+ not drag a tokenizer in behind it. The package also declares `sideEffects: false` now.
44
+
45
+ The two existing implementations live in other repos; the duplication ends when
46
+ the language server and the module validator import this entry point, not when
47
+ it ships.
48
+
49
+ Recognising a block the way `ActionDslCompiler.ParseBlocks` recognises one:
50
+ five kinds including `schema:`, anchored at column 0, matched case-insensitively,
51
+ with a leading BOM stripped and `execute:` running to end-of-file. The anchor is
52
+ the load-bearing part — an indented `params:` is an object key inside a block
53
+ body, and treating it as a header fragmented the document and fabricated a
54
+ param list. A header that *is* indented is now reported (`dsl/indented-block-header`),
55
+ since the compiler reads it as body text and runs the block it meant to open
56
+ as empty.
57
+
58
+ `[field]` detection is the compiler's `(?<!\w)\[(\w+)\]`, character for
59
+ character rather than by previous token: the lookbehind does not skip
60
+ whitespace, so a read that opens a line is a read however the line above
61
+ ended. Column and param names match case-insensitively, as `EntityColumnLookup`
62
+ and `ValidateParamUsages` do.
63
+
64
+ A rule is scoped to the block whose compiler actually imposes it. Single-hop
65
+ ref navigation is the DSL's `RxRefNav`, so `canExecute:` — handed to the
66
+ formula engine verbatim, where a chain has no depth limit — is exempt. The
67
+ batch-mode "no current record" rule is `execute:` alone, since
68
+ `onBeforeStart:` is compiled per-record in every mode. And `records[0][field]`
69
+ is one indexed read, matching `RxRecordsFieldRead`, which runs ahead of the
70
+ generic `[field]` rewrite: its column is still checked, but it is not a
71
+ current-record read.
72
+
73
+ What the single-hop rule refuses is a second **bracketed** hop, not a second
74
+ hop: the compiler rewrites the first one and then throws on a surviving `.[`,
75
+ so `[vehicle].plate.length` is one hop followed by ordinary JavaScript on the
76
+ value it returned. Batch mode applies no `[field]` rewrite at all, so
77
+ `var ids = [id]` is the array literal it looks like — the same local-name
78
+ disambiguation the column rule uses. And a param declared inline with its
79
+ header (`params: qty: number required`) is declared: `^params:\s*` swallows
80
+ the newline, making the header's own line the block's first.
81
+
82
+ The column check covers every way the compiler reaches a column, not only the
83
+ bare `[field]`: `old[field]` and a batch loop variable's `x[field]` are fed
84
+ into the same `fieldRefs` set server-side, so a typo in either is the same
85
+ compile error. A template literal is tokenized as prose and holes rather than
86
+ as one opaque token, so `${…}` is analyzed and the text around it is not.
87
+ A param may be named after a block (`execute: number` is a legal `RxParamDecl`
88
+ line) without being read as a header — though its type has to share its line,
89
+ as that single-line regex requires. Nothing inside `params:` is a field read
90
+ at all: `RxKeyValue` takes a bracketed option list, so `options=[cash]` is an
91
+ option list.
92
+
93
+ Case is folded on both sides of the column lookup — the names once per check
94
+ when the lookup can enumerate them, which `Set` and `Map` both can, and the
95
+ reference on the way in — so a mixed-case column name matches whichever case
96
+ the script spells it in. `ColumnLookup` gained an optional `keys()` for that.
97
+
98
+ `dsl/top-level-return` reads function scope rather than brace depth, and is
99
+ an error rather than a warning: both blocks compile to a bare script, and
100
+ Esprima's `ParseScript` refuses a return outside a function wherever it sits,
101
+ so `if (x) { return }` and `if (x) return` fail exactly as a return on its own
102
+ line does. `return` is reserved, so it is a statement unless it is being used
103
+ as a name — a property, an object key, or a method shorthand, told from
104
+ `return (expr)` by the body brace after the parameter list. `function`, function expressions, arrows and method shorthand all
105
+ open a scope where it is legal; an `if` or `for` body does not.
106
+
107
+ A regex literal is one token, so `/return/` and `/[^0-9]/` carry no statement
108
+ and no field read. The `/` is read as division after a value and as a regex
109
+ after an operator or a keyword. `)` and `}` each depend on what they closed —
110
+ a control condition and a statement block leave a statement position, so
111
+ `if (x) /re/.test(s)` and `if (x) {}` then `/re/` are regexes, while
112
+ `(a + b) / 2` and `{ a: 1 }` then `/` divide — and a template chunk on whether
113
+ it stopped at `${` or ran to the backtick.
114
+
115
+ A keyword literal is never a column: in batch `execute:`, which applies no
116
+ `[field]` rewrite at all, `[true]`, `[false]` and `[NULL]` are the array
117
+ elements they look like. In every other block the compiler does rewrite them,
118
+ so the check stands there.
119
+
120
+ `locals` covers function parameters as well as `var`/`let`/`const` and `for`
121
+ heads — declarations, expressions, arrows and method shorthand alike — since a
122
+ parameter binds a name the same way, and `function wrap(value) { return [value] }`
123
+ holds an array literal rather than a field read.
124
+
125
+ Local-name disambiguation is confined to the bare bracket that needs it.
126
+ `var ids = [orderId]` is ambiguous with a field read and only the name tells
127
+ them apart, but `r[stauts]` and `records[0][stauts]` name a column outright —
128
+ the compiler rewrites each to `.get('stauts')` with the literal name — so a
129
+ same-named local no longer hides the typo.
130
+
131
+ Checked against every action in the module tree — 70 actions, each against its
132
+ own trait-expanded entity: no false positives.
133
+
134
+ ## [0.0.22] — 2026-08-28
135
+
136
+ ### Changed
137
+
138
+ - `entity.schema.json` — per-constraint validation: `expression` is check-only,
139
+ and a `unique` constraint needs its key. Shipped with the DB-enforced stock
140
+ availability work (`54622c5c`).
141
+
8
142
  ## [0.0.21] — 2026-08-24
9
143
 
10
144
  ### Changed
package/README.md CHANGED
@@ -32,6 +32,7 @@ pnpm add @dforge-core/metadata
32
32
  | **Scalar enums** | `FieldTypeCd`, `BaseDatatypeCd`, `ColumnTypeCd`, `FlagCd`, `AlignType` |
33
33
  | **Module-file types** | `ManifestDef`, `EntityDef`, `DataViewsFile`, `ReportsFile`, `MenusFile`, `FoldersFile`, `RolesFile`, `SettingsFile`, `JobsFile`, `TriggersFile`, `WebhooksFile`, `PrintTemplatesFile`, `SeedDataFile`, `ActionsFile`, … |
34
34
  | **Filter expression** | `Filter`, `FilterCondition`, `FilterGroup`, `SortClause` |
35
+ | **DSL analyzer** | `./dsl` — `checkDsl`, `parseDsl`, `tokenize`, `BUILTINS` (see [Action DSL](#action-dsl)) |
35
36
  | **JSON schemas** | `./schemas/<name>.schema.json` (see [Schemas](#json-schemas)) |
36
37
 
37
38
  ## Registries & derivation
@@ -94,6 +95,50 @@ const entity: EntityDef = {
94
95
  > returned by the API (`metadata.getModel`) is a different contract — see
95
96
  > `@dforge/sdk`'s `Model*` types, which reuse the scalar enums from this package.
96
97
 
98
+ ## Action DSL
99
+
100
+ `@dforge-core/metadata/dsl` is a separate entry point holding the lexer, parser
101
+ and static rules for an action body (`logic/actions/*.dsl`). One implementation
102
+ serves both hosts that check these files: the language server, which turns
103
+ issues into squiggles, and the module validator, which gates a pack.
104
+
105
+ ```ts
106
+ import { checkDsl } from "@dforge-core/metadata/dsl";
107
+
108
+ const issues = checkDsl(source, {
109
+ moduleCode: "wms",
110
+ action: { code: "transfer_stock", executionMode: "single" },
111
+ currentEntity: { qualified: "wms.stock_movement", columns: entityColumns },
112
+ });
113
+ // → [{ rule: "dsl/unknown-column", severity: "error", message, start, end, line, column }]
114
+ ```
115
+
116
+ What a rule knows about the module arrives through that context — a module code,
117
+ the action, and a column lookup (any `Set` or `Map` will do) — rather than
118
+ through a module loader, which is what lets the same rule run in an editor and
119
+ in a file walker. **Every field is optional and every rule that reads one fails
120
+ open**: with an empty context the text-only rules still run and the rest stand
121
+ down silently. A false "unknown column" underlines working code and blocks a
122
+ pack; a missed one costs an install round trip.
123
+
124
+ One obligation comes with `currentEntity`: `columns` must be **trait-expanded**,
125
+ built-in traits and the module's own `traits.json` alike, because the identity
126
+ PK and `[status]` live in traits rather than in `fields`. An unexpanded set
127
+ turns the rule into a wall of false errors. When the entity can't be resolved —
128
+ a bridge module acting on another module's entity — pass `null` rather than the
129
+ fragment you do have.
130
+
131
+ Issues carry both byte offsets and a 1-indexed `line`/`column`, so a
132
+ range-based host and a `file.dsl:12` reporter each get what they need.
133
+
134
+ The blocks, the `[field]` lookbehind and the case rules follow
135
+ `ActionDslCompiler` rather than the prose docs: five block kinds including
136
+ `schema:`, headers anchored at column 0 (an indented one is body text, and is
137
+ reported), and `execute:` running to end-of-file.
138
+
139
+ It is not re-exported from the package root: importing `@dforge-core/metadata`
140
+ in a browser bundle must not pull a tokenizer in behind it.
141
+
97
142
  ## JSON schemas
98
143
 
99
144
  The 14 canonical schemas are shipped alongside the types and stay byte-identical
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Execution mode. 'single' (default) and 'each' run the DSL per selected record;
3
+ * 'batch' runs once with all records exposed as `__records`.
4
+ */
5
+ type ActionExecutionMode = "single" | "each" | "batch";
6
+ /** A single action declaration (value in the `ui/actions.json` map). */
7
+ interface ActionDef {
8
+ /** Human-readable description. */
9
+ description: string;
10
+ /** Button label. */
11
+ label: string;
12
+ /** Bootstrap icon class (e.g. 'bi-telephone-check'). */
13
+ icon: string;
14
+ /** Entity code this action targets. */
15
+ entityCode: string;
16
+ /** Per-record vs. batch execution. */
17
+ executionMode: ActionExecutionMode;
18
+ /** DSL script name (file under `logic/actions/<script>.dsl`). */
19
+ script: string;
20
+ /**
21
+ * Run the whole selection in one transaction, so a failure on any record
22
+ * rolls back the records already processed. **Defaults to `true`** when
23
+ * omitted (`ActionDef.IsTransacted` in the installer is a non-nullable
24
+ * `bool` initialised to `true`), so omit it only when you want atomicity.
25
+ *
26
+ * `false` opens no transaction, and what follows a failure depends on
27
+ * {@link ActionDef.executionMode}: `single` / `each` report it and continue
28
+ * with the next record, so use that for independent per-record work that
29
+ * should process as many records as pass; `batch` is a single script
30
+ * invocation with no loop to continue, so the run simply ends with its
31
+ * earlier writes committed.
32
+ *
33
+ * On a run queued to the background the **rollback** guarantee is lost
34
+ * outright — the worker opens no transaction, so nothing is ever undone. What
35
+ * remains of the flag there depends on the mode: the worker's `single`/`each`
36
+ * loop still reads it for control flow (stop after the first failed record vs.
37
+ * continue), while its `batch` path never reads it at all — one invocation,
38
+ * which ends on failure under either value.
39
+ */
40
+ isTransacted?: boolean;
41
+ /** Display order. */
42
+ orderNum?: number;
43
+ /**
44
+ * @deprecated Not read by the installer — use {@link ActionDef.isAsync}.
45
+ * The module manifest model binds `isAsync` only, so this key is silently
46
+ * ignored and the action installs as synchronous.
47
+ */
48
+ async?: boolean;
49
+ /**
50
+ * Permit background execution. It does not force it: `action.execute`
51
+ * carries its own `async` argument and the server branches on that, so an
52
+ * action with parameters is offered to the user as both "Run" (inline) and
53
+ * "Run in Background". A parameterless `isAsync` action is queued directly.
54
+ */
55
+ isAsync?: boolean;
56
+ }
57
+ /** A `ui/actions.json` file: action code → definition. */
58
+ type ActionsFile = Record<string, ActionDef>;
59
+
60
+ export type { ActionExecutionMode as A, ActionDef as a, ActionsFile as b };
@@ -0,0 +1,223 @@
1
+ import { A as ActionExecutionMode } from '../actions-DvYTrCbP.js';
2
+
3
+ type TokenKind = "ident" | "number" | "string" | "template" | "regex" | "punct" | "comment";
4
+ interface Token {
5
+ kind: TokenKind;
6
+ /** Source text, verbatim — quotes included for strings. */
7
+ text: string;
8
+ start: number;
9
+ end: number;
10
+ line: number;
11
+ character: number;
12
+ /** True when a line break sits between this token and the previous one. */
13
+ startsLine: boolean;
14
+ }
15
+ declare function tokenize(text: string): Token[];
16
+ /** Strip the surrounding quotes from a string token's text. */
17
+ declare function stringValue(token: Token): string;
18
+
19
+ type BlockKind = "params" | "canExecute" | "schema" | "onBeforeStart" | "execute";
20
+ /** In the order `ActionDslCompiler.ParseBlocks` expects them in a file. */
21
+ declare const BLOCK_KINDS: BlockKind[];
22
+ interface Span {
23
+ start: number;
24
+ end: number;
25
+ }
26
+ interface DslBlock {
27
+ kind: BlockKind;
28
+ label: Span;
29
+ /** Span of the block body — label end to the next label (or EOF). */
30
+ body: Span;
31
+ }
32
+ interface DslParam {
33
+ name: string;
34
+ nameSpan: Span;
35
+ /** fieldTypeCd, or the referenced entity code when `isRef`. */
36
+ type: string;
37
+ typeSpan: Span;
38
+ isRef: boolean;
39
+ required: boolean;
40
+ label?: string;
41
+ }
42
+ /**
43
+ * A `[field]` read. `navigation` holds the property hops that follow it, each
44
+ * remembering whether it was written `.[target]` or `.target`: the compiler
45
+ * rewrites the FIRST hop either way, and then only a surviving `.[` is the
46
+ * multi-hop compile error. A dotted tail is plain JavaScript on the value the
47
+ * hop returned, so `[customer].[code]`, `[a].b.c` and `[plate].length` all
48
+ * compile.
49
+ */
50
+ interface FieldRef {
51
+ name: string;
52
+ span: Span;
53
+ /** Span including the brackets. */
54
+ outerSpan: Span;
55
+ navigation: Array<{
56
+ name: string;
57
+ span: Span;
58
+ bracketed: boolean;
59
+ }>;
60
+ block: BlockKind | null;
61
+ /**
62
+ * False when the read is bound to a numbered record rather than the current
63
+ * one — `records[0][status]`, which `RxRecordsFieldRead` rewrites ahead of
64
+ * the generic `[field]` pass. The name is still a column of the entity, so
65
+ * the column rules apply; the batch-mode rule does not.
66
+ */
67
+ isCurrentRecord: boolean;
68
+ /** Index of the token that opens this read — the `[`. */
69
+ startIndex: number;
70
+ /**
71
+ * Index of the last token this read consumes, navigation included. A hop is
72
+ * 2 tokens as `.prop` but 4 as `.[prop]`, so callers that need the token
73
+ * after the read cannot compute it from `navigation.length`.
74
+ */
75
+ endIndex: number;
76
+ }
77
+ /** `params[x]` / `old[x]` — subscript reads off a DSL global. */
78
+ interface GlobalRef {
79
+ global: "params" | "old" | "records";
80
+ property: string;
81
+ span: Span;
82
+ block: BlockKind | null;
83
+ }
84
+ interface CallRef {
85
+ name: string;
86
+ nameSpan: Span;
87
+ /** First argument when it is a string literal — the entity code, usually. */
88
+ firstStringArg?: {
89
+ value: string;
90
+ span: Span;
91
+ };
92
+ block: BlockKind | null;
93
+ }
94
+ interface DslDocument {
95
+ blocks: DslBlock[];
96
+ params: DslParam[];
97
+ fieldRefs: FieldRef[];
98
+ globalRefs: GlobalRef[];
99
+ calls: CallRef[];
100
+ tokens: Token[];
101
+ /**
102
+ * Every mention of `records`, subscript (`records[x]`) or bare
103
+ * (`for x in records`). `globalRefs` only holds the subscript form, so a
104
+ * rule that needs to point at the batch loop has nothing to anchor to.
105
+ */
106
+ recordsRefs: Span[];
107
+ /** Names bound by `var`/`let`/`const` or a `for` head, plus declared params. */
108
+ locals: Set<string>;
109
+ /**
110
+ * Loop variables bound by `for x in records {` — `RxForLoop` and nothing
111
+ * else, so an ordinary `for (var x of xs)` is not one. Their `x[field]`
112
+ * subscripts read the entity's columns, which is why they come back as
113
+ * field reads.
114
+ */
115
+ recordsLoopVars: Set<string>;
116
+ }
117
+ declare function parseDsl(text: string): DslDocument;
118
+
119
+ interface Builtin {
120
+ name: string;
121
+ signature: string;
122
+ returns: string;
123
+ doc: string;
124
+ /**
125
+ * Index of the argument that names an entity, when the built-in takes one.
126
+ * Drives entity-code completion and the qualify-your-codes diagnostic.
127
+ */
128
+ entityArg?: number;
129
+ }
130
+ declare const BUILTINS: Builtin[];
131
+ declare const BUILTIN_BY_NAME: Map<string, Builtin>;
132
+ /** Bare identifiers the runtime injects — no parentheses. */
133
+ declare const BUILTIN_VALUES: Record<string, string>;
134
+
135
+ type DslSeverity = "error" | "warning" | "info";
136
+ /** One reported problem. `rule` is stable — hosts filter and suppress on it. */
137
+ interface DslIssue {
138
+ /** Stable rule id, namespaced: `dsl/unknown-column`. */
139
+ rule: string;
140
+ severity: DslSeverity;
141
+ message: string;
142
+ /** Byte offsets into the source, for range-based hosts. */
143
+ start: number;
144
+ end: number;
145
+ /** 1-indexed position of `start`, for line-based reporters. */
146
+ line: number;
147
+ column: number;
148
+ }
149
+ /**
150
+ * Column membership test. Structurally satisfied by both `Set<string>` and
151
+ * `Map<string, unknown>`, so a host that already holds a column map passes it
152
+ * straight in rather than copying it into a set on every check.
153
+ */
154
+ interface ColumnLookup {
155
+ has(name: string): boolean;
156
+ readonly size: number;
157
+ /**
158
+ * The column names, if the lookup can enumerate them — `Set` and `Map` both
159
+ * can. Matching is case-insensitive at the other end (`EntityColumnLookup`
160
+ * builds an OrdinalIgnoreCase set) and a mixed-case column name is legal, so
161
+ * the checker folds case itself when it can read the keys.
162
+ *
163
+ * Without it, a lookup that is not already case-insensitive will report a
164
+ * spelling the server accepts. A host that cannot expose keys should make
165
+ * `has` case-insensitive instead.
166
+ */
167
+ keys?(): Iterable<string>;
168
+ }
169
+ /** The resolved entity a script's record context refers to. */
170
+ interface EntityShape {
171
+ /** Module-qualified, e.g. `fin.invoice` — used in messages. */
172
+ qualified: string;
173
+ /**
174
+ * Every declared name on the entity, **trait-expanded** — built-in traits
175
+ * and the module's own `traits.json` alike — and including the virtual
176
+ * columns (reference, set, formula), exactly as `EntityColumnLookup` builds
177
+ * the compiler's set.
178
+ *
179
+ * Handing over an unexpanded set is the one mistake that turns this rule
180
+ * into a wall of false errors: the identity PK and `[status]` live in
181
+ * traits, not in `fields`. When in doubt pass `currentEntity: null` — a
182
+ * skipped check costs an install round trip, a wrong one blocks a pack.
183
+ */
184
+ columns: ColumnLookup;
185
+ }
186
+ /**
187
+ * What the checker knows about the script's surroundings. Every field is
188
+ * optional and every rule that reads one FAILS OPEN: with an empty context the
189
+ * text-only rules still run and the rest stand down silently.
190
+ *
191
+ * That is deliberate. A false "unknown column" on a correct script is far more
192
+ * damaging than a missed one — an editor would underline working code, and a
193
+ * validator would block a pack — so a rule that cannot resolve what it needs
194
+ * reports nothing.
195
+ */
196
+ interface DslContext {
197
+ /** Owning module code, for the qualify-your-entity-codes message. */
198
+ moduleCode?: string;
199
+ /** The action this script implements, from `ui/actions.json`. */
200
+ action?: {
201
+ code: string;
202
+ executionMode?: ActionExecutionMode;
203
+ };
204
+ /**
205
+ * Entity behind the record context (`[field]`). `null` or absent when it
206
+ * can't be resolved — a bridge module acting on another module's entity —
207
+ * and then every column rule stands down. A partial set is worse than
208
+ * none: pass `null`, not the fragment. See `EntityShape.columns`.
209
+ */
210
+ currentEntity?: EntityShape | null;
211
+ }
212
+
213
+ /**
214
+ * Check a DSL body. Returns [] for a clean script.
215
+ *
216
+ * Issues come back in source-feature order (field reads, params, execution
217
+ * mode, block spelling, built-ins, top-level returns, inline assignments)
218
+ * rather than sorted by position — a host that wants them in file order can
219
+ * sort on `start`.
220
+ */
221
+ declare function checkDsl(text: string, ctx?: DslContext): DslIssue[];
222
+
223
+ export { BLOCK_KINDS, BUILTINS, BUILTIN_BY_NAME, BUILTIN_VALUES, type BlockKind, type Builtin, type CallRef, type ColumnLookup, type DslBlock, type DslContext, type DslDocument, type DslIssue, type DslParam, type DslSeverity, type EntityShape, type FieldRef, type GlobalRef, type Span, type Token, type TokenKind, checkDsl, parseDsl, stringValue, tokenize };