1 | [![Oreo](oreo.png)](https://github.com/will123195/oreo)
|
2 |
|
3 |
|
4 | [![Build Status](https://travis-ci.org/will123195/oreo.svg?branch=master)](https://travis-ci.org/will123195/oreo)
|
5 |
|
6 | # Features
|
7 |
|
8 | - Zero configuration -- auto-detects columns, primary keys and foreign keys
|
9 | - Saves multi-table nested objects with an atomic transaction
|
10 | - Detects primary and read-only hosts
|
11 | - Use callbacks or plug in your own Promise library
|
12 | - No dependencies
|
13 |
|
14 | # Database Support
|
15 |
|
16 | - PostgreSQL 9+
|
17 | - MySQL
|
18 |
|
19 | # Installation
|
20 |
|
21 | ```bash
|
22 | npm install oreo
|
23 | npm install pg
|
24 | #npm install mysql
|
25 | ```
|
26 |
|
27 | # Quick Example
|
28 |
|
29 | ```js
|
30 | var oreo = require('oreo')
|
31 |
|
32 | var db = oreo({
|
33 | driver: 'mysql',
|
34 | hosts: ['localhost'],
|
35 | name: 'my_db',
|
36 | user: 'root',
|
37 | pass: ''
|
38 | }, function (err) {
|
39 | // Assuming you have a table "artists"
|
40 | // Get an artist by primary key
|
41 | db.artists.get(id, function (err, artist) {
|
42 | console.log(artist)
|
43 | })
|
44 | }
|
45 | ```
|
46 |
|
47 | <hr />
|
48 |
|
49 | # Documentation
|
50 |
|
51 | ## Usage
|
52 |
|
53 | * [`oreo`](#instantiate)
|
54 |
|
55 | ## Db
|
56 |
|
57 | * [`execute`](#execute)
|
58 | * [`executeWrite`](#executeWrite)
|
59 | * [`onReady`](#onReady)
|
60 | * [`end`](#end)
|
61 |
|
62 | ## Table
|
63 |
|
64 | * [`find`](#find)
|
65 | * [`findOne`](#findOne)
|
66 | * [`get`](#get)
|
67 | * [`insert`](#insert)
|
68 | * [`mget`](#mget)
|
69 | * [`save`](#table_save)
|
70 |
|
71 | ## Row
|
72 |
|
73 | * [`delete`](#delete)
|
74 | * [`hydrate`](#hydrate)
|
75 | * [`save`](#save)
|
76 | * [`set`](#set)
|
77 | * [`update`](#update)
|
78 |
|
79 | <hr />
|
80 |
|
81 | # Full Example
|
82 |
|
83 | † see the example database schema below
|
84 |
|
85 | ```js
|
86 | var oreo = require('oreo')
|
87 |
|
88 | // initialize oreo: auto-detects the schema and determines writable/read-only hosts
|
89 | var db = oreo({
|
90 | driver: 'pg',
|
91 | hosts: ['localhost:5432'],
|
92 | name: 'my_db',
|
93 | user: 'username',
|
94 | pass: 'password',
|
95 | debug: console.log,
|
96 | memoize: 150, // optional duration in ms to memoize rows
|
97 | cache: redisClient, // optional
|
98 | Promise: Promise, // optional, default: global.Promise
|
99 | models: {} // optional
|
100 | }).onReady(runExampleQueries)
|
101 |
|
102 | function runExampleQueries () {
|
103 |
|
104 | // Insert a new book, its author and some reviews (in a single transaction)
|
105 | db.books.insert({
|
106 | title: 'Fear and Loathing in Las Vegas',
|
107 | author: {
|
108 | name: 'Hunter S.Thompson'
|
109 | },
|
110 | reviews: [ // shorthand for 'book:reviews'
|
111 | { stars: 5, body: 'Psychadelic!'},
|
112 | { stars: 4, body: 'Bizarre, unpredictable yet strangely alluring.'}
|
113 | ]
|
114 | }).then(function (book) {
|
115 | console.log(book) // { id: 1, title: Fear and Loathing in Las Vegas, author_id: 1 }
|
116 |
|
117 | // Hydrate a book's author (1-to-1 linked row)
|
118 | book.hydrate('author').then(function () {
|
119 | console.log(book.author) // { id: 1, name: Hunter S. Thompson }
|
120 | })
|
121 |
|
122 | // Hydrate a book's reviews (1-to-many linked rows)
|
123 | book.hydrate('reviews').then(function () {
|
124 | console.log(book.reviews) // array
|
125 | })
|
126 |
|
127 | // Update a book
|
128 | book.update({
|
129 | title: 'The Rum Diary'
|
130 | }).then(function (book) {
|
131 | console.log(book) // { id: 1, title: The Rum Diary, author_id: 1 }
|
132 | })
|
133 |
|
134 | // Delete a book
|
135 | book.delete().then(function () {
|
136 | console.log(book) // {}
|
137 | })
|
138 | })
|
139 |
|
140 | // Get an author by primary key
|
141 | db.authors.get(1).then(function (author) {
|
142 | console.log(author) // { id: 1, name: Hunter S. Thompson }
|
143 | })
|
144 |
|
145 | // Get multiple authors by primary key
|
146 | db.authors.mget([1]).then(function (authors) {
|
147 | console.log(authors) // [ { id: 1, name: Hunter S. Thompson } ]
|
148 | })
|
149 |
|
150 | // Find authors
|
151 | db.authors.find({
|
152 | where: {
|
153 | name: 'Hunter S. Thompson'
|
154 | },
|
155 | order: 'name asc',
|
156 | limit: 10,
|
157 | offset: 0
|
158 | }
|
159 | }).then(function (authors) {
|
160 | console.log(authors) // [ { id: 1, name: Hunter S. Thompson } ]
|
161 | })
|
162 |
|
163 | // Find one author
|
164 | db.authors.findOne({
|
165 | where: [
|
166 | "name like 'Hunter %'"
|
167 | ]
|
168 | }).then(function (author) {
|
169 | console.log(author) // { id: 1, name: Hunter S. Thompson }
|
170 | })
|
171 | }
|
172 | ```
|
173 |
|
174 | Example database schema:
|
175 | ```sql
|
176 | CREATE TABLE authors (
|
177 | id SERIAL,
|
178 | name VARCHAR,
|
179 | CONSTRAINT author_pkey PRIMARY KEY(id)
|
180 | );
|
181 |
|
182 | CREATE TABLE books (
|
183 | id SERIAL,
|
184 | title VARCHAR,
|
185 | author_id INTEGER,
|
186 | CONSTRAINT book_pkey PRIMARY KEY(id),
|
187 | CONSTRAINT author FOREIGN KEY (author_id) REFERENCES authors(id)
|
188 | );
|
189 |
|
190 | CREATE TABLE reviews (
|
191 | id SERIAL,
|
192 | book_id INTEGER,
|
193 | stars INTEGER,
|
194 | body VARCHAR,
|
195 | CONSTRAINT review_pkey PRIMARY KEY(id),
|
196 | CONSTRAINT book FOREIGN KEY (book_id) REFERENCES book(id)
|
197 | );
|
198 | ```
|
199 | **Pro Tip:** [Create a trigger](https://github.com/will123195/oreo/wiki/Trigger-to-populate-array) to auto-populate `author.books[]`.
|
200 |
|
201 | **Hacker Tip:** [Replicate to Redis](https://github.com/will123195/oreo/wiki/Replicate-to-Redis) so your cache is never stale.
|
202 |
|
203 | <hr />
|
204 |
|
205 | # Usage
|
206 |
|
207 | <a name="instantiate" />
|
208 | ## oreo( opts, [cb] )
|
209 |
|
210 | Instantiates the `db` object and configures the database connection string(s).
|
211 |
|
212 | - **opts** {Object} options
|
213 | - **driver** {String} `pg` or `mysql`
|
214 | - **hosts** {Array} list of possible hosts, each is checked to see if it is online and writable or read-only
|
215 | - **name** {String} the database name
|
216 | - **user** {String} the username
|
217 | - **password** {String} the password
|
218 | - **debug** {Function} *(optional, default `false`)* set to `console.log` to see info about running queries
|
219 | - **memoize** {Integer} *(optional, default `false`)* duration in milliseconds to cache rows in process memory. Setting this to `150` is generally a no-brainer to prevent redundant queries.
|
220 | - **cache** {Object} *(optional, default `false`)* object with `get(key)` and/or `set(key, val)` methods (i.e. redis) to cache full rows (indefinitely). Cached rows are recached after `save()`/`insert()`/`update()`/`delete()`. The [Table functions](#table) fetch rows from the cache (and only fetch from sql the rows that are not cached).
|
221 | - **Promise** {Object} *(optional, default `global.Promise`)* You may plug in your own Promise library that is compatible with native promises, i.e. `Promise: require('bluebird')`. Then a promise will be returned if a callback is not specified.
|
222 | - **models** {Object} *(optional)* each table may have a model "class" specified which will be used to instantiate rows from that table. For example, `models.my_table = class MyTable {}`
|
223 | - **cb** {Function} *(optional)* callback(err)
|
224 |
|
225 | ```js
|
226 | var oreo = require('oreo')
|
227 | var db = oreo({
|
228 | driver: 'pg',
|
229 | hosts: ['localhost:5432'],
|
230 | name: 'database',
|
231 | user: 'username',
|
232 | pass: 'password',
|
233 | //debug: false, //console.log
|
234 | //memoize: 0,
|
235 | //cache: null,
|
236 | //Promise: global.Promise
|
237 | //models: {}
|
238 | }, function (err) {
|
239 | db.execute('select now() as now', function (err, rs) {
|
240 | console.log('now:', rs[0].now)
|
241 | })
|
242 | })
|
243 | ```
|
244 |
|
245 | # Db
|
246 |
|
247 | <a name="execute" />
|
248 | ## db.execute( sql, [data], [opts], [cb] )
|
249 |
|
250 | Executes an arbitrary SQL query.
|
251 |
|
252 | - **sql** {String|Array} the SQL statement
|
253 | - **data** {Object} *(optional, unless `options` is specified)* parameterized query data
|
254 | - **opts** {Object} *(optional)* query options
|
255 | - **write** {Boolean} if truthy, forces query to run on master db, otherwise attempts to run on a read-only host
|
256 | - **conString** {String} the connection string of the db
|
257 | - **cb** {Function} *(optional)* callback(err, rows) If *cb* is not provided, a Promise is returned.
|
258 |
|
259 | ```js
|
260 | db.execute([
|
261 | 'select now()', // arrays can be used for es5 multi-line convenience
|
262 | 'as now'
|
263 | ], function (err, rs) {
|
264 | console.log(rs[0]) // 2014-06-24 21:03:08.652861-04
|
265 | })
|
266 | ```
|
267 |
|
268 | Parameterized query (SQL injection safe):
|
269 | ```js
|
270 | db.execute(`
|
271 | select id
|
272 | from authors
|
273 | where name = :name
|
274 | `, {
|
275 | name: 'Jack Kerouac',
|
276 | }, function (err, rows) {
|
277 | console.log(rows[0].id) // 1
|
278 | })
|
279 | ```
|
280 |
|
281 | If no callback is provided a Promise is returned:
|
282 | ```js
|
283 | db.execute('select now()')
|
284 | .then(function (rows) {
|
285 |
|
286 | })
|
287 | .catch(function (err) {
|
288 |
|
289 | })
|
290 | ```
|
291 |
|
292 | <a name="executeWrite" />
|
293 | ## db.executeWrite( sql, [data], [opts], [cb] )
|
294 |
|
295 | Same as [`execute`](#execute) but executes the query on a writable (primary) host.
|
296 |
|
297 | <a name="onReady" />
|
298 | ## db.onReady( cb )
|
299 |
|
300 | Queues a function to be called when oreo's schema detection is complete (i.e. when oreo is initialized).
|
301 |
|
302 | - **cb** {Function} callback()
|
303 |
|
304 | ```js
|
305 | var db = oreo(config, function (err) {
|
306 | if (err) return console.log(err)
|
307 | console.log('Ready!')
|
308 | }).onReady(function () {
|
309 | console.log('onReady #1')
|
310 | })
|
311 | db.onReady(function () {
|
312 | console.log('onReady #2')
|
313 | })
|
314 |
|
315 | /*
|
316 | Output:
|
317 | onReady #1
|
318 | onReady #2
|
319 | Ready!
|
320 | */
|
321 | ```
|
322 |
|
323 | <a name="end" />
|
324 | ## db.end( [cb] )
|
325 |
|
326 | Closes the db connection(s).
|
327 |
|
328 | # Table
|
329 |
|
330 | <a name="find" />
|
331 | ## db.***table***.find( [opts], [cb] )
|
332 |
|
333 | Finds multiple rows.
|
334 |
|
335 | - **opts** {Object} *(optional)* options
|
336 | - **where** {String|Array|Object} the where clause criteria
|
337 | - **order** {String} i.e. `last_name ASC, age DESC`
|
338 | - **limit** {Number}
|
339 | - **offset** {Number}
|
340 | - **hydrate** {String|Array} hydrates the specified foreign keys (see [`hydrate`](#hydrate))
|
341 | - **params** {Object} key/value pairs to be substituted for `:key` patterns in the query
|
342 | - **cb** {Function} *(optional)* callback(err, rows) If *cb* is not provided, a Promise is returned.
|
343 |
|
344 | ```js
|
345 | db.authors.find({
|
346 | where: [ "name like 'Jack%'" ],
|
347 | order: 'name asc',
|
348 | offset: 5,
|
349 | limit: 5,
|
350 | hydrate: ['books']
|
351 | }, function (err, authors) {
|
352 | console.log(authors)
|
353 | // [ { id: 1, name: Jack Kerouac, books: [ { id: 1, title: On the Road, author_id: 1 } ] } ]
|
354 | })
|
355 | ```
|
356 |
|
357 | The `where` option has several valid formats:
|
358 | - {String}
|
359 |
|
360 | ```js
|
361 | where: "field = :f1 and field2 > :f2",
|
362 | params: {
|
363 | f1: 'abc',
|
364 | f2: 1
|
365 | }
|
366 | ```
|
367 | - {Array}
|
368 |
|
369 | ```js
|
370 | where: [
|
371 | "field = :f1",
|
372 | "field2 > :f2"
|
373 | ],
|
374 | params: {
|
375 | f1: 'abc',
|
376 | f2: 1
|
377 | }
|
378 | ```
|
379 | - {Object}
|
380 |
|
381 | ```js
|
382 | where: {
|
383 | field: 'abc',
|
384 | field2: { $gt: 1 } // query operators are coming soon
|
385 | }
|
386 | ```
|
387 |
|
388 | <a name="findOne" />
|
389 | ## db.***table***.findOne( opts, [cb] )
|
390 |
|
391 | Finds exactly one row.
|
392 |
|
393 | - **opts** {Object} same options as [`find`](#find)
|
394 | - **cb** {Function} *(optional)* callback(err, row) If *cb* is not provided, a Promise is returned.
|
395 |
|
396 | ```js
|
397 | db.authors.findOne({
|
398 | where: [ "name like 'Jack%'" ],
|
399 | order: 'name asc',
|
400 | offset: 5
|
401 | }, function (err, author) {
|
402 | console.log(author.id) // 1
|
403 | })
|
404 | ```
|
405 |
|
406 | <a name="get" />
|
407 | ## db.***table***.get( primaryKey, [opts], [cb] )
|
408 |
|
409 | Gets a row by primary key.
|
410 |
|
411 | - **primaryKey** {String|Number|Object} the primary key of the row to get
|
412 | - **opts** {Object} *(optional)* options
|
413 | - **hydrate** {String|Array} hydrates the specified foreign keys (see [`hydrate`](#hydrate))
|
414 | - **cb** {Function} *(optional)* callback(err, row) If *cb* is not provided, a Promise is returned.
|
415 |
|
416 | ```js
|
417 | var primaryKey = 1 // var primaryKey = { id: 1 } // this also works
|
418 | db.authors.get(primaryKey, function (err, author) {
|
419 | console.log(author) // { id: 1, name: Jack Kerouak }
|
420 | })
|
421 | ```
|
422 |
|
423 | Multi-column (composite) primary key:
|
424 | ```js
|
425 | var primaryKey = {
|
426 | company: 'Cogswell Cogs',
|
427 | part_no: 'A-12345'
|
428 | }
|
429 | db.parts.get(primaryKey, function (err, part) {
|
430 | console.log(part) // { company: Cogswell Cogs, part_no: A-12345, price: 9.99, in_stock: true }
|
431 | })
|
432 | ```
|
433 |
|
434 | <a name="insert" />
|
435 | ## db.***table***.insert( data, [cb] )
|
436 |
|
437 | Inserts a new row.
|
438 |
|
439 | - **data** {Object} the data to insert into the db
|
440 | - **cb** {Function} *(optional)* callback(err, row) If *cb* is not provided, a Promise is returned.
|
441 |
|
442 | ```js
|
443 | db.books.insert({
|
444 | title: 'On the Road',
|
445 | author_id: 1
|
446 | }, function (err, book) {
|
447 | console.log(book)
|
448 | // { id: 1, title: On the Road, author_id: 1 }
|
449 | })
|
450 | ```
|
451 |
|
452 | Insert multiple rows into related tables in a single transaction:
|
453 | ```js
|
454 | db.books.insert({
|
455 | title: 'On the Road',
|
456 | author: { // "author" is the foreign key name (1-to-1)
|
457 | name: 'Jack Kerouac'
|
458 | },
|
459 | reviews: [ // shorthand for 'book:reviews' <foreignKeyName>:<tableName> (1-to-many)
|
460 | { stars: 5, body: 'Psychadelic!'},
|
461 | { stars: 4, body: 'Bizarre, unpredictable yet strangely alluring.'}
|
462 | ]
|
463 | }, function (err, book) {
|
464 | console.log(book)
|
465 | // { id: 1, title: On the Road, author_id: 1 }
|
466 | })
|
467 | ```
|
468 |
|
469 | See also: [`hydrate`](#hydrate)
|
470 |
|
471 | <a name="mget" />
|
472 | ## db.***table***.mget( primaryKeys, [opts], [cb] )
|
473 |
|
474 | Gets many rows by primary key in the specified order. A `null` value will be returned for each primary key that does not exist.
|
475 |
|
476 | - **primaryKeys** {Array} the primary keys of the rows to get
|
477 | - **opts** {Object} *(optional)* options
|
478 | - **hydrate** {String|Array} hydrates the specified foreign keys (see [`hydrate`](#hydrate))
|
479 | - **cb** {Function} *(optional)* callback(err, rows) If *cb* is not provided, a Promise is returned.
|
480 |
|
481 | ```js
|
482 | var bookIds = [1]
|
483 | db.books.mget(bookIds, function (err, books) {
|
484 | console.log(books)
|
485 | // [ { id: 1, title: On the Road, author_id: 1 } ]
|
486 | })
|
487 | ```
|
488 |
|
489 | <a name="table_save" />
|
490 | ## db.***table***.save( data, [cb] )
|
491 |
|
492 | Inserts or updates depending on whether the primary key exists in the db.
|
493 |
|
494 | - **data** {Object} the data to save to the db
|
495 | - **cb** {Function} *(optional)* callback(err, row) If *cb* is not provided, a Promise is returned.
|
496 |
|
497 | ```js
|
498 | var formPOST = {
|
499 | id: 1,
|
500 | title: 'New Title'
|
501 | }
|
502 | db.books.save(formPOST, function (err, book) {
|
503 | console.log(book)
|
504 | // { id: 1, title: New Title, author_id: 1 }
|
505 | })
|
506 | ```
|
507 |
|
508 | # Row
|
509 |
|
510 | <a name="delete" />
|
511 | ## row.delete( [cb] )
|
512 |
|
513 | Deletes an existing row from the database.
|
514 |
|
515 | - **cb** {Function} *(optional)* callback(err) If *cb* is not provided, a Promise is returned.
|
516 |
|
517 | ```js
|
518 | book.delete(function (err) {
|
519 | console.log(book)
|
520 | // {}
|
521 | })
|
522 | ```
|
523 |
|
524 | <a name="hydrate" />
|
525 | ## row.hydrate( propertyName, [cb] )
|
526 |
|
527 | Hydrates the row(s) linked with the specified foreign key(s) and/or foreign table(s).
|
528 |
|
529 | - **propertyName** {String|Array} the name of the hydratable property to fetch and attach to this row. There are two types of hydratable property names:
|
530 | - 1-to-1 foreign key constraint name
|
531 | - 1-to-many foreign table name
|
532 | - **cb** {Function} *(optional)* callback(err) If *cb* is not provided, a Promise is returned.
|
533 |
|
534 | ```js
|
535 | db.books.get(1, function (err, book) {
|
536 | console.log(book)
|
537 | // { id: 1, title: On the Road, author_id: 1 }
|
538 |
|
539 | // hydrate a 1-to-1 linked row
|
540 | book.hydrate('author', function (err) {
|
541 | console.log(book.author)
|
542 | // { id: 1, name: Jack Kerouac }
|
543 | })
|
544 |
|
545 | // hydrate 1-to-many linked rows
|
546 | book.hydrate('reviews', function (err) {
|
547 | console.log(book.reviews)
|
548 | // [ { stars: 5, body: 'Psychadelic!' }, { stars: 4, body: 'Bizarre...' } ]
|
549 | })
|
550 | })
|
551 | ```
|
552 |
|
553 | When hydrating a 1-to-1 row, the **propertyName** is the name of the foreign key constraint.
|
554 |
|
555 | For example, a book has one author, so we have a table `books` with a column `author_id` which has a foreign key constraint named `author` which links to `author.id`.
|
556 |
|
557 | ```js
|
558 | // 1-to-1
|
559 | book.hydrate('author', function (err) {
|
560 | console.log(book.author)
|
561 | // { id: 1, name: Jack Kerouac }
|
562 | })
|
563 | ```
|
564 |
|
565 | When hydrating 1-to-many rows, it is recommended to specify the fully qualified hydratable **propertyName** formatted as `foreignKeyName:tableName`. However, for convenience, if the foreign table has only one foreign key that references this table, you may omit `foreignKeyName:` and simply use `tableName` shorthand notation.
|
566 |
|
567 | For example, a book has many reviews, so we have a table `reviews` with a column `book_id` which has a foreign key constraint named `book` which links to `book.id`.
|
568 |
|
569 | ```js
|
570 | // 1-to-many (fully qualified notation)
|
571 | book.hydrate('book:reviews', function (err) {
|
572 | console.log(book['book:reviews'])
|
573 | // [ { stars: 5, body: 'Psychadelic!' }, { stars: 4, body: 'Bizarre...' } ]
|
574 | })
|
575 |
|
576 | // 1-to-many (shorthand notation)
|
577 | book.hydrate('reviews', function (err) {
|
578 | console.log(book.reviews)
|
579 | // [ { stars: 5, body: 'Psychadelic!' }, { stars: 4, body: 'Bizarre...' } ]
|
580 | })
|
581 | ```
|
582 |
|
583 | Hydrate multiple properties in parallel:
|
584 |
|
585 | ```js
|
586 | book.hydrate(['author', 'reviews'], function (err) {
|
587 | console.log(book)
|
588 | // {
|
589 | // id: 1,
|
590 | // title: On the Road,
|
591 | // author_id: 1,
|
592 | // author: { id: 1, name: Jack Kerouac },
|
593 | // reviews: [ { stars: 5, body: 'Psychadelic!' }, { stars: 4, body: 'Bizarre...' } ]
|
594 | // }
|
595 | })
|
596 | ```
|
597 |
|
598 | <a name="save" />
|
599 | ## row.save( [cb] )
|
600 |
|
601 | Saves the modified property values to the database (and saves linked rows recursively).
|
602 |
|
603 | - **cb** {Function} *(optional)* callback(err, row) If *cb* is not provided, a Promise is returned.
|
604 |
|
605 | ```js
|
606 | db.books.get(1, function (err, book) {
|
607 | console.log(book)
|
608 | // { id: 1, title: On the Road, author_id: 1 }
|
609 | book.author_id = 2
|
610 | book.save(function (err, book) {
|
611 | console.log(book)
|
612 | // { id: 1, title: On the Road, author_id: 2 }
|
613 | })
|
614 | })
|
615 | ```
|
616 |
|
617 | <a name="set" />
|
618 | ## row.set( data )
|
619 |
|
620 | Modifies multiple property values but does NOT save to the db.
|
621 |
|
622 | - **data** {Object} the data to modify
|
623 |
|
624 | ```js
|
625 | db.books.get(1, function (err, book) {
|
626 | console.log(book)
|
627 | // { id: 1, title: On the Road, author_id: 1 }
|
628 | book.set({
|
629 | title: 'New Title',
|
630 | author_id: 2
|
631 | })
|
632 | book.save(function (err, book) {
|
633 |
|
634 | })
|
635 | })
|
636 | ```
|
637 |
|
638 | <a name="update" />
|
639 | ## row.update( data, [cb] )
|
640 |
|
641 | Updates an existing row. A convenience method for `set()` then `save()`.
|
642 |
|
643 | - **data** {Object} the data to save
|
644 | - **cb** {Function} *(optional)* callback(err, row) If *cb* is not provided, a Promise is returned.
|
645 |
|
646 | ```js
|
647 | book.update({
|
648 | title: 'New Title'
|
649 | }, function (err, book) {
|
650 | console.log(book)
|
651 | // { id: 1, title: New Title, author_id: 1 }
|
652 | })
|
653 | ```
|
654 |
|
655 | ## Known Issues
|
656 |
|
657 | - Postgres tables containing `JSON` data type are not supported (use `JSONB` instead!)
|