murmuration 1.1.7 → 1.1.13
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.
- package/README.md +149 -79
- package/bin/statement.js +81 -43
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
# Murmuration
|
|
2
2
|
|
|
3
|
-
Database
|
|
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
|
|
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
|
|
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
|
-
|
|
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,120 +31,192 @@ Remember that it is the aforementioned specific packages that you should install
|
|
|
33
31
|
|
|
34
32
|
## Usage
|
|
35
33
|
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
+
### Generating statements
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
Connection.fromConfiguration(configuration, (error, connection) => {
|
|
40
|
+
Statements are generated dynamically, in a similar vein to an ORM, in this case with a simple, promise-like syntax.
|
|
44
41
|
|
|
45
|
-
|
|
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:
|
|
46
43
|
|
|
47
|
-
|
|
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
|
-
|
|
70
|
+
There are several points worth noting:
|
|
52
71
|
|
|
53
|
-
|
|
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.
|
|
54
75
|
|
|
55
|
-
|
|
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.
|
|
79
|
+
* The `where()` method takes a plain old JavaScript object.
|
|
80
|
+
* The `one()` method takes a handler that is called if one row is returned.
|
|
81
|
+
* The `else()` method takes a handler that is called in all other cases.
|
|
82
|
+
* The `catch()` method takes a handler that is called should execution fail.
|
|
83
|
+
|
|
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.
|
|
56
87
|
|
|
57
88
|
```
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
const using = require("../using");
|
|
90
|
+
|
|
91
|
+
const { unauthorized } = require("../utilities/states");
|
|
92
|
+
|
|
93
|
+
function deleteExpiredResetCodesOperation(connection, abort, proceed, complete, context) {
|
|
94
|
+
const { emailAddress, password } = context;
|
|
95
|
+
|
|
96
|
+
using(connection)
|
|
97
|
+
.deleteFromResetCode()
|
|
98
|
+
.where`
|
|
99
|
+
|
|
100
|
+
expires < NOW()
|
|
101
|
+
|
|
102
|
+
`
|
|
103
|
+
.success(proceed)
|
|
104
|
+
.catch(abort)
|
|
105
|
+
.execute();
|
|
106
|
+
}
|
|
66
107
|
```
|
|
67
|
-
Currently only the `error()`, `info()` and `debug()` functions are made use of, but in future others may be utilised.
|
|
68
108
|
|
|
69
|
-
|
|
109
|
+
The following points are worth noting:
|
|
70
110
|
|
|
71
|
-
|
|
111
|
+
* The `where()` method takes a template literal this time.
|
|
112
|
+
* The `success()` method takes a callback that is called should hte execution succeed.
|
|
72
113
|
|
|
73
|
-
In the
|
|
114
|
+
In the following example, a row is inserted into a table for software packages:
|
|
74
115
|
|
|
75
|
-
|
|
116
|
+
```
|
|
117
|
+
const using = require("../using");
|
|
76
118
|
|
|
77
|
-
|
|
119
|
+
function createReleaseOperation(connection, abort, proceed, complete, context) {
|
|
120
|
+
const { name, entriesBlob, versionNumber } = context;
|
|
121
|
+
|
|
122
|
+
using(connection)
|
|
123
|
+
.insertIntoRelease()
|
|
124
|
+
.values({ name, entriesBlob, versionNumber })
|
|
125
|
+
.success(proceed)
|
|
126
|
+
.catch(abort)
|
|
127
|
+
.execute();
|
|
128
|
+
}
|
|
129
|
+
```
|
|
78
130
|
|
|
79
|
-
|
|
131
|
+
Note the following:
|
|
132
|
+
|
|
133
|
+
* The `values()` method takes a plain old JavaScript object with the values to be inserted.
|
|
134
|
+
|
|
135
|
+
Finally, the following operation updates a user profile:
|
|
80
136
|
|
|
81
137
|
```
|
|
82
|
-
const
|
|
138
|
+
const using = require("../using");
|
|
139
|
+
|
|
140
|
+
function editProfileOperation(connection, abort, proceed, complete, context) {
|
|
141
|
+
const { name, notes, userIdentifier } = context,
|
|
142
|
+
identifier = userIdentifier; ///
|
|
143
|
+
|
|
144
|
+
using(connection)
|
|
145
|
+
.updateUser()
|
|
146
|
+
.set({ name, notes })
|
|
147
|
+
.where({ identifier })
|
|
148
|
+
.success(proceed)
|
|
149
|
+
.execute();
|
|
150
|
+
}
|
|
151
|
+
```
|
|
83
152
|
|
|
84
|
-
|
|
153
|
+
Note:
|
|
85
154
|
|
|
86
|
-
|
|
155
|
+
* The `set()` method takes a plain old JavaScript object.
|
|
87
156
|
|
|
88
|
-
|
|
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.
|
|
89
158
|
|
|
90
|
-
|
|
159
|
+
### Statement class specification
|
|
91
160
|
|
|
92
|
-
|
|
161
|
+
The following specification gives a complete and more detailed list of the methods available.
|
|
93
162
|
|
|
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.
|
|
163
|
+
* One of the `selectFrom()`, `insertInto()`, `deleteFrom()` or `delete()` methods must be called. Each takes a string for the name of a table or view.
|
|
97
164
|
|
|
98
|
-
|
|
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.
|
|
99
166
|
|
|
100
|
-
|
|
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
|
-
;
|
|
167
|
+
If the `selectFrom()` method is called, then one of the following of the following methods must be called. Each takes a callback:
|
|
107
168
|
|
|
108
|
-
|
|
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.
|
|
109
173
|
|
|
110
|
-
|
|
174
|
+
In all but the last case, you must also call the `else()` method with a callback.
|
|
111
175
|
|
|
112
|
-
|
|
176
|
+
* The `success()` method must accompany the `insertInto()`, `deleteFrom()` or `delete()` methods.
|
|
177
|
+
* The `catch()` method must always be called with a callback for when an exception occurs.
|
|
178
|
+
* Either the `getSQL()` or `execute()` methods must be called, usually the latter.
|
|
113
179
|
|
|
114
|
-
|
|
180
|
+
If
|
|
115
181
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
The
|
|
182
|
+
### Getting and releasing connections
|
|
183
|
+
|
|
184
|
+
The static `fromConfiguration()` method of the `Connection` class takes a configuration and a callback argument:
|
|
119
185
|
|
|
120
186
|
```
|
|
121
|
-
|
|
122
|
-
const filePath = `${SQL_DIRECTORY_PATH}/${SELECT_EMAIL_ADDRESS_FILE_NAME}`,
|
|
123
|
-
sql = sqlFromFilePath(filePath);
|
|
187
|
+
Connection.fromConfiguration(configuration, (error, connection) => {
|
|
124
188
|
|
|
125
|
-
|
|
126
|
-
if (error) {
|
|
127
|
-
log.error("selectEmailAddress() failed.");
|
|
128
|
-
}
|
|
189
|
+
...
|
|
129
190
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
191
|
+
connection.release();
|
|
192
|
+
};
|
|
193
|
+
```
|
|
133
194
|
|
|
134
|
-
|
|
135
|
-
const filePath = `${SQL_DIRECTORY_PATH}/${UPDATE_NAME_IDENTIFIER_FILE_NAME}`,
|
|
136
|
-
sql = sqlFromFilePath(filePath);
|
|
195
|
+
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.
|
|
137
196
|
|
|
138
|
-
|
|
139
|
-
if (error) {
|
|
140
|
-
log.error("updateNameIdentifier() failed.");
|
|
141
|
-
}
|
|
197
|
+
### Logging
|
|
142
198
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
199
|
+
Ideally you should add a `log` property to the `configuration` object that references an object of the following form:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
const log = {
|
|
203
|
+
trace: () => { ... },
|
|
204
|
+
debug: () => { ... },
|
|
205
|
+
info: () => { ... },
|
|
206
|
+
warning: () => { ... },
|
|
207
|
+
error: () => { ... },
|
|
208
|
+
fatal: () => { ... },
|
|
209
|
+
});
|
|
146
210
|
```
|
|
147
|
-
|
|
211
|
+
Currently only the `error()`, `info()` and `debug()` functions are made use of, but in future others may be utilised.
|
|
212
|
+
|
|
213
|
+
If you do not provide a `log` object, all logging is suppressed.
|
|
148
214
|
|
|
149
|
-
|
|
215
|
+
### Error handling
|
|
216
|
+
|
|
217
|
+
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.
|
|
218
|
+
|
|
219
|
+
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
220
|
|
|
151
221
|
### Using transactions
|
|
152
222
|
|
|
@@ -213,7 +283,7 @@ This approach leads to less SQL and more JavaScript, however, as already mention
|
|
|
213
283
|
|
|
214
284
|
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
285
|
|
|
216
|
-
The `migrate()` function takes the usual `configuration` argument followed by `migrationsDirectoryPath` argument and a `callback` argument. The callback is
|
|
286
|
+
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
287
|
|
|
218
288
|
```
|
|
219
289
|
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(
|
|
122
|
-
|
|
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
|
-
|
|
156
|
-
columnsAndValues = this.columnsAndValuesFromStringsAndParameters(strings, parameters);
|
|
173
|
+
const array = objectOrArray, ///
|
|
174
|
+
strings = array; ///
|
|
157
175
|
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
const
|
|
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
|
-
|
|
208
|
+
clause = columns.reduce((clause, column, index) => {
|
|
190
209
|
const placeholder = this.placeholder();
|
|
191
210
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
211
|
+
clause = (index === firstIndex) ?
|
|
212
|
+
`${column}=${placeholder}` :
|
|
213
|
+
` ${clause} AND ${column}=${placeholder}`;
|
|
195
214
|
|
|
196
|
-
return
|
|
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
|
|
223
|
+
return clause;
|
|
205
224
|
}
|
|
206
225
|
|
|
207
|
-
|
|
208
|
-
const
|
|
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
|
-
|
|
231
|
+
assignments = columns.reduce((assignments, column, index) => {
|
|
212
232
|
const placeholder = this.placeholder();
|
|
213
233
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
assignments = (index === firstIndex) ?
|
|
235
|
+
`${column}=${placeholder}` :
|
|
236
|
+
` ${assignments}, ${column}=${placeholder}`;
|
|
217
237
|
|
|
218
|
-
return
|
|
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
|
|
246
|
+
return assignments;
|
|
227
247
|
}
|
|
228
248
|
|
|
229
|
-
|
|
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
|
-
|
|
254
|
+
values = columns.reduce((values, column, index) => {
|
|
241
255
|
const placeholder = this.placeholder();
|
|
242
256
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
257
|
+
values = (index === firstIndex) ?
|
|
258
|
+
`${placeholder}` :
|
|
259
|
+
` ${values}, ${placeholder}`;
|
|
246
260
|
|
|
247
|
-
return
|
|
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
|
|
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
|
-
|
|
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.
|
|
4
|
+
"version": "1.1.13",
|
|
5
5
|
"license": "MIT, Anti-996",
|
|
6
6
|
"homepage": "https://github.com/djalbat/murmuration",
|
|
7
|
-
"description": "Database
|
|
7
|
+
"description": "Database statements, transactions and migrations.",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "https://github.com/djalbat/murmuration"
|