Compare commits

..

1 Commits

Author SHA1 Message Date
znetsixe
dbc36c2f57 cleaned up method 2025-06-12 17:04:02 +02:00
129 changed files with 4258 additions and 3841 deletions

View File

@@ -1,3 +0,0 @@
export function getAssetVariables() {
}

View File

@@ -1,243 +0,0 @@
// ChildRegistrationUtils.js
class ChildRegistrationUtils {
constructor(mainClass) {
this.mainClass = mainClass; // Reference to the main class
this.logger = mainClass.logger;
}
async registerChild(child, positionVsParent) {
const { softwareType } = child.config.functionality;
const { name, id, unit } = child.config.general;
const { type = "", subType = "" } = child.config.asset || {};
const emitter = child.emitter;
//define position vs parent in child
child.positionVsParent = positionVsParent;
child.parent = this.mainClass;
if (!this.mainClass.child) this.mainClass.child = {};
if (!this.mainClass.child[softwareType])
this.mainClass.child[softwareType] = {};
if (!this.mainClass.child[softwareType][type])
this.mainClass.child[softwareType][type] = {};
if (!this.mainClass.child[softwareType][type][subType])
this.mainClass.child[softwareType][type][subType] = {};
// Use an array to handle multiple subtypes
if (!Array.isArray(this.mainClass.child[softwareType][type][subType])) {
this.mainClass.child[softwareType][type][subType] = [];
}
// Update the child in the cloud when available and supply the new child on base of tagcode OLIFANT WE NEED TO FIX THIS SO ITS DYNAMIC!
/*
try{
const url = "https://pimmoerman.nl/rdlab/tagcode.app/v2.1/api/asset/create_asset.php?";
const TagCode = child.config.asset.tagCode;
//console.log(`Register child => ${TagCode}`);
const completeURL = url + `asset_product_model_id=1&asset_product_model_uuid=123456789&asset_name=AssetNaam&asset_description=Beschrijving&asset_status=actief&asset_profile_id=1&asset_location_id=1&asset_process_id=11&asset_tag_number=${TagCode}&child_assets=[L6616]`;
await fetch(completeURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
}catch(e){
console.log("Error saving assetID and tagnumber", e);
}*/
// Push the new child to the array of the mainclass so we can track the childs
this.mainClass.child[softwareType][type][subType].push({
name,
id,
unit,
emitter,
});
//then connect the child depending on the type subtype etc..
this.connectChild(
id,
softwareType,
emitter,
type,
child,
subType,
positionVsParent
);
}
connectChild(
id,
softwareType,
emitter,
type,
child,
subType,
positionVsParent
) {
this.logger.debug(
`Connecting child id=${id}: desc=${softwareType}, type=${type},subType=${subType}, position=${positionVsParent}`
);
switch (softwareType) {
case "measurement":
this.logger.debug(
`Registering measurement child: ${id} with type=${type}`
);
this.connectMeasurement(child, subType, positionVsParent);
break;
case "machine":
this.logger.debug(`Registering complete machine child: ${id}`);
this.connectMachine(child);
break;
case "valve":
this.logger.debug(`Registering complete valve child: ${id}`);
this.connectValve(child);
break;
case "actuator":
this.logger.debug(`Registering linear actuator child: ${id}`);
this.connectActuator(child,positionVsParent);
break;
default:
this.logger.error(`Child registration unrecognized desc: ${desc}`);
this.logger.error(`Unrecognized softwareType: ${softwareType}`);
}
}
connectMeasurement(child, subType, position) {
this.logger.debug(
`Connecting measurement child: ${subType} with position=${position}`
);
// Check if subType is valid
if (!subType) {
this.logger.error(`Invalid subType for measurement: ${subType}`);
return;
}
// initialize the measurement to a number - logging each step for debugging
try {
this.logger.debug(
`Initializing measurement: ${subType}, position: ${position} value: 0`
);
const typeResult = this.mainClass.measurements.type(subType);
const variantResult = typeResult.variant("measured");
const positionResult = variantResult.position(position);
positionResult.value(0);
this.logger.debug(
`Subscribing on mAbs event for measurement: ${subType}, position: ${position}`
);
// Listen for the mAbs event and update the measurement
this.logger.debug(
`Successfully initialized measurement: ${subType}, position: ${position}`
);
} catch (error) {
this.logger.error(`Failed to initialize measurement: ${error.message}`);
return;
}
child.emitter.on("mAbs", (value) => {
// Use the same method chaining approach that worked during initialization
this.mainClass.measurements
.type(subType)
.variant("measured")
.position(position)
.value(value);
this.mainClass.updateMeasurement("measured", subType, value, position);
//this.logger.debug(`--------->>>>>>>>>Updated measurement: ${subType}, value: ${value}, position: ${position}`);
});
}
connectMachine(machine) {
if (!machine) {
this.logger.error("Invalid machine provided.");
return;
}
const machineId = Object.keys(this.mainClass.machines).length + 1;
this.mainClass.machines[machineId] = machine;
this.logger.info(
`Setting up pressureChange listener for machine ${machineId}`
);
machine.emitter.on("pressureChange", () =>
this.mainClass.handlePressureChange(machine)
);
//update of child triggers the handler
this.mainClass.handleChildChange();
this.logger.info(`Machine ${machineId} registered successfully.`);
}
connectValve(valve) {
if (!valve) {
this.logger.warn("Invalid valve provided.");
return;
}
const valveId = Object.keys(this.mainClass.valves).length + 1;
this.mainClass.valves[valveId] = valve; // Gooit valve object in de valves attribute met valve objects
valve.state.emitter.on("positionChange", (data) => {
//ValveGroupController abboneren op klepstand verandering
this.mainClass.logger.debug(`Position change of valve detected: ${data}`);
this.mainClass.calcValveFlows();
}); //bepaal nieuwe flow per valve
valve.emitter.on("deltaPChange", () => {
this.mainClass.logger.debug("DeltaP change of valve detected");
this.mainClass.calcMaxDeltaP();
}); //bepaal nieuwe max deltaP
this.logger.info(`Valve ${valveId} registered successfully.`);
}
connectActuator(actuator, positionVsParent) {
if (!actuator) {
this.logger.warn("Invalid actuator provided.");
return;
}
//Special case gateGroupControl
if (
this.mainClass.config.functionality.softwareType == "gateGroupControl"
) {
if (Object.keys(this.mainClass.actuators).length < 2) {
if (positionVsParent == "downstream") {
this.mainClass.actuators[0] = actuator;
}
if (positionVsParent == "upstream") {
this.mainClass.actuators[1] = actuator;
}
//define emitters
actuator.state.emitter.on("positionChange", (data) => {
this.mainClass.logger.debug(`Position change of actuator detected: ${data}`);
this.mainClass.eventUpdate();
});
//define emitters
actuator.state.emitter.on("stateChange", (data) => {
this.mainClass.logger.debug(`State change of actuator detected: ${data}`);
this.mainClass.eventUpdate();
});
} else {
this.logger.error(
"Too many actuators registered. Only two are allowed."
);
}
}
}
//wanneer hij deze ontvangt is deltaP van een van de valves veranderd (kan ook zijn niet child zijn, maar dat maakt niet uit)
}
module.exports = ChildRegistrationUtils;

View File

@@ -1,94 +0,0 @@
/**
* @file configUtils.js
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to use it for personal
* or non-commercial purposes, with the following restrictions:
*
* 1. **No Copying or Redistribution**: The Software or any of its parts may not
* be copied, merged, distributed, sublicensed, or sold without explicit
* prior written permission from the author.
*
* 2. **Commercial Use**: Any use of the Software for commercial purposes requires
* a valid license, obtainable only with the explicit consent of the author.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
* OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Ownership of this code remains solely with the original author. Unauthorized
* use of this Software is strictly prohibited.
*
* @summary Utility for managing and validating configuration values.
* @description Utility for managing and validating configuration values.
* @module ConfigUtils
* @requires ValidationUtils
* @requires Logger
* @exports ConfigUtils
* @version 0.1.0
* @since 0.1.0
*/
const ValidationUtils = require("./validationUtils");
const Logger = require("./logger");
class ConfigUtils {
constructor(defaultConfig, IloggerEnabled , IloggerLevel) {
const loggerEnabled = IloggerEnabled || true;
const loggerLevel = IloggerLevel || "warn";
this.logger = new Logger(loggerEnabled, loggerLevel, 'ConfigUtils');
this.defaultConfig = defaultConfig;
this.validationUtils = new ValidationUtils(loggerEnabled, loggerLevel);
}
// Initialize configuration
initConfig(config) {
this.logger.info("Initializing configuration...");
// Validate the provided configuration
const validatedConfig = this.validationUtils.validateSchema(config, this.defaultConfig, this.defaultConfig.functionality.softwareType.default);
this.logger.info("Configuration initialized successfully.");
return validatedConfig;
}
// Update configuration
updateConfig(currentConfig, newConfig) {
this.logger.info("Updating configuration...");
// Merge 2 configs and validate the result
const mergedConfig = this.mergeObjects(currentConfig, newConfig);
// Merge current configuration with new values
const updatedConfig = this.validationUtils.validateSchema(mergedConfig, this.defaultConfig, this.defaultConfig.functionality.softwareType.default);
this.logger.info("Configuration updated successfully.");
return updatedConfig;
}
// loop through objects and merge them obj1 will be updated with obj2 values
mergeObjects(obj1, obj2) {
for (let key in obj2) {
if (obj2.hasOwnProperty(key)) {
if (typeof obj2[key] === 'object') {
if (!obj1[key]) {
obj1[key] = {};
}
this.mergeObjects(obj1[key], obj2[key]);
} else {
obj1[key] = obj2[key];
}
}
}
return obj1;
}
}
module.exports = ConfigUtils;

View File

@@ -1,57 +0,0 @@
class Logger {
constructor(logging = true, logLevel = 'debug', nameModule = 'N/A') {
this.logging = logging; // Boolean flag to enable/disable logging
this.logLevel = logLevel; // Default log level: 'debug', 'info', 'warn', 'error'
this.levels = ['debug', 'info', 'warn', 'error']; // Log levels in order of severity
this.nameModule = nameModule; // Name of the module that uses the logger
}
// Helper function to check if a log message should be displayed based on current log level
shouldLog(level) {
return this.levels.indexOf(level) >= this.levels.indexOf(this.logLevel);
}
// Log a debug message
debug(message) {
if (this.logging && this.shouldLog('debug')) {
console.debug(`[DEBUG] -> ${this.nameModule}: ${message}`);
}
}
// Log an info message
info(message) {
if (this.logging && this.shouldLog('info')) {
console.info(`[INFO] -> ${this.nameModule}: ${message}`);
}
}
// Log a warning message
warn(message) {
if (this.logging && this.shouldLog('warn')) {
console.warn(`[WARN] -> ${this.nameModule}: ${message}`);
}
}
// Log an error message
error(message) {
if (this.logging && this.shouldLog('error')) {
console.error(`[ERROR] -> ${this.nameModule}: ${message}`);
}
}
// Set the log level dynamically
setLogLevel(level) {
if (this.levels.includes(level)) {
this.logLevel = level;
} else {
console.error(`[ERROR ${nameModule}]: Invalid log level: ${level}`);
}
}
// Toggle the logging on or off
toggleLogging() {
this.logging = !this.logging;
}
}
module.exports = Logger;

View File

@@ -1,187 +0,0 @@
// Add unit conversion support
const convertModule = require('../../../convert/dependencies/index');
class Measurement {
constructor(type, variant, position, windowSize) {
this.type = type; // e.g. 'pressure', 'flow', etc.
this.variant = variant; // e.g. 'predicted' or 'measured', etc..
this.position = position; // Downstream or upstream of parent object
this.windowSize = windowSize; // Rolling window size
// Place all data inside an array
this.values = []; // Array to store all values
this.timestamps = []; // Array to store all timestamps
// Unit tracking
this.unit = null; // Current unit of measurement
// For tracking differences if this is a calculated difference measurement
this.isDifference = false;
this.sourcePositions = [];
}
// -- Updater methods --
setType(type) {
this.type = type;
return this;
}
setVariant(variant) {
this.variant = variant;
return this;
}
setPosition(position) {
this.position = position;
return this;
}
setValue(value, timestamp = Date.now()) {
/*
if (value === undefined || value === null) {
value = null ;
//throw new Error('Value cannot be null or undefined');
}
*/
//shift the oldest value
if(this.values.length >= this.windowSize){
this.values.shift();
this.timestamps.shift();
}
//push the new value
this.values.push(value);
this.timestamps.push(timestamp);
return this;
}
setUnit(unit) {
this.unit = unit;
return this;
}
// -- Getter methods --
getCurrentValue() {
if (this.values.length === 0) return null;
return this.values[this.values.length - 1];
}
getAverage() {
if (this.values.length === 0) return null;
const sum = this.values.reduce((acc, val) => acc + val, 0);
return sum / this.values.length;
}
getMin() {
if (this.values.length === 0) return null;
return Math.min(...this.values);
}
getMax() {
if (this.values.length === 0) return null;
return Math.max(...this.values);
}
getAllValues() {
return {
values: [...this.values],
timestamps: [...this.timestamps],
unit: this.unit
};
}
// -- Position-based methods --
// Create a new measurement that is the difference between two positions
static createDifference(upstreamMeasurement, downstreamMeasurement) {
console.log('hello:');
if (upstreamMeasurement.type !== downstreamMeasurement.type ||
upstreamMeasurement.variant !== downstreamMeasurement.variant) {
throw new Error('Cannot calculate difference between different measurement types or variants');
}
// Ensure units match
let downstream = downstreamMeasurement;
if (upstreamMeasurement.unit && downstream.unit &&
upstreamMeasurement.unit !== downstream.unit) {
downstream = downstream.convertTo(upstreamMeasurement.unit);
}
// Create a new difference measurement
const diffMeasurement = new Measurement(
upstreamMeasurement.type,
upstreamMeasurement.variant,
'difference',
Math.min(upstreamMeasurement.windowSize, downstream.windowSize)
);
// Mark as a difference measurement and keep track of sources
diffMeasurement.isDifference = true;
diffMeasurement.sourcePositions = ['upstream', 'downstream'];
// Calculate all differences where timestamps match
const upValues = upstreamMeasurement.getAllValues();
const downValues = downstream.getAllValues();
// Match timestamps and calculate differences
for (let i = 0; i < upValues.timestamps.length; i++) {
const upTimestamp = upValues.timestamps[i];
const downIndex = downValues.timestamps.indexOf(upTimestamp);
if (downIndex !== -1) {
const diff = upValues.values[i] - downValues.values[downIndex];
diffMeasurement.setValue(diff, upTimestamp);
}
}
diffMeasurement.setUnit(upstreamMeasurement.unit);
return diffMeasurement;
}
// -- Additional getter methods --
getLatestTimestamp() {
if (this.timestamps.length === 0) return null;
return this.timestamps[this.timestamps.length - 1];
}
getValueAtTimestamp(timestamp) {
const index = this.timestamps.indexOf(timestamp);
if (index === -1) return null;
return this.values[index];
}
// -- Unit conversion methods --
convertTo(targetUnit) {
if (!this.unit) {
throw new Error('Current unit not set, cannot convert');
}
try {
const convertedValues = this.values.map(value =>
convertModule.convert(value).from(this.unit).to(targetUnit)
);
const newMeasurement = new Measurement(
this.type,
this.variant,
this.position,
this.windowSize
);
// Copy values and timestamps
newMeasurement.values = convertedValues;
newMeasurement.timestamps = [...this.timestamps];
newMeasurement.unit = targetUnit;
return newMeasurement;
} catch (error) {
throw new Error(`Unit conversion failed: ${error.message}`);
}
}
}
module.exports = Measurement;

View File

@@ -1,56 +0,0 @@
const Measurement = require('./Measurement');
class MeasurementBuilder {
constructor() {
this.type = null;
this.variant = null;
this.position = null;
this.windowSize = 10; // Default window size
}
// e.g. 'pressure', 'flow', etc.
setType(type) {
this.type = type;
return this;
}
// e.g. 'predicted' or 'measured', etc..
setVariant(variant) {
this.variant = variant;
return this;
}
// Downstream or upstream of parent object
setPosition(position) {
this.position = position;
return this;
}
// default size of the data that gets tracked
setWindowSize(windowSize) {
this.windowSize = windowSize;
return this;
}
build() {
// Validate required fields
if (!this.type) {
throw new Error('Measurement type is required');
}
if (!this.variant) {
throw new Error('Measurement variant is required');
}
if (!this.position) {
throw new Error('Measurement position is required');
}
return new Measurement(
this.type,
this.variant,
this.position,
this.windowSize
);
}
}
module.exports = MeasurementBuilder;

View File

@@ -1,200 +0,0 @@
const MeasurementBuilder = require('./MeasurementBuilder');
class MeasurementContainer {
constructor(options = {}, logger) {
this.logger = logger;
this.measurements = {};
this.windowSize = options.windowSize || 10; // Default window size
// For chaining context
this._currentType = null;
this._currentVariant = null;
this._currentPosition = null;
}
// Chainable methods
type(typeName) {
this._currentType = typeName;
this._currentVariant = null;
this._currentPosition = null;
return this;
}
variant(variantName) {
if (!this._currentType) {
throw new Error('Type must be specified before variant');
}
this._currentVariant = variantName;
this._currentPosition = null;
return this;
}
position(positionName) {
if (!this._currentVariant) {
throw new Error('Variant must be specified before position');
}
this._currentPosition = positionName;
return this;
}
// Core methods that complete the chain
value(val, timestamp = Date.now()) {
if (!this._ensureChainIsValid()) return this;
const measurement = this._getOrCreateMeasurement();
measurement.setValue(val, timestamp);
return this;
}
unit(unitName) {
if (!this._ensureChainIsValid()) return this;
const measurement = this._getOrCreateMeasurement();
measurement.setUnit(unitName);
return this;
}
// Terminal operations - get data out
get() {
if (!this._ensureChainIsValid()) return null;
return this._getOrCreateMeasurement();
}
getCurrentValue() {
const measurement = this.get();
return measurement ? measurement.getCurrentValue() : null;
}
getAverage() {
const measurement = this.get();
return measurement ? measurement.getAverage() : null;
}
getMin() {
const measurement = this.get();
return measurement ? measurement.getMin() : null;
}
getMax() {
const measurement = this.get();
return measurement ? measurement.getMax() : null;
}
getAllValues() {
const measurement = this.get();
return measurement ? measurement.getAllValues() : null;
}
// Difference calculations between positions
difference() {
if (!this._currentType || !this._currentVariant) {
throw new Error('Type and variant must be specified for difference calculation');
}
// Save position to restore chain state after operation
const savedPosition = this._currentPosition;
// Get upstream measurement
this._currentPosition = 'upstream';
const upstream = this.get();
// Get downstream measurement
this._currentPosition = 'downstream';
const downstream = this.get();
// Restore chain state
this._currentPosition = savedPosition;
if (!upstream || !downstream || upstream.values.length === 0 || downstream.values.length === 0) {
return null;
}
// Ensure units match
let downstreamForCalc = downstream;
if (upstream.unit && downstream.unit && upstream.unit !== downstream.unit) {
try {
downstreamForCalc = downstream.convertTo(upstream.unit);
} catch (error) {
if (this.logger) {
this.logger.error(`Unit conversion failed: ${error.message}`);
}
return null;
}
}
return {
value: downstreamForCalc.getCurrentValue() - upstream.getCurrentValue() ,
avgDiff: downstreamForCalc.getAverage() - upstream.getAverage() ,
unit: upstream.unit
};
}
// Helper methods
_ensureChainIsValid() {
if (!this._currentType || !this._currentVariant || !this._currentPosition) {
if (this.logger) {
this.logger.error('Incomplete measurement chain, required: type, variant, and position');
}
return false;
}
return true;
}
_getOrCreateMeasurement() {
// Initialize nested structure if needed
if (!this.measurements[this._currentType]) {
this.measurements[this._currentType] = {};
}
if (!this.measurements[this._currentType][this._currentVariant]) {
this.measurements[this._currentType][this._currentVariant] = {};
}
if (!this.measurements[this._currentType][this._currentVariant][this._currentPosition]) {
this.measurements[this._currentType][this._currentVariant][this._currentPosition] =
new MeasurementBuilder()
.setType(this._currentType)
.setVariant(this._currentVariant)
.setPosition(this._currentPosition)
.setWindowSize(this.windowSize)
.build();
}
return this.measurements[this._currentType][this._currentVariant][this._currentPosition];
}
// Additional utility methods
getTypes() {
return Object.keys(this.measurements);
}
getVariants() {
if (!this._currentType) {
throw new Error('Type must be specified before listing variants');
}
return this.measurements[this._currentType] ?
Object.keys(this.measurements[this._currentType]) : [];
}
getPositions() {
if (!this._currentType || !this._currentVariant) {
throw new Error('Type and variant must be specified before listing positions');
}
if (!this.measurements[this._currentType] ||
!this.measurements[this._currentType][this._currentVariant]) {
return [];
}
return Object.keys(this.measurements[this._currentType][this._currentVariant]);
}
clear() {
this.measurements = {};
this._currentType = null;
this._currentVariant = null;
this._currentPosition = null;
}
}
module.exports = MeasurementContainer;

