breeze-client
Version:
Breeze data management for JavaScript clients
1,450 lines (1,435 loc) • 605 kB
JavaScript
/*
* Copyright 2012-2023 IdeaBlade, Inc. All Rights Reserved.
* Use, reproduction, distribution, and modification of this code is subject to the terms and
* conditions of the IdeaBlade Breeze license, available at http://www.breezejs.com/license
*
* Author: Jay Traband
*/
/**
Base class for all Breeze enumerations, such as EntityState, DataType, FetchStrategy, MergeStrategy etc.
A Breeze Enum is a namespaced set of constant values. Each Enum consists of a group of related constants, called 'symbols'.
Unlike enums in some other environments, each 'symbol' can have both methods and properties.
> class DayOfWeek extends BreezeEnum {
> dayIndex: number;
> isWeekend?: boolean;
> nextDay() {
> let nextIndex = (this.dayIndex + 1) % 7;
> return DayOfWeek.getSymbols()[nextIndex];
> }
>
> static Monday = new DayOfWeek( { dayIndex: 0});
> static Tuesday = new DayOfWeek( { dayIndex: 1 });
> static Wednesday = new DayOfWeek( { dayIndex: 2 });
> static Thursday = new DayOfWeek( { dayIndex: 3 });
> static Friday = new DayOfWeek( { dayIndex: 4 });
> static Saturday = new DayOfWeek( { dayIndex: 5, isWeekend: true });
> static Sunday = new DayOfWeek( { dayIndex: 6, isWeekend: true });
> }
>
> describe("DayOfWeek", () => {
> test("should support full enum capabilities", function() {
> // // custom methods
> let dowSymbols = DayOfWeek.getSymbols();
> expect(dowSymbols.length).toBe(7);
> expect(DayOfWeek.Monday.nextDay()).toBe(DayOfWeek.Tuesday);
> expect(DayOfWeek.Sunday.nextDay()).toBe(DayOfWeek.Monday);
> // // custom properties
> expect(DayOfWeek.Tuesday.isWeekend).toBe(undefined);
> expect(DayOfWeek.Saturday.isWeekend).toBe(true);
> // // Standard enum capabilities
> expect(DayOfWeek.Thursday instanceof DayOfWeek).toBe(true);
> expect(BreezeEnum.isSymbol(DayOfWeek.Wednesday)).toBe(true);
> expect(DayOfWeek.contains(DayOfWeek.Thursday)).toBe(true);
> expect(DayOfWeek.Friday.toString()).toBe("Friday");
> });
> });
Note that we have Error['x'] = ... in some places in the code to prevent Terser from optimizing out some important calls.
@dynamic
*/
class BreezeEnum {
/** @hidden @internal */
static _resolvedNamesAndSymbols;
/** */
constructor(propertiesObj) {
if (propertiesObj) {
Object.keys(propertiesObj).forEach((key) => this[key] = propertiesObj[key]);
}
}
/**
Returns all of the symbols contained within this Enum.
> let symbols = DayOfWeek.getSymbols();
@return All of the symbols contained within this Enum.
**/
static getSymbols() {
return this.resolveSymbols().map(ks => ks.symbol);
}
/**
Returns the names of all of the symbols contained within this Enum.
> let symbols = DayOfWeek.getNames();
@return All of the names of the symbols contained within this Enum.
**/
static getNames() {
return this.resolveSymbols().map(ks => ks.name);
}
/**
Returns an Enum symbol given its name.
> let dayOfWeek = DayOfWeek.from("Thursday");
> // nowdayOfWeek === DayOfWeek.Thursday
@param name - Name for which an enum symbol should be returned.
@return The symbol that matches the name or 'undefined' if not found.
**/
static fromName(name) {
return this[name];
}
/**
Seals this enum so that no more symbols may be added to it. This should only be called after all symbols
have already been added to the Enum. This method also sets the 'name' property on each of the symbols.
> DayOfWeek.resolveSymbols();
**/
static resolveSymbols() {
if (this._resolvedNamesAndSymbols)
return this._resolvedNamesAndSymbols;
let result = [];
for (let key in this) {
if (this.hasOwnProperty(key)) {
let symb = this[key];
if (symb instanceof BreezeEnum) {
result.push({ name: key, symbol: symb });
this[key] = symb;
symb.name = key;
}
}
}
this._resolvedNamesAndSymbols = result;
return result;
}
/**
Returns whether an Enum contains a specified symbol.
> let symbol = DayOfWeek.Friday;
> if (DayOfWeek.contains(symbol)) {
> // do something
> }
@param sym - Object or symbol to test.
@return Whether this Enum contains the specified symbol.
**/
static contains(sym) {
if (!(sym instanceof BreezeEnum)) {
return false;
}
return this[sym.name] != null;
}
// /**
// Checks if an object is an Enum 'symbol'. Use the 'contains' method instead of this one
// if you want to test for a specific Enum.
// > if (Enum.isSymbol(DayOfWeek.Wednesday)) {
// > // do something ...
// > };
// **/
// static isSymbol(obj: any) {
// return obj instanceof BreezeEnum;
// };
/** Returns the string name of this Enum */
toString() {
return this.name;
}
/** Return enum name and symbol name */
toJSON() {
return {
_$typeName: this['_$typeName'] || this.constructor.name,
name: this.name
};
}
}
/** See if this comment will make it into .d.ts */
let hasOwnProperty = uncurry(Object.prototype.hasOwnProperty);
let arraySlice = uncurry(Array.prototype.slice);
let isES5Supported = function () {
try {
return !!(Object.getPrototypeOf && Object.defineProperty({}, 'x', {}));
}
catch (e) {
return false;
}
}();
// iterate over object
function objectForEach(obj, kvFn) {
for (let key in obj) {
if (hasOwnProperty(obj, key)) {
kvFn(key, obj[key]);
}
}
}
function objectMap(obj, kvFn) {
let results = [];
for (let key in obj) {
if (hasOwnProperty(obj, key)) {
let result = kvFn ? kvFn(key, obj[key]) : obj[key];
if (result !== undefined) {
results.push(result);
}
}
}
return results;
}
function objectFirst(obj, kvPredicate) {
for (let key in obj) {
if (hasOwnProperty(obj, key)) {
let value = obj[key];
if (kvPredicate(key, value)) {
return { key: key, value: value };
}
}
}
return null;
}
function arrayFlatMap(arr, mapFn) {
return Array.prototype.concat.apply([], arr.map(mapFn));
}
function isSettable(obj, propertyName) {
let pd = getPropDescriptor(obj, propertyName);
if (pd == null)
return true;
return !!(pd.writable || pd.set);
}
function getPropDescriptor(obj, propertyName) {
if (!isES5Supported)
return undefined;
if (obj.hasOwnProperty(propertyName)) {
return Object.getOwnPropertyDescriptor(obj, propertyName);
}
else {
let nextObj = Object.getPrototypeOf(obj);
if (nextObj == null)
return undefined;
return getPropDescriptor(nextObj, propertyName);
}
}
// Functional extensions
/** can be used like: persons.filter(propEq("firstName", "John")) */
function propEq(propertyName, value) {
return function (obj) {
return obj[propertyName] === value;
};
}
/** can be used like: persons.filter(propEq("firstName", "FirstName", "John")) */
function propsEq(property1Name, property2Name, value) {
return function (obj) {
return obj[property1Name] === value || obj[property2Name] === value;
};
}
/** can be used like persons.map(pluck("firstName")) */
function pluck(propertyName) {
return function (obj) {
return obj[propertyName];
};
}
// end functional extensions
/** Return an array of property values from source */
function getOwnPropertyValues(source) {
let result = [];
for (let name in source) {
if (hasOwnProperty(source, name)) {
result.push(source[name]);
}
}
return result;
}
/** Copy properties from source to target. Returns target. */
function extend(target, source, propNames) {
if (!source)
return target;
if (propNames) {
propNames.forEach(function (propName) {
target[propName] = source[propName];
});
}
else {
for (let propName in source) {
if (hasOwnProperty(source, propName)) {
target[propName] = source[propName];
}
}
}
return target;
}
/** Copy properties from defaults iff undefined on target. Returns target. */
function updateWithDefaults(target, defaults) {
for (let name in defaults) {
if (target[name] === undefined) {
target[name] = defaults[name];
}
}
return target;
}
/** Set ctor.defaultInstance to an instance of ctor with properties from target.
We want to insure that the object returned by ctor.defaultInstance is always immutable
Use 'target' as the primary template for the ctor.defaultInstance;
Use current 'ctor.defaultInstance' as the template for any missing properties
creates a new instance for ctor.defaultInstance
returns target unchanged */
function setAsDefault(target, ctor) {
ctor.defaultInstance = updateWithDefaults(new ctor(target), ctor.defaultInstance);
return target;
}
/**
'source' is an object that will be transformed into another
'template' is a map where the
keys: are the keys to return
if a key contains ','s then the key is treated as a delimited string with first of the
keys being the key to return and the others all valid aliases for this key
'values' are either
1) the 'default' value of the key
2) a function that takes in the source value and should return the value to set
The value from the source is then set on the target,
after first passing thru the fn, if provided, UNLESS:
1) it is the default value
2) it is undefined ( nulls WILL be set)
'target' is optional
- if it exists then properties of the target will be set ( overwritten if the exist)
- if it does not exist then a new object will be created as filled.
'target is returned.
*/
function toJson(source, template, target = {}) {
for (let key in template) {
let aliases = key.split(",");
let defaultValue = template[key];
// using some as a forEach with a 'break'
aliases.some(function (propName) {
if (!(propName in source))
return false;
let value = source[propName];
// there is a functional property defined with this alias ( not what we want to replace).
if (typeof value === 'function')
return false;
// '==' is deliberate here - idea is that null or undefined values will never get serialized
// if default value is set to null.
// tslint:disable-next-line
if (value == defaultValue)
return true;
if (Array.isArray(value) && value.length === 0)
return true;
if (typeof (defaultValue) === "function") {
value = defaultValue(value);
}
else if (typeof (value) === "object") {
if (value && value instanceof BreezeEnum) {
value = value.name;
}
}
if (value === undefined)
return true;
target[aliases[0]] = value;
return true;
});
}
return target;
}
/** Replacer function for toJSONSafe, when serializing entities. Excludes entityAspect and other internal properties. */
function toJSONSafeReplacer(prop, val) {
if (prop === "entityAspect" || prop === "complexAspect" || prop === "entityType" || prop === "complexType"
|| prop === "getProperty" || prop === "setProperty"
|| prop === "constructor" || prop.charAt(0) === '_' || prop.charAt(0) === '$')
return;
return val;
}
/** Safely perform toJSON logic on objects with cycles. */
function toJSONSafe(obj, replacer) {
if (obj !== Object(obj))
return obj; // primitive value
if (obj._$visited)
return undefined;
if (obj.toJSON) {
let newObj = obj.toJSON();
if (newObj !== Object(newObj))
return newObj; // primitive value
if (newObj !== obj)
return toJSONSafe(newObj, replacer);
// toJSON returned the object unchanged.
obj = newObj;
}
obj._$visited = true;
let result;
if (obj instanceof Array) {
result = obj.map(function (o) {
return toJSONSafe(o, replacer);
});
}
else if (typeof (obj) === "function") {
result = undefined;
}
else {
result = {};
for (let prop in obj) {
if (prop === "_$visited")
continue;
let val = obj[prop];
if (replacer) {
val = replacer(prop, val);
if (val === undefined)
continue;
}
val = toJSONSafe(val, replacer);
if (val === undefined)
continue;
result[prop] = val;
}
}
delete obj._$visited;
return result;
}
/** Resolves the values of a list of properties by checking each property in multiple sources until a value is found. */
function resolveProperties(sources, propertyNames) {
let r = {};
let length = sources.length;
propertyNames.forEach(function (pn) {
for (let i = 0; i < length; i++) {
let src = sources[i];
if (src) {
let val = src[pn];
if (val !== undefined) {
r[pn] = val;
break;
}
}
}
});
return r;
}
// array functions
function toArray(item) {
if (item == null) {
return [];
}
else if (Array.isArray(item)) {
return item;
}
else {
return [item];
}
}
/** a version of Array.map that doesn't require an array, i.e. works on arrays and scalars. */
// function map<T, U>(items: T | T[], fn: (v: T, ix?: number) => U, includeNull?: boolean): U | U[] {
function map(items, fn, includeNull) {
// whether to return nulls in array of results; default = true;
includeNull = includeNull == null ? true : includeNull;
if (items == null)
return items;
// let result: U[];
if (Array.isArray(items)) {
let result = [];
items.forEach(function (v, ix) {
let r = fn(v, ix);
if (r != null || includeNull) {
result[ix] = r;
}
});
return result;
}
else {
let result = fn(items);
return result;
}
}
function arrayFirst(array, predicate) {
for (let i = 0, j = array.length; i < j; i++) {
if (predicate(array[i])) {
return array[i];
}
}
return null;
}
function arrayIndexOf(array, predicate) {
for (let i = 0, j = array.length; i < j; i++) {
if (predicate(array[i]))
return i;
}
return -1;
}
/** Add item if not already in array */
function arrayAddItemUnique(array, item) {
let ix = array.indexOf(item);
if (ix === -1)
array.push(item);
}
/** Remove items from the array
* @param array
* @param predicateOrItem - item to remove, or function to determine matching item
* @param shouldRemoveMultiple - true to keep removing after first match, false otherwise
*/
function arrayRemoveItem(array, predicateOrItem, shouldRemoveMultiple) {
let predicate = (isFunction(predicateOrItem) ? predicateOrItem : undefined);
let lastIx = array.length - 1;
let removed = false;
for (let i = lastIx; i >= 0; i--) {
if (predicate ? predicate(array[i]) : (array[i] === predicateOrItem)) {
array.splice(i, 1);
removed = true;
if (!shouldRemoveMultiple) {
return true;
}
}
}
return removed;
}
/** Combine array elements using the callback. Returns array with length == min(a1.length, a2.length) */
function arrayZip(a1, a2, callback) {
let result = [];
let n = Math.min(a1.length, a2.length);
for (let i = 0; i < n; ++i) {
result.push(callback(a1[i], a2[i]));
}
return result;
}
//function arrayDistinct(array) {
// array = array || [];
// let result = [];
// for (let i = 0, j = array.length; i < j; i++) {
// if (result.indexOf(array[i]) < 0)
// result.push(array[i]);
// }
// return result;
//}
// Not yet needed
//// much faster but only works on array items with a toString method that
//// returns distinct string for distinct objects. So this is safe for arrays with primitive
//// types but not for arrays with object types, unless toString() has been implemented.
//function arrayDistinctUnsafe(array) {
// let o = {}, i, l = array.length, r = [];
// for (i = 0; i < l; i += 1) {
// let v = array[i];
// o[v] = v;
// }
// for (i in o) r.push(o[i]);
// return r;
//}
function arrayEquals(a1, a2, equalsFn) {
//Check if the arrays are undefined/null
if (!a1 || !a2)
return false;
if (a1.length !== a2.length)
return false;
//go thru all the vars
for (let i = 0; i < a1.length; i++) {
//if the let is an array, we need to make a recursive check
//otherwise we'll just compare the values
if (Array.isArray(a1[i])) {
if (!arrayEquals(a1[i], a2[i]))
return false;
}
else {
if (equalsFn) {
if (!equalsFn(a1[i], a2[i]))
return false;
}
else {
if (a1[i] !== a2[i])
return false;
}
}
}
return true;
}
// end of array functions
/** Returns an array for a source and a prop, and creates the prop if needed. */
function getArray(source, propName) {
let arr = source[propName];
if (!arr) {
arr = [];
source[propName] = arr;
}
return arr;
}
/** Calls requireLibCore on semicolon-separated libNames */
function requireLib(libNames, errMessage) {
let arrNames = libNames.split(";");
for (let i = 0, j = arrNames.length; i < j; i++) {
let lib = requireLibCore(arrNames[i]);
if (lib)
return lib;
}
if (errMessage) {
throw new Error("Unable to initialize " + libNames + ". " + errMessage);
}
}
/** Returns the 'libName' module if loaded or else returns undefined */
function requireLibCore(libName) {
let win = window || (global ? global.window : undefined);
if (!win)
return; // Must run in a browser. Todo: add commonjs support
// get library from browser globals if we can
let lib = win[libName];
if (lib)
return lib;
// if require exists, maybe require can get it.
// This method is synchronous so it can't load modules with AMD.
// It can only obtain modules from require that have already been loaded.
// Developer should bootstrap such that the breeze module
// loads after all other libraries that breeze should find with this method
// See documentation
let r = win.require;
if (r) { // if require exists
if (r.defined) { // require.defined is not standard and may not exist
// require.defined returns true if module has been loaded
return r.defined(libName) ? r(libName) : undefined;
}
else {
// require.defined does not exist so we have to call require('libName') directly.
// The require('libName') overload is synchronous and does not load modules.
// It throws an exception if the module isn't already loaded.
try {
return r(libName);
}
catch (e) {
// require('libName') threw because module not loaded
return;
}
}
}
}
/** Execute fn while obj has tempValue for property */
function using(obj, property, tempValue, fn) {
if (!obj) {
return fn();
}
let originalValue = obj[property];
if (tempValue === originalValue) {
return fn();
}
obj[property] = tempValue;
try {
return fn();
}
finally {
if (originalValue === undefined) {
delete obj[property];
}
else {
obj[property] = originalValue;
}
}
}
/** Call state = startFn(), call fn(), call endFn(state) */
function wrapExecution(startFn, endFn, fn) {
let state;
try {
state = startFn();
return fn();
}
catch (e) {
if (typeof (state) === 'object') {
state.error = e;
}
throw e;
}
finally {
endFn(state);
}
}
/** Remember & return the value of fn() when it was called with its current args */
function memoize(fn) {
return function () {
let args = arraySlice(arguments), hash = "", i = args.length, currentArg = null;
while (i--) {
currentArg = args[i];
hash += (currentArg === Object(currentArg)) ? JSON.stringify(currentArg) : currentArg;
fn.memoize || (fn.memoize = {});
}
return (hash in fn.memoize) ?
fn.memoize[hash] :
fn.memoize[hash] = fn.apply(this, args);
};
}
const uuidrex = /[xy]/g;
function getUuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(uuidrex, function (c) {
// tslint:disable-next-line
let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
const durationrex = /^P((\d+Y)?(\d+M)?(\d+D)?)?(T(\d+H)?(\d+M)?(\d+S)?)?$/;
const lettersrex = /[A-Za-z]+/g;
function durationToSeconds(duration) {
// basic algorithm from https://github.com/nezasa/iso8601-js-period
if (typeof duration !== "string")
throw new Error("Invalid ISO8601 duration '" + duration + "'");
// regex splits as follows - grp0, grp1, y, m, d, grp2, h, m, s
// 0 1 2 3 4 5 6 7 8
let struct = durationrex.exec(duration);
if (!struct)
throw new Error("Invalid ISO8601 duration '" + duration + "'");
let ymdhmsIndexes = [2, 3, 4, 6, 7, 8]; // -> grp1,y,m,d,grp2,h,m,s
let factors = [31104000,
2592000,
86400,
3600,
60,
1]; // second (1)
let seconds = 0;
for (let i = 0; i < 6; i++) {
let digit = struct[ymdhmsIndexes[i]];
// remove letters, replace by 0 if not defined
digit = (digit ? +digit.replace(lettersrex, '') : 0);
seconds += digit * factors[i];
}
return seconds;
}
// is functions
function noop() {
// does nothing
}
function identity(x) {
return x;
}
function classof(o) {
if (o === null) {
return "null";
}
if (o === undefined) {
return "undefined";
}
return Object.prototype.toString.call(o).slice(8, -1).toLowerCase();
}
function isDate(o) {
return classof(o) === "date" && !isNaN(o.getTime());
}
const isdaterex = /^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/;
function isDateString(s) {
// let rx = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
return (typeof s === "string") && isdaterex.test(s);
}
function isFunction(o) {
return classof(o) === "function";
}
// function isString(o: any) {
// return (typeof o === "string");
// }
// function isObject(o: any) {
// return (typeof o === "object");
// }
const isguidrex = /^[a-fA-F\d]{8}-(?:[a-fA-F\d]{4}-){3}[a-fA-F\d]{12}$/;
function isGuid(value) {
return (typeof value === "string") && isguidrex.test(value);
}
const isdurationrex = /^(-|)?P[T]?[\d\.,\-]+[YMDTHS]/;
function isDuration(value) {
return (typeof value === "string") && isdurationrex.test(value);
}
function isEmpty(obj) {
if (obj === null || obj === undefined) {
return true;
}
for (let key in obj) {
if (hasOwnProperty(obj, key)) {
return false;
}
}
return true;
}
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
// end of is Functions
// string functions
function stringStartsWith$1(str, prefix) {
// returns true for empty string or null prefix
if ((!str))
return false;
if (prefix === "" || prefix == null)
return true;
return str.indexOf(prefix, 0) === 0;
}
function stringEndsWith$1(str, suffix) {
// returns true for empty string or null suffix
if ((!str))
return false;
if (suffix === "" || suffix == null)
return true;
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
// Based on fragment from Dean Edwards' Base 2 library
/** format("a %1 and a %2", "cat", "dog") -> "a cat and a dog" */
function formatString(str, ...params) {
let args = arguments;
let pattern = RegExp("%([1-" + (arguments.length - 1) + "])", "g");
return str.replace(pattern, function (match, index) {
return args[index];
});
}
// See http://stackoverflow.com/questions/7225407/convert-camelcasetext-to-camel-case-text
/** Change text to title case with spaces, e.g. 'myPropertyName12' to 'My Property Name 12' */
const camelEdges = /([A-Z](?=[A-Z][a-z])|[^A-Z](?=[A-Z])|[a-zA-Z](?=[^a-zA-Z]))/g;
function titleCaseSpace(text) {
text = text.replace(camelEdges, '$1 ');
text = text.charAt(0).toUpperCase() + text.slice(1);
return text;
}
// end of string functions
// See Mark Miller’s explanation of what this does.
// http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
function uncurry(f) {
let call = Function.call;
return function () {
return call.apply(f, arguments);
};
}
// shims
if (!Object.create) {
Object.create = function (parent) {
let F = function () {
};
F.prototype = parent;
return new F();
};
}
// strings for error messages
const strings = {
"TO_TYPE": "Add 'EntityQuery.toType()' to your query, or call 'MetadataStore.setEntityTypeForResourceName()' to register an EntityType for this resourceName."
};
// // not all methods above are exported
const core = {
isES5Supported: isES5Supported,
hasOwnProperty: hasOwnProperty,
getOwnPropertyValues: getOwnPropertyValues,
getPropertyDescriptor: getPropDescriptor,
objectForEach: objectForEach,
objectFirst: objectFirst,
objectMap: objectMap,
extend: extend,
propEq: propEq,
propsEq: propsEq,
pluck: pluck,
map: map,
resolveProperties: resolveProperties,
setAsDefault: setAsDefault,
updateWithDefaults: updateWithDefaults,
getArray: getArray,
toArray: toArray,
arrayEquals: arrayEquals,
arraySlice: arraySlice,
arrayFirst: arrayFirst,
arrayIndexOf: arrayIndexOf,
arrayRemoveItem: arrayRemoveItem,
arrayZip: arrayZip,
arrayAddItemUnique: arrayAddItemUnique,
arrayFlatMap: arrayFlatMap,
requireLib: requireLib,
using: using,
wrapExecution: wrapExecution,
memoize: memoize,
getUuid: getUuid,
durationToSeconds: durationToSeconds,
isSettable: isSettable,
isDate: isDate,
isDateString: isDateString,
isGuid: isGuid,
isDuration: isDuration,
isFunction: isFunction,
isEmpty: isEmpty,
isNumeric: isNumeric,
identity: identity,
noop: noop,
stringStartsWith: stringStartsWith$1,
stringEndsWith: stringEndsWith$1,
formatString: formatString,
titleCase: titleCaseSpace,
toJson: toJson,
toJSONSafe: toJSONSafe,
toJSONSafeReplacer: toJSONSafeReplacer,
strings: strings
};
// Unused
/*
// returns true for booleans, numbers, strings and dates
// false for null, and non-date objects, functions, and arrays
function isPrimitive(obj: any) {
if (obj == null) return false;
// true for numbers, strings, booleans and null, false for objects
if (obj != Object(obj)) return true;
return isDate(obj);
}
*/
/** @hidden @internal */
class Param {
// The %1 parameter
// is required
// must be a %2
// must be an instance of %2
// must be an instance of the %2 enumeration
// must have a %2 property
// must be an array where each element
// is optional or
v;
name;
defaultValue;
parent;
/** @hidden @internal */
_context;
/** @hidden @internal */
_contexts;
constructor(v, name) {
this.v = v;
this.name = name;
this._contexts = [null];
}
isObject() {
return this.isTypeOf('object');
}
isBoolean() {
return this.isTypeOf('boolean');
}
isString() {
return this.isTypeOf('string');
}
isNumber() {
return this.isTypeOf('number');
}
isFunction() {
return this.isTypeOf('function');
}
isNonEmptyString() {
return addContext(this, {
fn: isNonEmptyString,
msg: "must be a nonEmpty string"
});
}
isTypeOf(typeName) {
return addContext(this, {
fn: isTypeOf,
typeName: typeName,
msg: "must be a '" + typeName + "'"
});
}
isInstanceOf(type, typeName) {
typeName = typeName || type.prototype._$typeName;
return addContext(this, {
fn: isInstanceOf,
type: type,
typeName: typeName,
msg: "must be an instance of '" + typeName + "'"
});
}
hasProperty(propertyName) {
return addContext(this, {
fn: hasProperty,
propertyName: propertyName,
msg: "must have a '" + propertyName + "' property"
});
}
isEnumOf(enumType) {
return addContext(this, {
fn: isEnumOf,
enumType: enumType,
msg: "must be an instance of the '" + (enumType.name || 'unknown') + "' enumeration"
});
}
isRequired(allowNull = false) {
return addContext(this, {
fn: isRequired,
allowNull: allowNull,
msg: "is required"
});
}
isOptional() {
let context = {
fn: isOptional,
prevContext: null,
msg: isOptionalMessage
};
return addContext(this, context);
}
isNonEmptyArray() {
return this.isArray(true);
}
isArray(mustNotBeEmpty) {
let context = {
fn: isArray,
mustNotBeEmpty: mustNotBeEmpty,
prevContext: null,
msg: isArrayMessage
};
return addContext(this, context);
}
or() {
this._contexts.push(null);
this._context = null;
return this;
}
check(defaultValue) {
let ok = exec(this);
if (ok === undefined)
return;
if (!ok) {
throw new Error(this.getMessage());
}
if (this.v !== undefined) {
return this.v;
}
else {
return defaultValue;
}
}
/** @hidden @internal */
// called from outside this file.
_addContext(context) {
return addContext(this, context);
}
getMessage() {
let that = this;
let message = this._contexts.map(function (context) {
return getMessage(context, that.v);
}).join(", or it ");
return core.formatString(this.MESSAGE_PREFIX, this.name) + " " + message;
}
withDefault(defaultValue) {
this.defaultValue = defaultValue;
return this;
}
whereParam(propName) {
return this.parent.whereParam(propName);
}
applyAll(instance, checkOnly = false) {
let parentTypeName = instance._$typeName;
let allowUnknownProperty = (parentTypeName && this.parent.config._$typeName === parentTypeName);
let clone = core.extend({}, this.parent.config);
this.parent.params.forEach(function (p) {
if (!allowUnknownProperty)
delete clone[p.name];
try {
p.check();
}
catch (e) {
throwConfigError(instance, e.message);
}
(!checkOnly) && p._applyOne(instance);
});
// should be no properties left in the clone
if (!allowUnknownProperty) {
for (let key in clone) {
// allow props with an undefined value
if (clone[key] !== undefined) {
throwConfigError(instance, core.formatString("Unknown property: '%1'.", key));
}
}
}
}
/** @hidden @internal */
_applyOne = function (instance) {
if (this.v !== undefined) {
instance[this.name] = this.v;
}
else {
if (this.defaultValue !== undefined) {
instance[this.name] = this.defaultValue;
}
}
};
MESSAGE_PREFIX = "The '%1' parameter ";
}
/** @hidden @internal */
let assertParam = function (v, name) {
return new Param(v, name);
};
function isTypeOf(context, v) {
if (v == null)
return false;
if (typeof (v) === context.typeName)
return true;
return false;
}
function isNonEmptyString(context, v) {
if (v == null)
return false;
return (typeof (v) === 'string') && v.length > 0;
}
function isInstanceOf(context, v) {
if (v == null || context.type == null)
return false;
return (v instanceof context.type);
}
function isEnumOf(context, v) {
if (v == null || context.enumType == null)
return false;
return context.enumType.contains(v);
}
function hasProperty(context, v) {
if (v == null || context.propertyName == null)
return false;
return (v[context.propertyName] !== undefined);
}
function isRequired(context, v) {
if (context.allowNull) {
return v !== undefined;
}
else {
return v != null;
}
}
function isOptional(context, v) {
if (v == null)
return true;
let prevContext = context.prevContext;
if (prevContext && prevContext.fn) {
return prevContext.fn(prevContext, v);
}
else {
return true;
}
}
function isOptionalMessage(context, v) {
let prevContext = context.prevContext;
let element = prevContext ? " or it " + getMessage(prevContext, v) : "";
return "is optional" + element;
}
function isArray(context, v) {
if (!Array.isArray(v)) {
return false;
}
if (context.mustNotBeEmpty) {
if (v.length === 0)
return false;
}
// allow standalone is array call.
let prevContext = context.prevContext;
if (!prevContext)
return true;
let pc = prevContext;
return v.every(function (v1) {
return pc.fn && pc.fn(pc, v1);
});
}
function isArrayMessage(context, v) {
let arrayDescr = context.mustNotBeEmpty ? "a nonEmpty array" : "an array";
let prevContext = context.prevContext;
let element = prevContext ? " where each element " + getMessage(prevContext, v) : "";
return " must be " + arrayDescr + element;
}
function getMessage(context, v) {
let msg = context.msg;
if (typeof (msg) === "function") {
msg = msg(context, v);
}
return msg;
}
function addContext(that, context) {
if (that._context) {
let curContext = that._context;
while (curContext.prevContext != null) {
curContext = curContext.prevContext;
}
if (curContext.prevContext === null) {
curContext.prevContext = context;
// just update the prevContext but don't change the curContext.
return that;
}
else if (context.prevContext == null) {
context.prevContext = that._context;
}
else {
throw new Error("Illegal construction - use 'or' to combine checks");
}
}
return setContext(that, context);
}
function setContext(that, context) {
that._contexts[that._contexts.length - 1] = context;
that._context = context;
return that;
}
function exec(self) {
// clear off last one if null
let contexts = self._contexts;
if (contexts[contexts.length - 1] == null) {
contexts.pop();
}
if (contexts.length === 0) {
return undefined;
}
return contexts.some(function (context) {
return context.fn ? context.fn(context, self.v) : false;
});
}
function throwConfigError(instance, message) {
throw new Error(core.formatString("Error configuring an instance of '%1'. %2", (instance && instance._$typeName) || "object", message));
}
class ConfigParam {
config;
params;
constructor(config) {
if (typeof (config) !== "object") {
throw new Error("Configuration parameter should be an object, instead it is a: " + typeof (config));
}
this.config = config;
this.params = [];
}
whereParam(propName) {
let param = new Param(this.config[propName], propName);
param.parent = this;
this.params.push(param);
return param;
}
}
/** @hidden @internal */
let assertConfig = function (config) {
return new ConfigParam(config);
};
// Param is exposed so that additional 'is' methods can be added to the prototype.
core.Param = Param;
core.assertParam = assertParam;
core.assertConfig = assertConfig;
function publishCore(that, data, errorCallback) {
let subscribers = that._subscribers;
if (!subscribers)
return true;
// subscribers from outer scope.
subscribers.forEach(function (s) {
try {
s.callback(data);
}
catch (e) {
e.context = "unable to publish on topic: " + that.name;
if (errorCallback) {
errorCallback(e);
}
else if (that._defaultErrorCallback) {
that._defaultErrorCallback(e);
}
else {
fallbackErrorHandler(e);
}
}
});
}
function fallbackErrorHandler(e) {
// TODO: maybe log this
// for now do nothing;
}
/**
Class to support basic event publication and subscription semantics.
@dynamic
**/
class BreezeEvent {
/** @hidden @internal */
static __eventNameMap = {};
/** @hidden @internal */
static __nextUnsubKey = 1;
/** The name of this Event */
name;
/** The object doing the publication. i.e. the object to which this event is attached. */
publisher;
/** @hidden @internal */
_subscribers;
/** @hidden @internal */
_defaultErrorCallback;
/**
Constructor for an Event
> salaryEvent = new BreezeEvent("salaryEvent", person);
@param name - The name of the event.
@param publisher - The object that will be doing the publication. i.e. the object to which this event is attached.
@param defaultErrorCallback - Function to call when an error occurs during subscription execution.
If omitted then subscriber notification failures will be ignored.
**/
constructor(name, publisher, defaultErrorCallback) {
assertParam(name, "eventName").isNonEmptyString().check();
assertParam(publisher, "publisher").isObject().check();
this.name = name;
// register the name
BreezeEvent.__eventNameMap[name] = true;
this.publisher = publisher;
if (defaultErrorCallback) {
this._defaultErrorCallback = defaultErrorCallback;
}
}
/**
Publish data for this event.
> // Assume 'salaryEvent' is previously constructed Event
> salaryEvent.publish( { eventType: "payRaise", amount: 100 });
This event can also be published asychronously
> salaryEvent.publish( { eventType: "payRaise", amount: 100 }, true);
And we can add a handler in case the subscriber 'mishandles' the event.
> salaryEvent.publish( { eventType: "payRaise", amount: 100 }, true, function(error) {
> // do something with the 'error' object
> });
@param data - Data to publish
@param publishAsync - (default=false) Whether to publish asynchonously or not.
@param errorCallback - Function to be called for any errors that occur during publication. If omitted,
errors will be eaten.
@return false if event is disabled; true otherwise.
**/
publish(data, publishAsync = false, errorCallback) {
if (!BreezeEvent._isEnabled(this.name, this.publisher))
return false;
if (publishAsync === true) {
setTimeout(publishCore, 0, this, data, errorCallback);
}
else {
publishCore(this, data, errorCallback);
}
return true;
}
/**
Publish data for this event asynchronously.
> // Assume 'salaryEvent' is previously constructed Event
> salaryEvent.publishAsync( { eventType: "payRaise", amount: 100 });
And we can add a handler in case the subscriber 'mishandles' the event.
> salaryEvent.publishAsync( { eventType: "payRaise", amount: 100 }, function(error) {
> // do something with the 'error' object
> });
@param data - Data to publish
@param errorCallback - Function to be called for any errors that occur during publication. If omitted,
errors will be eaten.
**/
publishAsync(data, errorCallback) {
this.publish(data, true, errorCallback);
}
/**
Subscribe to this event.
> // Assume 'salaryEvent' is previously constructed Event
> salaryEvent.subscribe(function (eventArgs) {
> if (eventArgs.eventType === "payRaise") {
> // do something
> }
> });
There are several built in Breeze events, such as [[EntityAspect.propertyChanged]], [[EntityAspect.validationErrorsChanged]] as well.
> // Assume order is a preexisting 'order' entity
> order.entityAspect.propertyChanged.subscribe(function (pcEvent) {
> if ( pcEvent.propertyName === "OrderDate") {
> // do something
> }
> });
@param callback- Function to be called whenever 'data' is published for this event.
@param callback.data - {Object} Whatever 'data' was published. This should be documented on the specific event.
@return This is a key for 'unsubscription'. It can be passed to the 'unsubscribe' method.
**/
subscribe(callback) {
if (!this._subscribers) {
this._subscribers = [];
}
let unsubKey = BreezeEvent.__nextUnsubKey;
this._subscribers.push({ unsubKey: unsubKey, callback: callback });
++BreezeEvent.__nextUnsubKey;
return unsubKey;
}
/**
Unsubscribe from this event.
> // Assume order is a preexisting 'order' entity
> let token = order.entityAspect.propertyChanged.subscribe(function (pcEvent) {
> // do something
> });
> // sometime later
> order.entityAspect.propertyChanged.unsubscribe(token);
@param unsubKey - The value returned from the 'subscribe' method may be used to unsubscribe here.
@return Whether unsubscription occured. This will return false if already unsubscribed or if the key simply
cannot be found.
**/
unsubscribe = function (unsubKey) {
if (!this._subscribers)
return false;
let subs = this._subscribers;
let ix = core.arrayIndexOf(subs, function (s) {
return s.unsubKey === unsubKey;
});
if (ix !== -1) {
subs.splice(ix, 1);
if (subs.length === 0) {
this._subscribers = null;
}
return true;
}
else {
return false;
}
};
/** remove all subscribers */
clear() {
this._subscribers = null;
}
/** event bubbling - document later. */
// null or undefined 'getParentFn' means Event does not need to bubble i.e. that it is always enabled - .
static bubbleEvent(target, getParentFn) {
target._getEventParent = getParentFn || null;
}
/**
Enables or disables the named event for an object and all of its children.
> BreezeEvent.enable(“propertyChanged”, myEntityManager, false)
will disable all EntityAspect.propertyChanged events within a EntityManager.
> BreezeEvent.enable(“propertyChanged”, myEntityManager, true)
will enable all EntityAspect.propertyChanged events within a EntityManager.
> BreezeEvent.enable(“propertyChanged”, myEntity.entityAspect, false)
will disable EntityAspect.propertyChanged events for a specific entity.
> BreezeEvent.enable(“propertyChanged”, myEntity.entityAspect, null)
will removes any enabling / disabling at the entity aspect level so now any 'Event.enable' calls at the EntityManager level,
made either previously or in the future, will control notification.
> BreezeEvent.enable(“validationErrorsChanged”, myEntityManager, function(em) {
> return em.customTag === “blue”;
> })
will either enable or disable myEntityManager based on the current value of a ‘customTag’ property on myEntityManager.
Note that this is dynamic, changing the customTag value will cause events to be enabled or disabled immediately.
@param eventName - The name of the event.
@param target - The object at which enabling or disabling will occur. All event notifications that occur to this object or
children of this object will be enabled or disabled.
@param isEnabled - A boolean, a null or a function that returns either a boolean or a null.
**/
static enable(eventName, obj, isEnabled) {
assertParam(eventName, "eventName").isNonEmptyString().check();
assertParam(obj, "obj").isObject().check();
assertParam(isEnabled, "isEnabled").isBoolean().isOptional().or().isFunction().check();
let ob = obj;
if (!ob._$eventMap) {
ob._$eventMap = {};
}
ob._$eventMap[eventName] = isEnabled;
}
/**
Returns whether for a specific event and a specific object and its children, notification is enabled or disabled or not set.
> BreezeEvent.isEnabled(“propertyChanged”, myEntityManager)
>
@param eventName - The name of the event.
@param target - The object for which we want to know if notifications are enabled.
@return A null is returned if this value has not been set.
**/
static isEnabled(eventName, obj) {
assertParam(eventName, "eventName").isNonEmptyString().check();
assertParam(obj, "obj").isObject().check();
// null is ok - it just means that the object is at the top level.
if (obj._getEventParent === undefined) {
throw new Error("This object does not support event enabling/disabling");
}
// return ctor._isEnabled(getFullEventName(eventName), obj);
return BreezeEvent._isEnabled(eventName, 3);
}
/** @hidden @internal */
static _isEnabled = function (eventName, obj) {
let isEnabled = null;
let ob = obj;
let eventMap = ob._$eventMap;
if (eventMap) {
isEnabled = eventMap[eventName];
}
if (isEnabled != null) {
if (typeof isEnabled === 'function') {
return !!isEnabled(obj);
}
else {
return !!isEnabled;
}
}
else {
let parent = ob._getEventParent && ob._getEventParent();
if (parent) {
return !!this._isEnabled(eventName, parent);
}
else {
// default if not explicitly disabled.
return true;
}
}
};
}
// legacy support - deliberately not typed
core.Event = BreezeEvent;
class InterfaceDef {
name;
defaultInstance;
/** @hidden @internal */
_impl