murmuration 1.1.9 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +204 -107
  2. package/bin/statement.js +81 -43
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,19 +1,17 @@
1
1
  # Murmuration
2
2
 
3
- Database connections, transactions and migrations.
3
+ Database statements, transactions and migrations.
4
4
 
5
5
  There are two specific packages that you should make use of instead this one:
6
6
 
7
7
  * [Murmuration-MariaDB](https://github.com/djalbat/murmuration-mariadb)
8
8
  * [Murmuration-PostGreSQL](https://github.com/djalbat/murmuration-postgresql)
9
9
 
10
- This readme file largely pertains to both, although there are also specific instructions given in readme file for each.
10
+ This readme file pertains to both, although there are specific instructions for each of the above in their respective readme files.
11
11
 
12
- Murmuration is meant to be used as alternative to a database [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping). Aside from migrations, it is deliberately simple and low level, in the sense that it provides no more than the bare minimum functionality needed to connect to a database and run commands, optionally in the context of transactions.
12
+ Murmuration can be used as alternative to a database [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping). It is deliberately simple and low level in the sense that it provides no more than the bare minimum functionality needed to connect to a database and generate statements to be executed ideally in the context of transactions.
13
13
 
14
- The migration functionality, if used correctly, will guarantee that a Node application's codebase remains in line with the database it relies on, updating the latter each time the former is deployed.
15
-
16
- The prescriptions given below are an essential part of the package. They show how to write database utility functions at scale; how to employ them in the context of transactions; and they outline what needs to be done in order to guarantee the success of migrations.
14
+ There is also some adjunct migration functionality that may or may not suit your use case. If used as prescribed then it guarantees that an application remains in line with its database, as the latter will be migrated if needed each time the former is deployed.
17
15
 
18
16
  ## Installation
19
17
 
@@ -33,134 +31,257 @@ Remember that it is the aforementioned specific packages that you should install
33
31
 
34
32
  ## Usage
35
33
 
36
- Functionality across the specific packages is identical, aside from small differences in configuration and error handling, and is therefore covered here.
34
+ Aside from small differences in configuration and error handling the Functionality across the aforementioned specific packages is identical, and is therefore covered here.
37
35
 
38
- ### Getting and releasing connections
36
+ Statements are covered first up, but ideally they should be executed in the context of transactions and therefore the remainder of this section cannot be overlooked. In particular, the example statements that follow are executed within operations, which are covered in the later subsection on transactions.
39
37
 
40
- The static `fromConfiguration()` method of the `Connection` class takes a configuration and a callback argument:
38
+ ### Generating statements
41
39
 
42
- ```
43
- Connection.fromConfiguration(configuration, (error, connection) => {
40
+ Statements are generated dynamically with a simple, promise-like syntax.
44
41
 
45
- ...
42
+ In the first example a row is selected should its email address and password match the given values:
46
43
 
47
- connection.release();
48
- };
44
+ ```
45
+ const using = require("../using");
46
+
47
+ function checkAccountOperation(connection, abort, proceed, complete, context) {
48
+ const { emailAddress, password } = context;
49
+
50
+ using(connection)
51
+ .selectFromAccont()
52
+ .where({ emailAddress, password })
53
+ .one(({ id }) => {
54
+ Object.assign(context, {
55
+ id
56
+ });
57
+
58
+ proceed();
59
+ })
60
+ .else(() => {
61
+ ...
62
+
63
+ complete()
64
+ })
65
+ .catch(abort)
66
+ .execute();
67
+ }
49
68
  ```
50
69
 
51
- If successful, the `error` argument of the callback will be null and a `connection` object will be returned, otherwise the `error` argument will be truthy. Details of the format of the `configuration` object can be found in the configuration subsections of the specific package readme files.
70
+ There are several points worth noting:
52
71
 
53
- ### Logging
72
+ * A local `using()` function has been employed rather the function supplied by the package, because the `Statement` class it utilities has been extended for convenience. For example, `selectFromAccount()` is defined in the application's own `Statement` class.
73
+ * Each operation provides a connection and a context as well as three callbacks. These allow the result of the execution to determine whether the application should proceed to the next operation or if the transaction should be aborted or completed.
74
+ * The `where()` method takes a plain old JavaScript object.
75
+ * The `one()` method takes a handler that is called if one row is returned.
76
+ * The `else()` method takes a handler that is called in all other cases.
77
+ * The `catch()` method takes a handler that is called should execution fail.
54
78
 
55
- Ideally you should add a `log` property to the `configuration` object that references an object of the following form:
79
+ In the following example rows in a table holding temporary reset codes are deleted once they expire.
56
80
 
57
81
  ```
58
- const log = {
59
- trace: () => { ... },
60
- debug: () => { ... },
61
- info: () => { ... },
62
- warning: () => { ... },
63
- error: () => { ... },
64
- fatal: () => { ... },
65
- });
82
+ const using = require("../using");
83
+
84
+ const { unauthorized } = require("../utilities/states");
85
+
86
+ function deleteExpiredResetCodesOperation(connection, abort, proceed, complete, context) {
87
+ const { emailAddress, password } = context;
88
+
89
+ using(connection)
90
+ .deleteFromResetCode()
91
+ .where`
92
+
93
+ expires < NOW()
94
+
95
+ `
96
+ .success(proceed)
97
+ .catch(abort)
98
+ .execute();
99
+ }
66
100
  ```
67
- Currently only the `error()`, `info()` and `debug()` functions are made use of, but in future others may be utilised.
68
101
 
69
- If you do not provide a `log` object, all logging is suppressed.
102
+ The following points are worth noting:
70
103
 
71
- ### Error handling
104
+ * The `where()` method takes a template literal this time.
105
+ * The `success()` method takes a callback that is called should hte execution succeed.
72
106
 
73
- In the event of an error, if a `log` property has been added to the `configuration` object then the `log.error()` function will be called with a message containing a reasonable stab at the cause of the error. Details can be found in the subsections of the same name in the specific package readme files.
107
+ In this example a row is inserted into a table for software packages:
74
108
 
75
- These messages are meant to help with debugging simple mistakes such as providing incorrect configuration. If you do not find them helpful, do not provide a `log` object and be assured that the errors are always returned by way of callback function arguments for you to deal with as you see fit.
109
+ ```
110
+ const using = require("../using");
76
111
 
77
- ### Running queries
112
+ function createReleaseOperation(connection, abort, proceed, complete, context) {
113
+ const { name, entriesBlob, versionNumber } = context;
78
114
 
79
- Two functions are provided, namely `query()` and `execute()`. The former returns an error and an array of rows returned by the query by way of a callback, the latter only an error by way of a callback. Otherwise their signatures are the same:
115
+ using(connection)
116
+ .insertIntoRelease()
117
+ .values({ name, entriesBlob, versionNumber })
118
+ .success(proceed)
119
+ .catch(abort)
120
+ .execute();
121
+ }
122
+ ```
123
+
124
+ Note the following:
125
+
126
+ * The `values()` method takes a plain old JavaScript object with the values to be inserted.
80
127
 
128
+ Finally, the following operation updates a user profile:
129
+
130
+ ```
131
+ const using = require("../using");
132
+
133
+ function editProfileOperation(connection, abort, proceed, complete, context) {
134
+ const { name, notes, userIdentifier } = context,
135
+ identifier = userIdentifier; ///
136
+
137
+ using(connection)
138
+ .updateUser()
139
+ .set({ name, notes })
140
+ .where({ identifier })
141
+ .success(proceed)
142
+ .execute();
143
+ }
81
144
  ```
82
- const sql = ... ;
83
145
 
84
- query(connection, sql, ...parameters, (error, rows) => {
146
+ Note:
85
147
 
86
- ...
148
+ * The `set()` method takes a plain old JavaScript object.
87
149
 
88
- });
150
+ In general, the assumption with passing plain old JavaScript objects is that clauses and so forth can easily be constructed from them. The `set()`, `where()` and `values()` methods can also take appended template literals, however, so that you can define parts of the SQL with more freedom. More detail is given towards the end of the next subsection.
89
151
 
90
- execute(connection, sql, ...parameters, status, (error) => {
152
+ ### Statement class specification
91
153
 
92
- ...
154
+ The following specification gives a complete and more detailed list of the methods available.
93
155
 
94
- });
95
- ```
96
- In both cases, a variable length list of parameters can be passed between the `sql` and `callback` arguments. These replace the placeholders in the SQL you provide. For more information, see the specific packages.
156
+ * One of the `selectFrom()`, `insertInto()`, `deleteFrom()` or `delete()` methods must be called. Each takes a string for the name of a table or view.
157
+
158
+ It is recommended that these methods are not called directly, by the way, rather methods that call them indirectly are created on a subclass of the `Statement` class, in order to avoid repetition. This is covered in the later subsection on extending the `Statement` class.
159
+
160
+ * The `success()` method must accompany the `insertInto()`, `deleteFrom()` or `delete()` methods.
161
+ * If the `selectFrom()` method is called, then one of the following of the following methods must also be called. Each takes a callback. In all but the last case, you must also call the `else()` method with a callback:
162
+ * The `none()` method for when no rows are returned.
163
+ * The `one()` method for when exactly one row is returned.
164
+ * The `first()` method for when one or more rows are returned.
165
+ * The `many()` method for when any number of rows are returned, including none.
97
166
 
98
- To make use of these functions, it is recommended that you create a file corresponding to each table or view, naming the functions therein to reflect the SQL statements and parameters. The SQL they employ can be read from files, the names of which exactly match the function names. For example:
167
+ * The `catch()` method must always be called with a callback for when an exception occurs.
168
+ * The `set()` method must be called along with the `update()` method.
169
+ * The `where()` method can be called with all but the `insertInto()` method.
170
+ * The `values()` method must be called along with the `insertInto()` method.
171
+ * Either the `getSQL()` or `execute()` methods must be called, usually the latter.
172
+
173
+ Each of the `set()`, `where()` and `values()` methods can take a plain old JavaScript object or an appended template literal. You cannot pass a string as an argument because there is a danger that it might contain an un-escaped value. By forcing you to pass an appended template literal, the method in question is able to pass the array of arguments it receives directly on to the underlying database package, thereby guaranteeing that they will be correctly cast and escaped.
174
+
175
+ ### Extending the Statement class
176
+
177
+ This is recommended for no other reason than to avoid repetitively passing table or view names to `selectFrom()` methods and the like. A simple exapmle will amply demonstrate:
99
178
 
100
179
  ```
101
- const SELECT_USERNAME_FILE_NAME = "table/user/selectUsername.sql",
102
- SELECT_IDENTIFIER_FILE_NAME = "table/user/selectIdentifier.sql",
103
- SELECT_EMAIL_ADDRESS_FILE_NAME = "table/user/selectEmailAddress.sql",
104
- UPDATE_NAME_IDENTIFIER_FILE_NAME = "table/user/updateNameIdentifier.sql",
105
- ...
106
- ;
180
+ "use strict";
181
+
182
+ const { Statement: BaseStatement } = require("./murmuration-...");
183
+
184
+ const USER_TABLE = "user",
185
+ RELEASE_TABLE = "release",
186
+ DISTRIBUTION_TABLE = "attribution",
187
+ LATEST_RELEASE_VIEW = "latest_release";
107
188
 
108
- function selectUsername(connection, username, callback) { ... }
189
+ class Statement extends BaseStatement {
190
+ updateUser() { return this.update(USER_TABLE); }
191
+ updateRelease() { return this.update(RELEASE_TABLE); }
192
+ updateDistribution() { return this.update(DISTRIBUTION_TABLE); }
109
193
 
110
- function selectIdentifier(connection, identifier, callback) { ... }
194
+ insertIntoUser() { return this.insertInto(USER_TABLE); }
195
+ insertIntoRelease() { return this.insertInto(RELEASE_TABLE); }
196
+ insertIntoDistribution() { return this.insertInto(DISTRIBUTION_TABLE); }
111
197
 
112
- function selectEmailAddress(connection, emailAddress, callback) { ... }
198
+ deleteFromUser() { return this.deleteFrom(USER_TABLE); }
199
+ deleteFromRelease() { return this.deleteFrom(RELEASE_TABLE); }
200
+ deleteFromDistribution() { return this.deleteFrom(DISTRIBUTION_TABLE); }
113
201
 
114
- function updateNameIdentifier(connection, name, identifier, callback) { ... }
202
+ selectFromUser() { return this.selectFrom(USER_TABLE); }
203
+ selectFromRelease() { return this.selectFrom(RELEASE_TABLE); }
204
+ selectFromDistribution() { return this.selectFrom(DISTRIBUTION_TABLE); }
115
205
 
116
- ...
206
+ selectFromLatestRelease() { return this.selectFrom(LATEST_RELEASE_VIEW); }
207
+
208
+ static fromConnection(connection) { return BaseStatement.fromConnection(Statement, connection); }
209
+ }
210
+
211
+ module.exports = Statement;
117
212
  ```
118
- The body of each of the function should be identical bar the parameters and the use of the `query()` versus the `execute()` function:
213
+
214
+ Here the ellipsis in the `require()` statement should be replaced as needed.
215
+
216
+ Now make use of this subclass in your own `using()` function...
119
217
 
120
218
  ```
121
- function selectEmailAddress(connection, emailAddress, callback) {
122
- const filePath = `${SQL_DIRECTORY_PATH}/${SELECT_EMAIL_ADDRESS_FILE_NAME}`,
123
- sql = sqlFromFilePath(filePath);
219
+ "use strict";
220
+
221
+ const Statement = require("./statement");
124
222
 
125
- query(connection, sql, emailAddress, (error, rows) => {
126
- if (error) {
127
- log.error("selectEmailAddress() failed.");
128
- }
223
+ function using(connection) {
224
+ const statement = Statement.fromConnection(connection);
129
225
 
130
- callback(error, rows);
131
- });
226
+ return statement;
132
227
  }
133
228
 
134
- function updateNameIdentifier(connection, name, identifier, callback) {
135
- const filePath = `${SQL_DIRECTORY_PATH}/${UPDATE_NAME_IDENTIFIER_FILE_NAME}`,
136
- sql = sqlFromFilePath(filePath);
229
+ module.exports = using;
230
+ ```
231
+
232
+ ...or require and instantiate it directly. The `using()` function is purely for convenience.
137
233
 
138
- execute(connection, sql, name, identifier, (error) => {
139
- if (error) {
140
- log.error("updateNameIdentifier() failed.");
141
- }
234
+ ### Getting and releasing connections
142
235
 
143
- callback(error);
144
- });
145
- }
236
+ The static `fromConfiguration()` method of the `Connection` class takes a configuration and a callback argument:
237
+
238
+ ```
239
+ Connection.fromConfiguration(configuration, (error, connection) => {
240
+
241
+ ...
242
+
243
+ connection.release();
244
+ };
245
+ ```
246
+
247
+ If successful, the `error` argument of the callback will be null and a `connection` object will be returned, otherwise the `error` argument will be truthy. Details of the format of the `configuration` object can be found in the configuration subsections of the specific package readme files.
248
+
249
+ ### Logging
250
+
251
+ Ideally you should add a `log` property to the `configuration` object that references an object of the following form:
252
+
253
+ ```
254
+ const log = {
255
+ trace: () => { ... },
256
+ debug: () => { ... },
257
+ info: () => { ... },
258
+ warning: () => { ... },
259
+ error: () => { ... },
260
+ fatal: () => { ... },
261
+ });
146
262
  ```
