@lumjs/core 1.25.2 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,16 @@ and work in CommonJS/Node.js and modern browsers without any modifications.
6
6
 
7
7
  Used by all the rest of my *Lum.js* libraries.
8
8
 
9
+ ## Notes
10
+
11
+ As of version `1.30.0` the `opt.Opts` class has been split into its the new
12
+ [@lumjs/opts] package, which itself depends on a new [@lumjs/errors] package.
13
+
14
+ Until the next major release (`2.0`), this package will have
15
+ a dependency on the split-away pacakages, so that it can have a deprecated
16
+ alias to the old class name available. As of `2.0` that alias will be
17
+ removed, and the dependencies will also be removed.
18
+
9
19
  ## Documentation
10
20
 
11
21
  ### [API Docs](https://supernovus.github.io/docs/js/@lumjs/core/)
@@ -34,3 +44,7 @@ Timothy Totten <2010@totten.ca>
34
44
 
35
45
  [MIT](https://spdx.org/licenses/MIT.html)
36
46
 
47
+ ---
48
+
49
+ [@lumjs/opts]: https://github.com/supernovus/lum.opts.js
50
+ [@lumjs/errors]: https://github.com/supernovus/lum.errors.js
package/TODO.md CHANGED
@@ -17,6 +17,8 @@ for the future would be a good idea. A few of the things I want to do:
17
17
  - Cut anything that seems superfluous or rarely used
18
18
  - Add ability to copy `Symbol` properties
19
19
  - Replace `obj.syncNested` with `obj.sync` using the new `obj.copy` API
20
+ - Move `opt.Opts` into its own separate package
21
+ - Give `observable` some TLC
20
22
 
21
23
  I will likely update this list a bit before I get around to starting the
22
24
  new branch that will eventually become the `2.0.0` release.
package/lib/console.js CHANGED
@@ -177,4 +177,58 @@ for (const method of DEFAULT_METHODS)
177
177
  addMethod(method);
178
178
  }
179
179
 
180
+ /**
181
+ * Export our wrapper to replace the real console
182
+ * in the global namespace.
183
+ *
184
+ * @param {*} [handler] Set a handler while we're exported.
185
+ *
186
+ * See the description of `handler` for possible values.
187
+ * Any existing handler value will be saved to the `preHandler`
188
+ * static property.
189
+ *
190
+ * @function module:@lumjs/core/console.export
191
+ */
192
+ def(LC, 'export', function(handler)
193
+ {
194
+ if (handler !== undefined)
195
+ { // Save the existing handler as 'preHandler'
196
+ LC.preHandler = LC.handler ?? null; // Prevent `undefined`
197
+ LC.handler = handler;
198
+ }
199
+
200
+ globalThis.console = LC;
201
+ return LC;
202
+ });
203
+
204
+ /**
205
+ * Restore the real console.
206
+ *
207
+ * If a `handler` was passed to `export()`, then
208
+ * it will be removed, and the previous handler value
209
+ * will be restored.
210
+ *
211
+ * @function module:@lumjs/core/console.restore
212
+ */
213
+ def(LC, 'restore', function()
214
+ {
215
+ if (LC.preHandler !== undefined)
216
+ {
217
+ LC.handler = LC.preHandler;
218
+ delete LC.preHandler;
219
+ }
220
+
221
+ globalThis.console = LC.real;
222
+ return LC;
223
+ });
224
+
225
+ /**
226
+ * A shortcut for `LC.export(false)`
227
+ * @function module:@lumjs/core/console.mute
228
+ */
229
+ def(LC, 'mute', function()
230
+ {
231
+ return LC.export(false);
232
+ });
233
+
180
234
  module.exports = LC;
package/lib/enum.js CHANGED
@@ -2,7 +2,10 @@ const {S,def,notNil,isObj,needObj,TYPES} = require('./types');
2
2
  const {InternalObjectId} = require('./objectid');
3
3
 
4
4
  // Internal id instances should never be exported.
5
- const ENUM_ID = new InternalObjectId({name: '$Enum'});
5
+ const EID = '@lumjs/core/enum';
6
+ const ENUM_ID = new InternalObjectId({name: EID});
7
+ const isEnum = ENUM_ID.isFunction();
8
+ const EOPT = Symbol(EID+':opts');
6
9
 
