murmuration 1.1.13 → 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 (2) hide show
  1. package/README.md +83 -56
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -37,9 +37,9 @@ Statements are covered first up, but ideally they should be executed in the cont
37
37
 
38
38
  ### Generating statements
39
39
 
40
- Statements are generated dynamically, in a similar vein to an ORM, in this case with a simple, promise-like syntax.
40
+ Statements are generated dynamically with a simple, promise-like syntax.
41
41
 
42
- In the first example, a `SELECT` statement is generated that checks an `account` table and returns an `id` should the email address and password match:
42
+ In the first example a row is selected should its email address and password match the given values:
43
43
 
44
44
  ```
45
45
  const using = require("../using");
@@ -69,21 +69,14 @@ function checkAccountOperation(connection, abort, proceed, complete, context) {
69
69
 
70
70
  There are several points worth noting:
71
71
 
72
- * As already mentioned, ideally statements should be executed within operations. Each operation provides a connection and a context as well as three callbacks.
73
- * The three callbacks 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
- * A local `using()` function has been employed rather the the function supplied by the package, because the `Statement` class it utilities has been extended for convenience. More on this later.
75
-
76
- The remainder of the points pertain to the statement itself. An exhaustive description of the various methods available is given at the end of this subsection.
77
-
78
- * The `selectFromAccount()` is defined in the application's own `Statement` class, again more on this later.
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.
79
74
  * The `where()` method takes a plain old JavaScript object.
80
75
  * The `one()` method takes a handler that is called if one row is returned.
81
76
  * The `else()` method takes a handler that is called in all other cases.
82
77
  * The `catch()` method takes a handler that is called should execution fail.
83
78
 
84
- Note that the assumption with passing a plain old JavaScript object in order to generate a `WHERE` clause is that the property values should be equated. Note also that the `abort()` function is provided directly to the `catch()` method.
85
-
86
- In the following example, rows in a table holding temporary reset codes are deleted once they expire.
79
+ In the following example rows in a table holding temporary reset codes are deleted once they expire.
87
80
 
88
81
  ```
89
82
  const using = require("../using");
@@ -111,7 +104,7 @@ The following points are worth noting:
111
104
  * The `where()` method takes a template literal this time.
112
105
  * The `success()` method takes a callback that is called should hte execution succeed.
113
106
 
114
- In the following example, a row is inserted into a table for software packages:
107
+ In this example a row is inserted into a table for software packages:
115
108
 
116
109
  ```
117
110
  const using = require("../using");
@@ -154,7 +147,7 @@ Note:
154
147
 
155
148
  * The `set()` method takes a plain old JavaScript object.
156
149
 
157
- Generally the idea is to be able to generate the most commonly used kinds of statements are deal with the outcomes of their execution with the minimum of fuss. If passing plain old JavaScript objects will not suffice then template literals can be used instead.
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.
158
151
 
159
152
  ### Statement class specification
160
153
 
@@ -162,22 +155,81 @@ The following specification gives a complete and more detailed list of the metho
162
155
 
163
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.
164
157
 
165
- It is recommended that these methods are not called directly, 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.
166
-
167
- If the `selectFrom()` method is called, then one of the following of the following methods must be called. Each takes a callback:
168
-
169
- * The `none()` method for when no rows are returned.
170
- * The `one()` method for when exactly one row is returned.
171
- * The `first()` method for when one or more rows are returned.
172
- * The `many()` method for when any number of rows are returned, including none.
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.
173
159
 
174
- In all but the last case, you must also call the `else()` method with a callback.
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.
175
166
 
176
- * The `success()` method must accompany the `insertInto()`, `deleteFrom()` or `delete()` methods.
177
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.
178
171
  * Either the `getSQL()` or `execute()` methods must be called, usually the latter.
179
172
 
180
- If
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:
178
+
179
+ ```
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";
188
+
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); }
193
+
194
+ insertIntoUser() { return this.insertInto(USER_TABLE); }
195
+ insertIntoRelease() { return this.insertInto(RELEASE_TABLE); }
196
+ insertIntoDistribution() { return this.insertInto(DISTRIBUTION_TABLE); }
197
+
198
+ deleteFromUser() { return this.deleteFrom(USER_TABLE); }
199
+ deleteFromRelease() { return this.deleteFrom(RELEASE_TABLE); }
200
+ deleteFromDistribution() { return this.deleteFrom(DISTRIBUTION_TABLE); }
201
+
202
+ selectFromUser() { return this.selectFrom(USER_TABLE); }
203
+ selectFromRelease() { return this.selectFrom(RELEASE_TABLE); }
204
+ selectFromDistribution() { return this.selectFrom(DISTRIBUTION_TABLE); }
205
+
206
+ selectFromLatestRelease() { return this.selectFrom(LATEST_RELEASE_VIEW); }
207
+
208
+ static fromConnection(connection) { return BaseStatement.fromConnection(Statement, connection); }
209
+ }
210
+
211
+ module.exports = Statement;
212
+ ```
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...
217
+
218
+ ```
219
+ "use strict";
220
+
221
+ const Statement = require("./statement");
222
+
223
+ function using(connection) {
224
+ const statement = Statement.fromConnection(connection);
225
+
226
+ return statement;
227
+ }
228
+
229
+ module.exports = using;
230
+ ```
231
+
232
+ ...or require and instantiate it directly. The `using()` function is purely for convenience.
181
233
 
182
234
  ### Getting and releasing connections
183
235
 
@@ -222,15 +274,14 @@ These messages are meant to help with debugging simple mistakes such as providin
222
274
 
223
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.
224
276
 
225
- 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:
226
278
 
227
279
  ```
228
280
  const configuration = ... ,
229
281
  operations = [
230
- checkUsernameAvailable,
231
- checkEmailAddressAvailable,
232
- addEmailAddressUsernamePasswordAndStatus,
233
- retrieveUserIdentifier,
282
+ checkUsernameOperation,
283
+ checkEmailAddressOperation,
284
+ addEmailOperation
234
285
  ],
235
286
  context = {
236
287
  emailAddress,
@@ -244,32 +295,10 @@ transaction(configuration, operations, (completed) => {
244
295
 
245
296
  }, context);
246
297
  ```
247
- The signature of the operations must be identical to the following example:
248
-
249
- ```
250
- function checkUsernameAvailable(connection, abort, proceed, complete, context) {
251
- const { username } = context;
252
-
253
- selectUsername(connection, username, (error, rows) => {
254
- if (error) {
255
- abort();
256
-
257
- return;
258
- }
259
-
260
- const rowsLength = rows.length;
261
-
262
- (rowsLength === 0) ?
263
- proceed() :
264
- complete();
265
- });
266
- }
267
- ```
268
- 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.
269
298
 
270
- 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:
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.
271
300
 
272
- * 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.
273
302
 
274
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.
275
304
 
@@ -277,8 +306,6 @@ It is entirely possible to conflate the first three of these operations into a s
277
306
 
278
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.
279
308
 
280
- 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.
281
-
282
309
  ### Making use of migrations
283
310
 
284
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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "murmuration",
3
3
  "author": "James Smith",
4
- "version": "1.1.13",
4
+ "version": "2.0.1",
5
5
  "license": "MIT, Anti-996",
6
6
  "homepage": "https://github.com/djalbat/murmuration",
7
7
  "description": "Database statements, transactions and migrations.",