UNPKG

17.5 kBPlain TextView Raw
1require! {
2 '../../': N
3 \./ChangeWatcher
4 \./Schema
5 \prelude-ls
6 \../Helpers/Debug
7 \./Connectors : DB
8 async
9 underscore: __
10 validator: Validator
11 hacktiv: Hacktiv
12 polyparams: ParamWraper
13}
14
15cache = null
16Wrappers = null
17
18debug-res = new Debug 'N::Resource', Debug.colors.blue
19
20global import prelude-ls
21
22N.Validator = Validator
23
24N.inited = {}
25
26module.exports = (config, routes, name) ->
27
28 if not cache?
29 cache := require \./Cache
30 if not Wrappers?
31 Wrappers := require \./Wrappers
32
33 debug-resource = new Debug "N::Resource::#name"
34
35 debug-res.Log "Creating new Resource : #name"
36
37 error = new Hacktiv.Value
38
39 class Resource extends Wrappers
40
41 @DEFAULT_DEPTH = 1
42 @INITED = false
43 @error = error
44 @_schema = null
45
46 #
47 # Public
48 # Instance Methods
49 #
50
51 # Constructor
52 (blob) ->
53
54 @_table = @.__proto__.constructor._table
55 @_schema = @.__proto__.constructor._schema
56 @_type = @.__proto__.constructor.lname
57 @_config = @.__proto__.constructor.config
58
59 if blob.promise?
60 debug-resource.Log "Defered instanciation"
61 @_promise = blob.promise
62 return
63
64
65 debug-resource.Log "Instantiate with {id: #{blob.id}}"
66
67 import @_schema.Populate @, blob
68
69 _WrapReturnThis: (done) ->
70 (arg) ~>
71 res = done arg
72 res?._promise || res || arg
73
74 Then: ->
75 @_promise = @_promise.then @_WrapReturnThis it if @_promise?
76 @
77 #
78 Catch: ->
79 @_promise = @_promise.catch @_WrapReturnThis it if @_promise?
80 @
81
82 Fail: ->
83 @_promise = @_promise.fail @_WrapReturnThis it if @_promise?
84 @
85
86 # Wrap the _SaveUnwrapped() call
87 Save: @_WrapFlipDone @_WrapPromise @_WrapResolvePromise @_WrapDebugError debug-resource~Error, -> @_SaveUnwrapped ...
88
89 # Wrap the _DeleteUnwrapped() call
90 Delete: @_WrapFlipDone @_WrapPromise @_WrapResolvePromise @_WrapDebugError debug-resource~Error, -> @_DeleteUnwrapped ...
91
92 # Get what to send to the database
93 Serialize: ->
94 @_schema.Filter @
95
96 # Get what to send to client
97 ToJSON: ->
98 res = @Serialize()
99
100 res = @_schema.RemoveInternals res
101 res <<< @_schema.GetVirtuals @
102 each ~>
103 if @[it.name]?
104 switch
105 | Array.isArray @[it.name] and @[it.name].0? => res[it.name] = __(@[it.name]).invoke 'ToJSON'
106 | @[it.name].ToJSON? => res[it.name] = @[it.name].ToJSON()
107 , @_schema.assocs
108
109 res
110
111 # Preserve the assocs while extending
112 ExtendSafe: (blob) ->
113 newBlob = __({}).extend blob
114
115 each (-> delete newBlob[it.name]), @_schema.assocs
116
117 __(@).extend newBlob
118
119 # Watch the instance refetch itself
120 Watch: (done) ->
121 N.Watch ~>
122 N[capitalize name + \s ].Fetch @id, (err, res) ~>
123 return done err if err? and done?
124 return console.error err if err?
125
126 @ <<<< res
127 done @ if done?
128 @
129
130 #
131 # Private
132 # Instance Methods
133 #
134
135 # Save without wrap
136 _SaveUnwrapped: (config, done) ->
137 if not done?
138 done = config
139 config = @_config
140
141 serie = @Serialize()
142 @_schema.Validate serie, (err) ~>
143 exists = @id?
144
145 if exists => debug-resource.Log "Saving {id: #{@id}}"
146 else => debug-resource.Log "Saving New"
147
148 switch
149 | err? => done err
150 | _ =>
151 @_table.Save serie, config, (err, instance) ~>
152 | err? => done err
153 | _ =>
154 if !exists
155 @id = instance.id
156 N.bus.emit \new_ + name, @
157 else
158 N.bus.emit \update_ + name, @
159 ChangeWatcher.Invalidate()
160
161 debug-resource.Log "Saved {id: #{@id}}"
162 done null, @
163 @
164
165 # Delete without wrap
166 _DeleteUnwrapped: (done) ->
167 debug-resource.Log "Deleting {id: #{@id}}"
168 @_table.Delete @id, (err, affected) ~>
169 switch
170 | err? => done err
171 | _ =>
172 cache.Delete @_type + 'Fetch' + @id, ~>
173 @id = undefined
174 N.bus.emit \delete_ + name, @
175
176 ChangeWatcher.Invalidate()
177
178 debug-resource.Log "Deleted {id: #{@id}}"
179
180 done null, @
181
182 @
183
184 #
185 # Public
186 # Class Methods
187 #
188
189 # Populate an existing instance with a new blob
190 @Hydrate = (blob) ->
191
192 res = new @ blob
193
194 @_schema.assocs |> each ->
195 if blob[it.name]?
196 if is-type \Array blob[it.name]
197 res[it.name] = blob[it.name] |> map (it.type~Hydrate)
198 else
199 res[it.name] = it.type.Hydrate blob[it.name]
200
201 res
202
203 # _Deserialize and Save from a blob or an array of blob
204 @Create = @_WrapFlipDone @_WrapPromise @_WrapWatchArgs @_WrapDebugError debug-resource~Error, ->
205 @Init!
206 @_CreateUnwrapped ...
207
208 # Create without wraps
209 @_CreateUnwrapped = @_WrapParams do
210 * \Object : optional: true
211 * \Array : optional: true
212 * \Object : optional: true
213 * \Function
214 * \Number : optional: true
215 (arg, args, config, done, _depth = if @config?.maxDepth? => @config.maxDepth else @@DEFAULT_DEPTH) ->
216
217 if args?
218 debug-resource.Log "Creating from array: #{args.length} entries"
219
220 @_HandleArrayArg arg || args || {}, (blob, done) ~>
221
222 async.mapSeries obj-to-pairs(blob), (pair, done) ~>
223 if pair.0 in (@_schema.assocs |> map (.foreign)) and pair.1?._promise
224 pair.1.Then -> done null [pair.0, it.id]
225 pair.1.Catch done
226 else
227 done null, pair
228 , (err, results) ~>
229 return done err if err?
230
231 blob = pairs-to-obj results
232
233 debug-resource.Log "Creating #{JSON.stringify blob}"
234 @resource._Deserialize blob, (err, instance) ~>
235 | err? => done err
236 | _ =>
237 c = {}
238 if config?.db?
239 @_table.AddDriver config
240 c = config
241 else
242 c = @config
243 instance._SaveUnwrapped c, (err, instance) ~>
244 | err? => done err
245 | _ =>
246 if instance._schema.assocs.length
247 @_schema.FetchAssoc instance, (err, blob) ~>
248 | err? => done err
249 | _ =>
250 instance import blob
251 debug-resource.Log "Created {id: #{instance.id}}"
252 done null instance
253 , _depth
254 else
255 debug-resource.Log "Created {id: #{instance.id}}"
256 done null instance
257
258 , _depth
259 , done
260
261 # Fetch from id or id array
262 @Fetch = @_WrapFlipDone @_WrapPromise @_WrapCache 'Fetch' @_WrapWatchArgs @_WrapDebugError debug-resource~Error, ->
263 @Init!
264 @_FetchUnwrapped ...
265
266 # Fetch from id or id array
267 @_FetchUnwrapped = (arg, done, _depth = if @config?.maxDepth? => @config.maxDepth else @@DEFAULT_DEPTH) ->
268
269 if is-type \Array arg
270 debug-resource.Log "Fetching from array: #{arg.length} entries"
271
272 cb = (done) ~> (err, blob) ~>
273 | err? => done err
274 | _ =>
275 debug-resource.Log "Fetched {id: #{blob.id}}"
276 Debug.Depth!
277 @resource._Deserialize blob, done, _depth
278
279 @_HandleArrayArg arg, (constraints, done) ~>
280
281 debug-resource.Log "Fetch #{JSON.stringify constraints}"
282
283 if is-type 'Object', constraints
284 @_table.FindWhere '*', constraints, cb done
285 else
286 @_table.Find constraints, cb done
287 , done
288
289 # Get a list of records from DB
290 @List = @_WrapFlipDone @_WrapPromise @_WrapCache 'List' @_WrapWatchArgs @_WrapDebugError debug-resource~Error, ->
291 @Init!
292 @_ListUnwrapped ...
293
294 # Get a list of records from DB
295 @_ListUnwrapped = (arg, done, _depth = if @config?.maxDepth? => @config.maxDepth else @@DEFAULT_DEPTH) ->
296
297 if typeof(arg) is 'function'
298 if typeof(done) is 'number'
299 _depth = done
300
301 done = arg
302 arg = {}
303
304 if is-type \Array arg
305 debug-resource.Log "Listing from array: #{arg.length} entries"
306 # Debug.Depth!
307
308 @_HandleArrayArg arg, (constraints, _done) ~>
309 done = (err, data) ->
310 Debug.UnDepth!
311 _done err, data
312
313 debug-resource.Log "List #{JSON.stringify constraints}"
314 Debug.Depth!
315
316 @_table.Select '*', (constraints || {}), {}, (err, blobs) ~>
317 | err? => done err?
318 | _ =>
319 async.map blobs, (blob, done) ~>
320 debug-resource.Log "Listed {id: #{blob.id}}"
321 @resource._Deserialize blob, done, _depth
322 , done
323
324 , done
325
326 # Delete given records from DB
327 @Delete = @_WrapFlipDone @_WrapPromise @_WrapDebugError debug-resource~Error, ->
328 @Init!
329 @_DeleteUnwrapped ...
330
331 # Delete given records from DB
332 @_DeleteUnwrapped = (arg, done) ->
333
334 if is-type \Array arg
335 debug-resource.Warn "Deleting from array: #{arg.length} entries"
336
337 @_HandleArrayArg arg, (constraints, done) ~>
338 debug-resource.Warn "Deleting #{JSON.stringify constraints}"
339 @resource._FetchUnwrapped constraints, (err, instance) ~>
340 | err? => done err
341 | _ => instance._DeleteUnwrapped done
342
343 , done
344
345 # Watch the Resource for a particular event or any changes
346 @Watch = (...args) ->
347
348 @Init!
349
350 query = {}
351 types = []
352 done = ->
353 for arg in args
354 switch
355 | is-type \Function arg => done := arg
356 | is-type \Array arg => types := arg
357 | is-type \String arg => types.push arg
358 | is-type \Object arg => query := arg
359
360 if not types.length
361 types.push \all
362
363 for type in types
364 switch
365 | type in <[new update delete]> => N.bus.on type + '_' + name, done
366 | \all => N.Watch ~> @List query .Then done .Catch done
367
368 @
369
370 @AttachRoute = (@_routes) ->
371 @Init!
372
373 @_AddRelationship = (res, isArray, isDistant, isRequired, key, fieldName, prepare = true) ->
374 @Init!
375
376 obj = type: res
377
378 if isDistant
379 res.Field key, \int .Required isRequired
380 obj.distantKey = key
381 else
382 @Field key, \int .Required isRequired
383 obj.localKey = key
384
385 if prepare
386 @_schema.PrepareRelationship isArray, capitalize(fieldName + if isArray => 's' else ''), obj
387
388 @HasOne = @_WrapParams do
389 * \Function
390 * \Boolean : default: true
391 * \String : optional: true
392 * \String : optional: true
393 * \Boolean : default: true
394 (res, belongsTo, fieldName, key, may) ->
395 @_AddRelationship res, false, true, may, key || @lname + \Id , fieldName || res.lname
396 res.BelongsTo @, fieldName || @lname, key || @lname + \Id , may if belongsTo
397 @
398
399 @HasMany = @_WrapParams do
400 * \Function
401 * \Boolean : default: true
402 * \String : optional: true
403 * \String : optional: true
404 * \Boolean : default: true
405 (res, belongsTo, fieldName, key, may) ->
406 @_AddRelationship res, true, true, may, key || @lname + \Id , fieldName || res.lname
407 res.BelongsTo @, fieldName || @lname, key || @lname + \Id , may if belongsTo
408 @
409
410 @BelongsTo = @_WrapParams do
411 * \Function
412 * \String : optional: true
413 * \String : optional: true
414 * \Boolean : default: true
415 (res, fieldName, key, may) ->
416 @_AddRelationship res, false, false, may, key || res.lname + \Id , fieldName || res.lname
417 @
418
419 @MayHasOne = @_WrapParams do
420 * \Function
421 * \Boolean : default: true
422 * \String : optional: true
423 * \String : optional: true
424 * \Boolean : default: false
425 (...args) -> @HasOne.apply @, args
426
427 @MayHasMany = @_WrapParams do
428 * \Function
429 * \Boolean : default: true
430 * \String : optional: true
431 * \String : optional: true
432 * \Boolean : default: false
433 (...args) -> @HasMany.apply @, args
434
435 @MayBelongsTo = @_WrapParams do
436 * \Function
437 * \String : optional: true
438 * \String : optional: true
439 * \Boolean : default: false
440 (...args) -> @BelongsTo.apply @, args
441
442 @HasOneThrough = (res, through) ->
443 @HasOne through
444 through.HasOne res
445 @_schema.HasOneThrough res, through
446
447 @HasManyThrough = (res, through) ->
448 @HasMany through
449 res.HasMany through
450 @_schema.HasManyThrough res, through
451
452 @HasAndBelongsToMany = (res, reverse = true) ->
453 names = sort [@lname, res.lname]
454 Assoc = N names.0 + \s_ + names.1, @config .Init!
455 @_schema.HasAndBelongsToMany res, Assoc
456 res._schema.HasAndBelongsToMany @, Assoc if reverse
457
458 @Field = (...args) ->
459 @Init!
460 @_schema.Field.apply @_schema, args
461
462 Fetch: @_WrapPromise @_WrapResolveArgPromise (done) ->
463 N[capitalize @_type].Fetch @id, done
464
465 Add: @_WrapPromise @_WrapResolvePromise @_WrapResolveArgPromise (instance, done) ->
466
467 names = sort [@_type, instance._type]
468 res = @_schema.habtm |> find (.lname is names.0 + \s_ + names.1)
469 if res?
470 return res._CreateUnwrapped {"#{@_type}Id": @id, "#{instance._type}Id": instance.id}, (err, newRes) ~>
471 return done err if err?
472
473 @_SaveUnwrapped ~>
474 return done it if it?
475
476 @Fetch done
477
478 res = @_schema.assocs |> find (.type.lname is instance._type)
479 if res?
480 if res.keyType is \distant
481 instance[res.foreign] = @id
482 instance._SaveUnwrapped ~>
483 return done it if it?
484
485 @Fetch done
486
487 else if res.keyType is \local
488 @[res.foreign] = instance.id
489 @_SaveUnwrapped ~>
490 return done it if it?
491
492 @Fetch done
493 else
494 done new Error "#{capitalize @_type}: Add: No assocs found for #{capitalize instance._type}"
495
496 Remove: @_WrapPromise @_WrapResolvePromise @_WrapResolveArgPromise (instance, done) ->
497 names = sort [@_type, instance._type]
498 res = __(@_schema.habtm).findWhere lname: names.0 + \s_ + names.1
499 if res?
500 return res.Delete {"#{@_type}Id": @id, "#{instance._type}Id": instance.id}, (err, newRes) ->
501 done err, instance
502
503 res = @_schema.assocs |> find (.type.lname is instance._type)
504 if res?
505 if res.keyType is \distant
506 instance[res.foreign] = null
507 instance._SaveUnwrapped ~>
508 return done it if it?
509
510 @Fetch done
511
512 else if res.keyType is \local
513 @[res.foreign] = null
514 @_SaveUnwrapped ~>
515 return done it if it?
516
517 @Fetch done
518 else
519 done new Error "#{capitalize @lname}: Add: No assocs found for #{capitalize instance.lname}"
520
521 # Change properties and save
522 Set: @_WrapPromise @_WrapResolvePromise (obj, done) ->
523 if is-type \Function obj
524 fun = ~>
525 obj.call @, @
526 @
527
528 @ExtendSafe fun!
529 else
530 @ExtendSafe obj
531 @Save done
532
533 Log: @_WrapPromise @_WrapResolvePromise ->
534
535 console.log @ToJSON!
536 it null @
537
538 #
539 # Private
540 # Class Methods
541 #
542
543 # Wrapper to allow simple variable or array as first argument
544 @_HandleArrayArg = (arg, callback, done) ->
545 do ->
546 | is-type 'Array', arg => async.mapSeries arg, callback, done
547 | _ => callback arg, done
548
549 @
550
551 # Pre-Instanciation and associated model retrival
552 @_Deserialize = (blob, done, _depth) ->
553 res = @
554
555 if @_schema.assocs.length
556 @_schema.FetchAssoc blob, (err, blob) ->
557 return done err if err?
558
559 done null, new res blob
560 , _depth - 1
561 else
562 done null, new res blob
563
564 #
565 # Private
566 # Init process
567 #
568
569 # Prepare the core of the Resource
570 @_PrepareResource = (_config, _routes, _name, _parent = null) ->
571 debug-res.Log 'Preparing resource'
572
573 @lname = _name.toLowerCase()
574
575 @_table = new DB @lname + \s
576 if not _config?.abstract
577 @_table.AddDriver _config
578 else if not _config? or (_config? and not _config.abstract)
579 @_table.AddDriver @config
580
581 @config = _config
582 @INITED = false
583
584 @_schema = new Schema @lname, _config?.schema
585 @_parent = _parent
586 if @_parent?
587 @_schema <<< @_parent._schema.Inherit!
588
589 @_schema.Resource = @
590
591 @Route = _routes
592 @_routes = _routes
593
594 @
595
596 # Setup inheritance
597 @Extend = (name, routes, config) ->
598 @Init!
599
600 config = config || @config
601
602 if config and config.abstract
603 deleteAbstract = true
604
605 if deleteAbstract
606 delete config.abstract
607
608 N.Resource name, routes, config, @
609
610 # Initialisation
611 @Init = (@config = @config, extendArgs) ->
612 if @INITED
613 return @
614
615 if N.inited[@lname]?
616 return @
617 throw new Error 'ALREADY INITED !!!! BUUUUUUUUUG' + @lname
618
619 @resource = @
620
621 N.resources[@lname] = @
622
623 debug-res.Log "Init() #{@lname}"
624
625 N.inited[@lname] = true
626
627 @INITED = true
628
629
630
631 if @_routes?
632 @routes = new @_routes(@, @config)
633
634 #FIXME
635 N[capitalize @lname] = @
636
637 @
638
639 Resource._PrepareResource(config, routes, name)