7
10
  /**
8
11
  * A function to build magic Enum objects.
@@ -72,7 +75,16 @@ function Enum (spec, opts={})
72
75
  needObj(spec, "Enum spec must be an object")
73
76
  needObj(opts, "Enum options must be an object")
74
77
 
75
- const anEnum = ENUM_ID.tag({});
78
+ const isExisting = isEnum(opts);
79
+ const anEnum = isExisting ? opts : ENUM_ID.tag({});
80
+ if (isExisting)
81
+ {
82
+ opts = anEnum[EOPT];
83
+ if (!isObj(opts))
84
+ {
85
+ throw new Error("existing Enum was not open for changes");
86
+ }
87
+ }
76
88
 
77
89
  const configurable = opts.configurable ?? false;
78
90
  const enumerable = opts.enumerable ?? true;
@@ -130,6 +142,10 @@ function Enum (spec, opts={})
130
142
  counter++;
131
143
  }
132
144
  }
145
+ if (opts.open)
146
+ { // Save the last counter value into the opts
147
+ opts.counter = counter;
148
+ }
133
149
  }
134
150
  else
135
151
  { // An object mapping of property name to value.
@@ -141,7 +157,14 @@ function Enum (spec, opts={})
141
157
  }
142
158
  }
143
159
 
144
- if (!opts.open)
160
+ if (opts.open)
161
+ {
162
+ if (!isExisting)
163
+ { // Save the options into a special Symbol property.
164
+ def(anEnum, EOPT, {value: opts});
165
+ }
166
+ }
167
+ else
145
168
  {
146
169
  if (notNil(opts.lock))
147
170
  { // Use lock() function.
@@ -180,13 +203,11 @@ function Enum (spec, opts={})
180
203
  * @param {*} obj - The expected object/function to test.
181
204
  * @returns {boolean}
182
205
  */
183
- const isEnum = ENUM_ID.isFunction()
184
206
  def(Enum, 'is', isEnum);
185
207
 
186
208
  /**
187
209
  * Is a value an *Enum* magic object?
188
- * @name module:@lumjs/core/types.isEnum
189
- * @function
210
+ * @function module:@lumjs/core/types.isEnum
190
211
  * @param {*} v - The value to test.
191
212
  * @returns {boolean}
192
213
  * @see module:@lumjs/core/enum.is
package/lib/index.js CHANGED
@@ -98,24 +98,19 @@ lazy(exports, 'traits', () => require('./traits'));
98
98
  * @name module:@lumjs/core.opt
99
99
  * @type {module:@lumjs/core/opt}
100
100
  */
101
- const optOpts = {enumerable: true, def:{autoDesc: false}}
102
- lazy(exports, 'opt', () => require('./opt'), optOpts);
101
+ lazy(exports, 'opt', () => require('./opt'), {def:{autoDesc: false}});
103
102
 
104
103
  // Get a bunch of properties from a submodule.
105
- function from(submod, ...libs)
104
+ function from(submod)
106
105
  {
107
- for (const lib of libs)
106
+ for (const key in submod)
108
107
  {
109
- def(exports, lib, submod[lib]);
108
+ def(exports, key, submod[key]);
110
109
  }
111
110
  }
112
111
 
113
112
  // ObjectID stuff is imported directly without registering a sub-module.
114
- const objectid = require('./objectid');
115
- from(objectid,
116
- 'randomNumber',
117
- 'UniqueObjectIds',
118
- 'InternalObjectId');
113
+ from(require('./objectid'));
119
114
 
120
115
  /**
121
116
  * Get a simplistic debugging stacktrace
@@ -143,15 +138,16 @@ from(objectid,
143
138
  * @see module:@lumjs/core/meta.NYI
144
139
  */
145
140
 
141
+ // TODO: documentation for 'deprecated' and 'wrapDepr'
142
+
146
143
  // These are exported directly, but a meta sub-module also exists.
147
144
  // Unlike most sub-modules there is no `meta` property in the main library.
148
145
  const meta = require('./meta');
