notification-processor 4.9.0 → 4.10.0
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/.github/PULL_REQUEST_TEMPLATE.md +2 -0
- package/copyToFunction +9 -0
- package/lib/exceptions/non.retryable.js +1 -1
- package/lib/index.js +1 -1
- package/lib/observers/deadLetterSucceeded.observer.js +1 -1
- package/lib/observers/delay.levels.js +1 -1
- package/lib/observers/delay.observer.js +1 -1
- package/lib/observers/didLastRetry.observer.js +1 -1
- package/lib/observers/incidentsApi.observer.js +1 -1
- package/lib/observers/logger.observer.js +1 -1
- package/lib/observers/redis.observer.js +1 -1
- package/lib/processor.builder.js +1 -1
- package/lib/processor.js +1 -1
- package/lib/processors/deadletter.processor.js +1 -1
- package/lib/processors/job/index.js +1 -1
- package/lib/processors/job/job.processor.js +4 -2
- package/lib/processors/job/notification.api.js +59 -23
- package/lib/processors/maxRetries.processor.js +1 -1
- package/lib/processors/request.async.processor.js +1 -1
- package/lib/processors/request.processor.js +1 -1
- package/lib/processors/requestWithRetries.async.processor.js +1 -1
- package/lib/senders/async.producteca.sender.js +1 -1
- package/lib/senders/index.js +1 -1
- package/lib/senders/meli.sender.js +1 -1
- package/lib/senders/producteca.sender.js +1 -1
- package/lib/services/oAuthApi.js +1 -1
- package/lib/services/redis.js +1 -1
- package/lib/sources/index.js +1 -1
- package/lib/sources/queue.source.js +1 -1
- package/lib/sources/service.bus.source.js +1 -1
- package/lib/sources/table.source.js +1 -1
- package/lib/sources/unknown.source.js +1 -1
- package/lib/specHelpers/fixture.js +1 -1
- package/lib/specHelpers/redis.observer.mock.js +1 -1
- package/notification-processor.sublime-workspace +1506 -0
- package/package.json +2 -1
package/copyToFunction
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
FUNCTION=$1
|
|
3
|
+
|
|
4
|
+
npm run build
|
|
5
|
+
echo "Removing old version from $FUNCTION"
|
|
6
|
+
rm -rf "../$FUNCTION/node_modules/notification-processor/lib/"
|
|
7
|
+
echo "Removed. Copying new version to $FUNCTION"
|
|
8
|
+
cp -R lib/ "../$FUNCTION/node_modules/notification-processor/lib/"
|
|
9
|
+
echo "Done"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
4
4
|
|
|
5
|
-
// Generated by CoffeeScript 2.
|
|
5
|
+
// Generated by CoffeeScript 2.7.0
|
|
6
6
|
(function () {
|
|
7
7
|
var NonRetryable;
|
|
8
8
|
|
package/lib/index.js
CHANGED
|
@@ -8,7 +8,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
|
|
|
8
8
|
|
|
9
9
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
10
10
|
|
|
11
|
-
// Generated by CoffeeScript 2.
|
|
11
|
+
// Generated by CoffeeScript 2.7.0
|
|
12
12
|
(function () {
|
|
13
13
|
var DeadLetterSucceededObserver,
|
|
14
14
|
Promise,
|
|
@@ -8,7 +8,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
|
|
|
8
8
|
|
|
9
9
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
10
10
|
|
|
11
|
-
// Generated by CoffeeScript 2.
|
|
11
|
+
// Generated by CoffeeScript 2.7.0
|
|
12
12
|
(function () {
|
|
13
13
|
var DelayObserver,
|
|
14
14
|
Promise,
|
|
@@ -8,7 +8,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
|
|
|
8
8
|
|
|
9
9
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
10
10
|
|
|
11
|
-
// Generated by CoffeeScript 2.
|
|
11
|
+
// Generated by CoffeeScript 2.7.0
|
|
12
12
|
(function () {
|
|
13
13
|
var DidLastRetry,
|
|
14
14
|
Promise,
|
|
@@ -4,7 +4,7 @@ var _createClass = function () { function defineProperties(target, props) { for
|
|
|
4
4
|
|
|
5
5
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
6
|
|
|
7
|
-
// Generated by CoffeeScript 2.
|
|
7
|
+
// Generated by CoffeeScript 2.7.0
|
|
8
8
|
(function () {
|
|
9
9
|
var IncidentsApiObserver, Promise, ServiceBusClient, _, debug, encode;
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ var _createClass = function () { function defineProperties(target, props) { for
|
|
|
4
4
|
|
|
5
5
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
6
|
|
|
7
|
-
// Generated by CoffeeScript 2.
|
|
7
|
+
// Generated by CoffeeScript 2.7.0
|
|
8
8
|
(function () {
|
|
9
9
|
var Promise, Redis, RedisObserver, _;
|
|
10
10
|
|
package/lib/processor.builder.js
CHANGED
|
@@ -4,7 +4,7 @@ var _createClass = function () { function defineProperties(target, props) { for
|
|
|
4
4
|
|
|
5
5
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
6
|
|
|
7
|
-
// Generated by CoffeeScript 2.
|
|
7
|
+
// Generated by CoffeeScript 2.7.0
|
|
8
8
|
(function () {
|
|
9
9
|
var MeliSender, Processor, ProcessorBuilder, ProductecaSender, QueueSource, ServiceBusSource, UnknownSource, _, logger;
|
|
10
10
|
|
package/lib/processor.js
CHANGED
|
@@ -8,7 +8,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
|
|
|
8
8
|
|
|
9
9
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
10
10
|
|
|
11
|
-
// Generated by CoffeeScript 2.
|
|
11
|
+
// Generated by CoffeeScript 2.7.0
|
|
12
12
|
(function () {
|
|
13
13
|
var ENABLE_EVENTS,
|
|
14
14
|
EventEmitter,
|
|
@@ -8,7 +8,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
|
|
|
8
8
|
|
|
9
9
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
10
10
|
|
|
11
|
-
// Generated by CoffeeScript 2.
|
|
11
|
+
// Generated by CoffeeScript 2.7.0
|
|
12
12
|
(function () {
|
|
13
13
|
var DeadletterProcessor, MaxRetriesProcessor, NonRetryable;
|
|
14
14
|
|
|
@@ -10,7 +10,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
|
|
|
10
10
|
|
|
11
11
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
12
12
|
|
|
13
|
-
// Generated by CoffeeScript 2.
|
|
13
|
+
// Generated by CoffeeScript 2.7.0
|
|
14
14
|
(function () {
|
|
15
15
|
var JobProcessor,
|
|
16
16
|
MaxRetriesProcessor,
|
|
@@ -54,6 +54,7 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function"
|
|
|
54
54
|
_this._notificationsApi = _this._notificationsApi.bind(_this);
|
|
55
55
|
_this._ifJobIsNotStopped = _this._ifJobIsNotStopped.bind(_this);
|
|
56
56
|
_this.notificationApiUrl = args.notificationApiUrl;
|
|
57
|
+
_this.notificationApiAsyncUrl = args.notificationApiAsyncUrl;
|
|
57
58
|
_this.nonRetryable = args.nonRetryable;
|
|
58
59
|
return _this;
|
|
59
60
|
}
|
|
@@ -125,7 +126,8 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function"
|
|
|
125
126
|
Key: "Authorization"
|
|
126
127
|
}).Value,
|
|
127
128
|
jobId: JobId,
|
|
128
|
-
notificationApiUrl: this.notificationApiUrl
|
|
129
|
+
notificationApiUrl: this.notificationApiUrl,
|
|
130
|
+
notificationApiAsyncUrl: this.notificationApiAsyncUrl
|
|
129
131
|
});
|
|
130
132
|
}
|
|
131
133
|
}, {
|
|
@@ -4,13 +4,13 @@ var _createClass = function () { function defineProperties(target, props) { for
|
|
|
4
4
|
|
|
5
5
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
6
|
|
|
7
|
-
// Generated by CoffeeScript 2.
|
|
7
|
+
// Generated by CoffeeScript 2.7.0
|
|
8
8
|
(function () {
|
|
9
|
-
var HOUR, NOTIFICATIONS_API_JOBS_CACHE_TTL, NOTIFICATIONS_API_STOPPED_JOB_CACHE_TTL, NodeCache, NotificationsApi, Promise, _, jobsCache,
|
|
9
|
+
var DEFAULT_NOTIFICATIONS_API_ASYNC_URL, HOUR, NOTIFICATIONS_API_JOBS_CACHE_TTL, NOTIFICATIONS_API_STOPPED_JOB_CACHE_TTL, NodeCache, NotificationsApi, Promise, _, jobsCache, requestPromise, retry, stoppedJobsCache;
|
|
10
10
|
|
|
11
11
|
_ = require("lodash");
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
requestPromise = require("request-promise");
|
|
14
14
|
|
|
15
15
|
retry = require("bluebird-retry");
|
|
16
16
|
|
|
@@ -22,6 +22,8 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
|
|
|
22
22
|
|
|
23
23
|
NOTIFICATIONS_API_STOPPED_JOB_CACHE_TTL = parseInt(process.env.NOTIFICATIONS_API_STOPPED_JOB_CACHE_TTL) || 2;
|
|
24
24
|
|
|
25
|
+
DEFAULT_NOTIFICATIONS_API_ASYNC_URL = process.env.DEFAULT_NOTIFICATIONS_API_ASYNC_URL || "https://apps.producteca.com/aws/notifications-api-async";
|
|
26
|
+
|
|
25
27
|
HOUR = 60 * 60;
|
|
26
28
|
|
|
27
29
|
//Para minimizar las requests a notifications-api, cachea unos segundos el estado del job
|
|
@@ -38,7 +40,9 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
|
|
|
38
40
|
function NotificationsApi(_ref) {
|
|
39
41
|
var notificationApiUrl = _ref.notificationApiUrl,
|
|
40
42
|
token = _ref.token,
|
|
41
|
-
jobId = _ref.jobId
|
|
43
|
+
jobId = _ref.jobId,
|
|
44
|
+
_ref$notificationApiA = _ref.notificationApiAsyncUrl,
|
|
45
|
+
notificationApiAsyncUrl = _ref$notificationApiA === undefined ? DEFAULT_NOTIFICATIONS_API_ASYNC_URL : _ref$notificationApiA;
|
|
42
46
|
|
|
43
47
|
_classCallCheck(this, NotificationsApi);
|
|
44
48
|
|
|
@@ -48,50 +52,61 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
|
|
|
48
52
|
this._jobIsStopped = this._jobIsStopped.bind(this);
|
|
49
53
|
this._fetchJob = this._fetchJob.bind(this);
|
|
50
54
|
this._doFetchJob = this._doFetchJob.bind(this);
|
|
55
|
+
this._retryViaAsyncOrIgnore = this._retryViaAsyncOrIgnore.bind(this);
|
|
51
56
|
this._makeRequest = this._makeRequest.bind(this);
|
|
52
57
|
this._shouldUseCachedValue = this._shouldUseCachedValue.bind(this);
|
|
53
58
|
this.notificationApiUrl = notificationApiUrl;
|
|
54
59
|
this.token = token;
|
|
55
60
|
this.jobId = jobId;
|
|
61
|
+
this.notificationApiAsyncUrl = notificationApiAsyncUrl;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
_createClass(NotificationsApi, [{
|
|
59
65
|
key: "success",
|
|
60
|
-
value: function success(
|
|
66
|
+
value: function success(response, options) {
|
|
61
67
|
var _this = this;
|
|
62
68
|
|
|
63
|
-
var
|
|
69
|
+
var __makeRequest, __retryRequest, statusCode;
|
|
70
|
+
statusCode = response.statusCode;
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
__makeRequest = function __makeRequest() {
|
|
66
73
|
return _this._makeRequest({
|
|
67
74
|
statusCode: statusCode,
|
|
68
75
|
success: true
|
|
76
|
+
}, options);
|
|
77
|
+
};
|
|
78
|
+
__retryRequest = function __retryRequest() {
|
|
79
|
+
return _this.success(response, {
|
|
80
|
+
useAsyncApi: true
|
|
69
81
|
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
}).catchReturn();
|
|
82
|
+
};
|
|
83
|
+
return this._retryViaAsyncOrIgnore(__makeRequest, __retryRequest, options);
|
|
73
84
|
}
|
|
74
85
|
}, {
|
|
75
86
|
key: "fail",
|
|
76
|
-
value: function fail(
|
|
87
|
+
value: function fail(response, options) {
|
|
77
88
|
var _this2 = this;
|
|
78
89
|
|
|
79
|
-
var
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
var __makeRequest, __retryRequest, error, message, request, statusCode;
|
|
91
|
+
statusCode = response.statusCode;
|
|
92
|
+
error = response.error;
|
|
93
|
+
request = response.request;
|
|
82
94
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
message = _.get(error, "message");
|
|
95
|
+
message = _.get(error, "message");
|
|
96
|
+
__makeRequest = function __makeRequest() {
|
|
86
97
|
return _this2._makeRequest({
|
|
87
98
|
statusCode: statusCode,
|
|
88
99
|
success: false,
|
|
89
100
|
message: message,
|
|
90
101
|
request: request
|
|
102
|
+
}, options);
|
|
103
|
+
};
|
|
104
|
+
__retryRequest = function __retryRequest() {
|
|
105
|
+
return _this2.fail(response, {
|
|
106
|
+
useAsyncApi: true
|
|
91
107
|
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
}).catchReturn();
|
|
108
|
+
};
|
|
109
|
+
return this._retryViaAsyncOrIgnore(__makeRequest, __retryRequest, options);
|
|
95
110
|
}
|
|
96
111
|
}, {
|
|
97
112
|
key: "jobIsStopped",
|
|
@@ -137,7 +152,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
|
|
|
137
152
|
|
|
138
153
|
var __fetchJob;
|
|
139
154
|
__fetchJob = function __fetchJob() {
|
|
140
|
-
return
|
|
155
|
+
return requestPromise({
|
|
141
156
|
url: _this5.notificationApiUrl + "/jobs/" + _this5.jobId,
|
|
142
157
|
method: "GET",
|
|
143
158
|
headers: {
|
|
@@ -150,11 +165,32 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
|
|
|
150
165
|
throw_original: true
|
|
151
166
|
});
|
|
152
167
|
}
|
|
168
|
+
}, {
|
|
169
|
+
key: "_retryViaAsyncOrIgnore",
|
|
170
|
+
value: function _retryViaAsyncOrIgnore(makeRequest, retryRequest) {
|
|
171
|
+
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
172
|
+
|
|
173
|
+
return retry(makeRequest, {
|
|
174
|
+
throw_original: true,
|
|
175
|
+
max_tries: 3
|
|
176
|
+
}).catch(function (e) {
|
|
177
|
+
if (options.useAsyncApi) {
|
|
178
|
+
throw e;
|
|
179
|
+
}
|
|
180
|
+
console.log("Error sending status to notifications-api. Retrying via notifications-api-async");
|
|
181
|
+
return retryRequest();
|
|
182
|
+
}).catchReturn();
|
|
183
|
+
}
|
|
153
184
|
}, {
|
|
154
185
|
key: "_makeRequest",
|
|
155
186
|
value: function _makeRequest(body) {
|
|
156
|
-
|
|
157
|
-
|
|
187
|
+
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
188
|
+
useAsyncApi = _ref2.useAsyncApi;
|
|
189
|
+
|
|
190
|
+
var url;
|
|
191
|
+
url = useAsyncApi ? this.notificationApiAsyncUrl : this.notificationApiUrl;
|
|
192
|
+
return requestPromise({
|
|
193
|
+
url: url + "/jobs/" + this.jobId + "/operations",
|
|
158
194
|
method: "POST",
|
|
159
195
|
headers: {
|
|
160
196
|
authorization: this.token
|
|
@@ -4,7 +4,7 @@ var _createClass = function () { function defineProperties(target, props) { for
|
|
|
4
4
|
|
|
5
5
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
6
|
|
|
7
|
-
// Generated by CoffeeScript 2.
|
|
7
|
+
// Generated by CoffeeScript 2.7.0
|
|
8
8
|
(function () {
|
|
9
9
|
var MaxRetriesProcessor;
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
// Generated by CoffeeScript 2.
|
|
3
|
+
// Generated by CoffeeScript 2.7.0
|
|
4
4
|
(function () {
|
|
5
5
|
var MESSAGE_PROPERTIES, NonRetryableError, Promise, RequestError, StatusCodeError, _, __isIncludedInStatusesError, _safeParse, _type, errorConditions, httpStatus, request;
|
|
6
6
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
|
|
4
4
|
|
|
5
|
-
// Generated by CoffeeScript 2.
|
|
5
|
+
// Generated by CoffeeScript 2.7.0
|
|
6
6
|
(function () {
|
|
7
7
|
var OAuthApi, Promise, _, _companyId;
|
|
8
8
|
|
package/lib/senders/index.js
CHANGED
package/lib/services/oAuthApi.js
CHANGED
|
@@ -4,7 +4,7 @@ var _createClass = function () { function defineProperties(target, props) { for
|
|
|
4
4
|
|
|
5
5
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
6
|
|
|
7
|
-
// Generated by CoffeeScript 2.
|
|
7
|
+
// Generated by CoffeeScript 2.7.0
|
|
8
8
|
(function () {
|
|
9
9
|
var AuthApi, OAUTH_API_URL, request;
|
|
10
10
|
|
package/lib/services/redis.js
CHANGED
package/lib/sources/index.js
CHANGED
|
@@ -4,7 +4,7 @@ var _createClass = function () { function defineProperties(target, props) { for
|
|
|
4
4
|
|
|
5
5
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
6
|
|
|
7
|
-
// Generated by CoffeeScript 2.
|
|
7
|
+
// Generated by CoffeeScript 2.7.0
|
|
8
8
|
(function () {
|
|
9
9
|
var MockRedis, MockRedisClient, Promise, _, proxyquire, sinon, stub;
|
|
10
10
|
|