@edgerules/node 0.0.0-alpha.202607061133 → 0.0.0-alpha.202607071338

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/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # @edgerules/node
2
+
3
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=rimvydasb_edgerules&metric=alert_status&token=98a392e505393c255f7de845e8b74a32911f2435)](https://sonarcloud.io/summary/new_code?id=rimvydasb_edgerules)
4
+ [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=rimvydasb_edgerules&metric=bugs&token=98a392e505393c255f7de845e8b74a32911f2435)](https://sonarcloud.io/summary/new_code?id=rimvydasb_edgerules)
5
+ [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=rimvydasb_edgerules&metric=code_smells&token=98a392e505393c255f7de845e8b74a32911f2435)](https://sonarcloud.io/summary/new_code?id=rimvydasb_edgerules)
6
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=rimvydasb_edgerules&metric=coverage&token=98a392e505393c255f7de845e8b74a32911f2435)](https://sonarcloud.io/summary/new_code?id=rimvydasb_edgerules)
7
+ [![Duplicated Lines](https://sonarcloud.io/api/project_badges/measure?project=rimvydasb_edgerules&metric=duplicated_lines_density&token=98a392e505393c255f7de845e8b74a32911f2435)](https://sonarcloud.io/summary/new_code?id=rimvydasb_edgerules)
8
+
9
+ EdgeRules decision service for **Node.js**. Ships two builds:
10
+
11
+ | Export | Description |
12
+ |---|---|
13
+ | `@edgerules/node` | Production build — execution only (`DecisionService`) |
14
+ | `@edgerules/node/mutable` | Development build — execution + CRUD (`MutableDecisionService`) |
15
+
16
+ ---
17
+
18
+ ## Installation
19
+
20
+ ```sh
21
+ npm install @edgerules/node @edgerules/portable
22
+ ```
23
+
24
+ > Requires Node.js 18 or later (ESM only).
25
+
26
+ ---
27
+
28
+ ## Production Usage
29
+
30
+ ### Load from an EdgeRules DSL string
31
+
32
+ ```typescript
33
+ import { DecisionService } from '@edgerules/node';
34
+
35
+ const service = new DecisionService(`{
36
+ applicant: {
37
+ age: <number, required: true>
38
+ income: <number, default: 0>
39
+ }
40
+ isEligible: applicant.age >= 18 and applicant.income >= 1000
41
+ }`);
42
+
43
+ // Execute the whole model — inputs are bound via a model-shaped request object
44
+ const result = service.execute('*', { applicant: { age: 25, income: 2500 } });
45
+ // { applicant: { age: 25, income: 2500 }, isEligible: true }
46
+ ```
47
+
48
+ ### Load from a Portable JSON model
49
+
50
+ ```typescript
51
+ import { DecisionService } from '@edgerules/node';
52
+ import type { PortableRootContext } from '@edgerules/portable';
53
+
54
+ const model: PortableRootContext = {
55
+ '@version': '1.0',
56
+ '@model-name': 'Tax Calculator',
57
+ taxRate: 0.21,
58
+ calculateTax: {
59
+ '@kind': 'function',
60
+ '@parameters': { amount: 'number' },
61
+ '@body': {
62
+ '@kind': 'context',
63
+ result: 'amount * taxRate',
64
+ },
65
+ },
66
+ };
67
+
68
+ const service = new DecisionService(model);
69
+
70
+ // Execute a specific function — pass its arguments as request bindings
71
+ const tax = service.execute('calculateTax', { amount: 100 });
72
+ // { result: 21 }
73
+ ```
74
+
75
+ ### Executing a ruleset
76
+
77
+ ```typescript
78
+ const service = new DecisionService(`{
79
+ ruleset risk(age: number, income: number): {
80
+ hitPolicy: "first-match"
81
+ rules: [
82
+ { when: { age: 18..25, income: < 30000 }, then: { level: "high", limit: 1000 } }
83
+ { when: { age: 18..25, income: >= 30000 }, then: { level: "medium", limit: 5000 } }
84
+ { when: { age: > 25 }, then: { level: "low", limit: 20000 } }
85
+ ]
86
+ default: { level: "none", limit: 0 }
87
+ }
88
+ decision: risk(age: applicant.age, income: applicant.income)
89
+ }`);
90
+
91
+ const result = service.execute('decision', { applicant: { age: 23, income: 25000 } });
92
+ // { level: "high", limit: 1000 }
93
+ ```
94
+
95
+ ### Error handling
96
+
97
+ Every execution or model error is a structured `PortableError` — never a plain string.
98
+
99
+ ```typescript
100
+ import { DecisionService } from '@edgerules/node';
101
+ import type { PortableError } from '@edgerules/portable';
102
+
103
+ try {
104
+ service.execute('unknownField');
105
+ } catch (err) {
106
+ const error = err as PortableError;
107
+ console.error(error.type); // "EntryNotFound"
108
+ console.error(error.message); // human-readable, safe to surface
109
+ console.error(error.path); // "unknownField"
110
+ }
111
+ ```
112
+
113
+ | `error.type` | Meaning |
114
+ |---|---|
115
+ | `EntryNotFound` | Path does not exist in the model |
116
+ | `WrongFieldPath` | Invalid path, out-of-bounds array index |
117
+ | `Parse` | Expression string failed to parse |
118
+ | `Linking` | Type mismatch or broken reference |
119
+ | `Execution` | Runtime error during evaluation |
120
+
121
+ ---
122
+
123
+ ## Mutable / Development Usage
124
+
125
+ Use the `./mutable` subpath import to access `MutableDecisionService`, which adds CRUD operations on top of execution.
126
+ This build is intended for IDEs, rule editors, and test tooling — **not** for production traffic.
127
+
128
+ ```typescript
129
+ import { MutableDecisionService } from '@edgerules/node/mutable';
130
+
131
+ const service = new MutableDecisionService(`{
132
+ taxRate: 0.21
133
+ tax: amount * taxRate
134
+ }`);
135
+
136
+ // Read a node enriched with linked type information
137
+ const node = service.get('taxRate');
138
+ // { "@kind": "expression", "expression": 0.21, "type": "number", "readOnly": true }
139
+
140
+ // Update a field at runtime — the engine relinks lazily on the next execute
141
+ service.set('taxRate', 0.25);
142
+
143
+ // Read the entire model snapshot
144
+ const model = service.get('*');
145
+
146
+ // Add a new typed input
147
+ service.set('amount', { '@kind': 'type', type: 'number', required: true });
148
+
149
+ // Remove a field
150
+ service.remove('taxRate');
151
+
152
+ // Rename a field
153
+ service.rename('tax', 'vat');
154
+ ```
155
+
156
+ ### `get` filters
157
+
158
+ ```typescript
159
+ // Default FIELDS view — returns expressions, typed holes, and function schemas
160
+ service.get('isEligible');
161
+
162
+ // Full function definition (body included)
163
+ service.get('isEligible', 'FUNCTION_DEFINITIONS');
164
+
165
+ // Type definition
166
+ service.get('Customer', 'TYPE_DEFINITIONS');
167
+
168
+ // Everything at once
169
+ service.get('*', 'ALL');
170
+ ```
171
+
172
+ ---
173
+
174
+ ## API Reference
175
+
176
+ ### `DecisionService`
177
+
178
+ | Method | Description |
179
+ |---|---|
180
+ | `new DecisionService(model)` | Create from DSL string or `PortableRootContext` |
181
+ | `execute(method, args?)` | Evaluate `method` (field path, function, or `"*"`) against optional request bindings |
182
+
183
+ ### `MutableDecisionService` (dev build)
184
+
185
+ Inherits `execute` and adds:
186
+
187
+ | Method | Description |
188
+ |---|---|
189
+ | `get(path, filter?)` | Read a node enriched with inferred types |
190
+ | `set(path, node)` | Write an expression, typed hole, function, type, or context |
191
+ | `remove(path)` | Delete an entry |
192
+ | `rename(oldPath, newPath)` | Rename an entry within its context |
193
+
194
+ ---
195
+
196
+ ## Related packages
197
+
198
+ - [`@edgerules/web`](https://www.npmjs.com/package/@edgerules/web) — same API for the browser
199
+ - [`@edgerules/portable`](https://www.npmjs.com/package/@edgerules/portable) — Portable JSON type definitions
@@ -0,0 +1,32 @@
1
+ /**
2
+ * One completion option from the dev WASM build's `completions()` export.
3
+ *
4
+ * A structural superset of `@codemirror/autocomplete`'s `Completion`: `label` is the text to
5
+ * insert, `type` the icon category CodeMirror renders, and `detail` an optional short hint shown
6
+ * after the label (a built-in's signature, a member's declared type).
7
+ */
8
+ export interface EditorCompletion {
9
+ label: string;
10
+ type: 'variable' | 'function' | 'type' | 'keyword' | 'property' | 'constant';
11
+ detail?: string;
12
+ }
13
+ /**
14
+ * A completion query result, directly usable as a CodeMirror `CompletionResult`.
15
+ *
16
+ * `from`/`to` delimit the identifier prefix being typed (UTF-16 code units into the document,
17
+ * already converted from the engine's UTF-8 byte offsets), so CodeMirror replaces the right range;
18
+ * an empty range at the cursor means no prefix. `options` are rank-ordered — user-scope names,
19
+ * then built-ins, then keywords — and unfiltered: CodeMirror's own fuzzy matcher filters by the
20
+ * typed prefix.
21
+ */
22
+ export interface EditorCompletionResult {
23
+ from: number;
24
+ to: number;
25
+ options: EditorCompletion[];
26
+ }
27
+ /**
28
+ * Parses the raw JSON emitted by the WASM `completions()` export and converts the replace range
29
+ * from UTF-8 byte offsets to UTF-16 code-unit offsets against the same `code` string, keeping
30
+ * `from <= to` and both within the document.
31
+ */
32
+ export declare function toEditorCompletions(rawJson: string, code: string): EditorCompletionResult;
@@ -0,0 +1,13 @@
1
+ import { byteToUtf16Mapper } from './offsets.js';
2
+ /**
3
+ * Parses the raw JSON emitted by the WASM `completions()` export and converts the replace range
4
+ * from UTF-8 byte offsets to UTF-16 code-unit offsets against the same `code` string, keeping
5
+ * `from <= to` and both within the document.
6
+ */
7
+ export function toEditorCompletions(rawJson, code) {
8
+ const raw = JSON.parse(rawJson);
9
+ const toUtf16 = byteToUtf16Mapper(code);
10
+ const from = toUtf16(raw.from);
11
+ const to = Math.max(from, toUtf16(raw.to));
12
+ return { from, to, options: raw.options };
13
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * A structured diagnostic from the dev WASM build's `diagnostics()` export.
3
+ *
4
+ * The shape is a structural superset of `@codemirror/lint`'s `Diagnostic`, so an editor can pass
5
+ * these straight to a CodeMirror linter callback: `from`/`to` are document positions in UTF-16
6
+ * code units (already converted from the engine's UTF-8 byte offsets and clamped to the document),
7
+ * `severity` matches CodeMirror's union, and `source`/`code` fill the optional fields of the same
8
+ * names. A diagnostic whose location could not be resolved (a linker error with no span) arrives
9
+ * as `from = to = 0` — render it document-wide or as a gutter marker.
10
+ */
11
+ export interface EditorDiagnostic {
12
+ from: number;
13
+ to: number;
14
+ severity: 'error';
15
+ source: 'parser' | 'linker';
16
+ /** Stable machine code: a `ParseErrorKind` name (`"UnexpectedToken"`) or a linker `Exxx` code. */
17
+ code?: string;
18
+ message: string;
19
+ }
20
+ /**
21
+ * Parses the raw JSON emitted by the WASM `diagnostics()` export and converts every span from
22
+ * UTF-8 byte offsets to UTF-16 code-unit offsets against the same `code` string, keeping
23
+ * `from <= to` and both within the document.
24
+ */
25
+ export declare function toEditorDiagnostics(rawJson: string, code: string): EditorDiagnostic[];
@@ -0,0 +1,15 @@
1
+ import { byteToUtf16Mapper } from './offsets.js';
2
+ /**
3
+ * Parses the raw JSON emitted by the WASM `diagnostics()` export and converts every span from
4
+ * UTF-8 byte offsets to UTF-16 code-unit offsets against the same `code` string, keeping
5
+ * `from <= to` and both within the document.
6
+ */
7
+ export function toEditorDiagnostics(rawJson, code) {
8
+ const raw = JSON.parse(rawJson);
9
+ const toUtf16 = byteToUtf16Mapper(code);
10
+ return raw.map((diagnostic) => {
11
+ const from = toUtf16(diagnostic.from);
12
+ const to = Math.max(from, toUtf16(diagnostic.to));
13
+ return { ...diagnostic, from, to };
14
+ });
15
+ }
@@ -1,5 +1,7 @@
1
1
  import type { PortableError, PortableNode, PortableRootContext } from '@edgerules/portable';
2
- import type { DecisionServiceWasmCtor, MutableDecisionServiceWasmInstance } from './wasm-types.js';
2
+ import type { MutableDecisionServiceWasmCtor, MutableDecisionServiceWasmInstance } from './wasm-types.js';
3
+ import { type EditorDiagnostic } from './diagnostics.js';
4
+ import { type EditorCompletionResult } from './completions.js';
3
5
  /**
4
6
  * Binds `MutableDecisionService` to a specific platform's dev wasm-pack class. Mirrors
5
7
  * `createDecisionService` (Clarification 8) but for the CRUD-enabled dev WASM build.
@@ -8,7 +10,7 @@ import type { DecisionServiceWasmCtor, MutableDecisionServiceWasmInstance } from
8
10
  * deliberately non-generic (see Clarification 14) — inheriting them unmodified would construct a base
9
11
  * `DecisionService`, not a `MutableDecisionService`, losing `set`/`remove`/`rename` at runtime.
10
12
  */
11
- export declare function createMutableDecisionService<TInstance extends MutableDecisionServiceWasmInstance>(WasmClass: DecisionServiceWasmCtor<TInstance>): {
13
+ export declare function createMutableDecisionService<TInstance extends MutableDecisionServiceWasmInstance>(WasmClass: MutableDecisionServiceWasmCtor<TInstance>): {
12
14
  new (wasm: TInstance): {
13
15
  set(path: string, node: PortableNode): PortableNode | PortableError;
14
16
  remove(path: string): void | PortableError;
@@ -29,6 +31,19 @@ export declare function createMutableDecisionService<TInstance extends MutableDe
29
31
  toPortable(): PortableRootContext;
30
32
  [Symbol.dispose](): void;
31
33
  };
34
+ /**
35
+ * Parses and links `code` without constructing a service, returning structured,
36
+ * CodeMirror-compatible diagnostics (empty when the source is valid). Positions are
37
+ * UTF-16 code units into `code`, ready to use as editor document offsets.
38
+ */
39
+ diagnostics(code: string): EditorDiagnostic[];
40
+ /**
41
+ * Computes completion options for the cursor at UTF-16 document position `pos` in `code`,
42
+ * in the shape of `@codemirror/autocomplete`'s `CompletionResult`. Works on syntactically
43
+ * broken source (the engine parses leniently) and never throws for arbitrary input; the
44
+ * worst case is an empty `options` list.
45
+ */
46
+ completions(code: string, pos: number): EditorCompletionResult;
32
47
  fromPortable(model: PortableRootContext): {
33
48
  set(path: string, node: PortableNode): PortableNode | PortableError;
34
49
  remove(path: string): void | PortableError;
@@ -1,5 +1,8 @@
1
1
  import { createDecisionService } from './decision-service.js';
2
2
  import { toPortableError } from './errors.js';
3
+ import { toEditorDiagnostics } from './diagnostics.js';
4
+ import { toEditorCompletions } from './completions.js';
5
+ import { utf16ToByteOffset } from './offsets.js';
3
6
  /**
4
7
  * Binds `MutableDecisionService` to a specific platform's dev wasm-pack class. Mirrors
5
8
  * `createDecisionService` (Clarification 8) but for the CRUD-enabled dev WASM build.
@@ -14,6 +17,23 @@ export function createMutableDecisionService(WasmClass) {
14
17
  static fromCode(code) {
15
18
  return new MutableDecisionService(WasmClass.from_code(code));
16
19
  }
20
+ /**
21
+ * Parses and links `code` without constructing a service, returning structured,
22
+ * CodeMirror-compatible diagnostics (empty when the source is valid). Positions are
23
+ * UTF-16 code units into `code`, ready to use as editor document offsets.
24
+ */
25
+ static diagnostics(code) {
26
+ return toEditorDiagnostics(WasmClass.diagnostics(code), code);
27
+ }
28
+ /**
29
+ * Computes completion options for the cursor at UTF-16 document position `pos` in `code`,
30
+ * in the shape of `@codemirror/autocomplete`'s `CompletionResult`. Works on syntactically
31
+ * broken source (the engine parses leniently) and never throws for arbitrary input; the
32
+ * worst case is an empty `options` list.
33
+ */
34
+ static completions(code, pos) {
35
+ return toEditorCompletions(WasmClass.completions(code, utf16ToByteOffset(code, pos)), code);
36
+ }
17
37
  static fromPortable(model) {
18
38
  return new MutableDecisionService(WasmClass.from_portable(JSON.stringify(model)));
19
39
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * UTF-8 byte offset ↔ UTF-16 code unit conversion for editor positions.
3
+ *
4
+ * The engine reports source ranges as byte offsets into the UTF-8 source; CodeMirror document
5
+ * positions are UTF-16 code units. For ASCII sources the two coincide and conversion is a clamp.
6
+ */
7
+ /**
8
+ * Builds a UTF-8-byte-offset → UTF-16-code-unit-offset mapper for `code`.
9
+ *
10
+ * For non-ASCII sources a per-code-point offset table is built once and each lookup binary-searches
11
+ * it. Offsets that fall inside a multi-byte sequence (impossible for well-formed engine spans) or
12
+ * past the end resolve to the nearest boundary.
13
+ */
14
+ export declare function byteToUtf16Mapper(code: string): (byteOffset: number) => number;
15
+ /**
16
+ * Converts a single UTF-16 code-unit position in `code` to a UTF-8 byte offset — the reverse
17
+ * direction of {@link byteToUtf16Mapper}, as a one-shot walk (completion sends one position per
18
+ * request, so no table is needed). Positions inside a surrogate pair or past the end resolve to
19
+ * the nearest code-point boundary.
20
+ */
21
+ export declare function utf16ToByteOffset(code: string, position: number): number;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * UTF-8 byte offset ↔ UTF-16 code unit conversion for editor positions.
3
+ *
4
+ * The engine reports source ranges as byte offsets into the UTF-8 source; CodeMirror document
5
+ * positions are UTF-16 code units. For ASCII sources the two coincide and conversion is a clamp.
6
+ */
7
+ /**
8
+ * Builds a UTF-8-byte-offset → UTF-16-code-unit-offset mapper for `code`.
9
+ *
10
+ * For non-ASCII sources a per-code-point offset table is built once and each lookup binary-searches
11
+ * it. Offsets that fall inside a multi-byte sequence (impossible for well-formed engine spans) or
12
+ * past the end resolve to the nearest boundary.
13
+ */
14
+ export function byteToUtf16Mapper(code) {
15
+ // eslint-disable-next-line no-control-regex
16
+ if (!/[^\x00-\x7f]/.test(code)) {
17
+ return (byteOffset) => Math.max(0, Math.min(byteOffset, code.length));
18
+ }
19
+ const byteStarts = [];
20
+ const utf16Starts = [];
21
+ let byteOffset = 0;
22
+ let utf16Offset = 0;
23
+ for (const char of code) {
24
+ byteStarts.push(byteOffset);
25
+ utf16Starts.push(utf16Offset);
26
+ const codePoint = char.codePointAt(0) ?? 0;
27
+ byteOffset += codePoint < 0x80 ? 1 : codePoint < 0x800 ? 2 : codePoint < 0x10000 ? 3 : 4;
28
+ utf16Offset += char.length;
29
+ }
30
+ byteStarts.push(byteOffset);
31
+ utf16Starts.push(utf16Offset);
32
+ return (target) => {
33
+ let low = 0;
34
+ let high = byteStarts.length - 1;
35
+ while (low < high) {
36
+ const mid = (low + high + 1) >> 1;
37
+ if (byteStarts[mid] <= target) {
38
+ low = mid;
39
+ }
40
+ else {
41
+ high = mid - 1;
42
+ }
43
+ }
44
+ return utf16Starts[low];
45
+ };
46
+ }
47
+ /**
48
+ * Converts a single UTF-16 code-unit position in `code` to a UTF-8 byte offset — the reverse
49
+ * direction of {@link byteToUtf16Mapper}, as a one-shot walk (completion sends one position per
50
+ * request, so no table is needed). Positions inside a surrogate pair or past the end resolve to
51
+ * the nearest code-point boundary.
52
+ */
53
+ export function utf16ToByteOffset(code, position) {
54
+ // eslint-disable-next-line no-control-regex
55
+ if (!/[^\x00-\x7f]/.test(code)) {
56
+ return Math.max(0, Math.min(position, code.length));
57
+ }
58
+ const clamped = Math.max(0, Math.min(position, code.length));
59
+ let byteOffset = 0;
60
+ let utf16Offset = 0;
61
+ for (const char of code) {
62
+ if (utf16Offset + char.length > clamped) {
63
+ break;
64
+ }
65
+ const codePoint = char.codePointAt(0) ?? 0;
66
+ byteOffset += codePoint < 0x80 ? 1 : codePoint < 0x800 ? 2 : codePoint < 0x10000 ? 3 : 4;
67
+ utf16Offset += char.length;
68
+ if (utf16Offset === clamped) {
69
+ break;
70
+ }
71
+ }
72
+ return byteOffset;
73
+ }
@@ -18,3 +18,12 @@ export interface DecisionServiceWasmCtor<TInstance extends DecisionServiceWasmIn
18
18
  from_code(code: string): TInstance;
19
19
  from_portable(json: string): TInstance;
20
20
  }
21
+ /**
22
+ * Ctor surface of the dev (`mutable_ds` + `analyzer`) WASM builds, which additionally export the
23
+ * static `diagnostics` and `completions` language-tooling entry points. Production builds
24
+ * deliberately lack both.
25
+ */
26
+ export interface MutableDecisionServiceWasmCtor<TInstance extends MutableDecisionServiceWasmInstance> extends DecisionServiceWasmCtor<TInstance> {
27
+ diagnostics(code: string): string;
28
+ completions(code: string, offset: number): string;
29
+ }
package/dist/mutable.d.ts CHANGED
@@ -20,6 +20,8 @@ export declare const MutableDecisionService: {
20
20
  toPortable(): import("@edgerules/portable").PortableRootContext;
21
21
  [Symbol.dispose](): void;
22
22
  };
23
+ diagnostics(code: string): import("./mutable.js").EditorDiagnostic[];
24
+ completions(code: string, pos: number): import("./mutable.js").EditorCompletionResult;
23
25
  fromPortable(model: import("@edgerules/portable").PortableRootContext): {
24
26
  set(path: string, node: import("@edgerules/portable").PortableNode): import("@edgerules/portable").PortableNode | import("@edgerules/portable").PortableError;
25
27
  remove(path: string): void | import("@edgerules/portable").PortableError;
@@ -33,4 +35,6 @@ export declare const MutableDecisionService: {
33
35
  };
34
36
  export type MutableDecisionService = InstanceType<typeof MutableDecisionService>;
35
37
  export type { GetFilter } from './_core/decision-service.js';
38
+ export type { EditorDiagnostic } from './_core/diagnostics.js';
39
+ export type { EditorCompletion, EditorCompletionResult } from './_core/completions.js';
36
40
  export type { PortableError, PortableNode, PortableRootContext } from '@edgerules/portable';
@@ -8,6 +8,38 @@ export class DecisionServiceWASM {
8
8
  private constructor();
9
9
  free(): void;
10
10
  [Symbol.dispose](): void;
11
+ /**
12
+ * Computes code-completion options for the cursor at UTF-8 byte `offset`
13
+ * into `code`, returning JSON `{from, to, options: [{label, type,
14
+ * detail?}]}` — the shape of `@codemirror/autocomplete`'s
15
+ * `CompletionResult`. `from`/`to` delimit the identifier prefix being
16
+ * typed (byte offsets; the JS wrapper converts to UTF-16) and `options`
17
+ * are rank-ordered: user-scope names, then built-ins, then keywords.
18
+ *
19
+ * Works on syntactically broken source (lenient parse) and never throws
20
+ * for arbitrary input; the worst case is an empty options list.
21
+ *
22
+ * Dev/IDE builds only (`analyzer`): production packages stay
23
+ * execution-only and never link this code in.
24
+ */
25
+ static completions(code: string, offset: number): string;
26
+ /**
27
+ * Parses and links `code`, returning a JSON array of structured
28
+ * diagnostics — empty when the source is valid.
29
+ *
30
+ * Each diagnostic is `{severity, source, code?, message, from, to}` with
31
+ * `from`/`to` as UTF-8 byte offsets into `code` (start/end exclusive).
32
+ * Parse failures report every collected `ParseError` with its own span;
33
+ * a link failure reports the single `LinkerError`, resolved to the
34
+ * offending definition's span via `EdgeRulesModel::span_of(node_id)`.
35
+ * Errors that carry no `node_id` (or an id allocated outside the parser)
36
+ * degrade to `from = to = 0` — the editor maps that to a whole-document
37
+ * diagnostic.
38
+ *
39
+ * Dev/IDE builds only (`analyzer`): production packages stay
40
+ * execution-only and never link this code in.
41
+ */
42
+ static diagnostics(code: string): string;
11
43
  /**
12
44
  * Executes a function defined in the model, or evaluates a field by path.
13
45
  *
@@ -21,6 +21,93 @@ class DecisionServiceWASM {
21
21
  const ptr = this.__destroy_into_raw();
22
22
  wasm.__wbg_decisionservicewasm_free(ptr, 0);
23
23
  }
24
+ /**
25
+ * Computes code-completion options for the cursor at UTF-8 byte `offset`
26
+ * into `code`, returning JSON `{from, to, options: [{label, type,
27
+ * detail?}]}` — the shape of `@codemirror/autocomplete`'s
28
+ * `CompletionResult`. `from`/`to` delimit the identifier prefix being
29
+ * typed (byte offsets; the JS wrapper converts to UTF-16) and `options`
30
+ * are rank-ordered: user-scope names, then built-ins, then keywords.
31
+ *
32
+ * Works on syntactically broken source (lenient parse) and never throws
33
+ * for arbitrary input; the worst case is an empty options list.
34
+ *
35
+ * Dev/IDE builds only (`analyzer`): production packages stay
36
+ * execution-only and never link this code in.
37
+ * @param {string} code
38
+ * @param {number} offset
39
+ * @returns {string}
40
+ */
41
+ static completions(code, offset) {
42
+ let deferred3_0;
43
+ let deferred3_1;
44
+ try {
45
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
46
+ const ptr0 = passStringToWasm0(code, wasm.__wbindgen_export, wasm.__wbindgen_export2);
47
+ const len0 = WASM_VECTOR_LEN;
48
+ wasm.decisionservicewasm_completions(retptr, ptr0, len0, offset);
49
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
50
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
51
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
52
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
53
+ var ptr2 = r0;
54
+ var len2 = r1;
55
+ if (r3) {
56
+ ptr2 = 0; len2 = 0;
57
+ throw takeObject(r2);
58
+ }
59
+ deferred3_0 = ptr2;
60
+ deferred3_1 = len2;
61
+ return getStringFromWasm0(ptr2, len2);
62
+ } finally {
63
+ wasm.__wbindgen_add_to_stack_pointer(16);
64
+ wasm.__wbindgen_export4(deferred3_0, deferred3_1, 1);
65
+ }
66
+ }
67
+ /**
68
+ * Parses and links `code`, returning a JSON array of structured
69
+ * diagnostics — empty when the source is valid.
70
+ *
71
+ * Each diagnostic is `{severity, source, code?, message, from, to}` with
72
+ * `from`/`to` as UTF-8 byte offsets into `code` (start/end exclusive).
73
+ * Parse failures report every collected `ParseError` with its own span;
74
+ * a link failure reports the single `LinkerError`, resolved to the
75
+ * offending definition's span via `EdgeRulesModel::span_of(node_id)`.
76
+ * Errors that carry no `node_id` (or an id allocated outside the parser)
77
+ * degrade to `from = to = 0` — the editor maps that to a whole-document
78
+ * diagnostic.
79
+ *
80
+ * Dev/IDE builds only (`analyzer`): production packages stay
81
+ * execution-only and never link this code in.
82
+ * @param {string} code
83
+ * @returns {string}
84
+ */
85
+ static diagnostics(code) {
86
+ let deferred3_0;
87
+ let deferred3_1;
88
+ try {
89
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
90
+ const ptr0 = passStringToWasm0(code, wasm.__wbindgen_export, wasm.__wbindgen_export2);
91
+ const len0 = WASM_VECTOR_LEN;
92
+ wasm.decisionservicewasm_diagnostics(retptr, ptr0, len0);
93
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
94
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
95
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
96
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
97
+ var ptr2 = r0;
98
+ var len2 = r1;
99
+ if (r3) {
100
+ ptr2 = 0; len2 = 0;
101
+ throw takeObject(r2);
102
+ }
103
+ deferred3_0 = ptr2;
104
+ deferred3_1 = len2;
105
+ return getStringFromWasm0(ptr2, len2);
106
+ } finally {
107
+ wasm.__wbindgen_add_to_stack_pointer(16);
108
+ wasm.__wbindgen_export4(deferred3_0, deferred3_1, 1);
109
+ }
110
+ }
24
111
  /**
25
112
  * Executes a function defined in the model, or evaluates a field by path.
26
113
  *
@@ -2,6 +2,8 @@
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
4
  export const __wbg_decisionservicewasm_free: (a: number, b: number) => void;
5
+ export const decisionservicewasm_completions: (a: number, b: number, c: number, d: number) => void;
6
+ export const decisionservicewasm_diagnostics: (a: number, b: number, c: number) => void;
5
7
  export const decisionservicewasm_execute: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
6
8
  export const decisionservicewasm_from_code: (a: number, b: number, c: number) => void;
7
9
  export const decisionservicewasm_from_portable: (a: number, b: number, c: number) => void;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@edgerules/node",
3
- "version": "0.0.0-alpha.202607061133",
3
+ "version": "0.0.0-alpha.202607071338",
4
4
  "type": "module",
5
5
  "description": "EdgeRules decision service for Node.js.",
6
6
  "dependencies": {
7
- "@edgerules/portable": "0.0.0-alpha.202607061133"
7
+ "@edgerules/portable": "0.0.0-alpha.202607071338"
8
8
  },
9
9
  "devDependencies": {
10
10
  "@edgerules/core": "*",