149
- from(meta,
150
- 'stacktrace',
151
- 'AbstractClass',
152
- 'AbstractError',
153
- 'Functions',
154
- 'NYI');
146
+ from(meta);
147
+ def(exports, 'AbstractClass',
148
+ {
149
+ get() { return meta.AbstractClass; }
150
+ });
155
151
 
156
152
  /**
157
153
  * Create a magic *Enum* object «Lazy»
package/lib/meta.js CHANGED
@@ -1,9 +1,6 @@
1
- /**
2
- * Meta-programming helpers.
3
- * @module @lumjs/core/meta
4
- */
1
+ "use strict";
5
2
 
6
- const {F,S,isArray,isa,console} = require('./types');
3
+ const {F} = require('./types/js');
7
4
 
8
5
  /**
9
6
  * Get a stacktrace. Differs from browser to browser.
@@ -16,7 +13,7 @@ const {F,S,isArray,isa,console} = require('./types');
16
13
  * @param {string} [msg] - A message for the Error object.
17
14
  *
18
15
  * @returns {string[]} An array of stack strings.
19
- * @alias module:@lumjs/core/meta.stacktrace
16
+ * @alias module:@lumjs/core.stacktrace
20
17
  */
21
18
  function stacktrace(msg)
22
19
  {
@@ -25,85 +22,6 @@ function stacktrace(msg)
25
22
 
26
23
  exports.stacktrace = stacktrace;
27
24
 
28
- /**
29
- * Abstract classes for Javascript.
30
- * @deprecated Just use `throw new AbstractError()` instead.
31
- * @alias module:@lumjs/core/meta.AbstractClass
32
- */
33
- class AbstractClass
34
- {
35
- /**
36
- * If you want to mark a method as abstract use this.
37
- */
38
- $abstract(name)
39
- {
40
- if (name.indexOf('(') === -1)
41
- { // Add empty method signature.
42
- name += '()';
43
- }
44
- throw new Error(`Abstract method ${name} was not implemented`);
45
- }
46
-
47
- /**
48
- * Check for required properties
49
- *
50
- * @param {...(string|Array)} needs - What is needed
51
- *
52
- * If this is a `string` it should be in a format like:
53
- *
54
- * - `methodName(arg1,arg2,arg3)`
55
- * - `anotherMethod(number, string, object) : boolean`
56
- * - `yetAnother (className) : resultClass`
57
- *
58
- * The names are case sensitive, and we'll look for the method after
59
- * stripping off anything from the first *non-word* character.
60
- *
61
- * If this is an `Array`, the first item must be the name of a property,
62
- * and each other item should be a type checking value, or array of type
63
- * checking values from the [TYPES]{@link module:@lumjs/core/types.TYPES}
64
- * object, as used by [isa()]{@link module:@lumjs/core/types.isa}.
65
- *
66
- * If you are calling this in an abstract class constructor, likely only
67
- * the method checks will be useful, as the `super()` call must be done
68
- * *before* any instance property assignments.
69
- *
70
- */
71
- $needs(...needs)
72
- {
73
- const className = this.constructor.name;
74
-
75
- const getName = fullName => fullName.replace(/\W.*/, '');
76
- const missing = propName =>
77
- {
78
- throw new Error(`${className} is missing ${propName}`);
79
- }
80
-
81
- for (const need of needs)
82
- {
83
- if (typeof need === S)
84
- { // A simple method
85
- const meth = getName(need);
86
- if (typeof this[meth] !== F)
87
- {
88
- missing(need);
89
- }
90
- }
91
- else if (isArray(need))
92
- {
93
- const prop = getName(need[0]);
94
- const types = need.slice(1);
95
- if (!isa(this[prop], ...types))
96
- {
97
- missing(need);
98
- }
99
- }
100
- }
101
- }
102
-
103
- }
104
-
105
- exports.AbstractClass = AbstractClass;
106
-
107
25
  /**
108
26
  * An Error that can be thrown from abstract methods.
109
27
  *
@@ -132,7 +50,7 @@ exports.AbstractClass = AbstractClass;
132
50
  * }
133
51
  * ```
134
52
  *
135
- * @alias module:@lumjs/core/meta.AbstractError
53
+ * @alias module:@lumjs/core.AbstractError
136
54
  */
137
55
  class AbstractError extends Error
138
56
  {
@@ -165,7 +83,7 @@ exports.AbstractError = AbstractError;
165
83
  /**
166
84
  * Function prototypes for async, generator, and async generator functions.
167
85
  * @namespace
168
- * @alias module:@lumjs/core/meta.Functions
86
+ * @alias module:@lumjs/core.Functions
169
87
  */
170
88
  const Functions =
171
89
  {
@@ -196,7 +114,7 @@ exports.Functions = Functions;
196
114
  * @param {string} [prefix=''] A prefix for the error message.
197
115
  *
198
116
  * @returns {void}
199
- * @alias module:@lumjs/core/meta.NYI
117
+ * @alias module:@lumjs/core.NYI
200
118
  */
201
119
  function NYI(fatal=true, prefix='')
202
120
  {
@@ -208,3 +126,65 @@ function NYI(fatal=true, prefix='')
208
126
  }
209
127
 
210
128
  exports.NYI = NYI;
129
+
130
+ /**
131
+ * Send a deprecation message with a stack trace.
132
+ *
133
+ * @param {string} dep - Name of what is deprecated
134
+ * @param {(string|string[])} [rep] Replacement suggestion(s).
135
+ * @param {*} [ret] Value to return
136
+ * @returns {mixed} `ret`
137
+ * @alias module:@lumjs/core.deprecated
138
+ */
139
+ function deprecated(dep, rep, ret)
140
+ {
141
+ const msgs = [dep,'is deprecated'];
142
+ if (rep)
143
+ {
144
+ msgs.push('replace with', rep);
145
+ }
146
+ console.trace(...msgs);
147
+ return ret;
148
+ }
149
+
150
+ exports.deprecated = deprecated;
151
+
152
+ /**
153
+ * Assign a getter property that when accessed will
154
+ * show a deprecation message via `deprecated()` function
155
+ * before returning the deprecated property value.
156
+ *
157
+ * @param {object} obj - Target to assign property on
158
+ * @param {string} prop - Property to assign
159
+ * @param {(object|function)} spec - Specification
160
+ *
161
+ * If this is a `function` it will be used as the `spec.get` value.
162
+ *
163
+ * @param {function} spec.get - The function that returns the real value
164
+ * @param {string} [spec.dep=prop] Name of what is deprecated;
165
+ * defaults to `prop` if omitted.
166
+ * @param {(string|string[])} [spec.rep] Replacement suggestion(s).
167
+ * @param {object} [spec.opts] Options for `def()` to add getter with.
168
+ *
169
+ * @returns {object} `obj`
170
+ */
171
+ function wrapDepr(obj,prop,spec)
172
+ {
173
+ if (typeof spec === F)
174
+ spec = {get: spec};
175
+ if (typeof spec.get !== F)
176
+ throw new TypeError("invalid init");
177
+
178
+ return def(obj, prop,
179
+ {
180
+ get: () =>
181
+ deprecated(spec.dep??prop, spec.rep, spec.get())
182
+ }, spec.opts);
183
+ }
184
+
185
+ exports.wrapDepr = wrapDepr;
186
+
187
+ // This is near the bottom, but before any calls to wrapDepr.
188
+ const def = require('./types/def');
189
+
190
+ wrapDepr(exports, 'AbstractClass', () => require('./old/abstractclass'));
package/lib/obj/clone.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Import *most* required bits here.
2
- const {N,F, isObj, isComplex, def, isArray, console} = require('../types');
2
+ const {N,F, isObj, isComplex, def, isArray} = require('../types');
3
3
  const copyProps = require('./copyprops');
4
4
  const getProp = require('./getproperty');
5
5
 
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Get some constants
3
- const {S,B,N,F,isObj,isArray,needObj,def,console} = require('../types');
3
+ const {S,B,N,F,isObj,isArray,needObj,def} = require('../types');
4
4
  const {copyAll, duplicateOne: clone} = require('./copyall');
5
5
 
6
6
  const RECURSE_NONE = 0;
package/lib/obj/merge.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Import required bits here.
2
- const {B, N, isObj} = require('../types');
2
+ const {B, isObj} = require('../types');
3
3
  const copyProps = require('./copyprops');
4
4
 
5
5
  // A shortcut for the recursive option.
package/lib/obj/ns.js CHANGED
@@ -3,7 +3,7 @@ const
3
3
  {
4
4
  B, S,
5
5
  root, isObj, needObj, def, nonEmptyArray, notNil,
6
- doesDescriptorTemplate, console,
6
+ doesDescriptorTemplate,
7
7
  } = require('../types');
8
8
 
9
9
  /**
@@ -83,6 +83,11 @@ exports.nsArray = nsArray;
83
83
  * @param {(object|boolean)} [opts] Options changing the behaviours.
84
84
  * If this is a `boolean` it's assumed to be the `opts.log` option.
85
85
  * @param {boolean} [opts.log=false] Log errors for missing namespaces?
86
+ * @param {boolean} [opts.allowFun=true] Allow `obj` to be a `function` ?
87
+ *
88
+ * By default both `object` and `function` are valid `obj` argument values;
89
+ * if this is set to `false`, only `object` values will be allowed.
90
+ *
86
91
  * @param {*} [opts.default] A default value if the namespace is not found.
87
92
  *
88
93
  * @return {*} The property if found, or `opts.default` if not.
@@ -90,13 +95,13 @@ exports.nsArray = nsArray;
90
95
  */
91
96
  function getObjectPath(obj, proppath, opts={})
92
97
  {
93
- needObj(obj, true);
94
-
95
98
  if (typeof opts === B)
96
99
  opts = {log: opts};
97
100
  else if (!isObj(opts))
98
101
  opts = {};
99
102
 
103
+ needObj(obj, (opts.allowFun ?? true));
104
+
100
105
  proppath = nsArray(proppath);
101
106
 
102
107
  for (let p = 0; p < proppath.length; p++)
package/lib/objectid.js CHANGED
@@ -1,20 +1,42 @@
1
1
 
2
2
  const {notNil,def,isNil,F,N,S,SY} = require('./types');
3
+ const argOpts = require('./opt/args');
3
4
 
4
5
  /**
5
- * Generate a large random number.
6
+ * Generate a random number.
6
7
  *
7
- * @param {number} [seed] A base number to use.
8
+ * @param {(object|number)} [opts] Options
8
9
  *
9
- * The default is to use `Date.now()` as the `seed`.
10
+ * @param {number} [opts.seed] A base number to use.
11
+ *
12
+ * If this is omitted, or an invalid value (including `0`),
13
+ * the default is to use `Date.now()` as the `seed`.
14
+ *
15
+ * @param {number} [mode=1] Determine how to handle return value
16
+ *
17
+ * | Mode | Description |
18
+ * | ------- | --------------------------------------------------------- |
19
+ * | `0` | Return unmodified floating point number |
20
+ * | `1` | `Math.round()` (default) |
21
+ * | `2` | `Math.floor()` |
22
+ * | `3` | `Math.ceil()` |
10
23
  *
11
24
  * @returns {number}
12
25
  * @alias module:@lumjs/core.randomNumber
13
26
  */
14
- function randomNumber(seed)
27
+ function randomNumber(seed=0, mode=1)
15
28
  {
16
- if (typeof seed !== N) seed = Date.now();
17
- return Math.floor(Math.random() * seed);
29
+ if (typeof seed !== N || seed === 0) seed = Date.now();
30
+
31
+ const rand = Math.random() * seed;
32
+
33
+ switch(mode)
34
+ {
35
+ case 1: return Math.round(rand);
36
+ case 2: return Math.floor(rand);
37
+ case 3: return Math.ceil(rand);
38
+ default: return rand;
39
+ }
18
40
  }
19
41
 
20
42
  exports.randomNumber = randomNumber;
@@ -206,20 +228,20 @@ class InternalObjectId
206
228
  * @param {object} opts - Named options to change default behaviours.
207
229
  * @param {string} [opts.name] A friendly name for diagnostics.
208
230
  * @param {(string|number)} [opts.id] An internal id value.
209
- * Default value is a large random number.
231
+ * A reasonable default will be generated if it's not specified.
210
232
  * @param {(string|Symbol)} [opts.property] The property name or Symbol.
211
233
  * This is the property that will be set on objects that are tagged
212
- * with this InternalObjectId. Default is `Symbol(this.id)`.
234
+ * with this InternalObjectId. Default is a unique (non-global) `Symbol`.
213
235
  * @param {boolean} [opts.useInstance=true] Store object or id value?
214
236
  * If this is `true` (now the default), we store the instance itself.
215
237
  * If this is `false` (the old default), we store just the `id` value.
216
238
  */
217
239
  constructor(opts={})
218
240
  {
241
+ const ui = this.useInstance = opts.useInstance ?? true;
219
242
  this.name = opts.name;
220
- this.id = opts.id ?? randomNumber();
221
- this.property = opts.property ?? Symbol(this.id);
222
- this.useInstance = opts.useInstance ?? true;
243
+ this.id = opts.id ?? (ui ? Date.now() : randomNumber());
244
+ this.property = opts.property ?? Symbol(this.name ?? this.id);
223
245
  }
224
246
 
225
247
  /**
@@ -268,8 +290,7 @@ class InternalObjectId
268
290
  */
269
291
  isFunction()
270
292
  {
271
- const oid = this;
272
- return function(obj) { return oid.is(obj); }
293
+ return obj => this.is(obj);
273
294
  }
274
295
 
275
296
  }
package/lib/observable.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Defining these after observable.
2
- const {B,F,S,def,isObj,isComplex,TYPES,console} = require('./types');
2
+ const {B,F,S,def,isObj,isComplex,TYPES} = require('./types');
3
3
  const {duplicateAll: clone} = require('./obj/copyall');
4
4
  const lock = Object.freeze;
5
5
 
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ const {F,S,isArray,isa} = require('../types');
4
+
5
+ /**
6
+ * Abstract classes for Javascript.
7
+ * @deprecated Just use `throw new AbstractError()` instead.
8
+ * @alias module:@lumjs/core/meta.AbstractClass
9
+ */
10
+ class AbstractClass
11
+ {
12
+ /**
13
+ * If you want to mark a method as abstract use this.
14
+ */
15
+ $abstract(name)
16
+ {
17
+ if (name.indexOf('(') === -1)
18
+ { // Add empty method signature.
19
+ name += '()';
20
+ }
21
+ throw new Error(`Abstract method ${name} was not implemented`);
22
+ }
23
+
24
+ /**
25
+ * Check for required properties
26
+ *
27
+ * @param {...(string|Array)} needs - What is needed
28
+ *
29
+ * If this is a `string` it should be in a format like:
30
+ *
31
+ * - `methodName(arg1,arg2,arg3)`
32
+ * - `anotherMethod(number, string, object) : boolean`
33
+ * - `yetAnother (className) : resultClass`
34
+ *
35
+ * The names are case sensitive, and we'll look for the method after
36
+ * stripping off anything from the first *non-word* character.
37
+ *
38
+ * If this is an `Array`, the first item must be the name of a property,
39
+ * and each other item should be a type checking value, or array of type
40
+ * checking values from the [TYPES]{@link module:@lumjs/core/types.TYPES}
41
+ * object, as used by [isa()]{@link module:@lumjs/core/types.isa}.
42
+ *
43
+ * If you are calling this in an abstract class constructor, likely only
44
+ * the method checks will be useful, as the `super()` call must be done
45
+ * *before* any instance property assignments.
46
+ *
47
+ */
48
+ $needs(...needs)
49
+ {
50
+ const className = this.constructor.name;
51
+
52
+ const getName = fullName => fullName.replace(/\W.*/, '');
53
+ const missing = propName =>
54
+ {
55
+ throw new Error(`${className} is missing ${propName}`);
56
+ }
57
+
58
+ for (const need of needs)
59
+ {
60
+ if (typeof need === S)
61
+ { // A simple method
62
+ const meth = getName(need);
63
+ if (typeof this[meth] !== F)
64
+ {
65
+ missing(need);
66
+ }
67
+ }
68
+ else if (isArray(need))
69
+ {
70
+ const prop = getName(need[0]);
71
+ const types = need.slice(1);
72
+ if (!isa(this[prop], ...types))
73
+ {
74
+ missing(need);
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ }
81
+
82
+ module.exports = AbstractClass;