View File

@@ -1,89 +0,0 @@
# Measurement System Documentation
This system provides a flexible way to store, retrieve, and analyze measurement data using a chainable API.
## Basic Usage
```javascript
const { MeasurementContainer } = require('./index');
const container = new MeasurementContainer({ windowSize: 20 });
// Set values
container.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
// Get values
const upstreamPressure = container.type('pressure').variant('measured').position('upstream').getCurrentValue();
console.log(`Upstream pressure: ${upstreamPressure}`);
```
## Chainable API Methods
### Setting Context
- `type(typeName)` - Set the measurement type (pressure, flow, etc.)
- `variant(variantName)` - Set the variant (measured, predicted, etc.)
- `position(positionName)` - Set the position (upstream, downstream, etc.)
### Setting Data
- `value(val, [timestamp])` - Add a value with optional timestamp
- `unit(unitName)` - Set the measurement unit
### Getting Data
- `get()` - Get the measurement object
- `getCurrentValue()` - Get the most recent value
- `getAverage()` - Calculate average of all values
- `getMin()` - Get minimum value
- `getMax()` - Get maximum value
### Calculations
- `difference()` - Calculate difference between upstream and downstream positions
### Listing Available Data
- `getTypes()` - Get all measurement types
- `listVariants()` - List variants for current type
- `listPositions()` - List positions for current type and variant
## Example Workflows
### Setting and retrieving values
```javascript
// Set a measurement
container.type('flow')
.variant('measured')
.position('upstream')
.value(120)
.unit('gpm');
// Retrieve the same measurement
const flow = container.type('flow')
.variant('measured')
.position('upstream')
.getCurrentValue();
```
### Calculating differences
```javascript
// Set upstream and downstream measurements
container.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
container.type('pressure').variant('measured').position('downstream').value(95).unit('psi');
// Calculate the difference
const diff = container.type('pressure').variant('measured').difference();
console.log(`Pressure drop: ${diff.currentDiff} ${diff.unit}`);
```
### Working with historical data
```javascript
// Add multiple values
container.type('temperature')
.variant('measured')
.position('outlet')
.value(72)
.value(74)
.value(73)
.unit('F');
// Get statistics
const avg = container.type('temperature').variant('measured').position('outlet').getAverage();
const min = container.type('temperature').variant('measured').position('outlet').getMin();
const max = container.type('temperature').variant('measured').position('outlet').getMax();
```

View File

