@necpp-engine/wasm 0.1.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.
package/README.md ADDED
@@ -0,0 +1,402 @@
1
+ # `@necpp-engine/wasm`
2
+
3
+ Stateful NEC2++ antenna simulation for Node and browsers, exposed through a
4
+ handwritten TypeScript API. The package owns the WebAssembly details: callers
5
+ do not copy artifacts, build NEC decks, parse reports, or handle native
6
+ pointers.
7
+
8
+ > **License:** this package and its `nec2pp.wasm` engine are
9
+ > **GPL-2.0-or-later**. Distributing an application that includes or serves the
10
+ > package is distribution of GPL software. Read [License](#license) before
11
+ > shipping it in a product.
12
+
13
+ ## Five-minute dipole
14
+
15
+ Install with `npm install @necpp-engine/wasm`. The package is ESM-only and
16
+ requires Node 24 or newer.
17
+
18
+ ```ts
19
+ import { createNecModel } from "@necpp-engine/wasm";
20
+
21
+ const model = await createNecModel();
22
+
23
+ try {
24
+ model.addWire({
25
+ tag: 1,
26
+ segments: 11,
27
+ start: [0, 0, -0.25],
28
+ end: [0, 0, 0.25],
29
+ radiusM: 0.001,
30
+ });
31
+ model.completeGeometry();
32
+ model.definePorts([{ tag: 1, segment: 6, name: "feed" }]);
33
+ model.prepare({ frequencyMHz: 300 });
34
+
35
+ const { impedance, admittance } = model.computeImpedanceMatrix();
36
+ const solution = model.solveCurrents({
37
+ real: new Float64Array([1]),
38
+ imag: new Float64Array([0]),
39
+ });
40
+ const field = model.computeFarField({
41
+ radiusM: 1,
42
+ theta: { startDeg: 0, count: 181, stepDeg: 1 },
43
+ phi: { startDeg: 0, count: 1, stepDeg: 0 },
44
+ });
45
+
46
+ console.log({
47
+ zOhm: [impedance.real[0], impedance.imag[0]],
48
+ ySiemens: [admittance.real[0], admittance.imag[0]],
49
+ requiredVoltageV: [solution.voltages.real[0], solution.voltages.imag[0]],
50
+ fieldSamples: field.eThetaReal.length,
51
+ });
52
+ } finally {
53
+ model.dispose();
54
+ }
55
+ ```
56
+
57
+ This complete example is executed from the packed npm tarball in CI.
58
+
59
+ ## Numerical conventions
60
+
61
+ - Coordinates, wire radius, and field radius are metres. Public frequencies
62
+ are MHz.
63
+ - Phasors use `e^(+j omega t)` and outgoing propagation uses `e^(-jkR)`.
64
+ - Port voltage is in complex volts. Port current is in complex amperes and is
65
+ positive **into** the modeled antenna.
66
+ - `V = Z I` and `I = Y V`; impedance is in ohms and admittance is in siemens.
67
+ - Complex far-field components are V/m. `radiusM` defaults to 1 m and is
68
+ retained in every result.
69
+ - Theta is the polar angle down from +Z. Phi is azimuth from +X toward +Y.
70
+ - Matrices are row-major: `index = row * columns + column`. Far-field samples
71
+ are theta-fast: `index = phiIndex * thetaCount + thetaIndex`.
72
+ - Fields are referenced to the model origin and remain far-field
73
+ approximations even when a small radius is requested.
74
+ - Every returned typed array is a JavaScript-owned copy. It remains valid
75
+ across later solves, WebAssembly memory growth, and model disposal.
76
+
77
+ Coordinate orientation:
78
+
79
+ ```text
80
+ +Z theta=0 deg
81
+ |
82
+ |\ r
83
+ | \
84
+ | * sample
85
+ | / theta
86
+ |/
87
+ +Y origin -------- +X phi=0 deg
88
+ \ /
89
+ \ phi /
90
+ \------/
91
+
92
+ Phi increases from +X toward +Y; theta increases from +Z toward the XY plane.
93
+ ```
94
+
95
+ The full normative contract, including loads, ground, tolerances, and every
96
+ state transition, is in
97
+ [`docs/wasm-api.md`](https://github.com/andrekuehne/necpp/blob/master/docs/wasm-api.md).
98
+
99
+ ## Geometry and ports
100
+
101
+ Build geometry first, complete it once, then define an ordered port list.
102
+ Wire tags are positive integers. A port segment is one-based among all
103
+ segments carrying that tag, so an 11-segment dipole is centre-fed at segment
104
+ 6. Port order fixes the order used by every vector, matrix row/column, and
105
+ embedded field basis.
106
+
107
+ The initial environment is free space with no loads. Call `addLoad()`,
108
+ `clearLoads()`, or `setGround()` after geometry completion and before
109
+ `prepare()`. Changing ground or loads later is allowed, but invalidates the
110
+ factorization and returns the model to `geometry-complete`.
111
+
112
+ ## Z and Y matrices
113
+
114
+ `computeImpedanceMatrix()` factors the electromagnetic interaction matrix once
115
+ and returns both port matrices. Entry `Z[row,column]` is the voltage at port
116
+ `row` produced by a unit current at port `column`, with all other requested
117
+ port currents zero. `Y` has the analogous voltage-driven interpretation.
118
+
119
+ ```ts
120
+ import type { ComplexMatrix } from "@necpp-engine/wasm";
121
+
122
+ function entry(matrix: ComplexMatrix, row: number, column: number) {
123
+ const index = row * matrix.columns + column;
124
+ return {
125
+ real: matrix.real[index],
126
+ imag: matrix.imag[index],
127
+ };
128
+ }
129
+
130
+ declare const z: ComplexMatrix;
131
+ const selfImpedance = entry(z, 0, 0);
132
+ const mutualImpedance = entry(z, 0, 1);
133
+ console.log({ selfImpedance, mutualImpedance });
134
+ ```
135
+
136
+ The returned `conditionEstimate` is omitted only when the native implementation
137
+ cannot estimate it. Matrix formation throws `NecConditioningError` instead of
138
+ returning a singular or excessively ill-conditioned inverse.
139
+
140
+ ## Voltage- and current-driven arrays
141
+
142
+ `solveVoltages()` applies exactly the requested simultaneous complex voltages.
143
+ `solveCurrents()` first computes the required voltages with `V = Z I`, then
144
+ executes one simultaneous source solve. Both return achieved voltages and
145
+ currents, per-port powers, and active impedances in stable port order.
146
+
147
+ ```ts
148
+ import type { NecModel } from "@necpp-engine/wasm";
149
+
150
+ declare const model: NecModel;
151
+
152
+ const voltageDriven = model.solveVoltages({
153
+ real: new Float64Array([1, 0]),
154
+ imag: new Float64Array([0, 1]),
155
+ });
156
+
157
+ const currentDriven = model.solveCurrents({
158
+ real: new Float64Array([1, 0]),
159
+ imag: new Float64Array([0, -1]),
160
+ });
161
+
162
+ console.log(voltageDriven.currents, currentDriven.voltages);
163
+ ```
164
+
165
+ Matrix impedance and active impedance are different quantities. `Z[i,j]` is a
166
+ fixed property of the prepared model. Active impedance is `V[i] / I[i]` for
167
+ one particular simultaneous excitation, so mutual coupling makes it change
168
+ when array weights change. An exactly zero achieved current produces
169
+ `NaN + jNaN` active impedance; inspect the voltage/current vectors instead of
170
+ dividing by zero. Time-average input power is
171
+ `0.5 * Re(V * conjugate(I))` watts.
172
+
173
+ ## Complex far fields and beamforming
174
+
175
+ `computeFarField()` uses the most recent public solve. At the default 1 m,
176
+ `eTheta*` and `ePhi*` are split real/imaginary V/m arrays. At another range,
177
+ the field follows `e^(-jkR) / R` while retaining the same angular far-field
178
+ approximation.
179
+
180
+ `computeEmbeddedFarFields()` returns one complex basis pattern per port.
181
+ Unit-current normalization makes array beamforming a direct weighted sum. The
182
+ arrays are basis-major, followed by the normal theta-fast sample layout.
183
+
184
+ ```ts
185
+ import type {
186
+ EmbeddedFarFieldResult,
187
+ NecModel,
188
+ } from "@necpp-engine/wasm";
189
+
190
+ declare const model: NecModel;
191
+
192
+ const embedded = model.computeEmbeddedFarFields(
193
+ {
194
+ radiusM: 1,
195
+ theta: { startDeg: 90, count: 1, stepDeg: 0 },
196
+ phi: { startDeg: 0, count: 361, stepDeg: 1 },
197
+ },
198
+ { kind: "unit-current", valueA: 1 },
199
+ );
200
+
201
+ const phaseStepRad = Math.PI / 3;
202
+ const weights = embedded.ports.map((_, port) => ({
203
+ real: Math.cos(port * phaseStepRad),
204
+ imag: Math.sin(port * phaseStepRad),
205
+ }));
206
+
207
+ function combineETheta(basis: EmbeddedFarFieldResult) {
208
+ const real = new Float64Array(basis.samplesPerPort);
209
+ const imag = new Float64Array(basis.samplesPerPort);
210
+ for (let port = 0; port < basis.ports.length; port += 1) {
211
+ const weight = weights[port]!;
212
+ for (let sample = 0; sample < basis.samplesPerPort; sample += 1) {
213
+ const index = port * basis.samplesPerPort + sample;
214
+ const er = basis.eThetaReal[index]!;
215
+ const ei = basis.eThetaImag[index]!;
216
+ real[sample] = real[sample]! + weight.real * er - weight.imag * ei;
217
+ imag[sample] = imag[sample]! + weight.real * ei + weight.imag * er;
218
+ }
219
+ }
220
+ return { real, imag };
221
+ }
222
+
223
+ console.log(combineETheta(embedded));
224
+ ```
225
+
226
+ For a one-off excitation, `solveCurrents()` plus `computeFarField()` is simpler.
227
+ Embedded fields are useful when many weight sets share one geometry and
228
+ frequency: compute the bases once, then combine them in JavaScript without
229
+ additional native solves.
230
+
231
+ ## Direct mode and worker mode
232
+
233
+ | Mode | Import | Calls | Best for |
234
+ |---|---|---|---|
235
+ | Direct | `@necpp-engine/wasm` | Synchronous after creation | Node, tests, small browser models |
236
+ | Worker | `@necpp-engine/wasm/worker` | Asynchronous and serialized | Browser UI and realistic solves |
237
+
238
+ The factory is always asynchronous because it instantiates WebAssembly. A
239
+ direct browser solve then occupies the main thread until it finishes. The
240
+ worker facade preserves model state in a package-supplied module worker and
241
+ transfers large result buffers back to the caller.
242
+
243
+ ```ts
244
+ import { createNecWorkerModel } from "@necpp-engine/wasm/worker";
245
+
246
+ const model = await createNecWorkerModel({
247
+ onProgress: ({ operation, phase }) => console.log(operation, phase),
248
+ });
249
+
250
+ try {
251
+ await model.addWire({
252
+ tag: 1,
253
+ segments: 11,
254
+ start: [0, 0, -0.25],
255
+ end: [0, 0, 0.25],
256
+ radiusM: 0.001,
257
+ });
258
+ await model.completeGeometry();
259
+ await model.definePorts([{ tag: 1, segment: 6 }]);
260
+ await model.prepare({ frequencyMHz: 300 });
261
+ console.log(await model.computeImpedanceMatrix());
262
+ } finally {
263
+ await model.dispose();
264
+ }
265
+ ```
266
+
267
+ Worker calls cannot interrupt a synchronous native calculation. Use
268
+ `model.terminate()` for immediate cancellation; it kills the worker and
269
+ rejects outstanding operations. Create a new model to continue afterward.
270
+
271
+ ## Lifecycle and disposal
272
+
273
+ The normal lifecycle is
274
+ `empty -> geometry-building -> geometry-complete -> prepared -> solved`.
275
+ `computeImpedanceMatrix()` and embedded-field calculation are legal while
276
+ prepared; combined far fields require a latest solution. Repeating
277
+ `prepare()` at the same frequency is idempotent. New excitations and field
278
+ grids reuse the retained factorization. Geometry cannot change after
279
+ `completeGeometry()`.
280
+
281
+ Always dispose in `finally`. Direct `dispose()` and worker `await dispose()`
282
+ are idempotent. Every other operation after disposal throws `NecStateError`.
283
+
284
+ ## Node, browser, Vite, and CDN loading
285
+
286
+ Node and browsers use the same package and public types. By default,
287
+ `nec2pp.wasm` is resolved beside the installed JavaScript with
288
+ `new URL("./nec2pp.wasm", import.meta.url)`; consumers do not copy it.
289
+
290
+ Direct mode needs no Vite configuration. For the module-worker entry point,
291
+ use this Vite configuration:
292
+
293
+ ```ts
294
+ import { defineConfig } from "vite";
295
+
296
+ export default defineConfig({
297
+ build: { target: "es2024" },
298
+ worker: { format: "es" },
299
+ });
300
+ ```
301
+
302
+ Production servers should serve `.wasm` as `application/wasm`. To host the
303
+ binary on a CDN, pass an HTTP(S) URL. Cross-origin servers must also send an
304
+ appropriate CORS header.
305
+
306
+ ```ts
307
+ import { createNecModel } from "@necpp-engine/wasm";
308
+
309
+ const model = await createNecModel({
310
+ wasmUrl: new URL("https://cdn.example.test/necpp/0.1.0/nec2pp.wasm"),
311
+ });
312
+ model.dispose();
313
+ ```
314
+
315
+ `wasmBinary` accepts an `ArrayBuffer` or `Uint8Array` when the host application
316
+ wants to fetch/cache the bytes itself. `wasmUrl` and `wasmBinary` are mutually
317
+ exclusive. `runDeck(deck)` remains available as a compatibility escape hatch
318
+ for complete NEC text decks.
319
+
320
+ ## Error handling
321
+
322
+ Every package-defined operational error derives from `NecError` and has a
323
+ stable `code`: `NEC_STATE`, `NEC_INPUT`, `NEC_GEOMETRY`, `NEC_PORT`,
324
+ `NEC_CONDITIONING`, `NEC_SOLVER`, or `NEC_RUNTIME`. Messages and `details` are
325
+ diagnostic rather than a compatibility contract.
326
+
327
+ ```ts
328
+ import {
329
+ NecConditioningError,
330
+ NecError,
331
+ createNecModel,
332
+ } from "@necpp-engine/wasm";
333
+
334
+ try {
335
+ const model = await createNecModel();
336
+ try {
337
+ model.computeImpedanceMatrix();
338
+ } finally {
339
+ model.dispose();
340
+ }
341
+ } catch (error: unknown) {
342
+ if (error instanceof NecConditioningError) {
343
+ console.error("Port matrix cannot be inverted reliably", error.details);
344
+ } else if (error instanceof NecError) {
345
+ console.error(error.code, error.message, error.details);
346
+ } else {
347
+ throw error;
348
+ }
349
+ }
350
+ ```
351
+
352
+ ## Performance and browser memory
353
+
354
+ - Segment count dominates factorization time and memory. Thin-wire modeling
355
+ still requires physically sensible segment length/radius ratios; begin with
356
+ modest odd segment counts and refine while checking convergence.
357
+ - Keep a prepared model alive while changing excitations or angular grids.
358
+ Recreating it discards the expensive factorization.
359
+ - Prefer embedded fields when exploring many array weights at one frequency.
360
+ - Field storage scales with `theta.count * phi.count`; embedded storage also
361
+ multiplies by port count and by four `Float64Array` components. A full
362
+ 181 x 361 field is about 2 MiB for the four component arrays; four embedded
363
+ bases are about 8 MiB, excluding axes and temporary/native storage.
364
+ - Returned arrays are copies, so release references when results are no longer
365
+ needed. Compute cuts instead of dense spheres when possible.
366
+ - Use worker mode for browser responsiveness. Each worker model owns an
367
+ isolated WebAssembly instance and memory, so dispose unused models rather
368
+ than pooling many idle workers.
369
+ - There is no shared-memory or thread requirement. Normal cross-origin
370
+ isolation headers are not needed for this package.
371
+
372
+ ## Complete Vite array example
373
+
374
+ The repository's
375
+ [four-element array application](https://github.com/andrekuehne/necpp/tree/master/examples/wasm-array-vite)
376
+ installs the packed package, computes Z/Y, applies progressive complex current
377
+ weights, displays achieved port quantities, and plots a normalized azimuth
378
+ cut. CI builds and runs that exact application in Chromium from the same
379
+ tarball used by every release gate.
380
+
381
+ ## Versions
382
+
383
+ | Export | Meaning |
384
+ |---|---|
385
+ | `packageVersion` | Semantic version of the public TypeScript API |
386
+ | `engineVersion` | NEC2++ version compiled into the shipped WebAssembly |
387
+ | `abiVersion` | Stable internal C ABI; currently `1` |
388
+
389
+ Instantiation rejects a binary whose ABI or engine version does not match the
390
+ facade. Package and engine versions intentionally have independent version
391
+ lines; the release records both.
392
+
393
+ ## License
394
+
395
+ `@necpp-engine/wasm` is distributed under **GPL-2.0-or-later**, matching
396
+ NEC2++. The npm tarball includes `COPYING` with the full license text.
397
+
398
+ If you convey the JavaScript loader, WebAssembly binary, or an application
399
+ containing them, review and satisfy the GPL's corresponding-source, license,
400
+ and notice requirements for your distribution. This README is a technical
401
+ notice, not legal advice. Consult qualified counsel for a product-specific
402
+ licensing decision.
package/dist/deck.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { DeckResult, RunDeckOptions } from "./types.js";
2
+ import type { NecWasmModule } from "./wasm-internal.js";
3
+ export declare function validateDeckText(deckText: unknown): asserts deckText is string;
4
+ export declare function runDeckWithModule(module: NecWasmModule, deckText: string, options?: RunDeckOptions): DeckResult;
package/dist/deck.js ADDED
@@ -0,0 +1,129 @@
1
+ import { NecConditioningError, NecGeometryError, NecInputError, NecPortError, NecRuntimeError, NecSolverError, } from "./errors.js";
2
+ const textEncoder = new TextEncoder();
3
+ const textDecoder = new TextDecoder();
4
+ function decodeBytes(module, pointer, length) {
5
+ if (!Number.isSafeInteger(pointer)
6
+ || pointer < 0
7
+ || !Number.isSafeInteger(length)
8
+ || length < 0
9
+ || pointer + length > module.HEAPU8.length) {
10
+ throw new NecRuntimeError("The native deck runner returned an invalid buffer");
11
+ }
12
+ return textDecoder.decode(module.HEAPU8.slice(pointer, pointer + length));
13
+ }
14
+ function decodeCString(module, pointer) {
15
+ if (!Number.isSafeInteger(pointer)
16
+ || pointer <= 0
17
+ || pointer >= module.HEAPU8.length) {
18
+ throw new NecRuntimeError("The native module returned an invalid string");
19
+ }
20
+ const end = module.HEAPU8.indexOf(0, pointer);
21
+ if (end < 0) {
22
+ throw new NecRuntimeError("The native module returned an unterminated string");
23
+ }
24
+ return decodeBytes(module, pointer, end - pointer);
25
+ }
26
+ function deckError(module, deck, status) {
27
+ let message = `Deck execution failed with native status ${status}`;
28
+ try {
29
+ const nativeMessage = decodeCString(module, module._necpp_wasm_v1_deck_last_error(deck));
30
+ if (nativeMessage.length > 0) {
31
+ message = nativeMessage;
32
+ }
33
+ }
34
+ catch {
35
+ // Keep the stable fallback message.
36
+ }
37
+ const details = { operation: "runDeck", nativeStatus: status };
38
+ switch (status) {
39
+ case 2:
40
+ throw new NecInputError(message, { details });
41
+ case 3:
42
+ throw new NecGeometryError(message, { details });
43
+ case 4:
44
+ throw new NecPortError(message, { details });
45
+ case 5:
46
+ throw new NecConditioningError(message, { details });
47
+ case 6:
48
+ throw new NecSolverError(message, { details });
49
+ default:
50
+ throw new NecRuntimeError(message, { details });
51
+ }
52
+ }
53
+ function assertNotAborted(options) {
54
+ if (options?.signal?.aborted === true) {
55
+ throw new NecInputError("Deck execution was aborted before it started", {
56
+ details: { operation: "runDeck", aborted: true },
57
+ });
58
+ }
59
+ }
60
+ export function validateDeckText(deckText) {
61
+ if (typeof deckText !== "string" || deckText.length === 0) {
62
+ throw new NecInputError("deck must be a nonempty string");
63
+ }
64
+ if (deckText.includes("\0")) {
65
+ throw new NecInputError("deck cannot contain embedded NUL characters");
66
+ }
67
+ }
68
+ export function runDeckWithModule(module, deckText, options) {
69
+ assertNotAborted(options);
70
+ validateDeckText(deckText);
71
+ const bytes = textEncoder.encode(deckText);
72
+ if (bytes.length === 0) {
73
+ throw new NecInputError("deck must contain UTF-8 input");
74
+ }
75
+ let deck = 0;
76
+ let inputPointer = 0;
77
+ try {
78
+ deck = module._necpp_wasm_v1_deck_create();
79
+ if (!Number.isSafeInteger(deck) || deck <= 0) {
80
+ throw new NecRuntimeError("Failed to create the native deck runner");
81
+ }
82
+ inputPointer = module._malloc(bytes.length);
83
+ if (!Number.isSafeInteger(inputPointer) || inputPointer <= 0) {
84
+ throw new NecRuntimeError("WASM memory allocation failed");
85
+ }
86
+ module.HEAPU8.set(bytes, inputPointer);
87
+ assertNotAborted(options);
88
+ const status = module._necpp_wasm_v1_deck_process(deck, inputPointer, bytes.length);
89
+ if (status !== 0) {
90
+ deckError(module, deck, status);
91
+ }
92
+ const reportLength = module._necpp_wasm_v1_deck_output_length(deck);
93
+ const report = decodeBytes(module, module._necpp_wasm_v1_deck_output(deck), reportLength);
94
+ const engineVersion = decodeCString(module, module._necpp_wasm_v1_engine_version());
95
+ return { report, engineVersion };
96
+ }
97
+ catch (error) {
98
+ if (error instanceof NecInputError
99
+ || error instanceof NecGeometryError
100
+ || error instanceof NecPortError
101
+ || error instanceof NecConditioningError
102
+ || error instanceof NecSolverError
103
+ || error instanceof NecRuntimeError) {
104
+ throw error;
105
+ }
106
+ throw new NecRuntimeError("runDeck failed at the WASM boundary", {
107
+ cause: error,
108
+ details: { operation: "runDeck" },
109
+ });
110
+ }
111
+ finally {
112
+ if (inputPointer !== 0) {
113
+ try {
114
+ module._free(inputPointer);
115
+ }
116
+ catch {
117
+ // Preserve the operation's result.
118
+ }
119
+ }
120
+ if (deck !== 0) {
121
+ try {
122
+ module._necpp_wasm_v1_deck_delete(deck);
123
+ }
124
+ catch {
125
+ // Cleanup is contained at the ABI.
126
+ }
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,35 @@
1
+ import type { NecModelState } from "./types.js";
2
+ export type NecErrorCode = "NEC_STATE" | "NEC_INPUT" | "NEC_GEOMETRY" | "NEC_PORT" | "NEC_SOLVER" | "NEC_CONDITIONING" | "NEC_RUNTIME";
3
+ export interface NecErrorOptions {
4
+ readonly cause?: unknown;
5
+ readonly details?: Readonly<Record<string, unknown>>;
6
+ }
7
+ /** Base class for every package-defined operational error. */
8
+ export declare class NecError<TCode extends NecErrorCode = NecErrorCode> extends Error {
9
+ readonly code: TCode;
10
+ readonly details: Readonly<Record<string, unknown>> | undefined;
11
+ constructor(code: TCode, message: string, options?: NecErrorOptions);
12
+ }
13
+ export declare class NecStateError extends NecError<"NEC_STATE"> {
14
+ readonly operation: string;
15
+ readonly state: NecModelState;
16
+ constructor(operation: string, state: NecModelState, message?: string);
17
+ }
18
+ export declare class NecInputError extends NecError<"NEC_INPUT"> {
19
+ constructor(message: string, options?: NecErrorOptions);
20
+ }
21
+ export declare class NecGeometryError extends NecError<"NEC_GEOMETRY"> {
22
+ constructor(message: string, options?: NecErrorOptions);
23
+ }
24
+ export declare class NecPortError extends NecError<"NEC_PORT"> {
25
+ constructor(message: string, options?: NecErrorOptions);
26
+ }
27
+ export declare class NecSolverError extends NecError<"NEC_SOLVER"> {
28
+ constructor(message: string, options?: NecErrorOptions);
29
+ }
30
+ export declare class NecConditioningError extends NecError<"NEC_CONDITIONING"> {
31
+ constructor(message: string, options?: NecErrorOptions);
32
+ }
33
+ export declare class NecRuntimeError extends NecError<"NEC_RUNTIME"> {
34
+ constructor(message: string, options?: NecErrorOptions);
35
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,57 @@
1
+ /** Base class for every package-defined operational error. */
2
+ export class NecError extends Error {
3
+ code;
4
+ details;
5
+ constructor(code, message, options = {}) {
6
+ super(message, { cause: options.cause });
7
+ this.name = "NecError";
8
+ this.code = code;
9
+ this.details = options.details;
10
+ }
11
+ }
12
+ export class NecStateError extends NecError {
13
+ operation;
14
+ state;
15
+ constructor(operation, state, message) {
16
+ super("NEC_STATE", message ?? `Operation ${operation} is not valid while the model is ${state}`, { details: { operation, state } });
17
+ this.name = "NecStateError";
18
+ this.operation = operation;
19
+ this.state = state;
20
+ }
21
+ }
22
+ export class NecInputError extends NecError {
23
+ constructor(message, options) {
24
+ super("NEC_INPUT", message, options ?? {});
25
+ this.name = "NecInputError";
26
+ }
27
+ }
28
+ export class NecGeometryError extends NecError {
29
+ constructor(message, options) {
30
+ super("NEC_GEOMETRY", message, options ?? {});
31
+ this.name = "NecGeometryError";
32
+ }
33
+ }
34
+ export class NecPortError extends NecError {
35
+ constructor(message, options) {
36
+ super("NEC_PORT", message, options ?? {});
37
+ this.name = "NecPortError";
38
+ }
39
+ }
40
+ export class NecSolverError extends NecError {
41
+ constructor(message, options) {
42
+ super("NEC_SOLVER", message, options ?? {});
43
+ this.name = "NecSolverError";
44
+ }
45
+ }
46
+ export class NecConditioningError extends NecError {
47
+ constructor(message, options) {
48
+ super("NEC_CONDITIONING", message, options ?? {});
49
+ this.name = "NecConditioningError";
50
+ }
51
+ }
52
+ export class NecRuntimeError extends NecError {
53
+ constructor(message, options) {
54
+ super("NEC_RUNTIME", message, options ?? {});
55
+ this.name = "NecRuntimeError";
56
+ }
57
+ }
@@ -0,0 +1,9 @@
1
+ export { NecConditioningError, NecError, NecGeometryError, NecInputError, NecPortError, NecRuntimeError, NecSolverError, NecStateError, } from "./errors.js";
2
+ export { abiVersion, engineVersion, packageVersion } from "./versions.js";
3
+ export type { NecErrorCode, NecErrorOptions } from "./errors.js";
4
+ export type { AngleSweep, CartesianPointM, CompleteGeometryOptions, ComplexMatrix, ComplexVector, ConductivityLoad, CreateNecModelOptions, CreateNecWorkerModelOptions, DeckResult, DistributedParallelRlcLoad, DistributedSeriesRlcLoad, EmbeddedFarFieldResult, EmbeddedFieldNormalization, FarFieldRequest, FarFieldResult, FiniteGround, FreeSpaceGround, GroundConnection, GroundModel, ImpedanceLoad, ImpedanceResult, LoadDefinition, NecModel, NecModelState, NecWorkerModel, NecWorkerOperation, NecWorkerProgressEvent, NecWorkerProgressListener, ParallelRlcLoad, PerfectGround, PortDefinition, PortSolution, PrepareOptions, RunDeckOptions, SegmentSelection, SeriesRlcLoad, WireDefinition, } from "./types.js";
5
+ import type { CreateNecModelOptions, DeckResult, NecModel, RunDeckOptions } from "./types.js";
6
+ /** Create an isolated stateful NEC model backed by a new WASM module instance. */
7
+ export declare function createNecModel(options?: CreateNecModelOptions): Promise<NecModel>;
8
+ /** Compatibility escape hatch for complete NEC text decks. */
9
+ export declare function runDeck(deck: string, options?: RunDeckOptions): Promise<DeckResult>;
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ export { NecConditioningError, NecError, NecGeometryError, NecInputError, NecPortError, NecRuntimeError, NecSolverError, NecStateError, } from "./errors.js";
2
+ export { abiVersion, engineVersion, packageVersion } from "./versions.js";
3
+ import { runDeckWithModule, validateDeckText } from "./deck.js";
4
+ import { NecInputError } from "./errors.js";
5
+ import { instantiateNecModule } from "./loader.js";
6
+ import { createModelFromModule } from "./model.js";
7
+ /** Create an isolated stateful NEC model backed by a new WASM module instance. */
8
+ export async function createNecModel(options) {
9
+ const module = await instantiateNecModule(options);
10
+ return createModelFromModule(module);
11
+ }
12
+ /** Compatibility escape hatch for complete NEC text decks. */
13
+ export async function runDeck(deck, options) {
14
+ validateDeckText(deck);
15
+ if (options?.signal?.aborted === true) {
16
+ throw new NecInputError("Deck execution was aborted before it started", {
17
+ details: { operation: "runDeck", aborted: true },
18
+ });
19
+ }
20
+ const module = await instantiateNecModule(options);
21
+ return runDeckWithModule(module, deck, options);
22
+ }
@@ -0,0 +1,3 @@
1
+ import type { CreateNecModelOptions } from "./types.js";
2
+ import type { NecWasmModule, NecWasmModuleFactory } from "./wasm-internal.js";
3
+ export declare function instantiateNecModule(options: CreateNecModelOptions | undefined, factory?: NecWasmModuleFactory): Promise<NecWasmModule>;