1
+ var resource = require ( 'resource' ) ,
2
+ jugglingdb = resource . define ( 'jugglingdb' ) ;
3
+
4
+ jugglingdb . schema . description = "enables jugglingdb for resources" ;
5
+
6
+ jugglingdb . method ( 'enable' , enable ) ;
7
+
8
+ //
9
+ // Enables a resource to jugglingdb by
10
+ // creating a JugglingDB model to back the resource,
11
+ // allowing the resource to be instantiable and backed by a datasource
12
+ //
13
+
14
+ function enable ( r , options ) {
15
+
16
+ if ( typeof options === "string" ) {
17
+ options = {
18
+ type : options
19
+ } ;
20
+ }
21
+
22
+ //
23
+ // Require JugglingDB.Schema
24
+ //
25
+ var Schema = require ( './vendor/jugglingdb' ) . Schema ,
26
+ uuid = require ( 'node-uuid' ) ,
27
+ path = require ( 'path' ) ;
28
+
29
+ //
30
+ // Map uuid library to jugglingdb resource
31
+ //
32
+ jugglingdb . uuid = uuid ;
33
+
34
+ //
35
+ // Create new JugglingDB schema, based on incoming datasource type
36
+ //
37
+ var _type = mappings [ options . type ] || options . type || 'fs' ;
38
+ var schema = new Schema ( _type , {
39
+ database : options . name || "big" ,
40
+ host : options . host ,
41
+ port : options . port ,
42
+ path : options . path || path . join ( resource . helper . appDir , 'db' ) ,
43
+ username : options . username ,
44
+ password : options . password ,
45
+ options : options . options ,
46
+ https : true // TODO: check that HTTPS actually does something
47
+ } ) ;
48
+
49
+ //
50
+ // Create empty schema object for mapping between resource and JugglingDB
51
+ //
52
+ var _schema = { } ;
53
+
54
+ //
55
+ // For every property in the resource schema, map the property to JugglingDB
56
+ //
57
+ Object . keys ( r . schema . properties ) . forEach ( function ( p ) {
58
+ var prop = r . schema . properties [ p ] ;
59
+ _schema [ p ] = { type : jugglingType ( prop ) } ;
60
+ } ) ;
61
+ function jugglingType ( prop ) {
62
+ var typeMap = {
63
+ 'string' : String ,
64
+ 'number' : Number ,
65
+ 'integer' : Number ,
66
+ 'array' : JSON ,
67
+ 'boolean' : Boolean ,
68
+ 'object' : JSON ,
69
+ 'null' : null ,
70
+ 'any' : String
71
+ } ;
72
+
73
+ return typeMap [ prop . type ] || String ;
74
+ }
75
+
76
+ //
77
+ // Create a new JugglingDB schema based on temp schema
78
+ //
79
+ var Model = schema . define ( r . name , _schema ) ;
80
+
81
+ //
82
+ // Attach the CRUD methods to the resource
83
+ //
84
+
85
+ //
86
+ // CREATE method
87
+ //
88
+ function create ( data , callback ) {
89
+ //
90
+ // If no id is specified, create one using node-uuid
91
+ //
92
+ if ( typeof data . id === 'undefined' || data . id . length === 0 ) {
93
+ data . id = uuid ( ) ;
94
+ }
95
+
96
+ //
97
+ // JugglingDB's "create" method can act like a "create or update"
98
+ // depending on the adapter, even though JugglingDB has a separate code
99
+ // path for "createOrUpdate" (for example, the cradle adapter has this
100
+ // behavior). So, we use our internal "get" function to ensure that it does
101
+ // not already exist.
102
+ //
103
+ // TODO: It is technically possible to have a uuid collision here,
104
+ // though very unlikely.
105
+ //
106
+ get ( data . id , function ( err , result ) {
107
+ if ( err ) {
108
+ //
109
+ // "not found" errors are good in this case, so call create. Note that
110
+ // this error message comes from the get function and not from
111
+ // JugglingDB.
112
+ //
113
+ if ( err . message . match ( / n o t f o u n d / ) ) {
114
+ return Model . create ( data , callback ) ;
115
+ }
116
+
117
+ //
118
+ // Other errors should be sent in the callback
119
+ //
120
+ return callback ( err ) ;
121
+ }
122
+ return callback ( new Error ( data . id + ' already exists' ) ) ;
123
+ } ) ;
124
+ }
125
+ r . method ( 'create' , create , {
126
+ "description" : "create a new " + r . name ,
127
+ "properties" : {
128
+ "options" : {
129
+ "type" : "object" ,
130
+ "properties" : r . schema . properties
131
+ } ,
132
+ "callback" : {
133
+ "type" : "function"
134
+ }
135
+ }
136
+ } ) ;
137
+
138
+ //
139
+ // Get method
140
+ //
141
+ function get ( id , callback ) {
142
+ // TODO: .all is now broken in fs adapter
143
+ // NOTE: JugglingDB.find is really resource.get
144
+ // NOTE: resource.get is JugglingDB.all with a filter
145
+ Model . find ( id , function ( err , result ) {
146
+ if ( result === null ) {
147
+ return callback ( new Error ( id + ' not found' ) ) ;
148
+ }
149
+ // TODO: check if any of the fields are keys, if so, fetch them
150
+ callback ( err , result ) ;
151
+ } ) ;
152
+ }
153
+ r . method ( 'get' , get , {
154
+ "description" : "get " + r . name + " by id" ,
155
+ "properties" : {
156
+ "id" : {
157
+ "type" : "any" ,
158
+ "description" : "the id of the object" ,
159
+ "required" : true
160
+ } ,
161
+ "callback" : {
162
+ "type" : "function"
163
+ }
164
+ }
165
+ } ) ;
166
+
167
+ //
168
+ // Find method
169
+ //
170
+ function find ( query , callback ) {
171
+ //
172
+ // Remove any empty values from the query
173
+ //
174
+ for ( var k in query ) {
175
+ if ( query [ k ] . length === 0 ) {
176
+ delete query [ k ] ;
177
+ }
178
+ }
179
+
180
+ Model . all ( { where : query } , function ( err , results ) {
181
+ if ( ! Array . isArray ( results ) ) {
182
+ results = [ results ] ;
183
+ }
184
+ callback ( err , results ) ;
185
+ } ) ;
186
+ }
187
+
188
+ var querySchema = {
189
+ properties : { }
190
+ }
191
+ Object . keys ( r . schema . properties ) . forEach ( function ( prop ) {
192
+ if ( typeof r . schema . properties [ prop ] === 'object' ) {
193
+ querySchema . properties [ prop ] = { } ;
194
+ for ( var p in r . schema . properties [ prop ] ) {
195
+ querySchema . properties [ prop ] [ p ] = r . schema . properties [ prop ] [ p ] ;
196
+ }
197
+ } else {
198
+ querySchema . properties [ prop ] = r . schema . properties [ prop ] || { } ;
199
+ }
200
+ querySchema . properties [ prop ] . default = "" ;
201
+ querySchema . properties [ prop ] . required = false ;
202
+ //
203
+ // TODO: remove the following two lines and make enum search work correctly
204
+ //
205
+ querySchema . properties [ prop ] . type = "any" ;
206
+ delete querySchema . properties [ prop ] . enum ;
207
+ delete querySchema . properties [ prop ] . format ;
208
+ } ) ;
209
+
210
+ r . method ( 'find' , find , {
211
+ "description" : "search for instances of " + r . name ,
212
+ "properties" : {
213
+ "options" : {
214
+ "type" : "object" ,
215
+ "properties" : querySchema . properties
216
+ } ,
217
+ "callback" : {
218
+ "type" : "function"
219
+ }
220
+ }
221
+ } ) ;
222
+
223
+ //
224
+ // All method
225
+ //
226
+ function all ( callback ) {
227
+ Model . all ( { } , callback ) ;
228
+ }
229
+
230
+ r . method ( 'all' , all , {
231
+ "description" : "gets all instances of " + r . name ,
232
+ "properties" : {
233
+ "callback" : {
234
+ "type" : "function"
235
+ }
236
+ }
237
+ } ) ;
238
+
239
+ //
240
+ // Update method
241
+ //
242
+ function update ( options , callback ) {
243
+ //
244
+ // JugglingDB does not have a strict update and instead has
245
+ // updateOrCreate, so do a get first and act accordingly
246
+ //
247
+ get ( options . id , function ( err , result ) {
248
+ if ( err ) {
249
+ //
250
+ // Unlike the case with strict create, "not found" errors mean we are
251
+ // unable to do an update
252
+ //
253
+ return callback ( err ) ;
254
+ }
255
+ Model . updateOrCreate ( options , function ( err , updated ) {
256
+ if ( err ) {
257
+ return callback ( err ) ;
258
+ }
259
+ get ( options . id , callback ) ;
260
+ } ) ;
261
+ } ) ;
262
+ }
263
+ r . method ( 'update' , update , {
264
+ "description" : "updates a " + r . name + " by id" ,
265
+ "properties" : {
266
+ "options" : {
267
+ "type" : "object" ,
268
+ "properties" : r . schema . properties
269
+ } ,
270
+ "callback" : {
271
+ "type" : "function"
272
+ }
273
+ }
274
+ } ) ;
275
+
276
+ //
277
+ // Update or create
278
+ //
279
+ function updateOrCreate ( options , callback ) {
280
+ if ( typeof options . id === 'undefined' || options . id . length === 0 ) {
281
+ options . id = uuid ( ) ;
282
+ }
283
+
284
+ get ( options . id , function ( err , record ) {
285
+ if ( err ) {
286
+ Model . create ( options , callback ) ;
287
+ } else {
288
+ for ( var p in options ) {
289
+ record [ p ] = options [ p ] ;
290
+ }
291
+ record . save ( callback ) ;
292
+ }
293
+ } ) ;
294
+
295
+ }
296
+ r . method ( 'updateOrCreate' , updateOrCreate , {
297
+ "description" : "updates a " + r . name + " by id, and creates if necessary" ,
298
+ "properties" : {
299
+ "options" : {
300
+ "type" : "object" ,
301
+ "properties" : r . schema . properties
302
+ } ,
303
+ "callback" : {
304
+ "type" : "function"
305
+ }
306
+ }
307
+ } ) ;
308
+
309
+ //
310
+ // Destroy method
311
+ //
312
+ function destroy ( id , callback ) {
313
+ Model . find ( id , function ( err , result ) {
314
+ if ( err ) {
315
+ return callback ( err ) ;
316
+ }
317
+ result . destroy ( function ( ) {
318
+ callback ( null , null ) ;
319
+ } ) ;
320
+ } ) ;
321
+ }
322
+ r . method ( 'destroy' , destroy , {
323
+ "description" : "destroys a " + r . name + " by id" ,
324
+ "properties" : {
325
+ "id" : {
326
+ "type" : "string" ,
327
+ "description" : "the id of the object" ,
328
+ "required" : true
329
+ } ,
330
+ "callback" : {
331
+ "type" : "function"
332
+ }
333
+ }
334
+ } ) ;
335
+
336
+ // assign model to resource
337
+ r . model = Model ;
338
+ }
339
+
340
+ //
341
+ // Provider API mapping for JugglingDB to datasource API for convenience
342
+ //
343
+ var mappings = {
344
+ "couch" : "cradle" ,
345
+ "couchdb" : "cradle"
346
+ } ;
347
+
348
+ jugglingdb . dependencies = {
349
+ "node-uuid" : "*" ,
350
+ "async" : "*"
351
+ } ;
352
+
353
+ exports . jugglingdb = jugglingdb ;
0 commit comments