@@ -1,58 +0,0 @@
const { MeasurementContainer } = require('./index');
// Create a container
const container = new MeasurementContainer({ windowSize: 20 });
// Example 1: Setting values with chaining
console.log('--- Example 1: Setting values ---');
container.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
container.type('pressure').variant('measured').position('downstream').value(95).unit('psi');
container.type('pressure').variant('measured').position('downstream').value(80);
// Example 2: Getting values with chaining
console.log('--- Example 2: Getting values ---');
const upstreamValue = container.type('pressure').variant('measured').position('upstream').getCurrentValue();
const upstreamUnit = container.type('pressure').variant('measured').position('upstream').get().unit;
console.log(`Upstream pressure: ${upstreamValue} ${upstreamUnit}`);
const downstreamValue = container.type('pressure').variant('measured').position('downstream').getCurrentValue();
const downstreamUnit = container.type('pressure').variant('measured').position('downstream').get().unit;
console.log(`Downstream pressure: ${downstreamValue} ${downstreamUnit}`);
// Example 3: Calculations using chained methods
console.log('--- Example 3: Calculations ---');
container.type('flow').variant('predicted').position('upstream').value(200).unit('gpm');
container.type('flow').variant('predicted').position('downstream').value(195).unit('gpm');
const flowAvg = container.type('flow').variant('predicted').position('upstream').getAverage();
console.log(`Average upstream flow: ${flowAvg} gpm`);
// Example 4: Getting pressure difference
console.log('--- Example 4: Difference calculations ---');
const pressureDiff = container.type('pressure').variant('measured').difference();
console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}`);
// Example 5: Adding multiple values to track history
console.log('--- Example 5: Multiple values ---');
// Add several values to upstream flow
container.type('flow').variant('measured').position('upstream')
.value(210).value(215).value(205).unit('gpm');
// Then get statistics
console.log('Flow statistics:');
console.log(`- Current: ${container.type('flow').variant('measured').position('upstream').getCurrentValue()} gpm`);
console.log(`- Average: ${container.type('flow').variant('measured').position('upstream').getAverage()} gpm`);
console.log(`- Min: ${container.type('flow').variant('measured').position('upstream').getMin()} gpm`);
console.log(`- Max: ${container.type('flow').variant('measured').position('upstream').getMax()} gpm`);
console.log(`Show all values : ${JSON.stringify(container.type('flow').variant('measured').position('upstream').getAllValues())}`);
// Example 6: Listing available data
console.log('--- Example 6: Listing available data ---');
console.log('Types:', container.getTypes());
console.log('Pressure variants:', container.type('pressure').getVariants());
console.log('Measured pressure positions:', container.type('pressure').variant('measured').getPositions());
module.exports = {
runExamples: () => {
console.log('Examples of the measurement chainable API');
}
};

View File

@@ -1,9 +0,0 @@
const MeasurementContainer = require('./MeasurementContainer');
const Measurement = require('./Measurement');
const MeasurementBuilder = require('./MeasurementBuilder');
module.exports = {
MeasurementContainer,
Measurement,
MeasurementBuilder
};

View File

@@ -1,489 +0,0 @@
class MenuUtils {
initBasicToggles(elements) {
// Toggle visibility for log level
elements.logCheckbox.addEventListener("change", function () {
elements.rowLogLevel.style.display = this.checked ? "block" : "none";
});
elements.rowLogLevel.style.display = elements.logCheckbox.checked
? "block"
: "none";
}
// Define the initialize toggles function within scope
initMeasurementToggles(elements) {
// Toggle visibility for scaling inputs
elements.scalingCheckbox.addEventListener("change", function () {
elements.rowInputMin.style.display = this.checked ? "block" : "none";
elements.rowInputMax.style.display = this.checked ? "block" : "none";
});
// Set initial states
elements.rowInputMin.style.display = elements.scalingCheckbox.checked
? "block"
: "none";
elements.rowInputMax.style.display = elements.scalingCheckbox.checked
? "block"
: "none";
}
initTensionToggles(elements, node) {
const currentMethod = node.interpolationMethod;
elements.rowTension.style.display =
currentMethod === "monotone_cubic_spline" ? "block" : "none";
console.log(
"Initial tension row display: ",
elements.rowTension.style.display
);
elements.interpolationMethodInput.addEventListener("change", function () {
const selectedMethod = this.value;
console.log(`Interpolation method changed: ${selectedMethod}`);
node.interpolationMethod = selectedMethod;
// Toggle visibility for tension input
elements.rowTension.style.display =
selectedMethod === "monotone_cubic_spline" ? "block" : "none";
console.log("Tension row display: ", elements.rowTension.style.display);
});
}
// Define the smoothing methods population function within scope
populateSmoothingMethods(configUrls, elements, node) {
this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const smoothingMethods =
configData.smoothing?.smoothMethod?.rules?.values?.map(
(o) => o.value
) || [];
this.populateDropdown(
elements.smoothMethod,
smoothingMethods,
node,
"smooth_method"
);
})
.catch((err) => {
console.error("Error loading smoothing methods", err);
});
}
populateInterpolationMethods(configUrls, elements, node) {
this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const interpolationMethods =
configData?.interpolation?.type?.rules?.values.map((m) => m.value) ||
[];
this.populateDropdown(
elements.interpolationMethodInput,
interpolationMethods,
node,
"interpolationMethod"
);
// Find the selected method and use it to spawn 1 more field to fill in tension
//const selectedMethod = interpolationMethods.find(m => m === node.interpolationMethod);
this.initTensionToggles(elements, node);
})
.catch((err) => {
console.error("Error loading interpolation methods", err);
});
}
populateLogLevelOptions(logLevelSelect, configData, node) {
// debug log level
//console.log("Displaying configData => ", configData) ;
const logLevels =
configData?.general?.logging?.logLevel?.rules?.values?.map(
(l) => l.value
) || [];
//console.log("Displaying logLevels => ", logLevels);
// Reuse your existing generic populateDropdown helper
this.populateDropdown(logLevelSelect, logLevels, node.logLevel);
}
//cascade dropdowns for asset type, supplier, subType, model, unit
fetchAndPopulateDropdowns(configUrls, elements, node) {
this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const assetType = configData.asset?.type?.default;
const localSuppliersUrl = this.constructUrl(configUrls.local.taggcodeAPI,`${assetType}s`,"suppliers.json");
const cloudSuppliersUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/vendor/get_vendors.php");
return this.fetchData(cloudSuppliersUrl, localSuppliersUrl)
.then((supplierData) => {
const suppliers = supplierData.map((supplier) => supplier.name);
// Populate suppliers dropdown and set up its change handler
return this.populateDropdown(
elements.supplier,
suppliers,
node,
"supplier",
function (selectedSupplier) {
if (selectedSupplier) {
this.populateSubTypes(configUrls, elements, node, selectedSupplier);
}
}
);
})
.then(() => {
// If we have a saved supplier, trigger subTypes population
if (node.supplier) {
this.populateSubTypes(configUrls, elements, node, node.supplier);
}
});
})
.catch((error) => {
console.error("Error in initial dropdown population:", error);
});
}
getSpecificConfigUrl(nodeName,cloudAPI) {
const cloudConfigURL = cloudAPI + "/config/" + nodeName + ".json";
const localConfigURL = "http://localhost:1880/"+ nodeName + "/dependencies/"+ nodeName + "/" + nodeName + "Config.json";
return { cloudConfigURL, localConfigURL };
}
// Save changes to API
async apiCall(node) {
try{
// OLFIANT when a browser refreshes the tag code is lost!!! fix this later!!!!!
// FIX UUID ALSO LATER
if(node.assetTagCode !== "" || node.assetTagCode !== null){ }
// API call to register or check asset in central database
let assetregisterAPI = node.configUrls.cloud.taggcodeAPI + "/asset/create_asset.php";
const assetModelId = node.modelMetadata.id; //asset_product_model_id
const uuid = node.uuid; //asset_product_model_uuid
const assetName = node.assetType; //asset_name / type?
const description = node.name; // asset_description
const assetStatus = "actief"; //asset_status -> koppel aan enable / disable node ? or make dropdown ?
const assetProfileId = 1; //asset_profile_id these are the rules to check if the childs are valid under this node (parent / child id?)
const child_assets = ["63247"]; //child_assets tagnummer of id?
const assetProcessId = node.processId; //asset_process_id
const assetLocationId = node.locationId; //asset_location_id
const tagCode = node.assetTagCode; // if already exists in the node information use it to tell the api it exists and it will update else we will get it from the api call
//console.log(`this is my tagCode: ${tagCode}`);
// Build base URL with required parameters
let apiUrl = `?asset_product_model_id=${assetModelId}&asset_product_model_uuid=${uuid}&asset_name=${assetName}&asset_description=${description}&asset_status=${assetStatus}&asset_profile_id=${assetProfileId}&asset_location_id=${assetLocationId}&asset_process_id=${assetProcessId}&child_assets=${child_assets}`;
// Only add tagCode to URL if it exists
if (tagCode) {
apiUrl += `&asset_tag_number=${tagCode}`;
console.log('hello there');
}
assetregisterAPI += apiUrl;
console.log("API call to register asset in central database", assetregisterAPI);
const response = await fetch(assetregisterAPI, {
method: "POST"
});
// Get the response text first
const responseText = await response.text();
console.log("Raw API response:", responseText);
// Try to parse the JSON, handling potential parsing errors
let jsonResponse;
try {
jsonResponse = JSON.parse(responseText);
} catch (parseError) {
console.error("JSON Parsing Error:", parseError);
console.error("Response that could not be parsed:", responseText);
throw new Error("Failed to parse API response");
}
console.log(jsonResponse);
if(jsonResponse.success){
console.log(`${jsonResponse.message}, tag number: ${jsonResponse.asset_tag_number}, asset id: ${jsonResponse.asset_id}`);
// Save the asset tag number and id to the node
} else {
console.log("Asset not registered in central database");
}
return jsonResponse;
} catch (error) {
console.log("Error saving changes to asset register API", error);
}
}
async fetchData(url, fallbackUrl) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const responsData = await response.json();
//responsData
const data = responsData.data;
/* .map(item => {
const { vendor_name, ...rest } = item;
return {
name: vendor_name,
...rest
};
}); */
console.log(url);
console.log("Response Data: ", data);
return data;
} catch (err) {
console.warn(
`Primary URL failed: ${url}. Trying fallback URL: ${fallbackUrl}`,
err
);
try {
const response = await fetch(fallbackUrl);
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
} catch (fallbackErr) {
console.error("Both primary and fallback URLs failed:", fallbackErr);
return [];
}
}
}
async fetchProjectData(url) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const responsData = await response.json();
console.log("Response Data: ", responsData);
return responsData;
} catch (err) {
}
}
async populateDropdown(
htmlElement,
options,
node,
property,
callback
) {
this.generateHtml(htmlElement, options, node[property]);
htmlElement.addEventListener("change", async (e) => {
const newValue = e.target.value;
console.log(`Dropdown changed: ${property} = ${newValue}`);
node[property] = newValue;
RED.nodes.dirty(true);
if (callback) await callback(newValue); // Ensure async callback completion
});
}
// Helper function to construct a URL from a base and path internal
constructUrl(base, ...paths) {
// Remove trailing slash from base and leading slashes from paths
const sanitizedBase = (base || "").replace(/\/+$/, "");
const sanitizedPaths = paths.map((path) => path.replace(/^\/+|\/+$/g, ""));
// Join sanitized base and paths
const url = `${sanitizedBase}/${sanitizedPaths.join("/")}`;
console.log("Base:", sanitizedBase);
console.log("Paths:", sanitizedPaths);
console.log("Constructed URL:", url);
return url;
}
//Adjust for API Gateway
constructCloudURL(base, ...paths) {
// Remove trailing slash from base and leading slashes from paths
const sanitizedBase = base.replace(/\/+$/, "");
const sanitizedPaths = paths.map((path) => path.replace(/^\/+|\/+$/g, ""));
// Join sanitized base and paths
const url = `${sanitizedBase}/${sanitizedPaths.join("/")}`;
return url;
}
populateSubTypes(configUrls, elements, node, selectedSupplier) {
this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const assetType = configData.asset?.type?.default;
const supplierFolder = this.constructUrl( configUrls.local.taggcodeAPI, `${assetType}s`, selectedSupplier );
const localSubTypesUrl = this.constructUrl(supplierFolder, "subtypes.json");
const cloudSubTypesUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_subtypesFromVendor.php?vendor_name=" + selectedSupplier);
return this.fetchData(cloudSubTypesUrl, localSubTypesUrl)
.then((subTypeData) => {
const subTypes = subTypeData.map((subType) => subType.name);
return this.populateDropdown(
elements.subType,
subTypes,
node,
"subType",
function (selectedSubType) {
if (selectedSubType) {
// When subType changes, update both models and units
this.populateModels(
configUrls,
elements,
node,
selectedSupplier,
selectedSubType
);
this.populateUnitsForSubType(
configUrls,
elements,
node,
selectedSubType
);
}
}
);
})
.then(() => {
// If we have a saved subType, trigger both models and units population
if (node.subType) {
this.populateModels(
configUrls,
elements,
node,
selectedSupplier,
node.subType
);
this.populateUnitsForSubType(configUrls, elements, node, node.subType);
}
//console.log("In fetch part of subtypes ");
// Store all data from selected model
/* node["modelMetadata"] = modelData.find(
(model) => model.name === node.model
);
console.log("Model Metadata: ", node["modelMetadata"]); */
});
})
.catch((error) => {
console.error("Error populating subtypes:", error);
});
}
populateUnitsForSubType(configUrls, elements, node, selectedSubType) {
// Fetch the units data
this.fetchData(configUrls.cloud.units, configUrls.local.units)
.then((unitsData) => {
// Find the category that matches the subType name
const categoryData = unitsData.units.find(
(category) =>
category.category.toLowerCase() === selectedSubType.toLowerCase()
);
if (categoryData) {
// Extract just the unit values and descriptions
const units = categoryData.values.map((unit) => ({
value: unit.value,
description: unit.description,
}));
// Create the options array with descriptions as labels
const options = units.map((unit) => ({
value: unit.value,
label: `${unit.value} - ${unit.description}`,
}));
// Populate the units dropdown
this.populateDropdown(
elements.unit,
options.map((opt) => opt.value),
node,
"unit"
);
// If there's no currently selected unit but we have options, select the first one
if (!node.unit && options.length > 0) {
node.unit = options[0].value;
elements.unit.value = options[0].value;
}
} else {
// If no matching category is found, provide a default % option
const defaultUnits = [{ value: "%", description: "Percentage" }];
this.populateDropdown(
elements.unit,
defaultUnits.map((unit) => unit.value),
node,
"unit"
);
console.warn(
`No matching unit category found for subType: ${selectedSubType}`
);
}
})
.catch((error) => {
console.error("Error fetching units:", error);
});
}
populateModels(
configUrls,
elements,
node,
selectedSupplier,
selectedSubType
) {
this.fetchData(configUrls.cloud.config, configUrls.local.config)
.then((configData) => {
const assetType = configData.asset?.type?.default;
// save assetType to fetch later
node.assetType = assetType;
const supplierFolder = this.constructUrl( configUrls.local.taggcodeAPI,`${assetType}s`,selectedSupplier);
const subTypeFolder = this.constructUrl(supplierFolder, selectedSubType);
const localModelsUrl = this.constructUrl(subTypeFolder, "models.json");
const cloudModelsUrl = this.constructCloudURL(configUrls.cloud.taggcodeAPI, "/product/get_product_models.php?vendor_name=" + selectedSupplier + "&product_subtype_name=" + selectedSubType);
return this.fetchData(cloudModelsUrl, localModelsUrl).then((modelData) => {
const models = modelData.map((model) => model.name); // use this to populate the dropdown
// If a model is already selected, store its metadata immediately
if (node.model) {
node["modelMetadata"] = modelData.find((model) => model.name === node.model);
}
this.populateDropdown(elements.model, models, node, "model", (selectedModel) => {
// Store only the metadata for the selected model
node["modelMetadata"] = modelData.find((model) => model.name === selectedModel);
});
/*
console.log('hello here I am:');
console.log(node["modelMetadata"]);
*/
});
})
.catch((error) => {
console.error("Error populating models:", error);
});
}
generateHtml(htmlElement, options, savedValue) {
htmlElement.innerHTML = options.length
? `<option value="">Select...</option>${options
.map((opt) => `<option value="${opt}">${opt}</option>`)
.join("")}`
: "<option value=''>No options available</option>";
if (savedValue && options.includes(savedValue)) {
htmlElement.value = savedValue;
}
}
}
module.exports = MenuUtils;

View File

@@ -1,56 +0,0 @@
const nodeTemplates = {
asset: {
category: "digital asset",
color: "#4f8582",
defaults: {
name: { value: "", required: true },
enableLog: { value: false },
logLevel: { value: "error" },
parent: { value: "downstream" }, // indicates the position vs the parent in the process downstream,upstream or none.
supplier: { value: "" },
subType: { value: "" },
model: { value: "" },
unit: { value: "" },
},
inputs: 1,
outputs: 3,
inputLabels: ["Machine Input"],
outputLabels: ["process", "dbase", "parent"],
icon: "font-awesome/fa-cogs",
elements: {
// Basic fields
name: "node-input-name",
// Logging fields
logCheckbox: "node-input-enableLog",
logLevelSelect: "node-input-logLevel",
rowLogLevel: "row-logLevel",
// Asset fields
supplier: "node-input-supplier",
subType: "node-input-subType",
model: "node-input-model",
unit: "node-input-unit",
//position vs parent
parent: "node-input-parent",
},
projectSettingsURL:
"http://localhost:1880/generalFunctions/settings/projectSettings.json",
},
exampleTemplate: {
category: "digital twin",
color: "#004080",
defaults: {
name: { value: "", required: true },
foo: { value: 42 },
},
inputs: 2,
outputs: 2,
inputLabels: ["In A", "In B"],
outputLabels: ["Out A", "Out B"],
icon: "font-awesome/fa-gears",
},
// …add more node “templates” here…
};
export default nodeTemplates;

View File

@@ -1,297 +0,0 @@
const ErrorMetrics = require('./errorMetrics');
// Dummy logger for tests
const logger = {
error: console.error,
debug: console.log,
info: console.log
};
const config = {
thresholds: {
NRMSE_LOW: 0.05,
NRMSE_MEDIUM: 0.10,
NRMSE_HIGH: 0.15,
LONG_TERM_LOW: 0.02,
LONG_TERM_MEDIUM: 0.04,
LONG_TERM_HIGH: 0.06
}
};
class ErrorMetricsTester {
constructor() {
this.totalTests = 0;
this.passedTests = 0;
this.failedTests = 0;
this.errorMetrics = new ErrorMetrics(config, logger);
}
assert(condition, message) {
this.totalTests++;
if (condition) {
console.log(`✓ PASS: ${message}`);
this.passedTests++;
} else {
console.log(`✗ FAIL: ${message}`);
this.failedTests++;
}
}
testMeanSquaredError() {
console.log("\nTesting Mean Squared Error...");
const predicted = [1, 2, 3];
const measured = [1, 3, 5];
const mse = this.errorMetrics.meanSquaredError(predicted, measured);
this.assert(Math.abs(mse - 1.67) < 0.1, "MSE correctly calculated");
}
testRootMeanSquaredError() {
console.log("\nTesting Root Mean Squared Error...");
const predicted = [1, 2, 3];
const measured = [1, 3, 5];
const rmse = this.errorMetrics.rootMeanSquaredError(predicted, measured);
this.assert(Math.abs(rmse - 1.29) < 0.1, "RMSE correctly calculated");
}
testNormalizedRMSE() {
console.log("\nTesting Normalized RMSE...");
const predicted = [100, 102, 104];
const measured = [98, 103, 107];
const processMin = 90, processMax = 110;
const nrmse = this.errorMetrics.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax);
this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalized RMSE calculated correctly");
}
testNormalizeUsingRealtime() {
console.log("\nTesting Normalize Using Realtime...");
const predicted = [100, 102, 104];
const measured = [98, 103, 107];
try {
const nrmse = this.errorMetrics.normalizeUsingRealtime(predicted, measured);
this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalize using realtime calculated correctly");
} catch (error) {
this.assert(false, `Normalize using realtime failed: ${error.message}`);
}
// Test with identical values to check error handling
const sameValues = [100, 100, 100];
try {
this.errorMetrics.normalizeUsingRealtime(sameValues, sameValues);
this.assert(false, "Should throw error with identical values");
} catch (error) {
this.assert(true, "Correctly throws error when min/max are the same");
}
}
testLongTermNRMSD() {
console.log("\nTesting Long Term NRMSD Accumulation...");
// Reset the accumulation values
this.errorMetrics.cumNRMSD = 0;
this.errorMetrics.cumCount = 0;
let lastValue = 0;
for (let i = 0; i < 100; i++) {
lastValue = this.errorMetrics.longTermNRMSD(0.1 + i * 0.001);
}
this.assert(
this.errorMetrics.cumCount === 100 &&
this.errorMetrics.cumNRMSD !== 0 &&
lastValue !== 0,
"Long term NRMSD accumulates over 100 iterations"
);
// Test that values are returned only after accumulating 100 samples
this.errorMetrics.cumNRMSD = 0;
this.errorMetrics.cumCount = 0;
for (let i = 0; i < 99; i++) {
const result = this.errorMetrics.longTermNRMSD(0.1);
this.assert(result === 0, "No longTermNRMSD returned before 100 samples");
}
// Use a different value for the 100th sample to ensure a non-zero result
const result = this.errorMetrics.longTermNRMSD(0.2);
this.assert(result !== 0, "longTermNRMSD returned after 100 samples");
}
testDetectImmediateDrift() {
console.log("\nTesting Immediate Drift Detection...");
// Test high drift
let drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_HIGH + 0.01);
this.assert(drift.level === 3, "Detects high immediate drift correctly");
// Test medium drift
drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_MEDIUM + 0.01);
this.assert(drift.level === 2, "Detects medium immediate drift correctly");
// Test low drift
drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW + 0.01);
this.assert(drift.level === 1, "Detects low immediate drift correctly");
// Test no drift
drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW - 0.01);
this.assert(drift.level === 0, "Detects no immediate drift correctly");
}
testDetectLongTermDrift() {
console.log("\nTesting Long Term Drift Detection...");
// Test high drift
let drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_HIGH + 0.01);
this.assert(drift.level === 3, "Detects high long-term drift correctly");
// Test medium drift
drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_MEDIUM + 0.01);
this.assert(drift.level === 2, "Detects medium long-term drift correctly");
// Test low drift
drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW + 0.01);
this.assert(drift.level === 1, "Detects low long-term drift correctly");
// Test no drift
drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW - 0.01);
this.assert(drift.level === 0, "Detects no long-term drift correctly");
// Test negative drift values
drift = this.errorMetrics.detectLongTermDrift(-config.thresholds.LONG_TERM_HIGH - 0.01);
this.assert(drift.level === 3, "Detects negative high long-term drift correctly");
}
testDriftDetection() {
console.log("\nTesting Combined Drift Detection...");
let nrmseHigh = config.thresholds.NRMSE_HIGH + 0.01;
let ltNRMSD = 0;
let result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
this.assert(
result !== null &&
result.ImmDrift &&
result.ImmDrift.level === 3 &&
result.LongTermDrift.level === 0,
"Detects high immediate drift with no long-term drift"
);
nrmseHigh = config.thresholds.NRMSE_LOW - 0.01;
ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01;
result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
this.assert(
result !== null &&
result.ImmDrift.level === 0 &&
result.LongTermDrift &&
result.LongTermDrift.level === 2,
"Detects medium long-term drift with no immediate drift"
);
nrmseHigh = config.thresholds.NRMSE_MEDIUM + 0.01;
ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01;
result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
this.assert(
result.ImmDrift.level === 2 &&
result.LongTermDrift.level === 2,
"Detects both medium immediate and medium long-term drift"
);
nrmseHigh = config.thresholds.NRMSE_LOW - 0.01;
ltNRMSD = config.thresholds.LONG_TERM_LOW - 0.01;
result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
this.assert(
result.ImmDrift.level === 0 &&
result.LongTermDrift.level === 0,
"No significant drift detected when under thresholds"
);
}
testAssessDrift() {
console.log("\nTesting assessDrift function...");
// Reset accumulation for testing
this.errorMetrics.cumNRMSD = 0;
this.errorMetrics.cumCount = 0;
const predicted = [100, 101, 102, 103];
const measured = [90, 91, 92, 93];
const processMin = 90, processMax = 110;
let result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax);
this.assert(
result !== null &&
typeof result.nrmse === 'number' &&
typeof result.longTermNRMSD === 'number' &&
typeof result.immediateLevel === 'number' &&
typeof result.immediateFeedback === 'string' &&
typeof result.longTermLevel === 'number' &&
typeof result.longTermFeedback === 'string',
"assessDrift returns complete result structure"
);
this.assert(
result.immediateLevel > 0,
"assessDrift detects immediate drift with significant difference"
);
// Test with identical values
result = this.errorMetrics.assessDrift(predicted, predicted, processMin, processMax);
this.assert(
result.nrmse === 0 &&
result.immediateLevel === 0,
"assessDrift indicates no immediate drift when predicted equals measured"
);
// Test with slight drift
const measuredSlight = [100, 100.5, 101, 101.5];
result = this.errorMetrics.assessDrift(predicted, measuredSlight, processMin, processMax);
this.assert(
result !== null &&
result.nrmse < 0.05 &&
(result.immediateLevel < 2),
"assessDrift returns appropriate levels for slight drift"
);
// Test long-term drift accumulation
for (let i = 0; i < 100; i++) {
this.errorMetrics.assessDrift(
predicted,
measured.map(m => m + (Math.random() * 2 - 1)), // Add small random fluctuation
processMin,
processMax
);
}
result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax);
this.assert(
result.longTermNRMSD !== 0,
"Long-term drift accumulates over multiple assessments"
);
}
async runAllTests() {
console.log("\nStarting Error Metrics Tests...\n");
this.testMeanSquaredError();
this.testRootMeanSquaredError();
this.testNormalizedRMSE();
this.testNormalizeUsingRealtime();
this.testLongTermNRMSD();
this.testDetectImmediateDrift();
this.testDetectLongTermDrift();
this.testDriftDetection();
this.testAssessDrift();
console.log("\nTest Summary:");
console.log(`Total Tests: ${this.totalTests}`);
console.log(`Passed: ${this.passedTests}`);
console.log(`Failed: ${this.failedTests}`);
process.exit(this.failedTests > 0 ? 1 : 0);
}
}
// Run all tests
const tester = new ErrorMetricsTester();
tester.runAllTests().catch(console.error);

View File

@@ -1,154 +0,0 @@
//load local dependencies
const EventEmitter = require('events');
//load all config modules
const defaultConfig = require('./nrmseConfig.json');
const ConfigUtils = require('../configUtils');
class ErrorMetrics {
constructor(config = {}, logger) {
this.emitter = new EventEmitter(); // Own EventEmitter
this.configUtils = new ConfigUtils(defaultConfig);
this.config = this.configUtils.initConfig(config);
// Init after config is set
this.logger = logger;
// For long-term NRMSD accumulation
this.cumNRMSD = 0;
this.cumCount = 0;
}
//INCLUDE timestamps in the next update OLIFANT
meanSquaredError(predicted, measured) {
if (predicted.length !== measured.length) {
this.logger.error("Comparing MSE Arrays must have the same length.");
return 0;
}
let sumSqError = 0;
for (let i = 0; i < predicted.length; i++) {
const err = predicted[i] - measured[i];
sumSqError += err * err;
}
return sumSqError / predicted.length;
}
rootMeanSquaredError(predicted, measured) {
return Math.sqrt(this.meanSquaredError(predicted, measured));
}
normalizedRootMeanSquaredError(predicted, measured, processMin, processMax) {
const range = processMax - processMin;
if (range <= 0) {
this.logger.error("Invalid process range: processMax must be greater than processMin.");
}
const rmse = this.rootMeanSquaredError(predicted, measured);
return rmse / range;
}
longTermNRMSD(input) {
const storedNRMSD = this.cumNRMSD;
const storedCount = this.cumCount;
const newCount = storedCount + 1;
// Update cumulative values
this.cumCount = newCount;
// Calculate new running average
if (storedCount === 0) {
this.cumNRMSD = input; // First value
} else {
// Running average formula: newAvg = oldAvg + (newValue - oldAvg) / newCount
this.cumNRMSD = storedNRMSD + (input - storedNRMSD) / newCount;
}
if(newCount >= 100) {
// Return the current NRMSD value, not just the contribution from this sample
return this.cumNRMSD;
}
return 0;
}
normalizeUsingRealtime(predicted, measured) {
const realtimeMin = Math.min(Math.min(...predicted), Math.min(...measured));
const realtimeMax = Math.max(Math.max(...predicted), Math.max(...measured));
const range = realtimeMax - realtimeMin;
if (range <= 0) {
throw new Error("Invalid process range: processMax must be greater than processMin.");
}
const rmse = this.rootMeanSquaredError(predicted, measured);
return rmse / range;
}
detectImmediateDrift(nrmse) {
let ImmDrift = {};
this.logger.debug(`checking immediate drift with thresholds : ${this.config.thresholds.NRMSE_HIGH} ${this.config.thresholds.NRMSE_MEDIUM} ${this.config.thresholds.NRMSE_LOW}`);
switch (true) {
case( nrmse > this.config.thresholds.NRMSE_HIGH ) :
ImmDrift = {level : 3 , feedback : "High immediate drift detected"};
break;
case( nrmse > this.config.thresholds.NRMSE_MEDIUM ) :
ImmDrift = {level : 2 , feedback : "Medium immediate drift detected"};
break;
case(nrmse > this.config.thresholds.NRMSE_LOW ):
ImmDrift = {level : 1 , feedback : "Low immediate drift detected"};
break;
default:
ImmDrift = {level : 0 , feedback : "No drift detected"};
}
return ImmDrift;
}
detectLongTermDrift(longTermNRMSD) {
let LongTermDrift = {};
this.logger.debug(`checking longterm drift with thresholds : ${this.config.thresholds.LONG_TERM_HIGH} ${this.config.thresholds.LONG_TERM_MEDIUM} ${this.config.thresholds.LONG_TERM_LOW}`);
switch (true) {
case(Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_HIGH) :
LongTermDrift = {level : 3 , feedback : "High long-term drift detected"};
break;
case (Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_MEDIUM) :
LongTermDrift = {level : 2 , feedback : "Medium long-term drift detected"};
break;
case ( Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_LOW ) :
LongTermDrift = {level : 1 , feedback : "Low long-term drift detected"};
break;
default:
LongTermDrift = {level : 0 , feedback : "No drift detected"};
}
return LongTermDrift;
}
detectDrift(nrmse, longTermNRMSD) {
const ImmDrift = this.detectImmediateDrift(nrmse);
const LongTermDrift = this.detectLongTermDrift(longTermNRMSD);
return { ImmDrift, LongTermDrift };
}
// asses the drift
assessDrift(predicted, measured, processMin, processMax) {
// Compute NRMSE and check for immediate drift
const nrmse = this.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax);
this.logger.debug(`NRMSE: ${nrmse}`);
// cmopute long-term NRMSD and add result to cumalitve NRMSD
const longTermNRMSD = this.longTermNRMSD(nrmse);
// return the drift
// Return the drift assessment object
const driftAssessment = this.detectDrift(nrmse, longTermNRMSD);
return {
nrmse,
longTermNRMSD,
immediateLevel: driftAssessment.ImmDrift.level,
immediateFeedback: driftAssessment.ImmDrift.feedback,
longTermLevel: driftAssessment.LongTermDrift.level,
longTermFeedback: driftAssessment.LongTermDrift.feedback
};
}
}
module.exports = ErrorMetrics;

View File

@@ -1,138 +0,0 @@
{
"general": {
"name": {
"default": "ErrorMetrics",
"rules": {
"type": "string",
"description": "A human-readable name for the configuration."
}
},
"id": {
"default": null,
"rules": {
"type": "string",
"nullable": true,
"description": "A unique identifier for this configuration, assigned dynamically when needed."
}
},
"unit": {
"default": "unitless",
"rules": {
"type": "string",
"description": "The unit used for the state values (e.g., 'meters', 'seconds', 'unitless')."
}
},
"logging": {
"logLevel": {
"default": "info",
"rules": {
"type": "enum",
"values": [
{
"value": "debug",
"description": "Log messages are printed for debugging purposes."
},
{
"value": "info",
"description": "Informational messages are printed."
},
{
"value": "warn",
"description": "Warning messages are printed."
},
{
"value": "error",
"description": "Error messages are printed."
}
]
}
},
"enabled": {
"default": true,
"rules": {
"type": "boolean",
"description": "Indicates whether logging is active. If true, log messages will be generated."
}
}
}
},
"functionality": {
"softwareType": {
"default": "errorMetrics",
"rules": {
"type": "string",
"description": "Logical name identifying the software type."
}
},
"role": {
"default": "error calculation",
"rules": {
"type": "string",
"description": "Functional role within the system."
}
}
},
"mode": {
"current": {
"default": "active",
"rules": {
"type": "enum",
"values": [
{
"value": "active",
"description": "The error metrics calculation is active."
},
{
"value": "inactive",
"description": "The error metrics calculation is inactive."
}
],
"description": "The operational mode of the error metrics calculation."
}
}
},
"thresholds": {
"NRMSE_LOW": {
"default": 0.05,
"rules": {
"type": "number",
"description": "Low threshold for normalized root mean squared error."
}
},
"NRMSE_MEDIUM": {
"default": 0.10,
"rules": {
"type": "number",
"description": "Medium threshold for normalized root mean squared error."
}
},
"NRMSE_HIGH": {
"default": 0.15,
"rules": {
"type": "number",
"description": "High threshold for normalized root mean squared error."
}
},
"LONG_TERM_LOW": {
"default": 0.02,
"rules": {
"type": "number",
"description": "Low threshold for long-term normalized root mean squared deviation."
}
},
"LONG_TERM_MEDIUM": {
"default": 0.04,
"rules": {
"type": "number",
"description": "Medium threshold for long-term normalized root mean squared deviation."
}
},
"LONG_TERM_HIGH": {
"default": 0.06,
"rules": {
"type": "number",
"description": "High threshold for long-term normalized root mean squared deviation."
}
}
}
}

View File

@@ -1,89 +0,0 @@
class DynamicClusterDeviation {
constructor() {
this.clusters = []; // Stores clusters as { center, spread, count }
}
update(value) {
console.log(`\nProcessing value: ${value}`);
// If no clusters exist, create the first one
if (this.clusters.length === 0) {
this.clusters.push({ center: value, spread: 0, count: 1 });
console.log(` → First cluster created at ${value}`);
return { value, isOutlier: false };
}
// Step 1: Find the closest cluster
let bestMatch = null;
let minDistance = Infinity;
for (const cluster of this.clusters) {
const distance = Math.abs(value - cluster.center);
console.log(` Checking against cluster at ${cluster.center} (spread: ${cluster.spread}, count: ${cluster.count}) → distance: ${distance}`);
if (distance < minDistance) {
bestMatch = cluster;
minDistance = distance;
}
}
console.log(` Closest cluster found at ${bestMatch.center} with distance: ${minDistance}`);
// Step 2: Compute dynamic threshold
const dynamicThreshold = 1 + 5 / Math.sqrt(bestMatch.count + 1);
const allowedDeviation = dynamicThreshold * (bestMatch.spread || 1);
console.log(` Dynamic threshold: ${dynamicThreshold.toFixed(2)}, Allowed deviation: ${allowedDeviation.toFixed(2)}`);
// Step 3: Check if value fits within the dynamically adjusted cluster spread
if (minDistance <= allowedDeviation) {
// Update cluster dynamically
const newCenter = (bestMatch.center * bestMatch.count + value) / (bestMatch.count + 1);
const newSpread = Math.max(bestMatch.spread, minDistance);
bestMatch.center = newCenter;
bestMatch.spread = newSpread;
bestMatch.count += 1;
console.log(` ✅ Value fits in cluster! Updating cluster:`);
console.log(` → New center: ${newCenter.toFixed(2)}`);
console.log(` → New spread: ${newSpread.toFixed(2)}`);
console.log(` → New count: ${bestMatch.count}`);
return { value, isOutlier: false };
} else {
// If too far, create a new cluster
this.clusters.push({ center: value, spread: 0, count: 1 });
console.log(` ❌ Outlier detected! New cluster created at ${value}`);
return { value, isOutlier: true };
}
}
}
// Rolling window simulation with outlier detection
/*
const detector = new DynamicClusterDeviation();
const dataStream = [10, 10.2, 10.5, 9.8, 11, 50, 10.3, 200, 201, 200.1, 205, 202, 250, 260, 270, 280, 290, 300];
// Define the number of elements per rolling window chunk.
const windowSize = 5;
let rollingWindow = [];
dataStream.forEach((value, index) => {
console.log(`\n=== Processing value ${index + 1} ===`);
rollingWindow.push(value);
const result = detector.update(value);
console.log(`Current rolling window: [${rollingWindow.join(', ')}]`);
console.log(`Result: value=${result.value} (${result.isOutlier ? 'Outlier' : 'Inlier'})`);
// Once the window size is reached, show current cluster states and reset the window for the next chunk.
if (rollingWindow.length === windowSize) {
console.log("\n--- Rolling window chunk finished ---");
console.log("Detector cluster states:", JSON.stringify(detector.clusters, null, 2));
rollingWindow = [];
}
});
console.log("\nFinal detector cluster states:", JSON.stringify(detector.clusters, null, 2));
*/

View File

@@ -1,132 +0,0 @@
//this class will handle the output events for the node red node
class OutputUtils {
constructor() {
this.output ={};
this.output['influxdb'] = {};
this.output['process'] = {};
}
checkForChanges(output, format) {
const changedFields = {};
for (const key in output) {
if (output.hasOwnProperty(key) && output[key] !== this.output[format][key]) {
let value = output[key];
// For fields: if the value is an object (and not a Date), stringify it.
if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
changedFields[key] = JSON.stringify(value);
} else {
changedFields[key] = value;
}
}
}
// Update the saved output state.
this.output[format] = { ...this.output[format], ...changedFields };
return changedFields;
}
formatMsg(output, config, format) {
//define emtpy message
let msg = {};
// Compare output with last output and only include changed values
const changedFields = this.checkForChanges(output,format);
if (Object.keys(changedFields).length > 0) {
switch (format) {
case 'influxdb':
// Extract the relevant config properties.
const relevantConfig = this.extractRelevantConfig(config);
// Flatten the tags so that no nested objects are passed on.
const flatTags = this.flattenTags(relevantConfig);
msg = this.influxDBFormat(changedFields, config, flatTags);
break;
case 'process':
// Compare output with last output and only include changed values
msg = this.processFormat(changedFields,config);
//console.log(msg);
break;
default:
console.log('Unknown format in output utils');
break;
}
return msg;
}
}
influxDBFormat(changedFields, config , flatTags) {
// Create the measurement and topic using softwareType and name config.functionality.softwareType + .
const measurement = config.general.name;
const payload = {
measurement: measurement,
fields: changedFields,
tags: flatTags,
timestamp: new Date(),
};
const topic = measurement;
const msg = { topic: topic, payload: payload };
return msg;
}
flattenTags(obj) {
const result = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
// Recursively flatten the nested object.
const flatChild = this.flattenTags(value);
for (const childKey in flatChild) {
if (flatChild.hasOwnProperty(childKey)) {
result[`${key}_${childKey}`] = String(flatChild[childKey]);
}
}
} else {
// InfluxDB tags must be strings.
result[key] = String(value);
}
}
}
return result;
}
extractRelevantConfig(config) {
return {
// general properties
id: config.general?.id,
name: config.general?.name,
unit: config.general?.unit,
// functionality properties
softwareType: config.functionality?.softwareType,
role: config.functionality?.role,
// asset properties (exclude machineCurve)
uuid: config.asset?.uuid,
geoLocation: config.asset?.geoLocation,
supplier: config.asset?.supplier,
type: config.asset?.type,
subType: config.asset?.subType,
model: config.asset?.model,
};
}
processFormat(changedFields,config) {
// Create the measurement and topic using softwareType and name config.functionality.softwareType + .
const measurement = config.general.name;
const payload = changedFields;
const topic = measurement;
const msg = { topic: topic, payload: payload };
return msg;
}
}
module.exports = OutputUtils;

View File

@@ -1,277 +0,0 @@
//const EventEmitter = require('events');
class movementManager {
constructor(config, logger, emitter) {
this.emitter = emitter; //new EventEmitter(); //state class emitter
const { min, max, initial } = config.position;
const { speed, maxSpeed, interval } = config.movement;
this.minPosition = min;
this.maxPosition = max;
this.currentPosition = initial;
this.speed = speed;
this.maxSpeed = maxSpeed;
this.interval = interval;
this.timeleft = 0; // timeleft of current movement
this.logger = logger;
this.movementMode = config.movement.mode;
}
getCurrentPosition() {
return this.currentPosition;
}
async moveTo(targetPosition, signal) {
// Constrain target position if necessary
if (
targetPosition < this.minPosition ||
targetPosition > this.maxPosition
) {
targetPosition = this.constrain(targetPosition);
this.logger.warn(
`New target position=${targetPosition} is constrained to fit between min=${this.minPosition} and max=${this.maxPosition}`
);
}
this.logger.info(
`Starting movement to position ${targetPosition} in ${this.movementMode} with avg speed=${this.speed}%/s.`
);
if (signal && signal.aborted) {
this.logger.debug("Movement aborted.");
return;
}
try {
// Execute the movement logic based on the mode
switch (this.movementMode) {
case "staticspeed":
const movelinFeedback = await this.moveLinear(targetPosition,signal);
this.logger.info(`Linear move: ${movelinFeedback} `);
break;
case "dynspeed":
const moveDynFeedback = await this.moveEaseInOut(targetPosition,signal);
this.logger.info(`Dynamic move : ${moveDynFeedback}`);
break;
default:
throw new Error(`Unsupported movement mode: ${this.movementMode}`);
}
} catch (error) {
this.logger.error(error);
}
}
moveLinear(targetPosition, signal) {
return new Promise((resolve, reject) => {
// Immediate abort if already signalled
if (signal?.aborted) {
return reject(new Error("Movement aborted"));
}
// Clamp the final target into [minPosition, maxPosition]
targetPosition = this.constrain(targetPosition);
// Compute direction and remaining distance
const direction = targetPosition > this.currentPosition ? 1 : -1;
const distance = Math.abs(targetPosition - this.currentPosition);
// Speed is a fraction [0,1] of full-range per second
this.speed = Math.min(Math.max(this.speed, 0), 1);
const fullRange = this.maxPosition - this.minPosition;
const velocity = this.speed * fullRange; // units per second
if (velocity === 0) {
return reject(new Error("Movement aborted: zero speed"));
}
// Duration and bookkeeping
const duration = distance / velocity; // seconds to go the remaining distance
this.timeleft = duration;
this.logger.debug(
`Linear move: dir=${direction}, dist=${distance}, vel=${velocity.toFixed(2)} u/s, dur=${duration.toFixed(2)}s`
);
// Compute how much to move each tick
const intervalMs = this.interval;
const intervalSec = intervalMs / 1000;
const stepSize = direction * velocity * intervalSec;
const startTime = Date.now();
// Kick off the loop
const intervalId = setInterval(() => {
// 7a) Abort check
if (signal?.aborted) {
clearInterval(intervalId);
return reject(new Error("Movement aborted"));
}
// Advance position and clamp
this.currentPosition += stepSize;
this.currentPosition = this.constrain(this.currentPosition);
this.emitPos(this.currentPosition);
// Update timeleft
const elapsed = (Date.now() - startTime) / 1000;
this.timeleft = Math.max(0, duration - elapsed);
this.logger.debug(
`pos=${this.currentPosition.toFixed(2)}, timeleft=${this.timeleft.toFixed(2)}`
);
// Completed the move?
if (
(direction > 0 && this.currentPosition >= targetPosition) ||
(direction < 0 && this.currentPosition <= targetPosition)
) {
clearInterval(intervalId);
this.currentPosition = targetPosition;
this.emitPos(this.currentPosition);
return resolve("Reached target move.");
}
}, intervalMs);
// 8) Also catch aborts that happen before the first tick
signal?.addEventListener("abort", () => {
clearInterval(intervalId);
reject(new Error("Movement aborted"));
});
});
}
moveLinearinTime(targetPosition,signal) {
return new Promise((resolve, reject) => {
// Abort immediately if already signalled
if (signal?.aborted) {
return reject(new Error("Movement aborted"));
}
const direction = targetPosition > this.currentPosition ? 1 : -1;
const distance = Math.abs(targetPosition - this.currentPosition);
// Ensure speed is a percentage [0, 1]
this.speed = Math.min(Math.max(this.speed, 0), 1);
// Calculate duration based on percentage of distance per second
const duration = 1 / this.speed; // 1 second for 100% of the distance
this.timeleft = duration; //set this so other classes can use it
this.logger.debug(
`Linear movement: Direction=${direction}, Distance=${distance}, Duration=${duration}s`
);
let elapsedTime = 0;
const interval = this.interval; // Update every x ms
const totalSteps = Math.ceil((duration * 1000) / interval);
const stepSize = direction * (distance / totalSteps);
// 2) Set up the abort listener once
const intervalId = setInterval(() => {
// 3) Check for abort on each tick
if (signal?.aborted) {
clearInterval(intervalId);
return reject(new Error("Movement aborted"));
}
// Update elapsed time
elapsedTime += interval / 1000;
this.timeleft = duration - elapsedTime; //set this so other classes can use it
// Update the position incrementally
this.currentPosition += stepSize;
this.emitPos(this.currentPosition);
this.logger.debug(
`Using ${this.movementMode} => Current position ${this.currentPosition}`
);
// Check if the target position has been reached
if (
(direction > 0 && this.currentPosition >= targetPosition) ||
(direction < 0 && this.currentPosition <= targetPosition)
) {
clearInterval(intervalId);
this.currentPosition = targetPosition;
resolve(`Reached target move.`);
}
}, interval);
// Also attach abort outside the interval in case it fires before the first tick:
signal?.addEventListener("abort", () => {
clearInterval(intervalId);
reject(new Error("Movement aborted"));
});
});
}
moveEaseInOut(targetPosition, signal) {
return new Promise((resolve, reject) => {
// 1) Bail immediately if already aborted
if (signal?.aborted) {
return reject(new Error("Movement aborted"));
}
const direction = targetPosition > this.currentPosition ? 1 : -1;
const totalDistance = Math.abs(targetPosition - this.currentPosition);
const startPosition = this.currentPosition;
this.speed = Math.min(Math.max(this.speed, 0), 1);
const easeFunction = (t) =>
t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
let elapsedTime = 0;
const duration = totalDistance / this.speed;
this.timeleft = duration;
const interval = this.interval;
// 2) Start the moving loop
const intervalId = setInterval(() => {
// 3) Check for abort on each tick
if (signal?.aborted) {
clearInterval(intervalId);
return reject(new Error("Movement aborted"));
}
elapsedTime += interval / 1000;
const progress = Math.min(elapsedTime / duration, 1);
this.timeleft = duration - elapsedTime;
const easedProgress = easeFunction(progress);
const newPosition =
startPosition + (targetPosition - startPosition) * easedProgress;
this.emitPos(newPosition);
this.logger.debug(
`Using ${this.movementMode} => Progress=${progress.toFixed(
2
)}, Eased=${easedProgress.toFixed(2)}`
);
if (progress >= 1) {
clearInterval(intervalId);
this.currentPosition = targetPosition;
resolve(`Reached target move.`);
} else {
this.currentPosition = newPosition;
}
}, interval);
// 4) Also listen once for abort before first tick
signal?.addEventListener("abort", () => {
clearInterval(intervalId);
reject(new Error("Movement aborted"));
});
});
}
emitPos(newPosition) {
this.emitter.emit("positionChange", newPosition);
}
constrain(value) {
return Math.min(Math.max(value, this.minPosition), this.maxPosition);
}
}
module.exports = movementManager;

View File

@@ -1,131 +0,0 @@
//load local dependencies
const EventEmitter = require('events');
const StateManager = require('./stateManager');
const MovementManager = require('./movementManager');
//load all config modules
const defaultConfig = require('./stateConfig.json');
const ConfigUtils = require('../../../generalFunctions/helper/configUtils');
class state{
constructor(config = {}, logger) {
this.emitter = new EventEmitter(); // Own EventEmitter
this.configUtils = new ConfigUtils(defaultConfig);
this.config = this.configUtils.initConfig(config);
this.abortController = null; // new abort controller for aborting async tasks
// Init after config is set
this.logger = logger;
// Initialize StateManager for state handling
this.stateManager = new StateManager(this.config,this.logger);
this.movementManager = new MovementManager(this.config, this.logger, this.emitter);
this.delayedMove = null;
this.mode = this.config.mode.current;
// Log initialization
this.logger.info("State class initialized.");
}
// -------- Delegate State Management -------- //
getMoveTimeLeft() {
return this.movementManager.timeleft;
}
getCurrentState() {
return this.stateManager.currentState;
}
getStateDescription() {
return this.stateManager.getStateDescription();
}
// -------- Movement Methods -------- //
getCurrentPosition() {
return this.movementManager.getCurrentPosition();
}
getRunTimeHours() {
return this.stateManager.getRunTimeHours();
}
async moveTo(targetPosition) {
// Check for invalid conditions and throw errors
if (targetPosition === this.getCurrentPosition()) {
this.logger.warn(`Target position=${targetPosition} is the same as the current position ${this.getCurrentPosition()}. Not executing move.`);
return;
}
if (this.stateManager.getCurrentState() !== "operational") {
if (this.config.mode.current === "auto") {
this.delayedMove = targetPosition;
this.logger.warn(`Saving setpoint=${targetPosition} to execute once back in 'operational' state.`);
}
else{
this.logger.warn(`Not able to accept setpoint=${targetPosition} while not in ${this.stateManager.getCurrentState()} state`);
}
//return early
return;
}
this.abortController = new AbortController();
const { signal } = this.abortController;
try {
const newState = targetPosition < this.getCurrentPosition() ? "decelerating" : "accelerating";
await this.transitionToState(newState,signal); // awaits transition
await this.movementManager.moveTo(targetPosition,signal); // awaits moving
this.emitter.emit("movementComplete", { position: targetPosition });
await this.transitionToState("operational");
} catch (error) {
this.logger.error(error);
}
}
// -------- State Transition Methods -------- //
async transitionToState(targetState, signal) {
const fromState = this.getCurrentState();
const position = this.getCurrentPosition();
try {
this.logger.debug(`Starting transition from ${fromState} to ${targetState}.`);
const feedback = await this.stateManager.transitionTo(targetState,signal);
this.logger.info(`Statemanager: ${feedback}`);
/* -- Auto pick setpoints in auto mode when operational--*/
if (
targetState === "operational" &&
this.config.mode.current === "auto" &&
this.delayedMove !== position &&
this.delayedMove
) {
this.logger.info(`Automatically picking up on last requested setpoint ${this.delayedMove}`);
//trigger move
await this.moveTo(this.delayedMove,signal);
this.delayedMove = null;
this.logger.info(`moveTo : ${feedback} `);
}
this.logger.info(`State change to ${targetState} completed.`);
this.emitter.emit('stateChange', targetState); // <-- Implement Here
} catch (error) {
if (
error.message === "Transition aborted" ||
error.message === "Movement aborted"
) {
throw error;
}
this.logger.error(error);
}
}
}
module.exports = state;

View File

@@ -1,331 +0,0 @@
{
"general": {
"name": {
"default": "State Configuration",
"rules": {
"type": "string",
"description": "A human-readable name for the state configuration."
}
},
"id": {
"default": null,
"rules": {
"type": "string",
"nullable": true,
"description": "A unique identifier for this configuration, assigned dynamically when needed."
}
},
"unit": {
"default": "unitless",
"rules": {
"type": "string",
"description": "The unit used for the state values (e.g., 'meters', 'seconds', 'unitless')."
}
},
"logging": {
"logLevel": {
"default": "info",
"rules": {
"type": "enum",
"values": [
{
"value": "debug",
"description": "Log messages are printed for debugging purposes."
},
{
"value": "info",
"description": "Informational messages are printed."
},
{
"value": "warn",
"description": "Warning messages are printed."
},
{
"value": "error",
"description": "Error messages are printed."
}
]
}
},
"enabled": {
"default": true,
"rules": {
"type": "boolean",
"description": "Indicates whether logging is active. If true, log messages will be generated."
}
}
}
},
"functionality": {
"softwareType": {
"default": "state class",
"rules": {
"type": "string",
"description": "Logical name identifying the software type."
}
},
"role": {
"default": "StateController",
"rules": {
"type": "string",
"description": "Functional role within the system."
}
}
},
"time": {
"starting": {
"default": 10,
"rules": {
"type": "number",
"description": "Time in seconds for the starting phase."
}
},
"warmingup": {
"default": 5,
"rules": {
"type": "number",
"description": "Time in seconds for the warming-up phase."
}
},
"stopping": {
"default": 5,
"rules": {
"type": "number",
"description": "Time in seconds for the stopping phase."
}
},
"coolingdown": {
"default": 10,
"rules": {
"type": "number",
"description": "Time in seconds for the cooling-down phase."
}
}
},
"movement": {
"mode": {
"default": "dynspeed",
"rules": {
"type": "enum",
"values": [
{
"value": "staticspeed",
"description": "Linear movement to setpoint."
},
{
"value": "dynspeed",
"description": "Ease-in and ease-out to setpoint."
}
]
}
},
"speed": {
"default": 1,
"rules": {
"type": "number",
"description": "Current speed setting."
}
},
"maxSpeed": {
"default": 10,
"rules": {
"type": "number",
"description": "Maximum speed setting."
}
},
"interval": {
"default": 1000,
"rules": {
"type": "number",
"description": "Feedback interval in milliseconds."
}
}
},
"position": {
"min": {
"default": 0,
"rules": {
"type": "number",
"description": "Minimum position value."
}
},
"max": {
"default": 100,
"rules": {
"type": "number",
"description": "Maximum position value."
}
},
"initial": {
"default": 0,
"rules": {
"type": "number",
"description": "Initial position value."
}
}
},
"state": {
"current": {
"default": "idle",
"rules": {
"type": "enum",
"values": [
{
"value": "idle",
"description": "Machine is idle."
},
{
"value": "starting",
"description": "Machine is starting up."
},
{
"value": "warmingup",
"description": "Machine is warming up."
},
{
"value": "operational",
"description": "Machine is running."
},
{
"value": "accelerating",
"description": "Machine is accelerating."
},
{
"value": "decelerating",
"description": "Machine is decelerating."
},
{
"value": "stopping",
"description": "Machine is stopping."
},
{
"value": "coolingdown",
"description": "Machine is cooling down."
},
{
"value": "off",
"description": "Machine is off."
}
],
"description": "Current state of the machine."
}
},
"allowedTransitions":{
"default": {},
"rules": {
"type": "object",
"schema": {
"idle": {
"default": ["starting", "off","emergencystop"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from idle state."
}
},
"starting": {
"default": ["starting","warmingup","emergencystop"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from starting state."
}
},
"warmingup": {
"default": ["operational","emergencystop"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from warmingup state."
}
},
"operational": {
"default": ["accelerating", "decelerating", "stopping","emergencystop"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from operational state."
}
},
"accelerating": {
"default": ["operational","emergencystop"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from accelerating state."
}
},
"decelerating": {
"default": ["operational","emergencystop"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from decelerating state."
}
},
"stopping": {
"default": ["idle","coolingdown","emergencystop"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from stopping state."
}
},
"coolingdown": {
"default": ["idle","off","emergencystop"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from coolingDown state."
}
},
"off": {
"default": ["idle","emergencystop"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from off state."
}
},
"emergencystop": {
"default": ["idle","off"],
"rules":{
"type": "set",
"itemType": "string",
"description": "Allowed transitions from emergency stop state."
}
}
},
"description": "Allowed transitions between states."
}
},
"activeStates":{
"default": ["operational", "starting", "warmingup", "accelerating", "decelerating"],
"rules": {
"type": "set",
"itemType": "string",
"description": "Active states."
}
}
},
"mode": {
"current": {
"default": "auto",
"rules": {
"type": "enum",
"values": [
{
"value": "auto",
"description": "Automatically tracks and handles delayed commands for setpoints > 0."
},
{
"value": "manual",
"description": "Requires explicit commands to start."
}
],
"description": "Current mode of the machine."
}
}
}
}

View File

@@ -1,164 +0,0 @@
/**
* @file stateManager.js
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to use it for personal
* or non-commercial purposes, with the following restrictions:
*
* 1. **No Copying or Redistribution**: The Software or any of its parts may not
* be copied, merged, distributed, sublicensed, or sold without explicit
* prior written permission from the author.
*
* 2. **Commercial Use**: Any use of the Software for commercial purposes requires
* a valid license, obtainable only with the explicit consent of the author.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
* OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Ownership of this code remains solely with the original author. Unauthorized
* use of this Software is strictly prohibited.
*
* @summary Class for managing state transitions and state descriptions.
* @description Class for managing state transitions and state descriptions.
* @module stateManager
* @exports stateManager
* @version 0.1.0
* @since 0.1.0
*
* Author:
* - Rene De Ren
* Email:
* - rene@thegoldenbasket.nl
*/
class stateManager {
constructor(config, logger) {
this.currentState = config.state.current;
this.availableStates = config.state.available;
this.descriptions = config.state.descriptions;
this.logger = logger;
this.transitionTimeleft = 0;
this.transitionTimes = config.time;
// Define valid transitions (can be extended dynamically if needed)
this.validTransitions = config.state.allowedTransitions;
// NEW: Initialize runtime tracking
this.runTimeHours = 0; // cumulative runtime in hours
this.runTimeStart = null; // timestamp when active state began
// Define active states (runtime counts only in these states)
this.activeStates = config.state.activeStates;
}
getCurrentState() {
return this.currentState;
}
transitionTo(newState,signal) {
return new Promise((resolve, reject) => {
if (signal && signal.aborted) {
this.logger.debug("Transition aborted.");
return reject("Transition aborted.");
}
if (!this.isValidTransition(newState)) {
return reject(
`Invalid transition from ${this.currentState} to ${newState}. Transition not executed.`
); //go back early and reject promise
}
// NEW: Handle runtime tracking based on active states
this.handleRuntimeTracking(newState);
const transitionDuration = this.transitionTimes[this.currentState] || 0; // Default to 0 if no transition time
this.logger.debug(
`Transition from ${this.currentState} to ${newState} will take ${transitionDuration}s.`
);
if (transitionDuration > 0) {
const timeoutId = setTimeout(() => {
this.currentState = newState;
resolve(`Transition from ${this.currentState} to ${newState} completed in ${transitionDuration}s.`);
}, transitionDuration * 1000);
if (signal) {
signal.addEventListener('abort', () => {
clearTimeout(timeoutId);
reject(new Error('Transition aborted'));
});
}
} else {
this.currentState = newState;
resolve(`Immediate transition to ${this.currentState} completed.`);
}
});
}
handleRuntimeTracking(newState) {
// NEW: Handle runtime tracking based on active states
const wasActive = this.activeStates.has(this.currentState);
const willBeActive = this.activeStates.has(newState);
if (wasActive && !willBeActive && this.runTimeStart) {
// stop runtime timer and accumulate elapsed time
const elapsed = (Date.now() - this.runTimeStart) / 3600000; // hours
this.runTimeHours += elapsed;
this.runTimeStart = null;
this.logger.debug(
`Runtime timer stopped; elapsed=${elapsed.toFixed(
3
)}h, total=${this.runTimeHours.toFixed(3)}h.`
);
} else if (!wasActive && willBeActive && !this.runTimeStart) {
// starting new runtime
this.runTimeStart = Date.now();
this.logger.debug("Runtime timer started.");
}
}
isValidTransition(newState) {
this.logger.debug(
`Check 1 Transition valid ? From ${
this.currentState
} To ${newState} => ${this.validTransitions[this.currentState]?.has(
newState
)} `
);
this.logger.debug(
`Check 2 Transition valid ? ${
this.currentState
} is not equal to ${newState} => ${this.currentState !== newState}`
);
// check if transition is valid and not the same as before
const valid =
this.validTransitions[this.currentState]?.has(newState) &&
this.currentState !== newState;
//if not valid
if (!valid) {
return false;
} else {
return true;
}
}
getStateDescription(state = this.currentState) {
return this.descriptions[state] || "No description available.";
}
// NEW: Getter to retrieve current cumulative runtime (active time) in hours.
getRunTimeHours() {
// If currently active add the ongoing duration.
let currentElapsed = 0;
if (this.runTimeStart) {
currentElapsed = (Date.now() - this.runTimeStart) / 3600000;
}
return this.runTimeHours + currentElapsed;
}
}
module.exports = stateManager;

View File

@@ -1,528 +0,0 @@
/**
* @file validation.js
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to use it for personal
* or non-commercial purposes, with the following restrictions:
*
* 1. **No Copying or Redistribution**: The Software or any of its parts may not
* be copied, merged, distributed, sublicensed, or sold without explicit
* prior written permission from the author.
*
* 2. **Commercial Use**: Any use of the Software for commercial purposes requires
* a valid license, obtainable only with the explicit consent of the author.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
* OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Ownership of this code remains solely with the original author. Unauthorized
* use of this Software is strictly prohibited.
* @summary Validation utility for validating and constraining configuration values.
* @description Validation utility for validating and constraining configuration values.
* @module ValidationUtils
* @requires Logger
* @exports ValidationUtils
* @version 0.1.0
* @since 0.1.0
*/
const Logger = require("./logger");
class ValidationUtils {
constructor(IloggerEnabled, IloggerLevel) {
const loggerEnabled = IloggerEnabled || true;
const loggerLevel = IloggerLevel || "warn";
this.logger = new Logger(loggerEnabled, loggerLevel, 'ValidationUtils');
}
constrain(value, min, max) {
if (typeof value !== "number") {
this.logger?.warn(`Value '${value}' is not a number. Defaulting to ${min}.`);
return min;
}
return Math.min(Math.max(value, min), max);
}
validateSchema(config, schema, name) {
const validatedConfig = {};
let configValue;
// 1. Remove any unknown keys (keys not defined in the schema).
// Log a warning and omit them from the final config.
for (const key of Object.keys(config)) {
if (!(key in schema)) {
this.logger.warn(
`[${name}] Unknown key '${key}' found in config. Removing it.`
);
delete config[key];
}
}
// Validate each key in the schema and loop over wildcards if they are not in schema
for ( const key in schema ) {
if (key === "rules" || key === "description" || key === "schema") {
continue;
}
const fieldSchema = schema[key];
const { rules = {} } = fieldSchema;
// Default to the schema's default value if the key is missing
if (config[key] === undefined) {
if (fieldSchema.default === undefined) {
// If there's a nested schema, go deeper with an empty object rather than logging "no rule"
if (rules.schema) {
this.logger.warn(`${name}.${key} has no default, but has a nested schema.`);
validatedConfig[key] = this.validateSchema({}, rules.schema, `${name}.${key}`);
}
else {
this.logger.info(
`There is no rule for ${name}.${key} and no default value. ` +
`Using full schema value but validating deeper levels first...`
);
const SubObject = this.validateSchema({}, fieldSchema, `${name}.${key}`);
validatedConfig[key] = SubObject;
continue;
}
} else {
this.logger.info(`There is no value provided for ${name}.${key}. Using default value.`);
configValue = fieldSchema.default;
}
//continue;
} else {
// Use the provided value if it exists, otherwise use the default value
configValue = config[key] !== undefined ? config[key] : fieldSchema.default;
}
// Attempt to parse the value to the expected type if possible
switch (rules.type) {
case "number":
configValue = this.validateNumber(configValue, rules, fieldSchema, name, key);
break;
case "boolean":
configValue = this.validateBoolean(configValue, name, key);
break;
case "string":
configValue = this.validateString(configValue,rules,fieldSchema, name, key);
break;
case "array":
configValue = this.validateArray(configValue, rules, fieldSchema, name, key);
break;
case "set":
configValue = this.validateSet(configValue, rules, fieldSchema, name, key);
break;
case "object":
configValue = this.validateObject(configValue, rules, fieldSchema, name, key);
break;
case "enum":
configValue = this.validateEnum(configValue, rules, fieldSchema, name, key);
break;
case "curve":
validatedConfig[key] = this.validateCurve(configValue,fieldSchema.default);
continue;
case "machineCurve":
validatedConfig[key] = this.validateMachineCurve(configValue,fieldSchema.default);
continue;
case "integer":
validatedConfig[key] = this.validateInteger(configValue, rules, fieldSchema, name, key);
continue;
case undefined:
// If we see 'rules.schema' but no 'rules.type', treat it like an object:
if (rules.schema && !rules.type) {
// Log a warning and skip the extra pass for nested schema
this.logger.warn(
`${name}.${key} has a nested schema but no type. ` +
`Treating it as type="object" to skip extra pass.`
);
} else {
// Otherwise, fallback to your existing "validateUndefined" logic
validatedConfig[key] = this.validateUndefined(configValue, fieldSchema, name, key);
}
continue;
default:
this.logger.warn(`${name}.${key} has an unknown validation type: ${rules.type}. Skipping validation.`);
validatedConfig[key] = fieldSchema.default;
continue;
}
// Assign the validated or converted value
validatedConfig[key] = configValue;
}
// Ignore unknown keys by not processing them at all
this.logger.info(`Validation completed for ${name}.`);
return validatedConfig;
}
removeUnwantedKeys(obj) {
if (Array.isArray(obj)) {
return obj.map((item) => this.removeUnwantedKeys(item));
}
if (obj && typeof obj === "object") {
const newObj = {};
for (const [k, v] of Object.entries(obj)) {
// Skip or remove keys like 'default', 'rules', 'description', etc.
if (["rules", "description"].includes(k)) {
continue;
}
if("default" in v){
//put the default value in the object
newObj[k] = v.default;
continue;
}
newObj[k] = this.removeUnwantedKeys(v);
}
return newObj;
}
return obj;
}
validateMachineCurve(curve, defaultCurve) {
if (!curve || typeof curve !== "object" || Object.keys(curve).length === 0) {
this.logger.warn("Curve is missing or invalid. Defaulting to basic curve.");
return defaultCurve;
}
// Validate that nq and np exist and are objects
const { nq, np } = curve;
if (!nq || typeof nq !== "object" || !np || typeof np !== "object") {
this.logger.warn("Curve must contain valid 'nq' and 'np' objects. Defaulting to basic curve.");
return defaultCurve;
}
// Validate that each dimension key points to a valid object with x and y arrays
const validatedNq = this.validateDimensionStructure(nq, "nq");
const validatedNp = this.validateDimensionStructure(np, "np");
if (!validatedNq || !validatedNp) {
return defaultCurve;
}
return { nq: validatedNq, np: validatedNp }; // Return the validated curve
}
validateCurve(curve, defaultCurve) {
if (!curve || typeof curve !== "object" || Object.keys(curve).length === 0) {
this.logger.warn("Curve is missing or invalid. Defaulting to basic curve.");
return defaultCurve;
}
// Validate that each dimension key points to a valid object with x and y arrays
const validatedCurve = this.validateDimensionStructure(curve, "curve");
if (!validatedCurve) {
return defaultCurve;
}
return validatedCurve; // Return the validated curve
}
validateDimensionStructure(dimension, name) {
const validatedDimension = {};
for (const [key, value] of Object.entries(dimension)) {
// Validate that each key points to an object with x and y arrays
if (typeof value !== "object") {
this.logger.warn(`Dimension '${name}' key '${key}' is not valid. Returning to default.`);
return false;
}
// Validate that x and y are arrays
else if (!Array.isArray(value.x) || !Array.isArray(value.y)) {
this.logger.warn(`Dimension '${name}' key '${key}' is missing x or y arrays. Converting to arrays.`);
// Try to convert to arrays first
value.x = Object.values(value.x);
value.y = Object.values(value.y);
// If still not arrays return false
if (!Array.isArray(value.x) || !Array.isArray(value.y)) {
this.logger.warn(`Dimension '${name}' key '${key}' is not valid. Returning to default.`);
return false;
}
}
// Validate that x and y arrays are the same length
else if (value.x.length !== value.y.length) {
this.logger.warn(`Dimension '${name}' key '${key}' has mismatched x and y lengths. Ignoring this key.`);
return false;
}
// Validate that x values are in ascending order
else if (!this.isSorted(value.x)) {
this.logger.warn(`Dimension '${name}' key '${key}' has unsorted x values. Sorting...`);
return false;
}
// Validate that x values are unique
else if (!this.isUnique(value.x)) {
this.logger.warn(`Dimension '${name}' key '${key}' has duplicate x values. Removing duplicates...`);
return false;
}
// Validate that y values are numbers
else if (!this.areNumbers(value.y)) {
this.logger.warn(`Dimension '${name}' key '${key}' has non-numeric y values. Ignoring this key.`);
return false;
}
validatedDimension[key] = value;
}
return validatedDimension;
}
isSorted(arr) {
return arr.every((_, i) => i === 0 || arr[i] >= arr[i - 1]);
}
isUnique(arr) {
return new Set(arr).size === arr.length;
}
areNumbers(arr) {
return arr.every((x) => typeof x === "number");
}
validateNumber(configValue, rules, fieldSchema, name, key) {
if (typeof configValue !== "number") {
const parsedValue = parseFloat(configValue);
if (!isNaN(parsedValue)) {
this.logger.warn(`${name}.${key} was parsed to a number: ${configValue} -> ${parsedValue}`);
configValue = parsedValue;
}
}
if (rules.min !== undefined && configValue < rules.min) {
this.logger.warn(
`${name}.${key} is below the minimum (${rules.min}). Using default value.`
);
return fieldSchema.default;
}
if (rules.max !== undefined && configValue > rules.max) {
this.logger.warn(
`${name}.${key} exceeds the maximum (${rules.max}). Using default value.`
);
return fieldSchema.default;
}
this.logger.debug(`${name}.${key} is a valid number: ${configValue}`);
return configValue;
}
validateInteger(configValue, rules, fieldSchema, name, key) {
if (typeof configValue !== "number" || !Number.isInteger(configValue)) {
const parsedValue = parseInt(configValue, 10);
if (!isNaN(parsedValue) && Number.isInteger(parsedValue)) {
this.logger.warn(`${name}.${key} was parsed to an integer: ${configValue} -> ${parsedValue}`);
configValue = parsedValue;
} else {
this.logger.warn(`${name}.${key} is not a valid integer. Using default value.`);
return fieldSchema.default;
}
}
if (rules.min !== undefined && configValue < rules.min) {
this.logger.warn(`${name}.${key} is below the minimum integer value (${rules.min}). Using default value.`);
return fieldSchema.default;
}
if (rules.max !== undefined && configValue > rules.max) {
this.logger.warn(`${name}.${key} exceeds the maximum integer value (${rules.max}). Using default value.`);
return fieldSchema.default;
}
this.logger.debug(`${name}.${key} is a valid integer: ${configValue}`);
return configValue;
}
validateBoolean(configValue, name, key) {
if (typeof configValue !== "boolean") {
if (configValue === "true" || configValue === "false") {
const parsedValue = configValue === "true";
this.logger.debug(`${name}.${key} was parsed to a boolean: ${configValue} -> ${parsedValue}`);
configValue = parsedValue;
}
}
return configValue;
}
validateString(configValue, rules, fieldSchema, name, key) {
let newConfigValue = configValue;
if (typeof configValue !== "string") {
//check if the value is nullable
if(rules.nullable){
if(configValue === null){
return null;
}
}
this.logger.warn(`${name}.${key} is not a string. Trying to convert to string.`);
newConfigValue = String(configValue); // Coerce to string if not already
}
//check if the string is a valid string after conversion
if (typeof newConfigValue !== "string") {
this.logger.warn(`${name}.${key} is not a valid string. Using default value.`);
return fieldSchema.default;
}
return newConfigValue;
}
validateSet(configValue, rules, fieldSchema, name, key) {
// 1. Ensure we have a Set. If not, use default.
if (!(configValue instanceof Set)) {
this.logger.info(`${name}.${key} is not a Set. Converting to one using default value.`);
return new Set(fieldSchema.default);
}
// 2. Convert the Set to an array for easier filtering.
const validatedArray = [...configValue]
.filter((item) => {
// 3. Filter based on `rules.itemType`.
switch (rules.itemType) {
case "number":
return typeof item === "number";
case "string":
return typeof item === "string";
case "null":
// "null" might mean no type restriction (your usage may vary).
return true;
default:
// Fallback if itemType is something else
return typeof item === rules.itemType;
}
})
.slice(0, rules.maxLength || Infinity);
// 4. Check if the filtered array meets the minimum length.
if (validatedArray.length < (rules.minLength || 1)) {
this.logger.warn(
`${name}.${key} contains fewer items than allowed (${rules.minLength}). Using default value.`
);
return new Set(fieldSchema.default);
}
// 5. Return a new Set containing only the valid items.
return new Set(validatedArray);
}
validateArray(configValue, rules, fieldSchema, name, key) {
if (!Array.isArray(configValue)) {
this.logger.info(`${name}.${key} is not an array. Using default value.`);
return fieldSchema.default;
}
// Validate individual items in the array
const validatedArray = configValue
.filter((item) => {
switch (rules.itemType) {
case "number":
return typeof item === "number";
case "string":
return typeof item === "string";
case "null":
// anything goes
return true;
default:
return typeof item === rules.itemType;
}
})
.slice(0, rules.maxLength || Infinity);
if (validatedArray.length < (rules.minLength || 1)) {
this.logger.warn(
`${name}.${key} contains fewer items than allowed (${rules.minLength}). Using default value.`
);
return fieldSchema.default;
}
return validatedArray;
}
validateObject(configValue, rules, fieldSchema, name, key) {
if (typeof configValue !== "object" || Array.isArray(configValue)) {
this.logger.warn(`${name}.${key} is not a valid object. Using default value.`);
return fieldSchema.default;
}
if (rules.schema) {
// Recursively validate nested objects if a schema is defined
return this.validateSchema(configValue || {}, rules.schema, `${name}.${key}`);
} else {
// If no schema is defined, log a warning and use the default
this.logger.warn(`${name}.${key} is an object with no schema. Using default value.`);
return fieldSchema.default;
}
}
validateEnum(configValue, rules, fieldSchema, name, key) {
if (Array.isArray(rules.values)) {
//if value is null take default
if(configValue === null){
this.logger.warn(`${name}.${key} is null. Using default value.`);
return fieldSchema.default;
}
const validValues = rules.values.map(e => e.value.toLowerCase());
//remove caps
configValue = configValue.toLowerCase();
if (!validValues.includes(configValue)) {
this.logger.warn(
`${name}.${key} has an invalid value : ${configValue}. Allowed values: [${validValues.join(", ")}]. Using default value.`
);
return fieldSchema.default;
}
} else {
this.logger.warn(
`${name}.${key} is an enum with no 'values' array. Using default value.`
);
return fieldSchema.default;
}
return configValue;
}
validateUndefined(configValue, fieldSchema, name, key) {
if (typeof configValue === "object" && !Array.isArray(configValue)) {
this.logger.debug(`${name}.${key} has no defined rules but is an object going 1 level deeper.`);
// Recursively validate the nested object
return this.validateSchema( configValue || {}, fieldSchema, `${name}.${key}`);
}
else {
this.logger.warn(`${name}.${key} has no defined rules. Using default value.`);
return fieldSchema.default;
}
}
}
module.exports = ValidationUtils;

View File

@@ -7,13 +7,15 @@
// Core helper modules // Core helper modules
const menuUtils = require('./src/helper/menuUtils.js'); const menuUtils = require('./src/helper/menuUtils.js');
const outputUtils = require('./src/helper/outputUtils.js');
const logger = require('./src/helper/logger.js'); const logger = require('./src/helper/logger.js');
const validation = require('./src/helper/validationUtils.js'); const validation = require('./src/helper/validationUtils.js');
const configUtils = require('./src/helper/configUtils.js');
// Domain-specific modules // Domain-specific modules
const measurements = require('./src/measurements/index.js'); const measurements = require('./src/measurements/index.js');
const nrmse = require('./src/nrmse/index.js'); const nrmse = require('./src/nrmse/ErrorMetrics.js');
const state = require('./src/state/index.js'); const state = require('./src/state/state.js');
// Configuration loader with error handling // Configuration loader with error handling
function loadConfig(path) { function loadConfig(path) {
@@ -59,6 +61,8 @@ function loadAssetDatasets() {
// Export everything // Export everything
module.exports = { module.exports = {
menuUtils, menuUtils,
outputUtils,
configUtils,
logger, logger,
validation, validation,
measurements, measurements,

View File

@@ -0,0 +1,49 @@
var angle;
angle = {
rad: {
name: {
singular: 'radian'
, plural: 'radians'
}
, to_anchor: 180/Math.PI
}
, deg: {
name: {
singular: 'degree'
, plural: 'degrees'
}
, to_anchor: 1
}
, grad: {
name: {
singular: 'gradian'
, plural: 'gradians'
}
, to_anchor: 9/10
}
, arcmin: {
name: {
singular: 'arcminute'
, plural: 'arcminutes'
}
, to_anchor: 1/60
}
, arcsec: {
name: {
singular: 'arcsecond'
, plural: 'arcseconds'
}
, to_anchor: 1/3600
}
};
module.exports = {
metric: angle
, _anchors: {
metric: {
unit: 'deg'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,49 @@
var apparentPower;
apparentPower = {
VA: {
name: {
singular: 'Volt-Ampere'
, plural: 'Volt-Amperes'
}
, to_anchor: 1
}
, mVA: {
name: {
singular: 'Millivolt-Ampere'
, plural: 'Millivolt-Amperes'
}
, to_anchor: .001
}
, kVA: {
name: {
singular: 'Kilovolt-Ampere'
, plural: 'Kilovolt-Amperes'
}
, to_anchor: 1000
}
, MVA: {
name: {
singular: 'Megavolt-Ampere'
, plural: 'Megavolt-Amperes'
}
, to_anchor: 1000000
}
, GVA: {
name: {
singular: 'Gigavolt-Ampere'
, plural: 'Gigavolt-Amperes'
}
, to_anchor: 1000000000
}
};
module.exports = {
metric: apparentPower
, _anchors: {
metric: {
unit: 'VA'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,93 @@
var metric
, imperial;
metric = {
mm2: {
name: {
singular: 'Square Millimeter'
, plural: 'Square Millimeters'
}
, to_anchor: 1/1000000
}
, cm2: {
name: {
singular: 'Centimeter'
, plural: 'Centimeters'
}
, to_anchor: 1/10000
}
, m2: {
name: {
singular: 'Square Meter'
, plural: 'Square Meters'
}
, to_anchor: 1
}
, ha: {
name: {
singular: 'Hectare'
, plural: 'Hectares'
}
, to_anchor: 10000
}
, km2: {
name: {
singular: 'Square Kilometer'
, plural: 'Square Kilometers'
}
, to_anchor: 1000000
}
};
imperial = {
'in2': {
name: {
singular: 'Square Inch'
, plural: 'Square Inches'
}
, to_anchor: 1/144
}
, yd2: {
name: {
singular: 'Square Yard'
, plural: 'Square Yards'
}
, to_anchor: 9
}
, ft2: {
name: {
singular: 'Square Foot'
, plural: 'Square Feet'
}
, to_anchor: 1
}
, ac: {
name: {
singular: 'Acre'
, plural: 'Acres'
}
, to_anchor: 43560
}
, mi2: {
name: {
singular: 'Square Mile'
, plural: 'Square Miles'
}
, to_anchor: 27878400
}
};
module.exports = {
metric: metric
, imperial: imperial
, _anchors: {
metric: {
unit: 'm2'
, ratio: 10.7639
}
, imperial: {
unit: 'ft2'
, ratio: 1/10.7639
}
}
};

View File

@@ -0,0 +1,35 @@
var current;
current = {
A: {
name: {
singular: 'Ampere'
, plural: 'Amperes'
}
, to_anchor: 1
}
, mA: {
name: {
singular: 'Milliampere'
, plural: 'Milliamperes'
}
, to_anchor: .001
}
, kA: {
name: {
singular: 'Kiloampere'
, plural: 'Kiloamperes'
}
, to_anchor: 1000
}
};
module.exports = {
metric: current
, _anchors: {
metric: {
unit: 'A'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,93 @@
var bits
, bytes;
bits = {
b: {
name: {
singular: 'Bit'
, plural: 'Bits'
}
, to_anchor: 1
}
, Kb: {
name: {
singular: 'Kilobit'
, plural: 'Kilobits'
}
, to_anchor: 1024
}
, Mb: {
name: {
singular: 'Megabit'
, plural: 'Megabits'
}
, to_anchor: 1048576
}
, Gb: {
name: {
singular: 'Gigabit'
, plural: 'Gigabits'
}
, to_anchor: 1073741824
}
, Tb: {
name: {
singular: 'Terabit'
, plural: 'Terabits'
}
, to_anchor: 1099511627776
}
};
bytes = {
B: {
name: {
singular: 'Byte'
, plural: 'Bytes'
}
, to_anchor: 1
}
, KB: {
name: {
singular: 'Kilobyte'
, plural: 'Kilobytes'
}
, to_anchor: 1024
}
, MB: {
name: {
singular: 'Megabyte'
, plural: 'Megabytes'
}
, to_anchor: 1048576
}
, GB: {
name: {
singular: 'Gigabyte'
, plural: 'Gigabytes'
}
, to_anchor: 1073741824
}
, TB: {
name: {
singular: 'Terabyte'
, plural: 'Terabytes'
}
, to_anchor: 1099511627776
}
};
module.exports = {
bits: bits
, bytes: bytes
, _anchors: {
bits: {
unit: 'b'
, ratio: 1/8
}
, bytes: {
unit: 'B'
, ratio: 8
}
}
};

View File

@@ -0,0 +1,30 @@
var metric
, imperial;
metric = {
ea: {
name: {
singular: 'Each'
, plural: 'Each'
}
, to_anchor: 1
},
dz: {
name: {
singular: 'Dozen'
, plural: 'Dozens'
}
, to_anchor: 12
}
};
module.exports = {
metric: metric
, imperial: {}
, _anchors: {
metric: {
unit: 'ea'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,63 @@
var energy;
energy = {
Wh: {
name: {
singular: 'Watt-hour'
, plural: 'Watt-hours'
}
, to_anchor: 3600
}
, mWh: {
name: {
singular: 'Milliwatt-hour'
, plural: 'Milliwatt-hours'
}
, to_anchor: 3.6
}
, kWh: {
name: {
singular: 'Kilowatt-hour'
, plural: 'Kilowatt-hours'
}
, to_anchor: 3600000
}
, MWh: {
name: {
singular: 'Megawatt-hour'
, plural: 'Megawatt-hours'
}
, to_anchor: 3600000000
}
, GWh: {
name: {
singular: 'Gigawatt-hour'
, plural: 'Gigawatt-hours'
}
, to_anchor: 3600000000000
}
, J: {
name: {
singular: 'Joule'
, plural: 'Joules'
}
, to_anchor: 1
}
, kJ: {
name: {
singular: 'Kilojoule'
, plural: 'Kilojoules'
}
, to_anchor: 1000
}
};
module.exports = {
metric: energy
, _anchors: {
metric: {
unit: 'J'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,78 @@
var frequency;
frequency = {
mHz: {
name: {
singular: 'millihertz'
, plural: 'millihertz'
}
, to_anchor: 1/1000
}
, Hz: {
name: {
singular: 'hertz'
, plural: 'hertz'
}
, to_anchor: 1
}
, kHz: {
name: {
singular: 'kilohertz'
, plural: 'kilohertz'
}
, to_anchor: 1000
}
, MHz: {
name: {
singular: 'megahertz'
, plural: 'megahertz'
}
, to_anchor: 1000 * 1000
}
, GHz: {
name: {
singular: 'gigahertz'
, plural: 'gigahertz'
}
, to_anchor: 1000 * 1000 * 1000
}
, THz: {
name: {
singular: 'terahertz'
, plural: 'terahertz'
}
, to_anchor: 1000 * 1000 * 1000 * 1000
}
, rpm: {
name: {
singular: 'rotation per minute'
, plural: 'rotations per minute'
}
, to_anchor: 1/60
}
, "deg/s": {
name: {
singular: 'degree per second'
, plural: 'degrees per second'
}
, to_anchor: 1/360
}
, "rad/s": {
name: {
singular: 'radian per second'
, plural: 'radians per second'
}
, to_anchor: 1/(Math.PI * 2)
}
};
module.exports = {
metric: frequency
, _anchors: {
frequency: {
unit: 'hz'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,37 @@
var metric,
imperial;
metric = {
'lx': {
name: {
singular: 'Lux',
plural: 'Lux'
},
to_anchor: 1
}
};
imperial = {
'ft-cd': {
name: {
singular: 'Foot-candle',
plural: 'Foot-candles'
},
to_anchor: 1
}
};
module.exports = {
metric: metric,
imperial: imperial,
_anchors: {
metric: {
unit: 'lx',
ratio: 1/10.76391
},
imperial: {
unit: 'ft-cd',
ratio: 10.76391
}
}
};

View File

@@ -0,0 +1,86 @@
var metric,
imperial;
metric = {
mm: {
name: {
singular: 'Millimeter',
plural: 'Millimeters'
},
to_anchor: 1/1000
},
cm: {
name: {
singular: 'Centimeter',
plural: 'Centimeters'
},
to_anchor: 1/100
},
m: {
name: {
singular: 'Meter',
plural: 'Meters'
},
to_anchor: 1
},
km: {
name: {
singular: 'Kilometer',
plural: 'Kilometers'
},
to_anchor: 1000
}
};
imperial = {
'in': {
name: {
singular: 'Inch',
plural: 'Inches'
},
to_anchor: 1/12
},
yd: {
name: {
singular: 'Yard',
plural: 'Yards'
},
to_anchor: 3
},
'ft-us': {
name: {
singular: 'US Survey Foot',
plural: 'US Survey Feet'
},
to_anchor: 1.000002
},
ft: {
name: {
singular: 'Foot',
plural: 'Feet'
},
to_anchor: 1
},
mi: {
name: {
singular: 'Mile',
plural: 'Miles'
},
to_anchor: 5280
}
};
module.exports = {
metric: metric,
imperial: imperial,
_anchors: {
metric: {
unit: 'm',
ratio: 3.28084
},
imperial: {
unit: 'ft',
ratio: 1/3.28084
}
}
};

View File

@@ -0,0 +1,78 @@
var metric
, imperial;
metric = {
mcg: {
name: {
singular: 'Microgram'
, plural: 'Micrograms'
}
, to_anchor: 1/1000000
}
, mg: {
name: {
singular: 'Milligram'
, plural: 'Milligrams'
}
, to_anchor: 1/1000
}
, g: {
name: {
singular: 'Gram'
, plural: 'Grams'
}
, to_anchor: 1
}
, kg: {
name: {
singular: 'Kilogram'
, plural: 'Kilograms'
}
, to_anchor: 1000
}
, mt: {
name: {
singular: 'Metric Tonne'
, plural: 'Metric Tonnes'
}
, to_anchor: 1000000
}
};
imperial = {
oz: {
name: {
singular: 'Ounce'
, plural: 'Ounces'
}
, to_anchor: 1/16
}
, lb: {
name: {
singular: 'Pound'
, plural: 'Pounds'
}
, to_anchor: 1
}, t: {
name: {
singular: 'Ton',
plural: 'Tons',
},
to_anchor: 2000,
},
};
module.exports = {
metric: metric
, imperial: imperial
, _anchors: {
metric: {
unit: 'g'
, ratio: 1/453.592
}
, imperial: {
unit: 'lb'
, ratio: 453.592
}
}
};

View File

@@ -0,0 +1,51 @@
var metric
, imperial;
metric = {
'min/km': {
name: {
singular: 'Minute per kilometre'
, plural: 'Minutes per kilometre'
}
, to_anchor: 0.06
}
,'s/m': {
name: {
singular: 'Second per metre'
, plural: 'Seconds per metre'
}
, to_anchor: 1
}
}
imperial = {
'min/mi': {
name: {
singular: 'Minute per mile'
, plural: 'Minutes per mile'
}
, to_anchor: 0.0113636
}
, 's/ft': {
name: {
singular: 'Second per foot'
, plural: 'Seconds per foot'
}
, to_anchor: 1
}
};
module.exports = {
metric: metric
, imperial: imperial
, _anchors: {
metric: {
unit: 's/m'
, ratio: 0.3048
}
, imperial: {
unit: 's/ft'
, ratio: 1/0.3048
}
}
};

View File

@@ -0,0 +1,44 @@
var metric
, imperial;
metric = {
ppm: {
name: {
singular: 'Part-per Million'
, plural: 'Parts-per Million'
}
, to_anchor: 1
}
, ppb: {
name: {
singular: 'Part-per Billion'
, plural: 'Parts-per Billion'
}
, to_anchor: .001
}
, ppt: {
name: {
singular: 'Part-per Trillion'
, plural: 'Parts-per Trillion'
}
, to_anchor: .000001
}
, ppq: {
name: {
singular: 'Part-per Quadrillion'
, plural: 'Parts-per Quadrillion'
}
, to_anchor: .000000001
}
};
module.exports = {
metric: metric
, imperial: {}
, _anchors: {
metric: {
unit: 'ppm'
, ratio: .000001
}
}
};

View File

@@ -0,0 +1,49 @@
var power;
power = {
W: {
name: {
singular: 'Watt'
, plural: 'Watts'
}
, to_anchor: 1
}
, mW: {
name: {
singular: 'Milliwatt'
, plural: 'Milliwatts'
}
, to_anchor: .001
}
, kW: {
name: {
singular: 'Kilowatt'
, plural: 'Kilowatts'
}
, to_anchor: 1000
}
, MW: {
name: {
singular: 'Megawatt'
, plural: 'Megawatts'
}
, to_anchor: 1000000
}
, GW: {
name: {
singular: 'Gigawatt'
, plural: 'Gigawatts'
}
, to_anchor: 1000000000
}
};
module.exports = {
metric: power
, _anchors: {
metric: {
unit: 'W'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,86 @@
var metric
, imperial;
metric = {
Pa: {
name: {
singular: 'pascal'
, plural: 'pascals'
}
, to_anchor: 1/1000
}
, kPa: {
name: {
singular: 'kilopascal'
, plural: 'kilopascals'
}
, to_anchor: 1
}
, MPa: {
name: {
singular: 'megapascal'
, plural: 'megapascals'
}
, to_anchor: 1000
}
, hPa: {
name: {
singular: 'hectopascal'
, plural: 'hectopascals'
}
, to_anchor: 1/10
}
, bar: {
name: {
singular: 'bar'
, plural: 'bar'
}
, to_anchor: 100
}
, mbar: {
name: {
singular: 'mbar'
, plural: 'mbar'
}
, to_anchor: 1/10
}
, torr: {
name: {
singular: 'torr'
, plural: 'torr'
}
, to_anchor: 101325/760000
}
};
imperial = {
psi: {
name: {
singular: 'pound per square inch'
, plural: 'pounds per square inch'
}
, to_anchor: 1/1000
}
, ksi: {
name: {
singular: 'kilopound per square inch'
, plural: 'kilopound per square inch'
}
, to_anchor: 1
}
};
module.exports = {
metric: metric
, imperial: imperial
, _anchors: {
metric: {
unit: 'kPa'
, ratio: 0.00014503768078
}
, imperial: {
unit: 'psi'
, ratio: 1/0.00014503768078
}
}
};

View File

@@ -0,0 +1,49 @@
var reactiveEnergy;
reactiveEnergy = {
VARh: {
name: {
singular: 'Volt-Ampere Reactive Hour'
, plural: 'Volt-Amperes Reactive Hour'
}
, to_anchor: 1
}
, mVARh: {
name: {
singular: 'Millivolt-Ampere Reactive Hour'
, plural: 'Millivolt-Amperes Reactive Hour'
}
, to_anchor: .001
}
, kVARh: {
name: {
singular: 'Kilovolt-Ampere Reactive Hour'
, plural: 'Kilovolt-Amperes Reactive Hour'
}
, to_anchor: 1000
}
, MVARh: {
name: {
singular: 'Megavolt-Ampere Reactive Hour'
, plural: 'Megavolt-Amperes Reactive Hour'
}
, to_anchor: 1000000
}
, GVARh: {
name: {
singular: 'Gigavolt-Ampere Reactive Hour'
, plural: 'Gigavolt-Amperes Reactive Hour'
}
, to_anchor: 1000000000
}
};
module.exports = {
metric: reactiveEnergy
, _anchors: {
metric: {
unit: 'VARh'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,49 @@
var reactivePower;
reactivePower = {
VAR: {
name: {
singular: 'Volt-Ampere Reactive'
, plural: 'Volt-Amperes Reactive'
}
, to_anchor: 1
}
, mVAR: {
name: {
singular: 'Millivolt-Ampere Reactive'
, plural: 'Millivolt-Amperes Reactive'
}
, to_anchor: .001
}
, kVAR: {
name: {
singular: 'Kilovolt-Ampere Reactive'
, plural: 'Kilovolt-Amperes Reactive'
}
, to_anchor: 1000
}
, MVAR: {
name: {
singular: 'Megavolt-Ampere Reactive'
, plural: 'Megavolt-Amperes Reactive'
}
, to_anchor: 1000000
}
, GVAR: {
name: {
singular: 'Gigavolt-Ampere Reactive'
, plural: 'Gigavolt-Amperes Reactive'
}
, to_anchor: 1000000000
}
};
module.exports = {
metric: reactivePower
, _anchors: {
metric: {
unit: 'VAR'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,58 @@
var metric
, imperial;
metric = {
'm/s': {
name: {
singular: 'Metre per second'
, plural: 'Metres per second'
}
, to_anchor: 3.6
}
, 'km/h': {
name: {
singular: 'Kilometre per hour'
, plural: 'Kilometres per hour'
}
, to_anchor: 1
}
}
imperial = {
'm/h': {
name: {
singular: 'Mile per hour'
, plural: 'Miles per hour'
}
, to_anchor: 1
}
, knot: {
name: {
singular: 'Knot'
, plural: 'Knots'
}
, to_anchor: 1.150779
}
, 'ft/s': {
name: {
singular: 'Foot per second'
, plural: 'Feet per second'
}
, to_anchor: 0.681818
}
};
module.exports = {
metric: metric
, imperial: imperial
, _anchors: {
metric: {
unit: 'km/h'
, ratio: 1/1.609344
}
, imperial: {
unit: 'm/h'
, ratio: 1.609344
}
}
};

View File

@@ -0,0 +1,55 @@
var metric
, imperial;
metric = {
C: {
name: {
singular: 'degree Celsius'
, plural: 'degrees Celsius'
}
, to_anchor: 1
, anchor_shift: 0
},
K: {
name: {
singular: 'degree Kelvin'
, plural: 'degrees Kelvin'
}
, to_anchor: 1
, anchor_shift: 273.15
}
};
imperial = {
F: {
name: {
singular: 'degree Fahrenheit'
, plural: 'degrees Fahrenheit'
}
, to_anchor: 1
},
R: {
name: {
singular: 'degree Rankine'
, plural: 'degrees Rankine'
}
, to_anchor: 1
, anchor_shift: 459.67
}
};
module.exports = {
metric: metric
, imperial: imperial
, _anchors: {
metric: {
unit: 'C'
, transform: function (C) { return C / (5/9) + 32 }
}
, imperial: {
unit: 'F'
, transform: function (F) { return (F - 32) * (5/9) }
}
}
};

View File

@@ -0,0 +1,86 @@
var time;
var daysInYear = 365.25;
time = {
ns: {
name: {
singular: 'Nanosecond'
, plural: 'Nanoseconds'
}
, to_anchor: 1/1000000000
}
, mu: {
name: {
singular: 'Microsecond'
, plural: 'Microseconds'
}
, to_anchor: 1/1000000
}
, ms: {
name: {
singular: 'Millisecond'
, plural: 'Milliseconds'
}
, to_anchor: 1/1000
}
, s: {
name: {
singular: 'Second'
, plural: 'Seconds'
}
, to_anchor: 1
}
, min: {
name: {
singular: 'Minute'
, plural: 'Minutes'
}
, to_anchor: 60
}
, h: {
name: {
singular: 'Hour'
, plural: 'Hours'
}
, to_anchor: 60 * 60
}
, d: {
name: {
singular: 'Day'
, plural: 'Days'
}
, to_anchor: 60 * 60 * 24
}
, week: {
name: {
singular: 'Week'
, plural: 'Weeks'
}
, to_anchor: 60 * 60 * 24 * 7
}
, month: {
name: {
singular: 'Month'
, plural: 'Months'
}
, to_anchor: 60 * 60 * 24 * daysInYear / 12
}
, year: {
name: {
singular: 'Year'
, plural: 'Years'
}
, to_anchor: 60 * 60 * 24 * daysInYear
}
};
module.exports = {
metric: time
, _anchors: {
metric: {
unit: 's'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,35 @@
var voltage;
voltage = {
V: {
name: {
singular: 'Volt'
, plural: 'Volts'
}
, to_anchor: 1
}
, mV: {
name: {
singular: 'Millivolt'
, plural: 'Millivolts'
}
, to_anchor: .001
}
, kV: {
name: {
singular: 'Kilovolt'
, plural: 'Kilovolts'
}
, to_anchor: 1000
}
};
module.exports = {
metric: voltage
, _anchors: {
metric: {
unit: 'V'
, ratio: 1
}
}
};

View File

@@ -0,0 +1,200 @@
var metric
, imperial;
metric = {
mm3: {
name: {
singular: 'Cubic Millimeter'
, plural: 'Cubic Millimeters'
}
, to_anchor: 1/1000000
}
, cm3: {
name: {
singular: 'Cubic Centimeter'
, plural: 'Cubic Centimeters'
}
, to_anchor: 1/1000
}
, ml: {
name: {
singular: 'Millilitre'
, plural: 'Millilitres'
}
, to_anchor: 1/1000
}
, cl: {
name: {
singular: 'Centilitre'
, plural: 'Centilitres'
}
, to_anchor: 1/100
}
, dl: {
name: {
singular: 'Decilitre'
, plural: 'Decilitres'
}
, to_anchor: 1/10
}
, l: {
name: {
singular: 'Litre'
, plural: 'Litres'
}
, to_anchor: 1
}
, kl: {
name: {
singular: 'Kilolitre'
, plural: 'Kilolitres'
}
, to_anchor: 1000
}
, m3: {
name: {
singular: 'Cubic meter'
, plural: 'Cubic meters'
}
, to_anchor: 1000
}
, km3: {
name: {
singular: 'Cubic kilometer'
, plural: 'Cubic kilometers'
}
, to_anchor: 1000000000000
}
// Swedish units
, krm: {
name: {
singular: 'Matsked'
, plural: 'Matskedar'
}
, to_anchor: 1/1000
}
, tsk: {
name: {
singular: 'Tesked'
, plural: 'Teskedar'
}
, to_anchor: 5/1000
}
, msk: {
name: {
singular: 'Matsked'
, plural: 'Matskedar'
}
, to_anchor: 15/1000
}
, kkp: {
name: {
singular: 'Kaffekopp'
, plural: 'Kaffekoppar'
}
, to_anchor: 150/1000
}
, glas: {
name: {
singular: 'Glas'
, plural: 'Glas'
}
, to_anchor: 200/1000
}
, kanna: {
name: {
singular: 'Kanna'
, plural: 'Kannor'
}
, to_anchor: 2.617
}
};
imperial = {
tsp: {
name: {
singular: 'Teaspoon'
, plural: 'Teaspoons'
}
, to_anchor: 1/6
}
, Tbs: {
name: {
singular: 'Tablespoon'
, plural: 'Tablespoons'
}
, to_anchor: 1/2
}
, in3: {
name: {
singular: 'Cubic inch'
, plural: 'Cubic inches'
}
, to_anchor: 0.55411
}
, 'fl-oz': {
name: {
singular: 'Fluid Ounce'
, plural: 'Fluid Ounces'
}
, to_anchor: 1
}
, cup: {
name: {
singular: 'Cup'
, plural: 'Cups'
}
, to_anchor: 8
}
, pnt: {
name: {
singular: 'Pint'
, plural: 'Pints'
}
, to_anchor: 16
}
, qt: {
name: {
singular: 'Quart'
, plural: 'Quarts'
}
, to_anchor: 32
}
, gal: {
name: {
singular: 'Gallon'
, plural: 'Gallons'
}
, to_anchor: 128
}
, ft3: {
name: {
singular: 'Cubic foot'
, plural: 'Cubic feet'
}
, to_anchor: 957.506
}
, yd3: {
name: {
singular: 'Cubic yard'
, plural: 'Cubic yards'
}
, to_anchor: 25852.7
}
};
module.exports = {
metric: metric
, imperial: imperial
, _anchors: {
metric: {
unit: 'l'
, ratio: 33.8140226
}
, imperial: {
unit: 'fl-oz'
, ratio: 1/33.8140226
}
}
};

View File

@@ -0,0 +1,282 @@
var metric
, imperial;
metric = {
'mm3/s': {
name: {
singular: 'Cubic Millimeter per second'
, plural: 'Cubic Millimeters per second'
}
, to_anchor: 1/1000000
}
, 'cm3/s': {
name: {
singular: 'Cubic Centimeter per second'
, plural: 'Cubic Centimeters per second'
}
, to_anchor: 1/1000
}
, 'ml/s': {
name: {
singular: 'Millilitre per second'
, plural: 'Millilitres per second'
}
, to_anchor: 1/1000
}
, 'cl/s': {
name: {
singular: 'Centilitre per second'
, plural: 'Centilitres per second'
}
, to_anchor: 1/100
}
, 'dl/s': {
name: {
singular: 'Decilitre per second'
, plural: 'Decilitres per second'
}
, to_anchor: 1/10
}
, 'l/s': {
name: {
singular: 'Litre per second'
, plural: 'Litres per second'
}
, to_anchor: 1
}
, 'l/min': {
name: {
singular: 'Litre per minute'
, plural: 'Litres per minute'
}
, to_anchor: 1/60
}
, 'l/h': {
name: {
singular: 'Litre per hour'
, plural: 'Litres per hour'
}
, to_anchor: 1/3600
}
, 'kl/s': {
name: {
singular: 'Kilolitre per second'
, plural: 'Kilolitres per second'
}
, to_anchor: 1000
}
, 'kl/min': {
name: {
singular: 'Kilolitre per minute'
, plural: 'Kilolitres per minute'
}
, to_anchor: 50/3
}
, 'kl/h': {
name: {
singular: 'Kilolitre per hour'
, plural: 'Kilolitres per hour'
}
, to_anchor: 5/18
}
, 'm3/s': {
name: {
singular: 'Cubic meter per second'
, plural: 'Cubic meters per second'
}
, to_anchor: 1000
}
, 'm3/min': {
name: {
singular: 'Cubic meter per minute'
, plural: 'Cubic meters per minute'
}
, to_anchor: 50/3
}
, 'm3/h': {
name: {
singular: 'Cubic meter per hour'
, plural: 'Cubic meters per hour'
}
, to_anchor: 5/18
}
, 'km3/s': {
name: {
singular: 'Cubic kilometer per second'
, plural: 'Cubic kilometers per second'
}
, to_anchor: 1000000000000
}
};
imperial = {
'tsp/s': {
name: {
singular: 'Teaspoon per second'
, plural: 'Teaspoons per second'
}
, to_anchor: 1/6
}
, 'Tbs/s': {
name: {
singular: 'Tablespoon per second'
, plural: 'Tablespoons per second'
}
, to_anchor: 1/2
}
, 'in3/s': {
name: {
singular: 'Cubic inch per second'
, plural: 'Cubic inches per second'
}
, to_anchor: 0.55411
}
, 'in3/min': {
name: {
singular: 'Cubic inch per minute'
, plural: 'Cubic inches per minute'
}
, to_anchor: 0.55411/60
}
, 'in3/h': {
name: {
singular: 'Cubic inch per hour'
, plural: 'Cubic inches per hour'
}
, to_anchor: 0.55411/3600
}
, 'fl-oz/s': {
name: {
singular: 'Fluid Ounce per second'
, plural: 'Fluid Ounces per second'
}
, to_anchor: 1
}
, 'fl-oz/min': {
name: {
singular: 'Fluid Ounce per minute'
, plural: 'Fluid Ounces per minute'
}
, to_anchor: 1/60
}
, 'fl-oz/h': {
name: {
singular: 'Fluid Ounce per hour'
, plural: 'Fluid Ounces per hour'
}
, to_anchor: 1/3600
}
, 'cup/s': {
name: {
singular: 'Cup per second'
, plural: 'Cups per second'
}
, to_anchor: 8
}
, 'pnt/s': {
name: {
singular: 'Pint per second'
, plural: 'Pints per second'
}
, to_anchor: 16
}
, 'pnt/min': {
name: {
singular: 'Pint per minute'
, plural: 'Pints per minute'
}
, to_anchor: 4/15
}
, 'pnt/h': {
name: {
singular: 'Pint per hour'
, plural: 'Pints per hour'
}
, to_anchor: 1/225
}
, 'qt/s': {
name: {
singular: 'Quart per second'
, plural: 'Quarts per second'
}
, to_anchor: 32
}
, 'gal/s': {
name: {
singular: 'Gallon per second'
, plural: 'Gallons per second'
}
, to_anchor: 128
}
, 'gal/min': {
name: {
singular: 'Gallon per minute'
, plural: 'Gallons per minute'
}
, to_anchor: 32/15
}
, 'gal/h': {
name: {
singular: 'Gallon per hour'
, plural: 'Gallons per hour'
}
, to_anchor: 8/225
}
, 'ft3/s': {
name: {
singular: 'Cubic foot per second'
, plural: 'Cubic feet per second'
}
, to_anchor: 957.506
}
, 'ft3/min': {
name: {
singular: 'Cubic foot per minute'
, plural: 'Cubic feet per minute'
}
, to_anchor: 957.506/60
}
, 'ft3/h': {
name: {
singular: 'Cubic foot per hour'
, plural: 'Cubic feet per hour'
}
, to_anchor: 957.506/3600
}
, 'yd3/s': {
name: {
singular: 'Cubic yard per second'
, plural: 'Cubic yards per second'
}
, to_anchor: 25852.7
}
, 'yd3/min': {
name: {
singular: 'Cubic yard per minute'
, plural: 'Cubic yards per minute'
}
, to_anchor: 25852.7/60
}
, 'yd3/h': {
name: {
singular: 'Cubic yard per hour'
, plural: 'Cubic yards per hour'
}
, to_anchor: 25852.7/3600
}
};
module.exports = {
metric: metric
, imperial: imperial
, _anchors: {
metric: {
unit: 'l/s'
, ratio: 33.8140227
}
, imperial: {
unit: 'fl-oz/s'
, ratio: 1/33.8140227
}
}
};

160
src/convert/fysics.js Normal file
View File

@@ -0,0 +1,160 @@
/*
Copyright:
Year : (c) 2023
Author : Rene De Ren
Contact details : zn375ix3@gmail.com
Location : The Netherlands
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The author shall be notified of any and all improvements or adaptations this software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
class Fysics{
constructor(){
//gasses
this.air_density = 0; // weight of air
this.atm_pressure = 1.01325 ; //atm pressure at sea level
this.earth_gravity = 9.80665 ; // acceleration of standard gravity on earth in m/s2
this.water_molar_mass = 18.01528 // g/mol
this.num_moles_water = 6.022 * 1023; // number of moles in water
this.water_density = 997 ; // this.water_molar_mass * this.num_moles_water // water density in kg/m3;
//load converter
this.convert = require('./index');
// o2 solubility curve based on pressure and temp
this.o2Solubility = {
1: // abs bar
{
x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius
y:[14.6,12.8,11.3,10.1,9.1,8.3,7.6,7,6.5,6,5.6], // mg/l
},
2: // abs bar
{
x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius
y:[29.2,25.5,22.6,20.2,18.2,16.5,15.2,14,12.9,12,11.3], // mg/l
},
4: // abs bar
{
x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius
y:[58.4,51.1,45.1,40.3,36.4,33.1,30.3,27.9,25.9,24,22.7], // mg/l
},
}
}
/*------------------- functions -------------------*/
//use input to calculate air density in kg/m3 is valid up to 100 degrees
//pressure in bar RH in % and temp in degrees celcius
// Antoine Equation is, Log P = A - B / ( T + C )
// D8 = temp , d7 = RH , pressure in mbar = d6
//=1.2923*(273.15/(273.15+temp))*(((pressure*100000)-0.3783*((((MACHT(10,(8.07-(1730.63/(233.43+temp)))))*1000/760)*RH)*100))/(1.01325*100000))
/*
calc_air_dens(pressure,RH,temp){
let air_density =
1.2923 *
(
273.15 / ( 273.15 + temp )
)
*
(
(
(
pressure * 100000
)
- 0.3783 *
(
(
(
(
Math.pow( 10, ( 8.07 - ( 1730.63 / ( 233.43 + temp) ) ) )
)
*1000/760
)
*RH
)
*100
)
)
/ (this.atm_pressure * 100000 )
);
return air_density ;
}
*/
calc_air_dens(pressure, RH, temp) {
const Rd = 287.05; // Specific gas constant for dry air in J/(kg·K)
const Rv = 461.495; // Specific gas constant for water vapor in J/(kg·K)
// Convert temperature to Kelvin
const T = temp + 273.15;
// Antoine constants for water vapor
const A = 8.07131;
const B = 1730.63;
const C = 233.426;
// Calculate saturation vapor pressure (e_s) using the Antoine equation (in hPa)
const e_s = Math.pow(10, (A - (B / (C + temp))));
// Actual vapor pressure (e) in hPa
const e = RH * e_s / 100;
// Convert pressure to Pascals
const pressurePa = this.convert(pressure).from('mbar').to('Pa');
// Partial pressure of dry air (Pa)
const p_d = pressurePa - (e * 100);
// Air density (kg/m³)
const air_density = (p_d / (Rd * T)) + ((e * 100) / (Rv * T));
return air_density;
}
//convert height to pressure ( density => kg/m3 , height => meter) output is in bar
heigth_to_pressure(density,height){
//calc pressure
let pressure = density * this.earth_gravity * height;
//convert Pa to bar
pressure = this.convert(pressure).from('Pa').to('mbar');
return pressure;
}
//input is in meters !
calc_volume(height,width,length){
let result = 0;
result = height * width * length;
return result ;
}
}
/*
let fysics = new Fysics();
console.log("converted pressure : " + fysics.heigth_to_pressure(fysics.water_density,10) + " mbar ");
console.log( "air density : " + fysics.calc_air_dens(1.012,0,0) + " kg / m3" );
//*/
module.exports = Fysics;

304
src/convert/index.js Normal file
View File

@@ -0,0 +1,304 @@
var convert
, keys = require('./lodash/lodash.keys')
, each = require('./lodash/lodash.foreach')
, measures = {
length: require('./definitions/length')
, area: require('./definitions/area')
, mass: require('./definitions/mass')
, volume: require('./definitions/volume')
, each: require('./definitions/each')
, temperature: require('./definitions/temperature')
, time: require('./definitions/time')
, digital: require('./definitions/digital')
, partsPer: require('./definitions/partsPer')
, speed: require('./definitions/speed')
, pace: require('./definitions/pace')
, pressure: require('./definitions/pressure')
, current: require('./definitions/current')
, voltage: require('./definitions/voltage')
, power: require('./definitions/power')
, reactivePower: require('./definitions/reactivePower')
, apparentPower: require('./definitions/apparentPower')
, energy: require('./definitions/energy')
, reactiveEnergy: require('./definitions/reactiveEnergy')
, volumeFlowRate: require('./definitions/volumeFlowRate')
, illuminance: require('./definitions/illuminance')
, frequency: require('./definitions/frequency')
, angle : require('./definitions/angle')
}
, Converter;
Converter = function (numerator, denominator) {
if(denominator)
this.val = numerator / denominator;
else
this.val = numerator;
};
/**
* Lets the converter know the source unit abbreviation
*/
Converter.prototype.from = function (from) {
if(this.destination)
throw new Error('.from must be called before .to');
this.origin = this.getUnit(from);
if(!this.origin) {
this.throwUnsupportedUnitError(from);
}
return this;
};
/**
* Converts the unit and returns the value
*/
Converter.prototype.to = function (to) {
if(!this.origin)
throw new Error('.to must be called after .from');
this.destination = this.getUnit(to);
var result
, transform;
if(!this.destination) {
this.throwUnsupportedUnitError(to);
}
// Don't change the value if origin and destination are the same
if (this.origin.abbr === this.destination.abbr) {
return this.val;
}
// You can't go from liquid to mass, for example
if(this.destination.measure != this.origin.measure) {
throw new Error('Cannot convert incompatible measures of '
+ this.destination.measure + ' and ' + this.origin.measure);
}
/**
* Convert from the source value to its anchor inside the system
*/
result = this.val * this.origin.unit.to_anchor;
/**
* For some changes it's a simple shift (C to K)
* So we'll add it when convering into the unit (later)
* and subtract it when converting from the unit
*/
if (this.origin.unit.anchor_shift) {
result -= this.origin.unit.anchor_shift
}
/**
* Convert from one system to another through the anchor ratio. Some conversions
* aren't ratio based or require more than a simple shift. We can provide a custom
* transform here to provide the direct result
*/
if(this.origin.system != this.destination.system) {
transform = measures[this.origin.measure]._anchors[this.origin.system].transform;
if (typeof transform === 'function') {
result = transform(result)
}
else {
result *= measures[this.origin.measure]._anchors[this.origin.system].ratio;
}
}
/**
* This shift has to be done after the system conversion business
*/
if (this.destination.unit.anchor_shift) {
result += this.destination.unit.anchor_shift;
}
/**
* Convert to another unit inside the destination system
*/
return result / this.destination.unit.to_anchor;
};
/**
* Converts the unit to the best available unit.
*/
Converter.prototype.toBest = function(options) {
if(!this.origin)
throw new Error('.toBest must be called after .from');
var options = Object.assign({
exclude: [],
cutOffNumber: 1,
}, options)
var best;
/**
Looks through every possibility for the 'best' available unit.
i.e. Where the value has the fewest numbers before the decimal point,
but is still higher than 1.
*/
each(this.possibilities(), function(possibility) {
var unit = this.describe(possibility);
var isIncluded = options.exclude.indexOf(possibility) === -1;
if (isIncluded && unit.system === this.origin.system) {
var result = this.to(possibility);
if (!best || (result >= options.cutOffNumber && result < best.val)) {
best = {
val: result,
unit: possibility,
singular: unit.singular,
plural: unit.plural
};
}
}
}.bind(this));
return best;
}
/**
* Finds the unit
*/
Converter.prototype.getUnit = function (abbr) {
var found;
each(measures, function (systems, measure) {
each(systems, function (units, system) {
if(system == '_anchors')
return false;
each(units, function (unit, testAbbr) {
if(testAbbr == abbr) {
found = {
abbr: abbr
, measure: measure
, system: system
, unit: unit
};
return false;
}
});
if(found)
return false;
});
if(found)
return false;
});
return found;
};
var describe = function(resp) {
return {
abbr: resp.abbr
, measure: resp.measure
, system: resp.system
, singular: resp.unit.name.singular
, plural: resp.unit.name.plural
};
}
/**
* An alias for getUnit
*/
Converter.prototype.describe = function (abbr) {
var resp = Converter.prototype.getUnit(abbr);
var desc = null;
try {
desc = describe(resp);
} catch(err) {
this.throwUnsupportedUnitError(abbr);
}
return desc;
};
/**
* Detailed list of all supported units
*/
Converter.prototype.list = function (measure) {
var list = [];
each(measures, function (systems, testMeasure) {
if(measure && measure !== testMeasure)
return;
each(systems, function (units, system) {
if(system == '_anchors')
return false;
each(units, function (unit, abbr) {
list = list.concat(describe({
abbr: abbr,
measure: testMeasure
, system: system
, unit: unit
}));
});
});
});
return list;
};
Converter.prototype.throwUnsupportedUnitError = function (what) {
var validUnits = [];
each(measures, function (systems, measure) {
each(systems, function (units, system) {
if(system == '_anchors')
return false;
validUnits = validUnits.concat(keys(units));
});
});
throw new Error('Unsupported unit ' + what + ', use one of: ' + validUnits.join(', '));
}
/**
* Returns the abbreviated measures that the value can be
* converted to.
*/
Converter.prototype.possibilities = function (measure) {
var possibilities = [];
if(!this.origin && !measure) {
each(keys(measures), function (measure){
each(measures[measure], function (units, system) {
if(system == '_anchors')
return false;
possibilities = possibilities.concat(keys(units));
});
});
} else {
measure = measure || this.origin.measure;
each(measures[measure], function (units, system) {
if(system == '_anchors')
return false;
possibilities = possibilities.concat(keys(units));
});
}
return possibilities;
};
/**
* Returns the abbreviated measures that the value can be
* converted to.
*/
Converter.prototype.measures = function () {
return keys(measures);
};
convert = function (value) {
return new Converter(value);
};
module.exports = convert;

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._basebind v2.3.0
The internal [Lo-Dash](http://lodash.com/) function `baseBind` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,58 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var baseCreate = require('./../lodash._basecreate'),
isObject = require('./../lodash.isobject'),
setBindData = require('./../lodash._setbinddata');
/**
* Used for `Array` method references.
*
* Normally `Array.prototype` would suffice, however, using an array literal
* avoids issues in Narwhal.
*/
var arrayRef = [];
/** Native method shortcuts */
var push = arrayRef.push;
/**
* The base implementation of `_.bind` that creates the bound function and
* sets its meta data.
*
* @private
* @param {Array} bindData The bind data array.
* @returns {Function} Returns the new bound function.
*/
function baseBind(bindData) {
var func = bindData[0],
partialArgs = bindData[2],
thisArg = bindData[4];
function bound() {
// `Function#bind` spec
// http://es5.github.io/#x15.3.4.5
if (partialArgs) {
var args = partialArgs.slice();
push.apply(args, arguments);
}
// mimic the constructor's `return` behavior
// http://es5.github.io/#x13.2.2
if (this instanceof bound) {
// ensure `new bound` is an instance of `func`
var thisBinding = baseCreate(func.prototype),
result = func.apply(thisBinding, args || arguments);
return isObject(result) ? result : thisBinding;
}
return func.apply(thisArg, args || arguments);
}
setBindData(bound, bindData);
return bound;
}
module.exports = baseBind;

