1 | "use strict";
|
2 | StandardLib = require 'art-standard-lib'
|
3 | WebpackHotLoader = require './WebpackHotLoader'
|
4 |
|
5 | {
|
6 | capitalize, decapitalize, log
|
7 | isFunction, objectName,
|
8 | isPlainObject, functionName, isString
|
9 | isPlainArray
|
10 | Unique
|
11 | callStack
|
12 | Log
|
13 | log
|
14 | inspectedObjectLiteral
|
15 | MinimalBaseObject
|
16 | getModuleBeingDefined
|
17 | concatInto
|
18 | mergeInto
|
19 | merge
|
20 | neq
|
21 | isString
|
22 | object
|
23 | getSuperclass
|
24 | } = StandardLib
|
25 |
|
26 | {nextUniqueObjectId} = Unique
|
27 |
|
28 | ExtendablePropertyMixin = require './ExtendablePropertyMixin'
|
29 |
|
30 | module.exports = class BaseClass extends ExtendablePropertyMixin MinimalBaseObject
|
31 | @objectsCreated: 0
|
32 | @objectsCreatedByType: {}
|
33 | @resetStats: =>
|
34 | @objectsCreated = 0
|
35 | @objectsCreatedByType = {}
|
36 |
|
37 | # override to dynamically set a class's name (useful for programmatically generated classes)
|
38 | # NOTE: must use klass.getName() and not klass.name if you want to "see" dynamically assigned class-names
|
39 | @_name: null
|
40 |
|
41 | ###
|
42 | NOTE: only hasOwnProperties are considered! Inherited properties are not touched.
|
43 | IN:
|
44 | targetObject: object will be altered to be an "imprint" of fromObject
|
45 | fromObject: object pattern used to imprint targetObject
|
46 | preserveState:
|
47 | false:
|
48 | targetObject has every property updated to exactly match fromObject
|
49 |
|
50 | This includes:
|
51 | 1. delete properties in targetObject that are not in fromObject
|
52 | 2. add every property in fromObject but not in targetObject
|
53 | 3. overwriting every property in targetObject also in fromObject
|
54 |
|
55 | true:
|
56 | Attempts to preserve the state of targetObject while updating its functionality.
|
57 | This means properties which are functions in either object are updated.
|
58 |
|
59 | WARNING: This is a grey area for JavaScript. It is not entirely clear what is
|
60 | state and what is 'functionality'. I, SBD, have made the following heuristic decisions:
|
61 |
|
62 | Imprint actions taken when preserving State:
|
63 |
|
64 | 1. DO NOTHING to properties in targetObject that are not in fromObject
|
65 | 2. add every property in fromObject but not in targetObject
|
66 | 3. properties in targetObject that are also in fromObject are updated
|
67 | if one of the following are true:
|
68 | - isFunction fromObject[propName]
|
69 | - isFunction targetObject[propName]
|
70 | - propName does NOT start with "_"
|
71 | NOTE: property existance is detected using Object.getOwnPropertyDescriptor
|
72 |
|
73 | ###
|
74 | thoroughDeleteProperty = (object, propName) ->
|
75 | Object.defineProperty object, propName,
|
76 | configurable: true
|
77 | writable: false
|
78 | value: 1
|
79 |
|
80 | delete object[propName]
|
81 |
|
82 | nonImprintableProps = ["__proto__", "prototype"]
|
83 |
|
84 | @imprintObject: imprintObject = (targetObject, sourceObject, preserveState = false, returnActionsTaken) ->
|
85 | targetPropertyNames = Object.getOwnPropertyNames targetObject
|
86 | sourcePropertyNames = Object.getOwnPropertyNames sourceObject
|
87 |
|
88 | if returnActionsTaken
|
89 | addedProps =
|
90 | removedProps =
|
91 | changedProps = undefined
|
92 |
|
93 | unless preserveState
|
94 | for targetPropName in targetPropertyNames when !(targetPropName in sourcePropertyNames)
|
95 | (removedProps?=[]).push targetPropName if returnActionsTaken
|
96 | thoroughDeleteProperty targetObject, targetPropName
|
97 |
|
98 | for sourcePropName in sourcePropertyNames when !(sourcePropName in nonImprintableProps)
|
99 | targetPropDescriptor = Object.getOwnPropertyDescriptor targetObject, sourcePropName
|
100 | sourcePropDescriptor = Object.getOwnPropertyDescriptor sourceObject, sourcePropName
|
101 |
|
102 | sourceValueIsFunction = isFunction sourceValue = sourcePropDescriptor.value
|
103 | targetValueIsFunction = isFunction targetValue = targetPropDescriptor?.value
|
104 | if (
|
105 | !preserveState || !targetPropDescriptor ||
|
106 | sourceValueIsFunction || targetValueIsFunction ||
|
107 | !sourcePropName.match /^_/
|
108 | )
|
109 | if returnActionsTaken
|
110 | if !targetPropDescriptor
|
111 | (addedProps?=[]).push sourcePropName if sourcePropName != "_name"
|
112 | else
|
113 | if neqResult = neq sourceValue, targetValue, true
|
114 | (changedProps?=[]).push sourcePropName
|
115 |
|
116 | Object.defineProperty targetObject, sourcePropName, sourcePropDescriptor
|
117 |
|
118 | if returnActionsTaken
|
119 | (removedProps || changedProps || addedProps) &&
|
120 | merge {removedProps, changedProps, addedProps}
|
121 |
|
122 | else
|
123 | sourceObject
|
124 |
|
125 | ###
|
126 | imprints both the class and its prototype.
|
127 |
|
128 | preserved in spite of imprintObject's rules:
|
129 | @namespace
|
130 | @::constructor
|
131 | ###
|
132 | @imprintFromClass: (updatedKlass, returnActionsTaken) ->
|
133 | unless updatedKlass == @
|
134 | {namespace, namespacePath, _name} = @
|
135 | oldConstructor = @::constructor
|
136 |
|
137 | classUpdates = imprintObject @, updatedKlass, true, returnActionsTaken
|
138 | prototypeUpdates = imprintObject @::, updatedKlass::, false, returnActionsTaken
|
139 |
|
140 | @::constructor = oldConstructor
|
141 | @namespace = namespace
|
142 | @namespacePath = namespacePath
|
143 | @_name = _name
|
144 |
|
145 | if returnActionsTaken
|
146 | merge
|
147 | class: classUpdates
|
148 | prototype: prototypeUpdates
|
149 | else
|
150 | @
|
151 |
|
152 | @getHotReloadKey: -> @getName()
|
153 |
|
154 | ###
|
155 | IN:
|
156 | _module should be the CommonJS 'module'
|
157 | klass: class object which extends BaseClass
|
158 |
|
159 | liveClass:
|
160 | On the first load, liveClass gets set.
|
161 | Each subsequent hot-load UPDATES liveClass,
|
162 | but liveClass always points to the initially created class object.
|
163 |
|
164 | OUT: the result of the call to liveClass.postCreate()
|
165 |
|
166 | postCreate is passed:
|
167 | hotReloaded: # true if this is anything but the initial load
|
168 | classModuleState:
|
169 | liveClass: # the original liveClass
|
170 | hotUpdatedFromClass: # the most recently hot-loaded class
|
171 | hotReloadVersion: # number starting at 0 and incremented with each hot reload
|
172 | _module: # the CommonJs module
|
173 |
|
174 | EFFECTS:
|
175 | The following two methods are invoked on liveClass:
|
176 |
|
177 | if hot-reloading
|
178 | liveClass.imprintFromClass klass
|
179 |
|
180 | # always:
|
181 | liveClass.postCreate hotReloaded, classModuleState, _module
|
182 |
|
183 | ###
|
184 | @createWithPostCreate: createWithPostCreate = (a, b) ->
|
185 | klass = if b
|
186 | _module = a
|
187 | b
|
188 | else a
|
189 |
|
190 | # TODO - maybe we should make an NPM just for defineModule, so this is cleaner?
|
191 | _module ||= getModuleBeingDefined() || global.__definingModule
|
192 |
|
193 | # if hot reloading is not supported:
|
194 | return klass unless klass?.postCreate
|
195 | unless _module?.hot
|
196 | return klass.postCreate(
|
197 | hotReloadEnabled: false
|
198 | hotReloaded: false
|
199 | classModuleState: {}
|
200 | module: _module
|
201 | ) || klass
|
202 |
|
203 | # hot reloading supported:
|
204 | WebpackHotLoader.runHot _module, (moduleState) ->
|
205 | hotReloadKey = klass.getHotReloadKey()
|
206 | if classModuleState = moduleState[hotReloadKey]
|
207 | # hot reloaded!
|
208 | {liveClass} = classModuleState
|
209 | hotReloaded = true
|
210 |
|
211 | classModuleState.hotReloadVersion++
|
212 | classModuleState.hotUpdatedFromClass = klass
|
213 |
|
214 | # set namespaceProps in case it uses them internally
|
215 | # NOTE: everyone else will access these props through liveClass, which is already correct
|
216 | liveClass.namespace._setChildNamespaceProps liveClass.getName(), klass
|
217 |
|
218 | klass._name = liveClass._name
|
219 | liveClass.classModuleState = classModuleState
|
220 | updates = liveClass.imprintFromClass klass, true
|
221 |
|
222 | log "Art.ClassSystem.BaseClass #{liveClass.getName?()} HotReload":
|
223 | version: classModuleState.hotReloadVersion
|
224 | updates: updates
|
225 | else
|
226 | # initial load
|
227 | hotReloaded = false
|
228 |
|
229 | klass._hotClassModuleState =
|
230 | moduleState[hotReloadKey] = klass.classModuleState = classModuleState =
|
231 | liveClass: liveClass = klass
|
232 | hotUpdatedFromClass: null
|
233 | hotReloadVersion: 0
|
234 |
|
235 | liveClass.postCreate
|
236 | hotReloadEnabled: true
|
237 | hotReloaded: hotReloaded
|
238 | classModuleState: classModuleState
|
239 | module: _module
|
240 |
|
241 | # depricated alias
|
242 | @createHotWithPostCreate: (a, b) ->
|
243 | log.error "createHotWithPostCreate is DEPRICATED"
|
244 | createWithPostCreate a, b
|
245 |
|
246 | ###
|
247 | called every load
|
248 | IN: options:
|
249 | NOTE: hot-loading inputs are only set if this class created as follows:
|
250 | createHotWithPostCreate module, class Foo extends BaseClass
|
251 |
|
252 | hotReload: true/false
|
253 | true if this class was hot-reloaded
|
254 |
|
255 | hotReloadEnabled: true/false
|
256 |
|
257 | classModuleState:
|
258 | liveClass: the first-loaded version of the class.
|
259 | This is the official version of the class at all times.
|
260 | The hot-reloaded version of the class is "imprinted" onto the liveClass
|
261 | but otherwise is not used (but can be accessed via classModuleState.hotUpdatedFromClass)
|
262 | hotUpdatedFromClass: The most recently loaded version of the class.
|
263 | hotReloadVersion: number, starting at 1, and counting up each load
|
264 |
|
265 | classModuleState is a plain-object specific to the class and its CommonJS module. If there is
|
266 | more than one hot-loaded class in the same module, each will have its own classModuleState.
|
267 |
|
268 | SBD NOTE: Though we could allow clients to add fields to classModuleState, I think it works
|
269 | just as well, and is cleaner, if any state is stored in the actual class objects and
|
270 | persisted via postCreate.
|
271 |
|
272 | module: the CommonJs module object.
|
273 |
|
274 | {hotReloadEnabled, hotReloaded, classModuleState, module} = options
|
275 | ###
|
276 | @postCreate: (options) ->
|
277 | # TODO - once we switch fully to ES6, we should revisit how we handle @namespace
|
278 | # Normally, @namespace and @namespacePath get set by the parent NeptuneNamespace's index file AFTER postCreate.
|
279 | # However, if you need to require a file directly without requiring everything else in the namespace,
|
280 | # you can add: @setNamespace require './namespace'
|
281 | # to your class and you'll still get all the useful namespace functions.
|
282 | # The above command makes your file work either way - as part of the full namespace or
|
283 | # included by itself.
|
284 | # SBD: I pulled the following code because it breaks hot-reloading.
|
285 | # @namespacePath = if @namespace = @_namespace ? null
|
286 | # "#{@namespace.namespacePath}.#{@getName()}"
|
287 | # else
|
288 | # null
|
289 |
|
290 | if @getIsAbstractClass()
|
291 | @postCreateAbstractClass options
|
292 | else
|
293 | @postCreateConcreteClass options
|
294 |
|
295 | @setNamespace: (ns) ->
|
296 | @_namespace = ns
|
297 |
|
298 | @postCreateAbstractClass: (options) -> @
|
299 | @postCreateConcreteClass: (options) -> @
|
300 |
|
301 | # excludedKeys = ["__super__", "namespace", "namespacePath"].concat Object.keys Neptune.Base
|
302 |
|
303 | constructor: ->
|
304 | @__uniqueId = null
|
305 | # Object.defineProperty @, "__uniqueId",
|
306 | # enumerable: false
|
307 | # value: null
|
308 | # Neptune.Lib.Art.DevTools.Profiler.sample && Neptune.Lib.Art.DevTools.Profiler.sample()
|
309 | # type = @classPathName
|
310 | # BaseClass.objectsCreatedByType[type] = (BaseClass.objectsCreatedByType[type]||0) + 1
|
311 | # BaseClass.objectsCreated++
|
312 |
|
313 | # True if object implementsInterface all methods (an array of strings)
|
314 | # (i.e. the named properties are all functions)
|
315 | @implementsInterface: (object, methods) ->
|
316 | for method in methods
|
317 | return false unless typeof object[method] is "function"
|
318 | true
|
319 |
|
320 | #####################################
|
321 | # Module-like features (mixins)
|
322 | #####################################
|
323 | ###
|
324 | mix-in class methods
|
325 | Define getters/setters example:
|
326 | class MyMixin
|
327 | included: ->
|
328 | @getter foo: -> @_foo
|
329 | @setter foo: (v) -> @_foo = v
|
330 |
|
331 | NOTE! This will NOT include any properties you defined with getter or setter!
|
332 | NOTE! This only copies over values if there aren't already values in the included-into class
|
333 | This somewhat mirrors Ruby's include where the included-into-class's methods take precidence.
|
334 | However, if you include two modules in a row, the first module gets priority here.
|
335 | In ruby the second module gets priority (I believe).
|
336 |
|
337 | DEPRICATED!!!
|
338 | Time to do it "right" - and it's just a simple pattern:
|
339 | Justin Fagnani figured this out. Thanks!
|
340 | Read More:
|
341 | http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/
|
342 |
|
343 | To define a mixin:
|
344 |
|
345 | MyMixin = (superClass) ->
|
346 | class MyMixin extends superClass
|
347 | ... write your mixin as-if it were part of the normal inheritance hierachy
|
348 |
|
349 | To use a mixin:
|
350 |
|
351 | class MyClass extends MyMixin MySuperClass
|
352 |
|
353 | To use two mixins:
|
354 |
|
355 | class MyClass extends MyMixin1 MyMixin2 MySuperClass
|
356 | ###
|
357 | warnedAboutIncludeOnce = false
|
358 | @include: (obj) ->
|
359 | log.error "DEPRICATED: BaseClass.include. Use pattern."
|
360 | unless warnedAboutIncludeOnce
|
361 | warnedAboutIncludeOnce = true
|
362 | console.warn """
|
363 | Mixin pattern:
|
364 |
|
365 | To define a mixin:
|
366 |
|
367 | MyMixin = (superClass) ->
|
368 | class MyMixin extends superClass
|
369 | ... write your mixin as-if it were part of the normal inheritance hierachy
|
370 |
|
371 | To use a mixin:
|
372 |
|
373 | class MyClass extends MyMixin MySuperClass
|
374 |
|
375 | To use two mixins:
|
376 |
|
377 | class MyClass extends MyMixin1 MyMixin2 MySuperClass
|
378 | """
|
379 |
|
380 | for key, value of obj when key != 'included'
|
381 | @[key] = value unless @[key]
|
382 |
|
383 | for key, value of obj.prototype when key
|
384 | @::[key] = value unless @::[key]
|
385 |
|
386 | obj.included? @
|
387 | this
|
388 |
|
389 | ######################################################
|
390 | # Class Info
|
391 | ######################################################
|
392 | @getNamespacePath: ->
|
393 | if @namespacePath?.match @getName()
|
394 | @namespacePath
|
395 | else
|
396 | @namespacePath = "(no parent namespace).#{@getName()}"
|
397 |
|
398 | @getNamespacePathWithExtendsInfo: ->
|
399 | "#{@getNamespacePath()} extends #{getSuperclass(@).getNamespacePath()}"
|
400 |
|
401 | # DEPRICATED - use NN stuff
|
402 | # @classGetter
|
403 | # classPath: -> @namespace.namespacePath
|
404 | # classPathArray: -> @namespacePathArray ||= @getClassPath().split "."
|
405 | # classPathName: ->
|
406 | # if p = @namespace?.namespacePath
|
407 | # p + "." + @getClassName()
|
408 | # else
|
409 | # @getClassName()
|
410 |
|
411 | @getClassName: (klass = @) ->
|
412 | klass.getName?() || klass.name
|
413 |
|
414 |
|
415 | ######################################################
|
416 | # inspect
|
417 | ######################################################
|
418 |
|
419 | ###
|
420 | inspect: ->
|
421 | IN: ()
|
422 | OUT: string
|
423 |
|
424 | Can override with same or alternate, recursion-block-supported signature:
|
425 | IN: (inspector) ->
|
426 | OUT: if inspector then null else string
|
427 |
|
428 | To handle the case where the inspector is not set, we
|
429 | recommneded declaring your 'inspect' as follows:
|
430 | inspect: (inspector) ->
|
431 | return StandardLib.inspect @ unless inspector
|
432 | # ...
|
433 | # custom code which writes all output to inspector.put
|
434 | # and uses inspector.inspect for inspecting sub-objects
|
435 | # ...
|
436 | null
|
437 |
|
438 | EFFECT:
|
439 | call inspector.put one or multiple times with strings to add to the inspected output
|
440 | call inspector.inspect foo to sub-inspect other objects WITH RECURSION BLOCK
|
441 |
|
442 | # Example 1:
|
443 | inspect: (inspector) ->
|
444 | return StandardLib.inspect @ unless inspector
|
445 | inspector.put @getNamespacePath()
|
446 |
|
447 | # Example 2:
|
448 | inspect: ->
|
449 | @getNamespacePath()
|
450 | ###
|
451 | @inspect: -> @getNamespacePath()
|
452 |
|
453 | inspect: -> "<#{@class.namespacePath}>"
|
454 |
|
455 | ###
|
456 | getInspectedObjects: -> plainObjects
|
457 |
|
458 | usually implemented this way:
|
459 | @getter inspectedObjects: -> plainObjects or objects which implement "inspect"
|
460 |
|
461 | TODO: I think I want to refactor inspectedObjects to ONLY return near-JSON-compatible objects:
|
462 | 1. strings
|
463 | 2. maps
|
464 | 3. arrays
|
465 |
|
466 | Everything else should be rendered to a string. In general, strings should Eval to the object
|
467 | they represent:
|
468 |
|
469 | toInspectedObject(null): 'null' # null becomes a string
|
470 | toInspectedObject(true): 'true' # true becomes a string
|
471 | toInspectedObject(false): 'false' # false becomes a string
|
472 | toInspectedObject(undefined): 'undefined' # undefined becomes a string
|
473 | toInspectedObject('hi'): '"hi"' # ESCAPED
|
474 | toInspectedObject((a) -> a): 'function(a){return a;}'
|
475 | toInspectedObject(rgbColor()) "rgbColor('#000000')"
|
476 |
|
477 | NOTE: inspectedObjects differs from plainObjects. The latter should be 100% JSON,
|
478 | and should return actual values where JSON allows, otherwise, return JSON data structures
|
479 | that encode the object's information in a human-readable format, ideally one that can be
|
480 | used as an input to the constructor of the object's class to recreate the original object.
|
481 |
|
482 | plainObjects:
|
483 | null: null
|
484 | true: true
|
485 | false: false
|
486 | 'str': 'str' # NOT escaped
|
487 | undefined: null
|
488 | ((a) -> a): 'function(a){return a;}'
|
489 | rgbColor(): r: 0, g: 0, b: 0, a: 0
|
490 |
|
491 | You can provide this function for fine-grained control of what Inspector2 outputs and hence
|
492 | what DomConsole displays.
|
493 |
|
494 | If you would like for a string to appear without quotes, use:
|
495 | {inspect: -> 'your string without quotes here'}
|
496 | ###
|
497 |
|
498 | @getter
|
499 | inspectObjects: ->
|
500 | console.warn "inspectObjects/getInspectObjects is DEPRICATED. Use: inspectedObjects/getInspectedObjects"
|
501 | @getInspectedObjects()
|
502 |
|
503 | inspectedObjects: ->
|
504 | inspectedObjectLiteral "<#{@class?.getNamespacePath()}>"
|
505 |
|
506 | @classGetter
|
507 | inspectedObjects: ->
|
508 | inspectedObjectLiteral "class #{@getNamespacePath()}"
|
509 |
|
510 | ######################################################
|
511 | # Abstract Classes
|
512 | ######################################################
|
513 |
|
514 | ###
|
515 | Define this class as an abstract class. Implicitly it means
|
516 | any class it extends is also abstract, at least in this context.
|
517 |
|
518 | Definition: Abstract classes are not intended to every be instantiated.
|
519 | i.e.: never do: new MyAbstractClass
|
520 |
|
521 | TODO: in Debug mode, in the constructor:
|
522 | throw new Error "cannot instantiate abstract classes" if @class.getIsAbstractClass()
|
523 |
|
524 | ###
|
525 | @abstractClass: ->
|
526 | throw new Error "abstract classes cannot also be singleton" if @getIsSingletonClass()
|
527 | @_firstAbstractAncestor = @
|
528 |
|
529 | @classGetter
|
530 | superclass: -> getSuperclass @
|
531 | isAbstractClass: -> !(@prototype instanceof @_firstAbstractAncestor)
|
532 | isConcreteClass: -> !@getIsAbstractClass()
|
533 | abstractPrototype: -> @_firstAbstractAncestor.prototype
|
534 | firstAbstractAncestor: -> @_firstAbstractAncestor
|
535 | isSingletonClass: -> @_singleton?.class == @
|
536 | concretePrototypeProperties: ->
|
537 | abstractClassPrototype = @getAbstractClass().prototype
|
538 | object @prototype, when: (v, k) ->
|
539 | k != "constructor" &&
|
540 | abstractClassPrototype[k] != v
|
541 |
|
542 | @getAbstractClass: -> @_firstAbstractAncestor
|
543 |
|
544 | # BaseClass is an abstract-class
|
545 | @abstractClass()
|
546 |
|
547 | @propertyIsAbstract: (propName) ->
|
548 | @getAbstractClass().prototype[propName] == @prototype[propName]
|
549 |
|
550 | @propertyIsConcrete: (propName) ->
|
551 | @getAbstractClass().prototype[propName] != @prototype[propName]
|
552 |
|
553 | ######################################################
|
554 | # SingletonClasses
|
555 | ######################################################
|
556 |
|
557 | ###
|
558 | SBD2017: this is the new path for singleton classes.
|
559 | WHY: We can elliminate the need to DECLARE classes singleton.
|
560 | Instead, we can just access the singleton for any class, if needed.
|
561 | TODO: once we are 100% CaffeineScript, switch this to a @classGetter
|
562 | ###
|
563 | @getSingleton: getSingleton = ->
|
564 | if @_singleton?.class == @
|
565 | @_singleton
|
566 | else
|
567 | throw new Error "singleton classes cannot be abstract" if @getIsAbstractClass()
|
568 | @_singleton = new @
|
569 |
|
570 | ###
|
571 | creates the classGetter "singleton" which returns a single instance of the current class.
|
572 |
|
573 | IN: args are passed to the singleton constructor
|
574 | OUT: null
|
575 |
|
576 | The singleton instance is created on demand the first time it is accessed.
|
577 |
|
578 | SBD2017: Possibly depricated; maybe we just need a singleton getter for everyone?
|
579 | The problem is coffeescript doesn't properly inherit class getters.
|
580 | BUT ES6 and CaffeineScript DO. So, when we switch over, I think we can do this.
|
581 | ###
|
582 | @singletonClass: ->
|
583 | throw new Error "singleton classes cannot be abstract" if @getIsAbstractClass()
|
584 |
|
585 | @classGetter
|
586 | singleton: getSingleton
|
587 | "#{decapitalize functionName @}": -> @getSingleton()
|
588 |
|
589 | null
|
590 |
|
591 | ######################################################
|
592 | # Instance Methods
|
593 | ######################################################
|
594 |
|
595 | @getter
|
596 | className: -> @class.getClassName()
|
597 | class: -> @constructor
|
598 | keys: -> Object.keys @
|
599 | namespacePath: -> @class.getNamespacePath()
|
600 | classPathNameAndId: -> "#{@classPathName}:#{@objectId}"
|
601 | uniqueId: -> @__uniqueId ||= nextUniqueObjectId() # unique across all things
|
602 | objectId: -> @__uniqueId ||= nextUniqueObjectId() # number unique across objects
|
603 |
|
604 | # freeze this object safely
|
605 | freeze: ->
|
606 | @getUniqueId() # ensure we have the unique id set
|
607 | Object.freeze @
|
608 | @
|
609 |
|
610 | implementsInterface: (methods) -> Function.BaseClass.implementsInterface @, methods
|
611 | tap: (f)-> f(@);@
|