cppmodel 0.0.0-stage → 0.9.1
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/LICENSE +9 -0
- package/README.md +99 -2
- package/examples/heater.mjs +29 -0
- package/index.d.ts +125 -0
- package/index.js +475 -0
- package/licenses/IXWebSocket-LICENSE.txt +29 -0
- package/licenses/cpp-httplib-LICENSE.txt +22 -0
- package/licenses/jwt-cpp-LICENSE.txt +21 -0
- package/licenses/nlohmann-json-LICENSE.txt +21 -0
- package/licenses/openssl-LICENSE.txt +16 -0
- package/licenses/picojson-LICENSE.txt +25 -0
- package/package.json +35 -4
- package/prebuilds/darwin/libcppmodel.dylib +0 -0
- package/prebuilds/linux-arm64/libcppmodel.so.1 +0 -0
- package/prebuilds/linux-x64/libcppmodel.so.1 +0 -0
- package/prebuilds/win32-arm64/cppmodel.dll +0 -0
- package/prebuilds/win32-x64/cppmodel.dll +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
CppModel
|
|
2
|
+
Copyright (c) Control Edge AB. All rights reserved.
|
|
3
|
+
|
|
4
|
+
This package and the CppModel library it contains are proprietary software of Control Edge AB.
|
|
5
|
+
They are licensed, not sold, and may only be used under the CppModel Terms of Service:
|
|
6
|
+
https://www.cppmodel.com/Tos.html
|
|
7
|
+
|
|
8
|
+
The package also contains third-party components, each under its own license; see the licenses
|
|
9
|
+
folder.
|
package/README.md
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CppModel for Node.js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Write [CppModel](https://cppmodel.com) simulations in JavaScript or TypeScript. Every run fetches
|
|
4
|
+
the inputs and parameters posted for it in your CppModel workspace and submits its execution there,
|
|
5
|
+
just like a C or C++ simulation built with the CppModel SDK.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install cppmodel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Works on Linux (x64 and arm64, glibc 2.28+), Windows (x64 and ARM64) and macOS 11+ (Apple silicon
|
|
12
|
+
and Intel) with Node.js 18 or later. The package contains the CppModel library for each of these
|
|
13
|
+
platforms, so nothing needs compiling or installing.
|
|
14
|
+
|
|
15
|
+
## A simulation
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { Simulation } from 'cppmodel';
|
|
19
|
+
|
|
20
|
+
class Heater extends Simulation {
|
|
21
|
+
init() {
|
|
22
|
+
this.temperature = 20.0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
step(timeMs) {
|
|
26
|
+
const setpoint = this.inputs.get('Setpoint', 22.0); // input, with its fallback
|
|
27
|
+
const gain = this.parameters.get('HeaterGain', 0.05); // parameter, with its fallback
|
|
28
|
+
|
|
29
|
+
const heaterOn = this.temperature < setpoint; // the controller under test
|
|
30
|
+
this.temperature += heaterOn ? gain : -0.01;
|
|
31
|
+
|
|
32
|
+
this.outputs
|
|
33
|
+
.set('Temperature', this.temperature)
|
|
34
|
+
.set('HeaterOn', heaterOn)
|
|
35
|
+
.set('CppModel.Step', timeMs)
|
|
36
|
+
.set('CppModel.StepResult', this.temperature < setpoint + 1.0);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const simulation = new Heater('Example - Heater (Node)', 60_000, 100);
|
|
41
|
+
process.exitCode = simulation.run();
|
|
42
|
+
simulation.close();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`run()` fetches the pending inputs and parameters, calls `init()`, then `step(timeMs)` for every step
|
|
46
|
+
from 0 to `totalTimeMs`, then `finalize()`, and submits the execution. It returns 0 when every step
|
|
47
|
+
passed, otherwise the `CppModel.Step` of the first step whose `CppModel.StepResult` was 0.
|
|
48
|
+
|
|
49
|
+
An exception thrown in `init()`, `step()` or `finalize()` aborts the run, submits nothing and is
|
|
50
|
+
rethrown by `run()`.
|
|
51
|
+
|
|
52
|
+
Instead of extending `Simulation`, you can pass the functions as options; they get the simulation as
|
|
53
|
+
their first argument:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
const sim = new Simulation('Ramp', 1000, 100, {
|
|
57
|
+
step: (sim, timeMs) => sim.outputs.set('Ramp', timeMs),
|
|
58
|
+
});
|
|
59
|
+
sim.run();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`require('cppmodel')` works too, and TypeScript types are included.
|
|
63
|
+
|
|
64
|
+
## Inputs, outputs and parameters
|
|
65
|
+
|
|
66
|
+
| | |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `sim.inputs.get(name, fallback = 0)` | The input's value at the current step, or `fallback` (recorded as its value) |
|
|
69
|
+
| `sim.inputs.load([{label, x, y}, ...])` | Loads input series locally (x in ms) |
|
|
70
|
+
| `sim.outputs.set(name, value)` | Sets an output; it holds until set again and is recorded every step. Values are numbers; booleans become 1/0 |
|
|
71
|
+
| `sim.outputs.get(name)` | The output's current value |
|
|
72
|
+
| `sim.parameters.get(name, fallback)` | The parameter, storing `fallback` when it isn't set; a fetched value replaces the fallback |
|
|
73
|
+
| `sim.parameters.set(name, value)` | Sets a parameter that fetched values never replace |
|
|
74
|
+
| `sim.parameters.toObject()` | Every parameter |
|
|
75
|
+
| `sim.results()` | The execution as submitted: `{inputs, results, parameters, ...}` |
|
|
76
|
+
|
|
77
|
+
Inputs and outputs are numbers. Parameters keep their JSON type. All three have `has(name)`,
|
|
78
|
+
`keys()` and `size`.
|
|
79
|
+
|
|
80
|
+
## Things to know
|
|
81
|
+
|
|
82
|
+
- **`run()` is synchronous.** It blocks the event loop until the simulation has finished, which is
|
|
83
|
+
fine for scripts and tests. In a server, run simulations in a
|
|
84
|
+
[worker thread](https://nodejs.org/api/worker_threads.html).
|
|
85
|
+
- **Workspace access:** every run reads its inputs from and records its execution in your
|
|
86
|
+
workspace. Set `CPPMODEL_USERNAME` and `CPPMODEL_PASSWORD` for scripts and CI. Without
|
|
87
|
+
credentials, the first run prints a sign-in link and waits 60 seconds for you to sign in
|
|
88
|
+
(`CPPMODEL_LOGIN_TIMEOUT` changes that, in seconds); without a sign-in, `run()` throws.
|
|
89
|
+
- **Fingerprint:** each execution records a `CppModel.BinaryFingerprint` of the simulation's code,
|
|
90
|
+
so the workspace can tell runs of the same code from runs of changed code. It is the fingerprint
|
|
91
|
+
of the file that creates the simulation. Set `sim.fingerprintFile` to use another file.
|
|
92
|
+
- **Cleanup:** call `close()` when you're done with a simulation. One that is garbage-collected
|
|
93
|
+
is freed too.
|
|
94
|
+
- **Errors:** a failing library call throws a `CppModelError` whose `status` is one of `Status`.
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
The CppModel library is proprietary software of Control Edge AB, used under the
|
|
99
|
+
[CppModel Terms of Service](https://www.cppmodel.com/Tos.html); see `LICENSE`. The package also
|
|
100
|
+
contains third-party components under their own licenses, in `licenses/`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// A first-order heater: the controller under test switches it on below the setpoint.
|
|
2
|
+
//
|
|
3
|
+
// The same simulation as examples/minimal/simulation.cpp, in JavaScript. Run it with
|
|
4
|
+
// CPPMODEL_USERNAME and CPPMODEL_PASSWORD set to submit the execution to your workspace.
|
|
5
|
+
import { Simulation } from 'cppmodel';
|
|
6
|
+
|
|
7
|
+
class Heater extends Simulation {
|
|
8
|
+
init() {
|
|
9
|
+
this.temperature = 20.0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
step(timeMs) {
|
|
13
|
+
const setpoint = this.inputs.get('Setpoint', 22.0);
|
|
14
|
+
const gain = this.parameters.get('HeaterGain', 0.05);
|
|
15
|
+
|
|
16
|
+
const heaterOn = this.temperature < setpoint; // the controller under test
|
|
17
|
+
this.temperature += (heaterOn ? gain : -0.01) * (this.stepMs / 100.0);
|
|
18
|
+
|
|
19
|
+
this.outputs
|
|
20
|
+
.set('Temperature', this.temperature)
|
|
21
|
+
.set('HeaterOn', heaterOn)
|
|
22
|
+
.set('CppModel.Step', timeMs)
|
|
23
|
+
.set('CppModel.StepResult', this.temperature < setpoint + 1.0);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const simulation = new Heater('Example - Heater (Node)', 60_000, 100);
|
|
28
|
+
process.exitCode = simulation.run();
|
|
29
|
+
simulation.close();
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** The major C API version this package is written against. */
|
|
2
|
+
export declare const API_VERSION: 1;
|
|
3
|
+
|
|
4
|
+
/** cm_status codes of the C API. */
|
|
5
|
+
export declare const Status: {
|
|
6
|
+
readonly OK: 0;
|
|
7
|
+
readonly INVALID_ARGUMENT: 1;
|
|
8
|
+
readonly NOT_FOUND: 2;
|
|
9
|
+
readonly TYPE_MISMATCH: 3;
|
|
10
|
+
readonly INVALID_JSON: 4;
|
|
11
|
+
readonly CALLBACK: 5;
|
|
12
|
+
readonly INTERNAL: 6;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** A CppModel library call failed. */
|
|
16
|
+
export declare class CppModelError extends Error {
|
|
17
|
+
readonly status: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A JSON value, as parameters hold them. */
|
|
21
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
|
|
22
|
+
|
|
23
|
+
/** A time series: x in milliseconds. */
|
|
24
|
+
export interface Series {
|
|
25
|
+
label: string;
|
|
26
|
+
x: number[];
|
|
27
|
+
y: number[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The execution as it is (or would be) submitted. */
|
|
31
|
+
export interface Results {
|
|
32
|
+
inputs: Series[];
|
|
33
|
+
results: Series[];
|
|
34
|
+
parameters: { [name: string]: JsonValue };
|
|
35
|
+
executionTime: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export declare class Inputs {
|
|
39
|
+
/** The input's value at the current step, or `fallback` (then recorded as its value) when nothing was loaded for it. */
|
|
40
|
+
get(name: string, fallback?: number): number;
|
|
41
|
+
/** Loads series locally. */
|
|
42
|
+
load(series: Series[]): void;
|
|
43
|
+
has(name: string): boolean;
|
|
44
|
+
/** The inputs read or loaded so far, in name order. */
|
|
45
|
+
keys(): string[];
|
|
46
|
+
readonly size: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export declare class Outputs {
|
|
50
|
+
/** Sets an output; it holds until set again and is recorded every step. Booleans are stored as 1 and 0. */
|
|
51
|
+
set(name: string, value: number | boolean): this;
|
|
52
|
+
/** The output's current value; reading an output that was never set creates it with 0. */
|
|
53
|
+
get(name: string): number;
|
|
54
|
+
has(name: string): boolean;
|
|
55
|
+
keys(): string[];
|
|
56
|
+
readonly size: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export declare class Parameters {
|
|
60
|
+
/** The parameter's value, storing `fallback` first when it isn't set. A fetched value replaces a stored fallback. */
|
|
61
|
+
get<T extends JsonValue>(name: string, fallback: T): T extends number ? number : T extends string ? string : T extends boolean ? boolean : JsonValue;
|
|
62
|
+
/** Sets a parameter that fetched values never replace. */
|
|
63
|
+
set(name: string, value: JsonValue): this;
|
|
64
|
+
has(name: string): boolean;
|
|
65
|
+
keys(): string[];
|
|
66
|
+
readonly size: number;
|
|
67
|
+
/** Every parameter as a plain object. */
|
|
68
|
+
toObject(): { [name: string]: JsonValue };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface SimulationOptions<S extends Simulation = Simulation> {
|
|
72
|
+
/** When true, run() doesn't reach the workspace and then throws: a run that skips the workspace verifies nothing. */
|
|
73
|
+
offline?: boolean;
|
|
74
|
+
init?: (simulation: S) => void;
|
|
75
|
+
step?: (simulation: S, timeMs: number) => void;
|
|
76
|
+
finalize?: (simulation: S) => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A CppModel simulation. Extend it and override init(), step(timeMs) and finalize(), or pass them
|
|
81
|
+
* as options. run() is synchronous.
|
|
82
|
+
*/
|
|
83
|
+
export declare class Simulation {
|
|
84
|
+
constructor(name: string, totalTimeMs: number, stepMs: number, options?: SimulationOptions);
|
|
85
|
+
|
|
86
|
+
readonly inputs: Inputs;
|
|
87
|
+
readonly outputs: Outputs;
|
|
88
|
+
readonly parameters: Parameters;
|
|
89
|
+
|
|
90
|
+
/** Called once before the first step. */
|
|
91
|
+
init(): void;
|
|
92
|
+
/** Called every step with the simulation time in milliseconds. */
|
|
93
|
+
step(timeMs: number): void;
|
|
94
|
+
/** Called once after the last step. */
|
|
95
|
+
finalize(): void;
|
|
96
|
+
|
|
97
|
+
/** Runs the simulation: 0 when every step passed, otherwise the CppModel.Step of the first failing step (or -1). */
|
|
98
|
+
run(): number;
|
|
99
|
+
/** Deletes the simulation on the server (same as deleting it in the UI). Call before run(). */
|
|
100
|
+
resetData(): void;
|
|
101
|
+
/** The execution as it is (or would be) submitted. */
|
|
102
|
+
results(): Results;
|
|
103
|
+
/** Applies a GET /simulations/{id}/inputs response. Values already set are kept. */
|
|
104
|
+
applyInputs(response: { inputs?: Series[]; parameters?: { [name: string]: JsonValue } }): void;
|
|
105
|
+
|
|
106
|
+
name: string;
|
|
107
|
+
totalTimeMs: number;
|
|
108
|
+
stepMs: number;
|
|
109
|
+
offline: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* The file whose fingerprint is recorded as CppModel.BinaryFingerprint. Defaults to the file that
|
|
112
|
+
* created the simulation; null means the Node.js executable's fingerprint.
|
|
113
|
+
*/
|
|
114
|
+
fingerprintFile: string | null;
|
|
115
|
+
result: number;
|
|
116
|
+
readonly passed: boolean;
|
|
117
|
+
|
|
118
|
+
/** Frees the simulation. It is also freed when garbage-collected. */
|
|
119
|
+
close(): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The shared library this package loads. */
|
|
123
|
+
export declare function libraryPath(): string;
|
|
124
|
+
/** The release version of the loaded CppModel library, e.g. "0.8.0". */
|
|
125
|
+
export declare function libraryVersion(): string;
|
package/index.js
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// CppModel for Node.js: a JavaScript wrapper over the CppModel C API (include/cppmodel.h), loaded
|
|
3
|
+
// with koffi. Persistent simulations are C/C++ only.
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { fileURLToPath } = require('node:url');
|
|
6
|
+
const koffi = require('koffi');
|
|
7
|
+
|
|
8
|
+
// The major C API version this wrapper is written against; the library must report the same.
|
|
9
|
+
const API_VERSION = 1;
|
|
10
|
+
|
|
11
|
+
const Status = Object.freeze({
|
|
12
|
+
OK: 0,
|
|
13
|
+
INVALID_ARGUMENT: 1,
|
|
14
|
+
NOT_FOUND: 2,
|
|
15
|
+
TYPE_MISMATCH: 3,
|
|
16
|
+
INVALID_JSON: 4,
|
|
17
|
+
CALLBACK: 5,
|
|
18
|
+
INTERNAL: 6,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const LIBRARY_NAMES = { win32: 'cppmodel.dll', darwin: 'libcppmodel.dylib' };
|
|
22
|
+
|
|
23
|
+
/** The shared library to load: $CPPMODEL_LIBRARY, else the one bundled for this platform. */
|
|
24
|
+
function libraryPath() {
|
|
25
|
+
if (process.env.CPPMODEL_LIBRARY) return process.env.CPPMODEL_LIBRARY;
|
|
26
|
+
// One universal library serves both macOS architectures.
|
|
27
|
+
const platform = process.platform === 'darwin' ? 'darwin' : `${process.platform}-${process.arch}`;
|
|
28
|
+
return path.join(__dirname, 'prebuilds', platform, LIBRARY_NAMES[process.platform] || 'libcppmodel.so.1');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function load() {
|
|
32
|
+
const file = libraryPath();
|
|
33
|
+
let library;
|
|
34
|
+
try {
|
|
35
|
+
library = koffi.load(file);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error(`cppmodel: cannot load the CppModel library ${file} (${error.message}). ` +
|
|
38
|
+
`Reinstall the cppmodel package on a supported platform, or set CPPMODEL_LIBRARY.`);
|
|
39
|
+
}
|
|
40
|
+
// Only the functions the wrapper uses; cm_require_non_interactive is left out because it exits the process.
|
|
41
|
+
koffi.proto('int cm_callback(void *simulation, uint64_t time_ms, void *user_data)');
|
|
42
|
+
const f = (definition) => library.func(definition);
|
|
43
|
+
const c = {
|
|
44
|
+
apiVersion: f('int cm_api_version(void)'),
|
|
45
|
+
version: f('const char *cm_version(void)'),
|
|
46
|
+
lastErrorMessage: f('const char *cm_last_error_message(void)'),
|
|
47
|
+
create: f('int cm_simulation_create(const char *name, uint64_t total_time_ms, uint64_t step_ms, _Out_ void **out)'),
|
|
48
|
+
destroy: f('int cm_simulation_destroy(void *simulation)'),
|
|
49
|
+
setName: f('int cm_simulation_set_name(void *simulation, const char *name)'),
|
|
50
|
+
getName: f('int cm_simulation_get_name(void *simulation, _Out_ const char **out)'),
|
|
51
|
+
setTiming: f('int cm_simulation_set_timing(void *simulation, uint64_t total_time_ms, uint64_t step_ms)'),
|
|
52
|
+
getTiming: f('int cm_simulation_get_timing(void *simulation, _Out_ uint64_t *total_time_ms, _Out_ uint64_t *step_ms)'),
|
|
53
|
+
setRunOffline: f('int cm_simulation_set_run_offline(void *simulation, int offline)'),
|
|
54
|
+
setFingerprintFile: f('int cm_simulation_set_fingerprint_file(void *simulation, const char *path)'),
|
|
55
|
+
setInitCallback: f('int cm_simulation_set_init_callback(void *simulation, cm_callback *callback, void *user_data)'),
|
|
56
|
+
setStepCallback: f('int cm_simulation_set_step_callback(void *simulation, cm_callback *callback, void *user_data)'),
|
|
57
|
+
setFinalizeCallback: f('int cm_simulation_set_finalize_callback(void *simulation, cm_callback *callback, void *user_data)'),
|
|
58
|
+
run: f('int cm_simulation_run(void *simulation)'),
|
|
59
|
+
resetData: f('int cm_simulation_reset_data(void *simulation)'),
|
|
60
|
+
getResult: f('int cm_simulation_get_result(void *simulation, _Out_ int *out)'),
|
|
61
|
+
setResult: f('int cm_simulation_set_result(void *simulation, int result)'),
|
|
62
|
+
resultsJson: f('int cm_simulation_results_json(void *simulation, _Out_ const char **out)'),
|
|
63
|
+
applyInputsJson: f('int cm_simulation_apply_inputs_json(void *simulation, const char *json)'),
|
|
64
|
+
inputs: f('int cm_simulation_inputs(void *simulation, _Out_ void **out)'),
|
|
65
|
+
outputs: f('int cm_simulation_outputs(void *simulation, _Out_ void **out)'),
|
|
66
|
+
inputGet: f('int cm_input_get(void *inputs, const char *name, double fallback, _Out_ double *out)'),
|
|
67
|
+
inputsLoadJson: f('int cm_inputs_load_json(void *inputs, const char *series_json)'),
|
|
68
|
+
inputsContains: f('int cm_inputs_contains(void *inputs, const char *name, _Out_ int *out)'),
|
|
69
|
+
inputsCount: f('int cm_inputs_count(void *inputs, _Out_ size_t *out)'),
|
|
70
|
+
inputsKeyAt: f('int cm_inputs_key_at(void *inputs, size_t index, _Out_ const char **out)'),
|
|
71
|
+
outputSet: f('int cm_output_set(void *outputs, const char *name, double value)'),
|
|
72
|
+
outputGet: f('int cm_output_get(void *outputs, const char *name, _Out_ double *out)'),
|
|
73
|
+
outputsContains: f('int cm_outputs_contains(void *outputs, const char *name, _Out_ int *out)'),
|
|
74
|
+
outputsCount: f('int cm_outputs_count(void *outputs, _Out_ size_t *out)'),
|
|
75
|
+
outputsKeyAt: f('int cm_outputs_key_at(void *outputs, size_t index, _Out_ const char **out)'),
|
|
76
|
+
paramSetDouble: f('int cm_param_set_double(void *simulation, const char *name, double value)'),
|
|
77
|
+
paramSetJson: f('int cm_param_set_json(void *simulation, const char *name, const char *json)'),
|
|
78
|
+
paramGetJson: f('int cm_param_get_json(void *simulation, const char *name, _Out_ const char **out)'),
|
|
79
|
+
paramGetOrJson: f('int cm_param_get_or_json(void *simulation, const char *name, const char *fallback_json, _Out_ const char **out)'),
|
|
80
|
+
paramContains: f('int cm_param_contains(void *simulation, const char *name, _Out_ int *out)'),
|
|
81
|
+
paramCount: f('int cm_param_count(void *simulation, _Out_ size_t *out)'),
|
|
82
|
+
paramKeyAt: f('int cm_param_key_at(void *simulation, size_t index, _Out_ const char **out)'),
|
|
83
|
+
};
|
|
84
|
+
if (c.apiVersion() !== API_VERSION) {
|
|
85
|
+
throw new Error(`cppmodel: ${file} implements C API version ${c.apiVersion()}, this package needs version ${API_VERSION}`);
|
|
86
|
+
}
|
|
87
|
+
return c;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const c = load();
|
|
91
|
+
const callbackPointer = koffi.pointer('cm_callback');
|
|
92
|
+
|
|
93
|
+
/** A CppModel library call failed. `status` is one of the Status codes. */
|
|
94
|
+
class CppModelError extends Error {
|
|
95
|
+
constructor(status, message) {
|
|
96
|
+
super(message);
|
|
97
|
+
this.name = 'CppModelError';
|
|
98
|
+
this.status = status;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function check(status) {
|
|
103
|
+
if (status !== Status.OK) throw new CppModelError(status, c.lastErrorMessage());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Calls a C function whose last argument is an out pointer and returns what it wrote.
|
|
107
|
+
function read(fn, ...args) {
|
|
108
|
+
const out = [null];
|
|
109
|
+
check(fn(...args, out));
|
|
110
|
+
return out[0];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function keys(count, keyAt, handle) {
|
|
114
|
+
const result = [];
|
|
115
|
+
const n = read(count, handle);
|
|
116
|
+
for (let index = 0; index < n; index++) result.push(read(keyAt, handle, index));
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The simulation's inputs: time series whose value is read at the current step. */
|
|
121
|
+
class Inputs {
|
|
122
|
+
#simulation;
|
|
123
|
+
#handle;
|
|
124
|
+
|
|
125
|
+
constructor(simulation, handle) {
|
|
126
|
+
this.#simulation = simulation;
|
|
127
|
+
this.#handle = handle;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#live() {
|
|
131
|
+
this.#simulation._live();
|
|
132
|
+
return this.#handle;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The input's value at the current step, or `fallback` (then recorded as its value) when nothing was loaded for it. */
|
|
136
|
+
get(name, fallback = 0) {
|
|
137
|
+
return read(c.inputGet, this.#live(), name, Number(fallback));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Loads series [{label: name, x: [ms, ...], y: [value, ...]}, ...]. */
|
|
141
|
+
load(series) {
|
|
142
|
+
check(c.inputsLoadJson(this.#live(), JSON.stringify(series)));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
has(name) {
|
|
146
|
+
return read(c.inputsContains, this.#live(), name) !== 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The inputs read or loaded so far, in name order. */
|
|
150
|
+
keys() {
|
|
151
|
+
return keys(c.inputsCount, c.inputsKeyAt, this.#live());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
get size() {
|
|
155
|
+
return read(c.inputsCount, this.#live());
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The simulation's outputs. An output holds its value until set again and is recorded at the end of every step. */
|
|
160
|
+
class Outputs {
|
|
161
|
+
#simulation;
|
|
162
|
+
#handle;
|
|
163
|
+
|
|
164
|
+
constructor(simulation, handle) {
|
|
165
|
+
this.#simulation = simulation;
|
|
166
|
+
this.#handle = handle;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#live() {
|
|
170
|
+
this.#simulation._live();
|
|
171
|
+
return this.#handle;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Sets an output. Values are numbers; booleans are stored as 1 and 0. */
|
|
175
|
+
set(name, value) {
|
|
176
|
+
check(c.outputSet(this.#live(), name, Number(value)));
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** The output's current value; reading an output that was never set creates it with 0. */
|
|
181
|
+
get(name) {
|
|
182
|
+
return read(c.outputGet, this.#live(), name);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
has(name) {
|
|
186
|
+
return read(c.outputsContains, this.#live(), name) !== 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
keys() {
|
|
190
|
+
return keys(c.outputsCount, c.outputsKeyAt, this.#live());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
get size() {
|
|
194
|
+
return read(c.outputsCount, this.#live());
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** The simulation's parameters. They keep their JSON type: numbers, booleans, strings, arrays, objects or null. */
|
|
199
|
+
class Parameters {
|
|
200
|
+
#simulation;
|
|
201
|
+
|
|
202
|
+
constructor(simulation) {
|
|
203
|
+
this.#simulation = simulation;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The parameter's value, storing `fallback` first when it isn't set. A fetched value replaces a
|
|
208
|
+
* stored fallback; a value set with set() is never replaced.
|
|
209
|
+
*/
|
|
210
|
+
get(name, fallback) {
|
|
211
|
+
if (fallback === undefined) throw new TypeError(`parameters.get("${name}"): a fallback is required`);
|
|
212
|
+
return JSON.parse(read(c.paramGetOrJson, this.#simulation._live(), name, JSON.stringify(fallback)));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Sets a parameter that fetched values never replace. */
|
|
216
|
+
set(name, value) {
|
|
217
|
+
const handle = this.#simulation._live();
|
|
218
|
+
if (typeof value === 'number' && !Number.isInteger(value)) check(c.paramSetDouble(handle, name, value));
|
|
219
|
+
else check(c.paramSetJson(handle, name, JSON.stringify(value)));
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
has(name) {
|
|
224
|
+
return read(c.paramContains, this.#simulation._live(), name) !== 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
keys() {
|
|
228
|
+
return keys(c.paramCount, c.paramKeyAt, this.#simulation._live());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
get size() {
|
|
232
|
+
return read(c.paramCount, this.#simulation._live());
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Every parameter as a plain object. */
|
|
236
|
+
toObject() {
|
|
237
|
+
const handle = this.#simulation._live();
|
|
238
|
+
const result = {};
|
|
239
|
+
for (const name of this.keys()) result[name] = JSON.parse(read(c.paramGetJson, handle, name));
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// The file that created a simulation: the first call site outside this package, else the main script.
|
|
245
|
+
function callerFile() {
|
|
246
|
+
const prepare = Error.prepareStackTrace;
|
|
247
|
+
try {
|
|
248
|
+
Error.prepareStackTrace = (_error, stack) => stack;
|
|
249
|
+
for (const site of new Error().stack) {
|
|
250
|
+
const file = site.getFileName();
|
|
251
|
+
if (!file || file === __filename || file.startsWith('node:')) continue;
|
|
252
|
+
return file.startsWith('file:') ? fileURLToPath(file) : file;
|
|
253
|
+
}
|
|
254
|
+
} finally {
|
|
255
|
+
Error.prepareStackTrace = prepare;
|
|
256
|
+
}
|
|
257
|
+
return process.argv[1];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Frees simulations that were garbage-collected without close().
|
|
261
|
+
const finalizer = new FinalizationRegistry((handle) => c.destroy(handle));
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* A CppModel simulation.
|
|
265
|
+
*
|
|
266
|
+
* Either extend it and override init(), step(timeMs) and finalize(), or pass those as options
|
|
267
|
+
* taking the simulation as their first argument.
|
|
268
|
+
*
|
|
269
|
+
* run() fetches the pending inputs and parameters from the workspace, calls init(), then step()
|
|
270
|
+
* for every step from 0 to totalTimeMs, then finalize(), and submits the execution. An exception
|
|
271
|
+
* thrown in init(), step() or finalize() aborts the run (nothing is submitted) and is rethrown by
|
|
272
|
+
* run(). run() is synchronous: it blocks the event loop until the simulation has finished.
|
|
273
|
+
*/
|
|
274
|
+
class Simulation {
|
|
275
|
+
#handle;
|
|
276
|
+
#offline = false;
|
|
277
|
+
#fingerprintFile = null;
|
|
278
|
+
#functions;
|
|
279
|
+
|
|
280
|
+
constructor(name, totalTimeMs, stepMs, { offline = false, init, step, finalize } = {}) {
|
|
281
|
+
this.#handle = read(c.create, name, totalTimeMs, stepMs);
|
|
282
|
+
finalizer.register(this, this.#handle, this);
|
|
283
|
+
this.#functions = { init, step, finalize };
|
|
284
|
+
this.offline = offline;
|
|
285
|
+
const caller = callerFile();
|
|
286
|
+
const file = caller ? path.resolve(caller) : null;
|
|
287
|
+
if (file && c.setFingerprintFile(this.#handle, file) === Status.OK) this.#fingerprintFile = file;
|
|
288
|
+
this.inputs = new Inputs(this, read(c.inputs, this.#handle));
|
|
289
|
+
this.outputs = new Outputs(this, read(c.outputs, this.#handle));
|
|
290
|
+
this.parameters = new Parameters(this);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Overridables ─────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
/** Called once before the first step. */
|
|
296
|
+
init() {
|
|
297
|
+
if (this.#functions.init) this.#functions.init(this);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Called every step with the simulation time in milliseconds. */
|
|
301
|
+
step(timeMs) {
|
|
302
|
+
if (this.#functions.step) this.#functions.step(this, timeMs);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Called once after the last step. */
|
|
306
|
+
finalize() {
|
|
307
|
+
if (this.#functions.finalize) this.#functions.finalize(this);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── Running ──────────────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Runs the simulation and returns its result: 0 when every step passed, otherwise the
|
|
314
|
+
* CppModel.Step of the first failing step (or -1).
|
|
315
|
+
*/
|
|
316
|
+
run() {
|
|
317
|
+
const handle = this._live();
|
|
318
|
+
let failure = null;
|
|
319
|
+
// A callback that throws must return an error status itself: koffi would otherwise let the
|
|
320
|
+
// C run continue (and submit) and only rethrow at the end.
|
|
321
|
+
const register = (method) => koffi.register((_simulation, timeMs) => {
|
|
322
|
+
try {
|
|
323
|
+
method(Number(timeMs));
|
|
324
|
+
return Status.OK;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
failure = { error };
|
|
327
|
+
return Status.CALLBACK;
|
|
328
|
+
}
|
|
329
|
+
}, callbackPointer);
|
|
330
|
+
// Registered only while running, so the simulation holds no callbacks that keep it alive.
|
|
331
|
+
const callbacks = [register(() => this.init()), register((t) => this.step(t)), register(() => this.finalize())];
|
|
332
|
+
try {
|
|
333
|
+
check(c.setInitCallback(handle, callbacks[0], null));
|
|
334
|
+
check(c.setStepCallback(handle, callbacks[1], null));
|
|
335
|
+
check(c.setFinalizeCallback(handle, callbacks[2], null));
|
|
336
|
+
const status = c.run(handle);
|
|
337
|
+
if (failure) throw failure.error;
|
|
338
|
+
check(status);
|
|
339
|
+
return this.result;
|
|
340
|
+
} finally {
|
|
341
|
+
c.setInitCallback(handle, null, null);
|
|
342
|
+
c.setStepCallback(handle, null, null);
|
|
343
|
+
c.setFinalizeCallback(handle, null, null);
|
|
344
|
+
callbacks.forEach((callback) => koffi.unregister(callback));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Deletes the simulation on the server (same as deleting it in the UI). Call before run(). */
|
|
349
|
+
resetData() {
|
|
350
|
+
check(c.resetData(this._live()));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** The execution as it is (or would be) submitted: {inputs, results, parameters, ...}. */
|
|
354
|
+
results() {
|
|
355
|
+
return JSON.parse(read(c.resultsJson, this._live()));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Applies a GET /simulations/{id}/inputs response ({inputs: [...], parameters: {...}}). Values already set are kept. */
|
|
359
|
+
applyInputs(response) {
|
|
360
|
+
check(c.applyInputsJson(this._live(), JSON.stringify(response)));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ── Properties ───────────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
get name() {
|
|
366
|
+
return read(c.getName, this._live());
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
set name(value) {
|
|
370
|
+
check(c.setName(this._live(), value));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#timing() {
|
|
374
|
+
const total = [0];
|
|
375
|
+
const step = [0];
|
|
376
|
+
check(c.getTiming(this._live(), total, step));
|
|
377
|
+
return [total[0], step[0]];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
get totalTimeMs() {
|
|
381
|
+
return this.#timing()[0];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
set totalTimeMs(value) {
|
|
385
|
+
check(c.setTiming(this._live(), value, this.stepMs));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
get stepMs() {
|
|
389
|
+
return this.#timing()[1];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
set stepMs(value) {
|
|
393
|
+
check(c.setTiming(this._live(), this.totalTimeMs, value));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* When true, run() doesn't reach the workspace: nothing is fetched or submitted, and run() then
|
|
398
|
+
* throws, because a run that skips the workspace verifies nothing.
|
|
399
|
+
*/
|
|
400
|
+
get offline() {
|
|
401
|
+
return this.#offline;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
set offline(value) {
|
|
405
|
+
check(c.setRunOffline(this._live(), value ? 1 : 0));
|
|
406
|
+
this.#offline = Boolean(value);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* The file whose fingerprint is recorded as CppModel.BinaryFingerprint, so that runs of the same
|
|
411
|
+
* code can be told apart from runs of changed code. Defaults to the file that created the
|
|
412
|
+
* simulation; null means the Node.js executable's fingerprint.
|
|
413
|
+
*/
|
|
414
|
+
get fingerprintFile() {
|
|
415
|
+
return this.#fingerprintFile;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
set fingerprintFile(file) {
|
|
419
|
+
const resolved = file === null || file === undefined ? null : path.resolve(file);
|
|
420
|
+
check(c.setFingerprintFile(this._live(), resolved));
|
|
421
|
+
this.#fingerprintFile = resolved;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** 0 when every step passed; otherwise the CppModel.Step of the first failing step (or -1). */
|
|
425
|
+
get result() {
|
|
426
|
+
return read(c.getResult, this._live());
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
set result(value) {
|
|
430
|
+
check(c.setResult(this._live(), value));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
get passed() {
|
|
434
|
+
return this.result === 0;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ── Lifetime ─────────────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
/** @internal */
|
|
440
|
+
_live() {
|
|
441
|
+
if (this.#handle === null) throw new Error('the simulation is closed');
|
|
442
|
+
return this.#handle;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Frees the simulation. It is also freed when garbage-collected. */
|
|
446
|
+
close() {
|
|
447
|
+
if (this.#handle === null) return;
|
|
448
|
+
finalizer.unregister(this);
|
|
449
|
+
c.destroy(this.#handle);
|
|
450
|
+
this.#handle = null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (typeof Symbol.dispose === 'symbol') {
|
|
455
|
+
Simulation.prototype[Symbol.dispose] = function dispose() {
|
|
456
|
+
this.close();
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** The release version of the loaded CppModel library, e.g. "0.8.0". */
|
|
461
|
+
function libraryVersion() {
|
|
462
|
+
return c.version();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
module.exports = {
|
|
466
|
+
API_VERSION,
|
|
467
|
+
CppModelError,
|
|
468
|
+
Inputs,
|
|
469
|
+
Outputs,
|
|
470
|
+
Parameters,
|
|
471
|
+
Simulation,
|
|
472
|
+
Status,
|
|
473
|
+
libraryPath,
|
|
474
|
+
libraryVersion,
|
|
475
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are
|
|
5
|
+
met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright
|
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the
|
|
13
|
+
distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived
|
|
17
|
+
from this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
20
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
21
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
22
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
23
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
24
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
25
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
26
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
27
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
28
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 yhirose
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Dominik Thalhammer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013-2025 Niels Lohmann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
|
|
4
|
+
Copyright (c) OpenSSL Project Contributors
|
|
5
|
+
|
|
6
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
you may not use this file except in compliance with the License.
|
|
8
|
+
You may obtain a copy of the License at
|
|
9
|
+
|
|
10
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
|
|
12
|
+
Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
See the License for the specific language governing permissions and
|
|
16
|
+
limitations under the License.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Copyright 2009-2010 Cybozu Labs, Inc.
|
|
2
|
+
Copyright 2011-2014 Kazuho Oku
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
this list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
18
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
19
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
20
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
21
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
22
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
23
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
24
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
25
|
+
POSSIBILITY OF SUCH DAMAGE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cppmodel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.9.1",
|
|
4
|
+
"description": "Write CppModel simulations in Node.js",
|
|
5
|
+
"keywords": ["cppmodel", "simulation", "plant model", "testing", "embedded"],
|
|
6
|
+
"homepage": "https://cppmodel.com",
|
|
7
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
8
|
+
"author": "Control Edge AB <support@cedge.se>",
|
|
9
|
+
"main": "index.js",
|
|
10
|
+
"types": "index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./index.d.ts",
|
|
14
|
+
"default": "./index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"index.js",
|
|
19
|
+
"index.d.ts",
|
|
20
|
+
"README.md",
|
|
21
|
+
"examples/",
|
|
22
|
+
"prebuilds/",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"licenses/"
|
|
25
|
+
],
|
|
26
|
+
"os": ["linux", "win32", "darwin"],
|
|
27
|
+
"cpu": ["x64", "arm64"],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"koffi": "3.3.2"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "node --test test/simulation.test.js"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|