This is Shyp's next gen data access layer. Maybe the best way to describe this is what was bad with the old one:
- Waterline's behavior is notably inconsistent. We don't have any flexibility with its interface, or the arguments it accepts.
- Waterline throws dictionaries, not Error objects, which are treated inconsistently
- Stubbing a database call required importing/loading all of Sails, which is slow
- Model objects are globals, which means their behavior is inconsistent
- The old API's had a lot of dangerous behavior - a failure to findOne would
return
null
instead of throwing an error, and calls toupdate
always returned a list, even if you knew you were only updating one record.
So! Here are some nice things about the functions in this folder:
- We can define better interfaces that are safer, shorter and easier to work with.
- You can stub database calls in tests without needing to load Sails.
- A function that's designed to find or update one of something, and doesn't find anything, will throw an error.
- Where we can, we throw instances of
DatabaseError
objects, which have useful properties and better error messages than PG/Waterline.
For an example, check out the UserDAO
file. It contains two methods for
accessing Users that we call often - creating a new user or updating a single
user by their ID. If a constraint or uniqueness check fails, we catch the error
and try to return a nicer one.
[ ... snipped ... ]
Say you have a table with a unique index, and you try to write a duplicate
record to the database. Waterline will throw an object with the following keys.
Note this is not an instance of Error; if your tests are reporting a failure
and there is no visible stack trace, it's probably a Waterline object without a
message
key. Come to think of it we should probably patch Mocha to make this
error more visible.
-
originalError
- The underlying Postgres error, with a bunch of useful fields including the failing constraint. This field will always be there for errors raised by Postgres, including type errors, not null failures, check constraints, and unique constraints. -
code
-'E_VALIDATION'
or'E_UNKNOWN'
. note thatE_VALIDATION
does not get raised in some cases where it should, for example check constraint failures, so this field is not reliable -
invalidAttributes
- Eitherundefined
or an object that looks something like this:{ 'lower(username)': [ { value: 'michaeljordan', rule: 'unique', message: 'A record with that `lower(username)` already exists (`michaeljordan`).' } ] }
NB: this field is
undefined
for check constraints -
_e
- ignore this -
rawStack
- Raw stack trace for the error message -
details
- Occasionally undefined but when defined, just takes fields from the PG error message. Ignore this. -
reason
- may beundefined
or the string"N attribute(s) are invalid"
. Ignore this -
status
- for some reason Waterline sets HTTP status codes on error objects. ignore this field. -
model
- Occasionallyundefined
, occasionally the table name. Don't use this field, theoriginalError
will tell you which table failed.
My goal in walking through this is to show you that Waterline errors are
untrustworthy. You really want to interact with and introspect on the
originalError
field, which contains more useful and consistent information
about what went wrong. That object looks like this:
{ [error: duplicate key value violates unique constraint "username_unique_lowercase"]
name: 'error',
length: 228,
severity: 'ERROR',
code: '23505',
detail: 'Key (lower(username))=(michaeljordan) already exists.',
hint: undefined,
position: undefined,
internalPosition: undefined,
internalQuery: undefined,
where: undefined,
schema: 'public',
table: 'users',
column: undefined,
dataType: undefined,
constraint: 'username_unique_lowercase',
file: 'nbtinsert.c',
line: '398',
routine: '_bt_check_unique' }
You should be able to pass this error object directly to the DatabaseError
constructor. Then you can access the detail
, code
, constraint
fields from there. You'll probably need to write a better error message though;
put some thought into whether users will be able to understand what went wrong
and how to fix it.