This repository was archived by the owner on Aug 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 605
Expand file tree
/
Copy pathindex.coffee
More file actions
executable file
·409 lines (369 loc) · 13.3 KB
/
Copy pathindex.coffee
File metadata and controls
executable file
·409 lines (369 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
###
Bongo.js
Unfancy models for MongoDB
@class: Base
@author: Christopher Thorn
###
{ EventEmitter2 } = require 'eventemitter2'
nodePath = require 'path'
module.exports = class Base extends EventEmitter2
# native
{ defineProperty } = Object
# contrib
createId = require('hat').rack()
Inflector = require 'inflector'
###
Static events
@description: see ./staticemitterdelegate.coffee
###
{
@on, @off, @addListener, @removeListener, @removeAllListeners, @once
@listeners, @setMaxListeners, @emit, @getEmitter
} = require './staticemitterdelegate'
# oldEmit = @emit
# @emit = (rest...) ->
# if @::mq?
# [event, data] = rest
# broadcastable = @getBroadcastable()
# if broadcastable
# if Array.isArray event
# event = event.join '.'
# @::mq?.trigger @name, event, data
# else
# oldEmit.apply this, rest
###
@method: property of the constructor
@signature: Base.mixin(stuff)
@description: copy all the static and prototype members.
###
mixin: @mixin = (source) ->
for own key, val of source when key isnt 'constructor'
if key is 'onTraitAdded'
val.call this
continue
@[key] = val
@trait = (root, trait) ->
if 'function' is typeof root
@mixin root
@::mixin root.prototype if root.prototype?
else
[trait, root] = [root, trait] unless trait
root ?= ''
err = null
traitPath = nodePath.join(root, trait)
traitModule = \
try require traitPath
catch e then err = e
if 'function' is typeof traitModule
@trait traitModule
else
throw new Error "Couldn't load the trait #{traitPath} #{err}"
@addGlobalListener = (event, listener) ->
@emit 'global-listener-added', event, listener
getInterfaceName: @getInterfaceName = (verb, noun, isPlural) ->
noun = Inflector.pluralize noun if isPlural
name = Inflector.camelize "#{verb}_#{noun}", yes, yes
invoke: @invoke = (verb, noun, rest...) ->
(@[@getInterfaceName(verb, noun)]?(rest...) and true) or false
###
@method.
@signatature: initializeSharedInstance()
###
initializeSharedInstance:(options) ->
model = this
if model.constructor.isShared
if options.bongo_?
{ instanceId } = options.bongo_
model.bongo_ = options.bongo_
else
instanceId = createId()
model.bongo_ = {
constructorName : model.constructor.name
instanceId
}
@wildcard = yes
@maxListeners = 0
warnAboutEventWhitelist = (konstructor, context, event) ->
console.warn """
No event whitelist in place for #{konstructor.name}!
Emitting unguarded #{context} event: #{event}
This needs to be fixed; we will stop supporting non-whitelisted events
at some point in the near future.
"""
constructor:(options = {}) ->
@initializeSharedInstance options
defineProperty this, '_events', { value: {} }, { writeable: yes }
defineProperty this, 'wildcard', { value: yes }, { writeable: yes }
defineProperty this, 'maxListeners', { value: 0 }, { writeable: yes }
defineProperty this, 'listenerTree', { value: {} }, { writeable: yes }
super()
@on '*', (rest...) =>
@constructor.bongos?.forEach (bongo) =>
if not @constructor.instanceEvents_?
warnAboutEventWhitelist @constructor, 'instance', @event
bongo.handleEvent 'instance', this, @event, rest
else if @event in @constructor.instanceEvents_
# TODO: this is insecure by default :(
{ filter } = @constructor.sharedEvents?.instance[@event] or {}
rest[0] =
if 'function' is typeof filter then filter.call this, rest[0]
else rest[0]
bongo.handleEvent 'instance', this, @event, rest
addGlobalListener: (event, listener) ->
@emit 'global-listener-added', event, listener
###
@property.
@signature: Base.constructors
@description - an array of all constructors (that have schema).
###
defineProperty(this, 'constructors', { value: Object.create null })
###
@property.
@signature: Base.sharedConstructors
@description - an array of shared constructors.
###
defineProperty(this, 'sharedConstructors', { value: Object.create null })
defineProperty(this, 'globalSharedConstructors', { value: Object.create null })
###
@method: property of the constructor.
@signature: Base.extend(constructor, proto)
@param: statik - members to be mixed into the constructor.
@param: proto - an optional object (or a function that returns
an object) that provides the prototype properties for the
model. You can use this for even more sugar to assign
instance methods to your models. It should contain a "constructor"
property:
@property - constructor - a named function that will be used as
the constructor for the new klass. If you don't name your
function some features may break. There is no way to change
the name of a function runtime, since the Function::name
property is read-only. So, do give it name. :)
@description: This is a sugar method intended to simplify
inheritance for those who are using Bongo with JS. Writing
your application in CoffeeScript is recommended, but in that
case you shouldn't use Model.extend—use classes instead.
###
@extend = (statik, proto) ->
props = [statik, proto]
for prop, index in props
if 'function' is typeof prop
props[index] = new prop
[proto, statik] = props unless proto
parent = this
child = proto.constructor
for own prop of parent
child[prop] = parent[prop]
ctor = ->
@constructor = child
return
ctor:: = parent::
child:: = new ctor
for own prop of proto
child::[prop] = proto[prop]
child.__super__ = parent::
child.set statik
child
@addBongo = (bongo) ->
defineProperty(this, 'bongos', { value: [] }) unless @bongos?
@bongos.push bongo
@on '*', (rest...) =>
@bongos.forEach (bongo) =>
{ event } = @getEmitter()
return if event is 'needsPopulated'
if not @staticEvents_?
bongo.handleEvent 'static', this, event, rest
warnAboutEventWhitelist this, 'static', event
else if event in @staticEvents_
# TODO: this is insecure by default :(
{ filter } = @sharedEvents?.static[event] or {}
rest[0] = filter.call this, rest[0] if 'function' is typeof filter
bongo.handleEvent 'static', this, event, rest
###
@method: property of the constructor.
@stub.
@description: this provides a a getter for the constructor
which can then be overridden by a subklass, which will in
any case return a constructor—but maybe not this exact one.
###
@getExactConstructor = (data) -> this
###
@method.
@signature: Base::Uber(arguments...)
@description: this is sugar to simplify inheritance for
those who are using Bongo and writing their application
with JS. Avoid Uber() when you are using CoffeeScript—
prefer super().
###
Uber: ->
@constructor.__super__.constructor.apply this, arguments
###
@method: property of the constructor.
@signature: Base.uber(methodName, arguments...)
@description: Call a method on the superclass.
@description: this is sugar to simplify inheritance for
those who are using Bongo and writing their application
with JS. Avoid uber() when you are using CoffeeScript—
prefer super().
###
@uber = (methodName, args...) ->
@__super__.constructor[methodName].apply this, args
###
@method.
@signature: Base::uber(methodName, arguments...)
@description: Call a method on the superclass.
@description: this is sugar to simplify inheritance for
those who are using Bongo and writing their application
with JS. Avoid uber() when you are using CoffeeScript—
prefer super().
###
uber:(methodName, args...) ->
@constructor.__super__[methodName].apply this, args
@registerConstructor = ->
Base.constructors[@name] = this unless Base.constructors[@name]?
###
@method: property of the constructor
@signature: Model.set(options)
@param: component - e.g.:
- "client" will be passed into setClient()
- "schema" will be passed into setSchema()
- "validators" will be passed into setValidators()
@description: overrides Base.set() and passes items
to their appropriate setter method. Ignores items
that do not have setters.
###
@set = (options) ->
@registerConstructor()
for own optionName, option of options
methodName =
new Inflector("set_#{Inflector.underscore optionName}")
.decapitalize()
.camelize(yes)
.value
if 'function' is typeof @[methodName]
@[methodName] option
else
@[optionName] = option
this
getGroupedUnique = (groups) ->
groupedUnique = {}
for own group, grouped of groups
unique = {}
for single in grouped
name = if 'string' is typeof single then single else single.name
unique[name] = single
groupedUnique[group] = unique
return groupedUnique
###
@method.
@signature: Model.setSharedMethods(methods)
@param: methods - an object containing the following
properties:
@property: static - an array of method names to share
with the client that should be attached to the
constructor.
@property: instance - an array of method names to share
with the client that should be attached to the
prototype.
@description: methods may be negated if they were
shared by parent constructors by prefixing the method
name with one or more "!"s (so don't do double
negation, because it doesn't seem to make any sense
in this context.)
###
@setSharedMethods = (methods = {}) ->
{ static: @staticMethods_, instance: @instanceMethods_ } = methods
@describeMethods = (methods = {}) ->
(for own method, signature of methods
[
method
if Array.isArray signature
then signature.map (s) -> s.slug
else [ signature.slug ]
]
).reduce (memo, [ method, descriptor ]) ->
memo[method] = descriptor if descriptor[0]?.length
memo
, {}
@getSharedMethods = ->
s = @describeMethods @staticMethods_
i = @describeMethods @instanceMethods_
a = @getClassAttributes()
{
statik : s
instance : i
attributes : a
}
@getClassAttributes = -> @classAttributes_ ?= {}
@setClassAttributes = (@classAttributes_) ->
@hasSharedMethod = (method) ->
method of @getSharedMethods()['statik']
hasSharedMethod:(method) ->
{ instance } = @constructor.getSharedMethods()
method of instance
@getSignature = (context, method) -> switch context
when 'static' then @staticMethods_[method]
when 'instance' then @instanceMethods_[method]
@testSignature = (context, method, args) ->
unless signatures = @getSignature context, method
return [no, []]
signatures = [signatures] unless Array.isArray signatures
for signature in signatures
# if signature isnt there, fail the test
unless signature
console.log "client requested a non existent function -> context:#{context} method:#{method}"
return [no, signatures]
return [yes, signatures] if signature.test args
return [no, signatures.map (signature) -> signature.slug]
@setSharedEvents = (events) ->
{ keys } = Object
groupedEvents = getGroupedUnique events
sharedEvents = {}
for own group, eventsByGroup of groupedEvents
defineProperty this, "#{group}Events_", { value: keys eventsByGroup }
sharedEvents[group] = (keys eventsByGroup).reduce (acc, key) ->
val = eventsByGroup[key]
val = { name: val } if 'string' is typeof val
acc[key] = val
return acc
, {}
defineProperty this, 'sharedEvents', { value: sharedEvents, enumerable: yes }
@share = (global) ->
konstructor = this
{ name } = konstructor
unless name
throw new Error "Can't share an unnamed constructor"
defineProperty konstructor, 'isShared', { value: yes }
# TODO: outsource this for scalability reasons.
# defineProperty constructor, 'sharedInstances', value: {}
Base.sharedConstructors[name] = konstructor
this
@inheritanceChain = (Klass = this, glue) ->
unless chain = @inheritanceChain_
proto = Klass.prototype
chain = [Klass]
while proto = proto.__proto__
chain.push proto.constructor
# memoize the inheritance chain because it ought not to change
# and it's a bit expensive to have to travese the inheritance
# chain every time.
defineProperty this, 'inheritanceChain_', { value: chain }
if glue
(constructor.name for constructor in chain).join glue
else
chain
inheritanceChain:(glue) ->
Base.inheritanceChain @constructor, glue
# emit:(event, data) ->
# super
# id = @getId?() or @bongo_.instanceId
# if id? and event isnt 'newListener'
# if Array.isArray event
# event = event.join('.')
# wrappedData = {}
# wrappedData[event] = data
# @mq?.trigger "updateInstance", "object-#{id}", wrappedData
@setBroadcastable = (@broadcastable_ = yes) ->
@getBroadcastable = ->
if @broadcastable_? then @broadcastable_ else yes
toObjectRef: ->
ref = new ObjectRef this