View File

@@ -0,0 +1,21 @@
{
"name": "lodash._basebind",
"version": "2.3.0",
"description": "The internal Lo-Dash function `baseBind` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash._basecreate": "~2.3.0",
"lodash.isobject": "~2.3.0",
"lodash._setbinddata": "~2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._basecreate v2.3.0
The internal [Lo-Dash](http://lodash.com/) function `baseCreate` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,42 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var isObject = require('./../lodash.isobject'),
noop = require('./../lodash.noop'),
reNative = require('./../lodash._renative');
/* Native method shortcuts for methods with the same name as other `lodash` methods */
var nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate;
/**
* The base implementation of `_.create` without support for assigning
* properties to the created object.
*
* @private
* @param {Object} prototype The object to inherit from.
* @returns {Object} Returns the new object.
*/
function baseCreate(prototype, properties) {
return isObject(prototype) ? nativeCreate(prototype) : {};
}
// fallback for browsers without `Object.create`
if (!nativeCreate) {
baseCreate = (function() {
function Object() {}
return function(prototype) {
if (isObject(prototype)) {
Object.prototype = prototype;
var result = new Object;
Object.prototype = null;
}
return result || global.Object();
};
}());
}
module.exports = baseCreate;

View File

@@ -0,0 +1,21 @@
{
"name": "lodash._basecreate",
"version": "2.3.0",
"description": "The internal Lo-Dash function `baseCreate` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash.isobject": "~2.3.0",
"lodash.noop": "~2.3.0",
"lodash._renative": "~2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._basecreatecallback v2.3.0
The internal [Lo-Dash](http://lodash.com/) function `baseCreateCallback` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,80 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var bind = require('./../lodash.bind'),
identity = require('./../lodash.identity'),
setBindData = require('./../lodash._setbinddata'),
support = require('./../lodash.support');
/** Used to detected named functions */
var reFuncName = /^\s*function[ \n\r\t]+\w/;
/** Used to detect functions containing a `this` reference */
var reThis = /\bthis\b/;
/** Native method shortcuts */
var fnToString = Function.prototype.toString;
/**
* The base implementation of `_.createCallback` without support for creating
* "_.pluck" or "_.where" style callbacks.
*
* @private
* @param {*} [func=identity] The value to convert to a callback.
* @param {*} [thisArg] The `this` binding of the created callback.
* @param {number} [argCount] The number of arguments the callback accepts.
* @returns {Function} Returns a callback function.
*/
function baseCreateCallback(func, thisArg, argCount) {
if (typeof func != 'function') {
return identity;
}
// exit early for no `thisArg` or already bound by `Function#bind`
if (typeof thisArg == 'undefined' || !('prototype' in func)) {
return func;
}
var bindData = func.__bindData__;
if (typeof bindData == 'undefined') {
if (support.funcNames) {
bindData = !func.name;
}
bindData = bindData || !support.funcDecomp;
if (!bindData) {
var source = fnToString.call(func);
if (!support.funcNames) {
bindData = !reFuncName.test(source);
}
if (!bindData) {
// checks if `func` references the `this` keyword and stores the result
bindData = reThis.test(source);
setBindData(func, bindData);
}
}
}
// exit early if there are no `this` references or `func` is bound
if (bindData === false || (bindData !== true && bindData[1] & 1)) {
return func;
}
switch (argCount) {
case 1: return function(value) {
return func.call(thisArg, value);
};
case 2: return function(a, b) {
return func.call(thisArg, a, b);
};
case 3: return function(value, index, collection) {
return func.call(thisArg, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(thisArg, accumulator, value, index, collection);
};
}
return bind(func, thisArg);
}
module.exports = baseCreateCallback;

View File

@@ -0,0 +1,22 @@
{
"name": "lodash._basecreatecallback",
"version": "2.3.0",
"description": "The internal Lo-Dash function `baseCreateCallback` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash.bind": "~2.3.0",
"lodash.identity": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash.support": "~2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._basecreatewrapper v2.3.0
The internal [Lo-Dash](http://lodash.com/) function `baseCreateWrapper` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,78 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var baseCreate = require('./../lodash._basecreate'),
isObject = require('./../lodash.isobject'),
setBindData = require('./../lodash._setbinddata'),
slice = require('./../lodash._slice');
/**
* Used for `Array` method references.
*
* Normally `Array.prototype` would suffice, however, using an array literal
* avoids issues in Narwhal.
*/
var arrayRef = [];
/** Native method shortcuts */
var push = arrayRef.push;
/**
* The base implementation of `createWrapper` that creates the wrapper and
* sets its meta data.
*
* @private
* @param {Array} bindData The bind data array.
* @returns {Function} Returns the new function.
*/
function baseCreateWrapper(bindData) {
var func = bindData[0],
bitmask = bindData[1],
partialArgs = bindData[2],
partialRightArgs = bindData[3],
thisArg = bindData[4],
arity = bindData[5];
var isBind = bitmask & 1,
isBindKey = bitmask & 2,
isCurry = bitmask & 4,
isCurryBound = bitmask & 8,
key = func;
function bound() {
var thisBinding = isBind ? thisArg : this;
if (partialArgs) {
var args = partialArgs.slice();
push.apply(args, arguments);
}
if (partialRightArgs || isCurry) {
args || (args = slice(arguments));
if (partialRightArgs) {
push.apply(args, partialRightArgs);
}
if (isCurry && args.length < arity) {
bitmask |= 16 & ~32;
return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
}
}
args || (args = arguments);
if (isBindKey) {
func = thisBinding[key];
}
if (this instanceof bound) {
thisBinding = baseCreate(func.prototype);
var result = func.apply(thisBinding, args);
return isObject(result) ? result : thisBinding;
}
return func.apply(thisBinding, args);
}
setBindData(bound, bindData);
return bound;
}
module.exports = baseCreateWrapper;

View File

@@ -0,0 +1,22 @@
{
"name": "lodash._basecreatewrapper",
"version": "2.3.0",
"description": "The internal Lo-Dash function `baseCreateWrapper` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash._basecreate": "~2.3.0",
"lodash.isobject": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash._slice": "~2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._createwrapper v2.3.0
The internal [Lo-Dash](http://lodash.com/) function `createWrapper` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,98 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var baseBind = require('./../lodash._basebind'),
baseCreateWrapper = require('./../lodash._basecreatewrapper'),
isFunction = require('./../lodash.isfunction');
/**
* Used for `Array` method references.
*
* Normally `Array.prototype` would suffice, however, using an array literal
* avoids issues in Narwhal.
*/
var arrayRef = [];
/** Native method shortcuts */
var push = arrayRef.push;
/**
* Creates a function that, when called, either curries or invokes `func`
* with an optional `this` binding and partially applied arguments.
*
* @private
* @param {Function|string} func The function or method name to reference.
* @param {number} bitmask The bitmask of method flags to compose.
* The bitmask may be composed of the following flags:
* 1 - `_.bind`
* 2 - `_.bindKey`
* 4 - `_.curry`
* 8 - `_.curry` (bound)
* 16 - `_.partial`
* 32 - `_.partialRight`
* @param {Array} [partialArgs] An array of arguments to prepend to those
* provided to the new function.
* @param {Array} [partialRightArgs] An array of arguments to append to those
* provided to the new function.
* @param {*} [thisArg] The `this` binding of `func`.
* @param {number} [arity] The arity of `func`.
* @returns {Function} Returns the new function.
*/
function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
var isBind = bitmask & 1,
isBindKey = bitmask & 2,
isCurry = bitmask & 4,
isCurryBound = bitmask & 8,
isPartial = bitmask & 16,
isPartialRight = bitmask & 32;
if (!isBindKey && !isFunction(func)) {
throw new TypeError;
}
if (isPartial && !partialArgs.length) {
bitmask &= ~16;
isPartial = partialArgs = false;
}
if (isPartialRight && !partialRightArgs.length) {
bitmask &= ~32;
isPartialRight = partialRightArgs = false;
}
var bindData = func && func.__bindData__;
if (bindData && bindData !== true) {
bindData = bindData.slice();
// set `thisBinding` is not previously bound
if (isBind && !(bindData[1] & 1)) {
bindData[4] = thisArg;
}
// set if previously bound but not currently (subsequent curried functions)
if (!isBind && bindData[1] & 1) {
bitmask |= 8;
}
// set curried arity if not yet set
if (isCurry && !(bindData[1] & 4)) {
bindData[5] = arity;
}
// append partial left arguments
if (isPartial) {
push.apply(bindData[2] || (bindData[2] = []), partialArgs);
}
// append partial right arguments
if (isPartialRight) {
push.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
}
// merge flags
bindData[1] |= bitmask;
return createWrapper.apply(null, bindData);
}
// fast path for `_.bind`
var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
}
module.exports = createWrapper;

View File

@@ -0,0 +1,21 @@
{
"name": "lodash._createwrapper",
"version": "2.3.0",
"description": "The internal Lo-Dash function `createWrapper` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash._basebind": "~2.3.0",
"lodash._basecreatewrapper": "~2.3.0",
"lodash.isfunction": "~2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._objecttypes v2.3.0
The internal [Lo-Dash](http://lodash.com/) variable `objectTypes` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,20 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/** Used to determine if values are of the language type Object */
var objectTypes = {
'boolean': false,
'function': true,
'object': true,
'number': false,
'string': false,
'undefined': false
};
module.exports = objectTypes;

View File

@@ -0,0 +1,16 @@
{
"name": "lodash._objecttypes",
"version": "2.3.0",
"description": "The internal Lo-Dash variable `objectTypes` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" }
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._renative v2.3.0
The internal [Lo-Dash](http://lodash.com/) variable `reNative` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,23 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/** Used for native method references */
var objectProto = Object.prototype;
/** Used to resolve the internal [[Class]] of values */
var toString = objectProto.toString;
/** Used to detect if a method is native */
var reNative = RegExp('^' +
String(toString)
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/toString| for [^\]]+/g, '.*?') + '$'
);
module.exports = reNative;

View File

@@ -0,0 +1,16 @@
{
"name": "lodash._renative",
"version": "2.3.0",
"description": "The internal Lo-Dash variable `reNative` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" }
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._setbinddata v2.3.0
The internal [Lo-Dash](http://lodash.com/) function `setBindData` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,43 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var noop = require('./../lodash.noop'),
reNative = require('./../lodash._renative');
/** Used as the property descriptor for `__bindData__` */
var descriptor = {
'configurable': false,
'enumerable': false,
'value': null,
'writable': false
};
/** Used to set meta data on functions */
var defineProperty = (function() {
// IE 8 only accepts DOM elements
try {
var o = {},
func = reNative.test(func = Object.defineProperty) && func,
result = func(o, o, o) && func;
} catch(e) { }
return result;
}());
/**
* Sets `this` binding data on a given function.
*
* @private
* @param {Function} func The function to set data on.
* @param {Array} value The data array to set.
*/
var setBindData = !defineProperty ? noop : function(func, value) {
descriptor.value = value;
defineProperty(func, '__bindData__', descriptor);
};
module.exports = setBindData;

View File

@@ -0,0 +1,20 @@
{
"name": "lodash._setbinddata",
"version": "2.3.0",
"description": "The internal Lo-Dash function `setBindData` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash.noop": "~2.3.0",
"lodash._renative": "~2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._shimkeys v2.3.0
The internal [Lo-Dash](http://lodash.com/) function `shimKeys` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,38 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var objectTypes = require('./../lodash._objecttypes');
/** Used for native method references */
var objectProto = Object.prototype;
/** Native method shortcuts */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* A fallback implementation of `Object.keys` which produces an array of the
* given object's own enumerable property names.
*
* @private
* @type Function
* @param {Object} object The object to inspect.
* @returns {Array} Returns an array of property names.
*/
var shimKeys = function(object) {
var index, iterable = object, result = [];
if (!iterable) return result;
if (!(objectTypes[typeof object])) return result;
for (index in iterable) {
if (hasOwnProperty.call(iterable, index)) {
result.push(index);
}
}
return result
};
module.exports = shimKeys;

View File

@@ -0,0 +1,19 @@
{
"name": "lodash._shimkeys",
"version": "2.3.0",
"description": "The internal Lo-Dash function `shimKeys` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash._objecttypes": "~2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash._slice v2.3.0
The internal [Lo-Dash](http://lodash.com/) function `slice` as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,38 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
/**
* Slices the `collection` from the `start` index up to, but not including,
* the `end` index.
*
* Note: This function is used instead of `Array#slice` to support node lists
* in IE < 9 and to ensure dense arrays are returned.
*
* @private
* @param {Array|Object|string} collection The collection to slice.
* @param {number} start The start index.
* @param {number} end The end index.
* @returns {Array} Returns the new array.
*/
function slice(array, start, end) {
start || (start = 0);
if (typeof end == 'undefined') {
end = array ? array.length : 0;
}
var index = -1,
length = end - start || 0,
result = Array(length < 0 ? 0 : length);
while (++index < length) {
result[index] = array[start + index];
}
return result;
}
module.exports = slice;

View File

@@ -0,0 +1,16 @@
{
"name": "lodash._slice",
"version": "2.3.0",
"description": "The internal Lo-Dash function `slice` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" }
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash.bind v2.3.0
The [Lo-Dash](http://lodash.com/) function [`_.bind`](http://lodash.com/docs#bind) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,41 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var createWrapper = require('./../lodash._createwrapper'),
reNative = require('./../lodash._renative'),
slice = require('./../lodash._slice');
/**
* Creates a function that, when called, invokes `func` with the `this`
* binding of `thisArg` and prepends any additional `bind` arguments to those
* provided to the bound function.
*
* @static
* @memberOf _
* @category Functions
* @param {Function} func The function to bind.
* @param {*} [thisArg] The `this` binding of `func`.
* @param {...*} [arg] Arguments to be partially applied.
* @returns {Function} Returns the new bound function.
* @example
*
* var func = function(greeting) {
* return greeting + ' ' + this.name;
* };
*
* func = _.bind(func, { 'name': 'fred' }, 'hi');
* func();
* // => 'hi fred'
*/
function bind(func, thisArg) {
return arguments.length > 2
? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
: createWrapper(func, 1, null, null, thisArg);
}
module.exports = bind;

View File

@@ -0,0 +1,22 @@
{
"name": "lodash.bind",
"version": "2.3.0",
"description": "The Lo-Dash function `_.bind` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash._createwrapper": "~2.3.0",
"lodash._renative": "~2.3.0",
"lodash._slice": "~2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash.foreach v2.3.0
The [Lo-Dash](http://lodash.com/) function [`_.forEach`](http://lodash.com/docs#forEach) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,55 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var baseCreateCallback = require('./../lodash._basecreatecallback'),
forOwn = require('./../lodash.forown');
/**
* Iterates over elements of a collection, executing the callback for each
* element. The callback is bound to `thisArg` and invoked with three arguments;
* (value, index|key, collection). Callbacks may exit iteration early by
* explicitly returning `false`.
*
* Note: As with other "Collections" methods, objects with a `length` property
* are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
* may be used for object iteration.
*
* @static
* @memberOf _
* @alias each
* @category Collections
* @param {Array|Object|string} collection The collection to iterate over.
* @param {Function} [callback=identity] The function called per iteration.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Array|Object|string} Returns `collection`.
* @example
*
* _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
* // => logs each number and returns '1,2,3'
*
* _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
* // => logs each number and returns the object (property order is not guaranteed across environments)
*/
function forEach(collection, callback, thisArg) {
var index = -1,
length = collection ? collection.length : 0;
callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
if (typeof length == 'number') {
while (++index < length) {
if (callback(collection[index], index, collection) === false) {
break;
}
}
} else {
forOwn(collection, callback);
}
return collection;
}
module.exports = forEach;

View File

@@ -0,0 +1,21 @@
{
"name": "lodash.foreach",
"version": "2.3.0",
"description": "The Lo-Dash function `_.forEach` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash._basecreatecallback": "~2.3.0",
"lodash.forown": "~2.3.0"
}
}

View File

@@ -0,0 +1,22 @@
Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js 1.5.2, copyright 2009-2013 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
# lodash.forown v2.3.0
The [Lo-Dash](http://lodash.com/) function [`_.forOwn`](http://lodash.com/docs#forOwn) as a [Node.js](http://nodejs.org/) module generated by [lodash-cli](https://npmjs.org/package/lodash-cli).
## Author
| [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter") |
|---|
| [John-David Dalton](http://allyoucanleet.com/) |
## Contributors
| [![twitter/blainebublitz](http://gravatar.com/avatar/ac1c67fd906c9fecd823ce302283b4c1?s=70)](https://twitter.com/blainebublitz "Follow @BlaineBublitz on Twitter") | [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
|---|---|---|
| [Blaine Bublitz](http://www.iceddev.com/) | [Kit Cambridge](http://kitcambridge.be/) | [Mathias Bynens](http://mathiasbynens.be/) |

View File

@@ -0,0 +1,50 @@
/**
* Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
* Build: `lodash modularize modern exports="npm" -o ./npm/`
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <http://lodash.com/license>
*/
var baseCreateCallback = require('./../lodash._basecreatecallback'),
keys = require('./../lodash.keys'),
objectTypes = require('./../lodash._objecttypes');
/**
* Iterates over own enumerable properties of an object, executing the callback
* for each property. The callback is bound to `thisArg` and invoked with three
* arguments; (value, key, object). Callbacks may exit iteration early by
* explicitly returning `false`.
*
* @static
* @memberOf _
* @type Function
* @category Objects
* @param {Object} object The object to iterate over.
* @param {Function} [callback=identity] The function called per iteration.
* @param {*} [thisArg] The `this` binding of `callback`.
* @returns {Object} Returns `object`.
* @example
*
* _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
* console.log(key);
* });
* // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
*/
var forOwn = function(collection, callback, thisArg) {
var index, iterable = collection, result = iterable;
if (!iterable) return result;
if (!objectTypes[typeof iterable]) return result;
callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
var ownIndex = -1,
ownProps = objectTypes[typeof iterable] && keys(iterable),
length = ownProps ? ownProps.length : 0;
while (++ownIndex < length) {
index = ownProps[ownIndex];
if (callback(iterable[index], index, collection) === false) return result;
}
return result
};
module.exports = forOwn;

View File

@@ -0,0 +1,22 @@
{
"name": "lodash.forown",
"version": "2.3.0",
"description": "The Lo-Dash function `_.forOwn` as a Node.js module generated by lodash-cli.",
"homepage": "http://lodash.com/custom-builds",
"license": "MIT",
"keywords": ["functional", "lodash", "lodash-modularized", "server", "util"],
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine@iceddev.com> (http://www.iceddev.com/)",
"Kit Cambridge <github@kitcambridge.be> (http://kitcambridge.be/)",
"Mathias Bynens <mathias@qiwi.be> (http://mathiasbynens.be/)"
],
"bugs": "https://github.com/lodash/lodash-cli/issues",
"repository": { "type": "git", "url": "https://github.com/lodash/lodash-cli.git" },
"dependencies": {
"lodash._basecreatecallback": "~2.3.0",
"lodash.keys": "~2.3.0",
"lodash._objecttypes": "~2.3.0"
}
}

Some files were not shown because too many files have changed in this diff Show More