147
- Note that the parameters, as well as matching the function names precisely, are passed directly to the `query()` or `execute()` functions. Essentially the only purpose of these functions is to retrieve the SQL, pass it to the requisite murmuration function and log an error if it occurs.
263
+ Currently only the `error()`, `info()` and `debug()` functions are made use of, but in future others may be utilised.
148
264
 
149
- Lastly, it is recommended that you avoid complex queries that span more than one table and always employ views instead. For information on views, see the MariaDB documentation [here](https://mariadb.com/kb/en/views/).
265
+ If you do not provide a `log` object, all logging is suppressed.
266
+
267
+ ### Error handling
268
+
269
+ In the event of an error, if a `log` property has been added to the `configuration` object then the `log.error()` function will be called with a message containing a reasonable stab at the cause of the error. Details can be found in the subsections of the same name in the specific package readme files.
270
+
271
+ These messages are meant to help with debugging simple mistakes such as providing incorrect configuration. If you do not find them helpful, do not provide a `log` object and be assured that the errors are always returned by way of callback function arguments for you to deal with as you see fit.
150
272
 
151
273
  ### Using transactions
152
274
 
153
275
  Ideally, all database operations should be run in the context of transactions. There is a single `transaction()` function that allows you to do this. It takes `configuration`, `operations`, `callback` and `context` arguments. The callback provided will have a `completed` argument while the context is mandatory and must be a plain old JavaScript object. The `transaction()` function makes use of the `context` object itself, in fact, with the object's `connection`, `operations` and `completed` properties being reserved.
154
276
 
155
- In the example below, four operations has been provided and the context has properties that they will make use of:
277
+ In the example below, three operations has been provided and the context has properties that they will make use of:
156
278
 
157
279
  ```
158
280
  const configuration = ... ,
159
281
  operations = [
160
- checkUsernameAvailable,
161
- checkEmailAddressAvailable,
162
- addEmailAddressUsernamePasswordAndStatus,
163
- retrieveUserIdentifier,
282
+ checkUsernameOperation,
283
+ checkEmailAddressOperation,
284
+ addEmailOperation
164
285
  ],
165
286
  context = {
166
287
  emailAddress,
@@ -174,32 +295,10 @@ transaction(configuration, operations, (completed) => {
174
295
 
175
296
  }, context);
176
297
  ```
177
- The signature of the operations must be identical to the following example:
178
-
179
- ```
180
- function checkUsernameAvailable(connection, abort, proceed, complete, context) {
181
- const { username } = context;
182
-
183
- selectUsername(connection, username, (error, rows) => {
184
- if (error) {
185
- abort();
186
298
 
187
- return;
188
- }
299
+ The signatures of the operations are given in the usage section. The bodies of the operations are immaterial, again see the usage section for idees. What follows are general prescriptions for how to make use of transactions.
189
300
 
190
- const rowsLength = rows.length;
191
-
192
- (rowsLength === 0) ?
193
- proceed() :
194
- complete();
195
- });
196
- }
197
- ```
198
- Note the provision of the `abort()`, `proceed()` and `complete()` callbacks, each of which is utilised. Their utility should be self evident. One important point to note is that there is an explicit `return` statement after the call to the `abort()` callback. It is easy to forget that invoking a callback does not prevent execution continuing in the current context.
199
-
200
- It is entirely possible to conflate the first three of these operations into a single SQL statement and then to combine that with the last SQL statement that recovers an auto-incremented identifier. Both of these statements can then be placed in a single SQL file and run with a call to the `query()` function. There is nothing to be gained from such a approach, however, and there are good reasons not to take it:
201
-
202
- * You can try to insert values into a table and test whether they are unique by whether or not the insert fails. However, the database will throw an error that is indistinguishable from errors that occur because of, say, syntax errors in the SQL. It could be considered bad practice to knowingly run a query that may result in an error and use the presence of such as an indication of whether or not an otherwise correct query has been successful.
301
+ * You can try to insert values into a table and test whether they are unique by whether or not the insert fails. However, the database will throw an error that is indistinguishable from errors that occur because of, say, syntax errors in the SQL. It could be argued that it is bad practice to knowingly run a query that may result in an error and use the presence of such as an indication of whether or not an otherwise correct query has been successful.
203
302
 
204
303
  * Often conflating operations means that application logic that is far more suited to, in this case, JavaScript must be added to the SQL itself. Or worse, the application logic is simply assumed to be implicit in the SQL. It is far better to implement this kind of logic explicitly in JavaScript than complicate SQL statements with it.
205
304
 
@@ -207,13 +306,11 @@ It is entirely possible to conflate the first three of these operations into a s
207
306
 
208
307
  The example above demonstrates the crux of the approach taken here, therefore. The application logic is to be found in easily readable, atomic form within the body of each operation. On the other hand the SQL statements are considered to be dumb in the sense that they do nothing but slavishly place or retrieve information into or from the database.
209
308
 
210
- This approach leads to less SQL and more JavaScript, however, as already mentioned but well worth repeating, that JavaScript is easily readable and atomic. The downside is a small amount of boilerplate JavaScript wrapping each operation, but this is a small price to pay.
211
-
212
309
  ### Making use of migrations
213
310
 
214
311
  The migration functionality will definitely not suit every use case, however it can provide surety for small applications running on multiple Node instances connecting to a single MariaDB instance. It is essential that the prescriptions below are followed pretty much to the letter. Failing to do so will doubtless result in failure.
215
312
 
216
- The `migrate()` function takes the usual `configuration` argument followed by `migrationsDirectoryPath` argument and a `callback` argument. The callback is invoked with the usual `error` argument, which is truthy if the migrations have succeeded and falsey otherwise.
313
+ The `migrate()` function takes the usual `configuration` argument followed by `migrationsDirectoryPath` argument and a `callback` argument. The callback is called with the usual `error` argument, which is truthy if the migrations have succeeded and falsey otherwise.
217
314
 
218
315
  ```
219
316
  const configuration = ... ,
package/bin/statement.js CHANGED
@@ -76,6 +76,10 @@ class Statement {
76
76
  this.query = query;
77
77
  }
78
78
 
79
+ setParameters(parameters) {
80
+ this.parameters = parameters;
81
+ }
82
+
79
83
  one(oneHandler) {
80
84
  this.oneHandler = oneHandler;
81
85
 
@@ -118,8 +122,21 @@ class Statement {
118
122
  return this;
119
123
  }
120
124
 
121
- set(object) {
122
- const assignments = this.assignmentsFromObject(object); ///
125
+ set(objectOrArray, ...parameters) {
126
+ let assignments;
127
+
128
+ const objectOrArrayIsArray = Array.isArray(objectOrArray);
129
+
130
+ if (objectOrArrayIsArray) {
131
+ const array = objectOrArray, ///
132
+ strings = array; ///
133
+
134
+ assignments = this.assignmentsFromStringsAndParameters(strings, parameters);
135
+ } else {
136
+ const object = objectOrArray; ///
137
+
138
+ assignments = this.assignmentsFromObject(object); ///
139
+ }
123
140
 
124
141
  this.sql = ` ${this.sql} SET ${assignments}`;
125
142
 
@@ -132,7 +149,7 @@ class Statement {
132
149
  const objectOrArrayIsArray = Array.isArray(objectOrArray);
133
150
 
134
151
  if (objectOrArrayIsArray) {
135
- const array = objectOrArray,
152
+ const array = objectOrArray, ///
136
153
  strings = array; ///
137
154
 
138
155
  clause = this.clauseFromStringsAndParameters(strings, parameters);
@@ -150,20 +167,21 @@ class Statement {
150
167
  values(objectOrArray, ...parameters) {
151
168
  const objectOrArrayIsArray = Array.isArray(objectOrArray);
152
169
 
170
+ let columnsAndValues;
171
+
153
172
  if (objectOrArrayIsArray) {
154
- const array = objectOrArray,
155
- strings = array, ///
156
- columnsAndValues = this.columnsAndValuesFromStringsAndParameters(strings, parameters);
173
+ const array = objectOrArray, ///
174
+ strings = array; ///
157
175
 
158
- this.sql = `${this.sql} ${columnsAndValues}`;
176
+ columnsAndValues = this.columnsAndValuesFromStringsAndParameters(strings, parameters);
159
177
  } else {
160
- const object = objectOrArray, ///
161
- values = this.valuesFromObject(object),
162
- columns = this.columnsFromObject(object)
178
+ const object = objectOrArray; ///
163
179
 
164
- this.sql = `${this.sql} (${columns}) VALUES (${values})`;
180
+ columnsAndValues = this.columnsAndValuesFromObject(object);
165
181
  }
166
182
 
183
+ this.sql = `${this.sql} ${columnsAndValues}`;
184
+
167
185
  return this;
168
186
  }
169
187
 
@@ -182,18 +200,19 @@ class Statement {
182
200
  return object;
183
201
  }
184
202
 
185
- valuesFromObject(object) {
186
- const columns = this.columnsFromObject(object),
203
+ clauseFromObject(object) {
204
+ const keys = Object.keys(object),
205
+ columns = keys.map((key) => this.columnFromKey(key)),
187
206
  parameters = Object.values(object), ///
188
207
  firstIndex = 0,
189
- values = columns.reduce((values, column, index) => {
208
+ clause = columns.reduce((clause, column, index) => {
190
209
  const placeholder = this.placeholder();
191
210
 
192
- values = (index === firstIndex) ?
193
- `${placeholder}` :
194
- ` ${values}, ${placeholder}`;
211
+ clause = (index === firstIndex) ?
212
+ `${column}=${placeholder}` :
213
+ ` ${clause} AND ${column}=${placeholder}`;
195
214
 
196
- return values;
215
+ return clause;
197
216
  }, EMPTY_STRING);
198
217
 
199
218
  this.parameters = [
@@ -201,21 +220,22 @@ class Statement {
201
220
  ...parameters
202
221
  ];
203
222
 
204
- return values;
223
+ return clause;
205
224
  }
206
225
 
207
- clauseFromObject(object) {
208
- const columns = this.columnsFromObject(object),
226
+ assignmentsFromObject(object) {
227
+ const keys = Object.keys(object),
228
+ columns = keys.map((key) => this.columnFromKey(key)),
209
229
  parameters = Object.values(object), ///
210
230
  firstIndex = 0,
211
- clause = columns.reduce((clause, column, index) => {
231
+ assignments = columns.reduce((assignments, column, index) => {
212
232
  const placeholder = this.placeholder();
213
233
 
214
- clause = (index === firstIndex) ?
215
- `${column}=${placeholder}` :
216
- ` ${clause} AND ${column}=${placeholder}`;
234
+ assignments = (index === firstIndex) ?
235
+ `${column}=${placeholder}` :
236
+ ` ${assignments}, ${column}=${placeholder}`;
217
237
 
218
- return clause;
238
+ return assignments;
219
239
  }, EMPTY_STRING);
220
240
 
221
241
  this.parameters = [
@@ -223,36 +243,31 @@ class Statement {
223
243
  ...parameters
224
244
  ];
225
245
 
226
- return clause;
246
+ return assignments;
227
247
  }
228
248
 
229
- columnsFromObject(object) {
249
+ columnsAndValuesFromObject(object) {
230
250
  const keys = Object.keys(object),
231
- columns = keys.map((key) => this.columnFromKey(key));
232
-
233
- return columns;
234
- }
235
-
236
- assignmentsFromObject(object) {
237
- const columns = this.columnsFromObject(object),
251
+ columns = keys.map((key) => this.columnFromKey(key)),
238
252
  parameters = Object.values(object), ///
239
253
  firstIndex = 0,
240
- assignments = columns.reduce((assignments, column, index) => {
254
+ values = columns.reduce((values, column, index) => {
241
255
  const placeholder = this.placeholder();
242
256
 
243
- assignments = (index === firstIndex) ?
244
- `${column}=${placeholder}` :
245
- ` ${assignments}, ${column}=${placeholder}`;
257
+ values = (index === firstIndex) ?
258
+ `${placeholder}` :
259
+ ` ${values}, ${placeholder}`;
246
260
 
247
- return assignments;
248
- }, EMPTY_STRING);
261
+ return values;
262
+ }, EMPTY_STRING),
263
+ columnsAndValues = `(${columns}) VALUES (${values})`;
249
264
 
250
265
  this.parameters = [
251
266
  ...this.parameters,
252
267
  ...parameters
253
268
  ];
254
269
 
255
- return assignments;
270
+ return columnsAndValues;
256
271
  }
257
272
 
258
273
  clauseFromStringsAndParameters(strings, parameters) {
@@ -272,12 +287,35 @@ class Statement {
272
287
 
273
288
  this.parameters = [
274
289
  ...this.parameters,
275
- ...parameters
290
+ ...parameters
276
291
  ];
277
292
 
278
293
  return clause;
279
294
  }
280
295
 
296
+ assignmentsFromStringsAndParameters(strings, parameters) {
297
+ const stringsLength = strings.length,
298
+ lastIndex = stringsLength - 1,
299
+ assignments = strings.reduce((assignments, string, index) => {
300
+ if (index < lastIndex) {
301
+ const placeholder = this.placeholder();
302
+
303
+ assignments = `${assignments}${string}${placeholder}`
304
+ } else {
305
+ assignments = `${assignments}${string}`;
306
+ }
307
+
308
+ return assignments;
309
+ }, EMPTY_STRING);
310
+
311
+ this.parameters = [
312
+ ...this.parameters,
313
+ ...parameters
314
+ ];
315
+
316
+ return assignments;
317
+ }
318
+
281
319
  columnsAndValuesFromStringsAndParameters(strings, parameters) {
282
320
  const stringsLength = strings.length,
283
321
  lastIndex = stringsLength - 1,
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "murmuration",
3
3
  "author": "James Smith",
4
- "version": "1.1.9",
4
+ "version": "2.0.1",
5
5
  "license": "MIT, Anti-996",
6
6
  "homepage": "https://github.com/djalbat/murmuration",
7
- "description": "Database connections, transactions and migrations.",
7
+ "description": "Database statements, transactions and migrations.",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/djalbat/murmuration"