@chevre/domain 21.1.0-alpha.0 → 21.1.0-alpha.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 (27) hide show
  1. package/example/src/chevre/aggregateAllEvents2.ts +10 -7
  2. package/example/src/chevre/aggregateEventReservation.ts +7 -4
  3. package/example/src/chevre/processPay.ts +7 -4
  4. package/example/src/chevre/processRegisterMembership.ts +0 -5
  5. package/example/src/chevre/processRegisterPaymentCard.ts +0 -5
  6. package/example/src/chevre/processReserve.ts +7 -4
  7. package/example/src/chevre/redisConfig.ts +24 -0
  8. package/example/src/chevre/searchEventTicketOffers.ts +9 -6
  9. package/example/src/chevre/transaction/callOrderMembershipServiceTask.ts +7 -4
  10. package/example/src/chevre/transaction/orderMembershipService.ts +7 -4
  11. package/lib/chevre/repo/action/registerServiceInProgress.d.ts +2 -2
  12. package/lib/chevre/repo/action/registerServiceInProgress.js +19 -53
  13. package/lib/chevre/repo/confirmationNumber.d.ts +2 -2
  14. package/lib/chevre/repo/confirmationNumber.js +40 -53
  15. package/lib/chevre/repo/itemAvailability/screeningEvent.d.ts +3 -3
  16. package/lib/chevre/repo/itemAvailability/screeningEvent.js +53 -138
  17. package/lib/chevre/repo/orderNumber.d.ts +2 -2
  18. package/lib/chevre/repo/orderNumber.js +44 -55
  19. package/lib/chevre/repo/rateLimit/offer.d.ts +2 -2
  20. package/lib/chevre/repo/rateLimit/offer.js +6 -45
  21. package/lib/chevre/repo/serviceOutputIdentifier.d.ts +2 -2
  22. package/lib/chevre/repo/serviceOutputIdentifier.js +32 -46
  23. package/lib/chevre/repo/transactionNumber.d.ts +2 -2
  24. package/lib/chevre/repo/transactionNumber.js +41 -52
  25. package/lib/chevre/service/task.d.ts +4 -4
  26. package/package.json +3 -6
  27. package/example/src/chevre/copyRedisKeys.ts +0 -102
@@ -36,34 +36,15 @@ class RedisRepository {
36
36
  lockIfNotLimitExceeded(lockKey, maximum) {
37
37
  return __awaiter(this, void 0, void 0, function* () {
38
38
  const key = `${RedisRepository.KEY_PREFIX}:${lockKey.eventId}`;
39
- yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
40
- this.redisClient.watch(key, (watchError) => {
41
- if (watchError !== null) {
42
- reject(watchError);
43
- return;
44
- }
45
- this.redisClient.hlen(key, (hlenError, hashCount) => __awaiter(this, void 0, void 0, function* () {
46
- if (hlenError !== null) {
47
- reject(hlenError);
48
- return;
49
- }
50
- // Process result
51
- // Heavy and time consuming operation here
52
- debug('checking hash count...hashCount:', hashCount);
53
- if (hashCount + lockKey.offers.length > maximum) {
54
- reject(new factory.errors.Argument('Event', 'maximumAttendeeCapacity exceeded'));
55
- return;
56
- }
57
- try {
58
- yield this.lock(lockKey);
59
- resolve();
60
- }
61
- catch (error) {
62
- reject(error);
63
- }
64
- }));
65
- });
66
- }));
39
+ yield this.redisClient.watch(key);
40
+ const hashCount = yield this.redisClient.hLen(key);
41
+ // Process result
42
+ // Heavy and time consuming operation here
43
+ debug('checking hash count...hashCount:', hashCount);
44
+ if (hashCount + lockKey.offers.length > maximum) {
45
+ throw new factory.errors.Argument('Event', 'maximumAttendeeCapacity exceeded');
46
+ }
47
+ yield this.lock(lockKey);
67
48
  });
68
49
  }
69
50
  /**
@@ -77,21 +58,12 @@ class RedisRepository {
77
58
  const multi = this.redisClient.multi();
78
59
  const fields = lockKey.offers.map((offer) => RedisRepository.OFFER2FIELD(offer));
79
60
  fields.forEach((field) => {
80
- multi.hsetnx(key, field, value);
81
- });
82
- const results = yield new Promise((resolve, reject) => {
83
- multi.expireat(key, moment(lockKey.expires)
84
- .unix())
85
- .exec((err, reply) => {
86
- debug('reply:', reply);
87
- if (err !== null) {
88
- reject(err);
89
- }
90
- else {
91
- resolve(reply);
92
- }
93
- });
61
+ multi.hSetNX(key, field, value);
94
62
  });
63
+ const results = yield multi.expireAt(key, moment(lockKey.expires)
64
+ .unix())
65
+ .exec();
66
+ debug('results:', results);
95
67
  const lockedFields = [];
96
68
  if (Array.isArray(results)) {
97
69
  results.slice(0, fields.length)
@@ -107,19 +79,9 @@ class RedisRepository {
107
79
  if (!lockedAll) {
108
80
  if (lockedFields.length > 0) {
109
81
  // 全て仮押さえできなければ仮押さえできたものは解除
110
- yield new Promise((resolve, reject) => {
111
- this.redisClient.multi()
112
- .hdel(key, lockedFields)
113
- .exec((err, reply) => {
114
- debug('reply:', reply);
115
- if (err !== null) {
116
- reject(err);
117
- }
118
- else {
119
- resolve();
120
- }
121
- });
122
- });
82
+ yield this.redisClient.multi()
83
+ .hDel(key, lockedFields)
84
+ .exec();
123
85
  }
124
86
  throw new factory.errors.AlreadyInUse('', [], 'Seat number already hold');
125
87
  }
@@ -132,19 +94,10 @@ class RedisRepository {
132
94
  return __awaiter(this, void 0, void 0, function* () {
133
95
  const key = `${RedisRepository.KEY_PREFIX}:${params.eventId}`;
134
96
  const field = RedisRepository.OFFER2FIELD(params.offer);
135
- yield new Promise((resolve, reject) => {
136
- this.redisClient.multi()
137
- .hdel(key, field)
138
- .exec((err, reply) => {
139
- debug('reply:', reply);
140
- if (err !== null) {
141
- reject(err);
142
- }
143
- else {
144
- resolve();
145
- }
146
- });
147
- });
97
+ const reply = yield this.redisClient.multi()
98
+ .hDel(key, field)
99
+ .exec();
100
+ debug('reply:', reply);
148
101
  });
149
102
  }
150
103
  /**
@@ -153,26 +106,17 @@ class RedisRepository {
153
106
  findUnavailableOffersByEventId(params) {
154
107
  return __awaiter(this, void 0, void 0, function* () {
155
108
  const key = `${RedisRepository.KEY_PREFIX}:${params.eventId}`;
156
- return new Promise((resolve, reject) => {
157
- this.redisClient.hgetall(key, (err, reply) => {
158
- debug('reply:', reply);
159
- if (err !== null) {
160
- reject(err);
161
- }
162
- else {
163
- let offers = [];
164
- if (reply !== null) {
165
- offers = Object.keys(reply)
166
- .map((field) => {
167
- const seatSection = field.split(':')[0];
168
- const seatNumber = field.split(':')[1];
169
- return { seatSection, seatNumber };
170
- });
171
- }
172
- resolve(offers);
173
- }
109
+ const reply = yield this.redisClient.hGetAll(key);
110
+ let offers = [];
111
+ if (reply !== null) {
112
+ offers = Object.keys(reply)
113
+ .map((field) => {
114
+ const seatSection = field.split(':')[0];
115
+ const seatNumber = field.split(':')[1];
116
+ return { seatSection, seatNumber };
174
117
  });
175
- });
118
+ }
119
+ return offers;
176
120
  });
177
121
  }
178
122
  /**
@@ -181,20 +125,12 @@ class RedisRepository {
181
125
  countUnavailableOffers(params) {
182
126
  return __awaiter(this, void 0, void 0, function* () {
183
127
  const key = `${RedisRepository.KEY_PREFIX}:${params.event.id}`;
184
- return new Promise((resolve, reject) => {
185
- this.redisClient.hlen(key, (err, reply) => {
186
- if (err !== null) {
187
- reject(err);
188
- }
189
- else {
190
- let fieldCount = 0;
191
- if (typeof reply === 'number') {
192
- fieldCount = Number(reply);
193
- }
194
- resolve(fieldCount);
195
- }
196
- });
197
- });
128
+ const reply = yield this.redisClient.hLen(key);
129
+ let fieldCount = 0;
130
+ if (typeof reply === 'number') {
131
+ fieldCount = Number(reply);
132
+ }
133
+ return fieldCount;
198
134
  });
199
135
  }
200
136
  /**
@@ -202,20 +138,9 @@ class RedisRepository {
202
138
  */
203
139
  getHolder(params) {
204
140
  return __awaiter(this, void 0, void 0, function* () {
205
- return new Promise((resolve, reject) => {
206
- const key = `${RedisRepository.KEY_PREFIX}:${params.eventId}`;
207
- const field = RedisRepository.OFFER2FIELD(params.offer);
208
- this.redisClient.hget(key, field, (err, result) => {
209
- debug('result:', err, result);
210
- if (err !== null) {
211
- reject(err);
212
- }
213
- else {
214
- // tslint:disable-next-line:no-magic-numbers
215
- resolve(result);
216
- }
217
- });
218
- });
141
+ const key = `${RedisRepository.KEY_PREFIX}:${params.eventId}`;
142
+ const field = RedisRepository.OFFER2FIELD(params.offer);
143
+ return this.redisClient.hGet(key, field);
219
144
  });
220
145
  }
221
146
  /**
@@ -224,29 +149,19 @@ class RedisRepository {
224
149
  */
225
150
  searchAvailability(params) {
226
151
  return __awaiter(this, void 0, void 0, function* () {
227
- return new Promise((resolve, reject) => {
228
- const key = `${RedisRepository.KEY_PREFIX}:${params.eventId}`;
229
- const fields = params.offers.map((o) => {
230
- return RedisRepository.OFFER2FIELD(o);
231
- });
232
- this.redisClient.hmget(key, fields, (err, result) => {
233
- if (err !== null) {
234
- reject(err);
235
- }
236
- else {
237
- if (!Array.isArray(result)) {
238
- reject(new factory.errors.ServiceUnavailable(`searchAvailability got non-array: ${typeof result}`));
239
- return;
240
- }
241
- // tslint:disable-next-line:no-magic-numbers
242
- resolve(params.offers.map((o, index) => {
243
- const value4offer = result[index];
244
- return Object.assign(Object.assign({}, o), { availability: (typeof value4offer === 'string')
245
- ? factory.itemAvailability.OutOfStock
246
- : factory.itemAvailability.InStock });
247
- }));
248
- }
249
- });
152
+ const key = `${RedisRepository.KEY_PREFIX}:${params.eventId}`;
153
+ const fields = params.offers.map((o) => {
154
+ return RedisRepository.OFFER2FIELD(o);
155
+ });
156
+ const result = yield this.redisClient.hmGet(key, fields);
157
+ if (!Array.isArray(result)) {
158
+ throw new factory.errors.ServiceUnavailable(`searchAvailability got non-array: ${typeof result}`);
159
+ }
160
+ return params.offers.map((o, index) => {
161
+ const value4offer = result[index];
162
+ return Object.assign(Object.assign({}, o), { availability: (typeof value4offer === 'string')
163
+ ? factory.itemAvailability.OutOfStock
164
+ : factory.itemAvailability.InStock });
250
165
  });
251
166
  });
252
167
  }
@@ -1,11 +1,11 @@
1
- import { RedisClient } from 'redis';
1
+ import { RedisClientType } from 'redis';
2
2
  /**
3
3
  * 注文番号リポジトリ
4
4
  */
5
5
  export declare class RedisRepository {
6
6
  static REDIS_KEY_PREFIX: string;
7
7
  private readonly redisClient;
8
- constructor(redisClient: RedisClient);
8
+ constructor(redisClient: RedisClientType);
9
9
  /**
10
10
  * タイムスタンプから発行する
11
11
  */
@@ -28,61 +28,50 @@ class RedisRepository {
28
28
  */
29
29
  publishByTimestamp(params) {
30
30
  return __awaiter(this, void 0, void 0, function* () {
31
- return new Promise((resolve, reject) => {
32
- const projectPrefix = params.project.alternateName
33
- .toUpperCase();
34
- const timestamp = moment(params.orderDate)
35
- .valueOf()
36
- .toString();
37
- const now = moment();
38
- const TTL = moment(params.orderDate)
39
- .add(1, 'minute') // ミリ秒でカウントしていくので、注文日時後1分で十分
40
- .diff(now, 'seconds');
41
- const key = util.format('%s:%s:%s', RedisRepository.REDIS_KEY_PREFIX, projectPrefix, timestamp);
42
- this.redisClient.multi()
43
- .incr(key)
44
- .expire(key, TTL)
45
- .exec((err, results) => {
46
- // tslint:disable-next-line:no-single-line-block-comment
47
- /* istanbul ignore if: please write tests */
48
- if (err instanceof Error) {
49
- reject(err);
50
- }
51
- else {
52
- if (!Array.isArray(results)) {
53
- // なぜかresults: nullのことがあるのでハンドリング
54
- reject(new factory.errors.ServiceUnavailable('incr orderNumber result not array'));
55
- return;
56
- }
57
- // tslint:disable-next-line:no-single-line-block-comment
58
- /* istanbul ignore else: please write tests */
59
- if (typeof results[0] === 'number') {
60
- let orderNumber = timestamp;
61
- const no = results[0];
62
- // orderNumber = `${orderNumber}${(`${no}`).slice(-1)}`; // ミリ秒あたり10件以内の注文想定
63
- orderNumber = `${orderNumber}${no}`;
64
- // checkdigit
65
- const cd = cdigit.luhn.compute(orderNumber);
66
- const cipher = fpe({ password: cd });
67
- orderNumber = cipher.encrypt(orderNumber);
68
- orderNumber = `${projectPrefix}${cd}${orderNumber}`;
69
- orderNumber = `${[
70
- // tslint:disable-next-line:no-magic-numbers
71
- orderNumber.slice(0, 4),
72
- // tslint:disable-next-line:no-magic-numbers
73
- orderNumber.slice(4, 11),
74
- // tslint:disable-next-line:no-magic-numbers
75
- orderNumber.slice(11)
76
- ].join('-')}`;
77
- resolve(orderNumber);
78
- }
79
- else {
80
- // 基本的にありえないフロー
81
- reject(new factory.errors.ServiceUnavailable('Order number not published'));
82
- }
83
- }
84
- });
85
- });
31
+ const projectPrefix = params.project.alternateName
32
+ .toUpperCase();
33
+ const timestamp = moment(params.orderDate)
34
+ .valueOf()
35
+ .toString();
36
+ const now = moment();
37
+ const TTL = moment(params.orderDate)
38
+ .add(1, 'minute') // ミリ秒でカウントしていくので、注文日時後1分で十分
39
+ .diff(now, 'seconds');
40
+ const key = util.format('%s:%s:%s', RedisRepository.REDIS_KEY_PREFIX, projectPrefix, timestamp);
41
+ const results = yield this.redisClient.multi()
42
+ .incr(key)
43
+ .expire(key, TTL)
44
+ .exec();
45
+ if (!Array.isArray(results)) {
46
+ // なぜかresults: nullのことがあるのでハンドリング
47
+ throw new factory.errors.ServiceUnavailable('incr orderNumber result not array');
48
+ }
49
+ // tslint:disable-next-line:no-single-line-block-comment
50
+ /* istanbul ignore else: please write tests */
51
+ if (typeof results[0] === 'number') {
52
+ let orderNumber = timestamp;
53
+ const no = results[0];
54
+ // orderNumber = `${orderNumber}${(`${no}`).slice(-1)}`; // ミリ秒あたり10件以内の注文想定
55
+ orderNumber = `${orderNumber}${no}`;
56
+ // checkdigit
57
+ const cd = cdigit.luhn.compute(orderNumber);
58
+ const cipher = fpe({ password: cd });
59
+ orderNumber = cipher.encrypt(orderNumber);
60
+ orderNumber = `${projectPrefix}${cd}${orderNumber}`;
61
+ orderNumber = `${[
62
+ // tslint:disable-next-line:no-magic-numbers
63
+ orderNumber.slice(0, 4),
64
+ // tslint:disable-next-line:no-magic-numbers
65
+ orderNumber.slice(4, 11),
66
+ // tslint:disable-next-line:no-magic-numbers
67
+ orderNumber.slice(11)
68
+ ].join('-')}`;
69
+ return orderNumber;
70
+ }
71
+ else {
72
+ // 基本的にありえないフロー
73
+ throw new factory.errors.ServiceUnavailable('Order number not published');
74
+ }
86
75
  });
87
76
  }
88
77
  }
@@ -1,4 +1,4 @@
1
- import * as redis from 'redis';
1
+ import { RedisClientType } from 'redis';
2
2
  import * as factory from '../../factory';
3
3
  /**
4
4
  * レート制限キーインターフェース
@@ -20,7 +20,7 @@ export interface IRateLimitKey {
20
20
  export declare class RedisRepository {
21
21
  static KEY_PREFIX: string;
22
22
  private readonly redisClient;
23
- constructor(redisClient: redis.RedisClient);
23
+ constructor(redisClient: RedisClientType);
24
24
  static CREATE_REDIS_KEY(ratelimitKey: IRateLimitKey): string;
25
25
  /**
26
26
  * ロックする
@@ -44,20 +44,10 @@ class RedisRepository {
44
44
  let multi = this.redisClient.multi();
45
45
  datasets.forEach((dataset) => {
46
46
  debug('setting if not exist...', dataset.key);
47
- multi.setnx(dataset.key, dataset.value)
47
+ multi.setNX(dataset.key, dataset.value)
48
48
  .expire(dataset.key, dataset.ttl);
49
49
  });
50
- const results = yield new Promise((resolve, reject) => {
51
- multi.exec((err, reply) => {
52
- debug('reply:', reply);
53
- if (err !== null) {
54
- reject(err);
55
- }
56
- else {
57
- resolve(reply);
58
- }
59
- });
60
- });
50
+ const results = yield multi.exec();
61
51
  const lockedFields = [];
62
52
  results.forEach((r, index) => {
63
53
  debug('r, index:', r, index);
@@ -78,17 +68,7 @@ class RedisRepository {
78
68
  debug('deleting...', key);
79
69
  multi.del(key);
80
70
  });
81
- yield new Promise((resolve, reject) => {
82
- multi.exec((err, reply) => {
83
- debug('reply:', reply);
84
- if (err !== null) {
85
- reject(err);
86
- }
87
- else {
88
- resolve();
89
- }
90
- });
91
- });
71
+ yield multi.exec();
92
72
  }
93
73
  throw new factory.errors.RateLimitExceeded('Offer');
94
74
  }
@@ -102,32 +82,13 @@ class RedisRepository {
102
82
  debug('deleting...', key);
103
83
  multi.del(key);
104
84
  });
105
- yield new Promise((resolve, reject) => {
106
- multi.exec((err, reply) => {
107
- debug('reply:', reply);
108
- if (err !== null) {
109
- reject(err);
110
- }
111
- else {
112
- resolve();
113
- }
114
- });
115
- });
85
+ yield multi.exec();
116
86
  });
117
87
  }
118
88
  getHolder(ratelimitKey) {
119
89
  return __awaiter(this, void 0, void 0, function* () {
120
- return new Promise((resolve, reject) => {
121
- const key = RedisRepository.CREATE_REDIS_KEY(ratelimitKey);
122
- this.redisClient.get(key, (err, result) => {
123
- if (err !== null) {
124
- reject(err);
125
- }
126
- else {
127
- resolve(result);
128
- }
129
- });
130
- });
90
+ const key = RedisRepository.CREATE_REDIS_KEY(ratelimitKey);
91
+ return this.redisClient.get(key);
131
92
  });
132
93
  }
133
94
  }
@@ -1,11 +1,11 @@
1
- import * as redis from 'redis';
1
+ import { RedisClientType } from 'redis';
2
2
  /**
3
3
  * サービスアウトプット識別子リポジトリ
4
4
  */
5
5
  export declare class RedisRepository {
6
6
  static REDIS_KEY_PREFIX: string;
7
7
  private readonly redisClient;
8
- constructor(redisClient: redis.RedisClient);
8
+ constructor(redisClient: RedisClientType);
9
9
  /**
10
10
  * タイムスタンプから発行する
11
11
  */
@@ -30,52 +30,38 @@ class RedisRepository {
30
30
  */
31
31
  publishByTimestamp(params) {
32
32
  return __awaiter(this, void 0, void 0, function* () {
33
- return new Promise((resolve, reject) => {
34
- // tslint:disable-next-line:no-magic-numbers
35
- // const projectPrefix = params.project.id.slice(0, 3)
36
- // .toUpperCase();
37
- const timestamp = moment(params.startDate)
38
- .valueOf()
39
- .toString();
40
- const now = moment();
41
- const TTL = moment(params.startDate)
42
- .add(1, 'minute') // ミリ秒でカウントしていくので、予約日時後1分で十分
43
- .diff(now, 'seconds');
44
- debug(`TTL:${TTL} seconds`);
45
- const key = util.format('%s:%s', RedisRepository.REDIS_KEY_PREFIX, timestamp);
46
- this.redisClient.multi()
47
- .incr(key, debug)
48
- .expire(key, TTL)
49
- .exec((err, results) => {
50
- debug('results:', results);
51
- // tslint:disable-next-line:no-single-line-block-comment
52
- /* istanbul ignore if: please write tests */
53
- if (err instanceof Error) {
54
- reject(err);
55
- }
56
- else {
57
- // tslint:disable-next-line:no-single-line-block-comment
58
- /* istanbul ignore else: please write tests */
59
- if (Array.isArray(results) && Number.isInteger(results[0])) {
60
- let identifier = timestamp;
61
- const no = results[0];
62
- debug('no incremented.', no);
63
- identifier = `${identifier}${no}`;
64
- // checkdigit
65
- const cd = cdigit.luhn.compute(identifier);
66
- const cipher = fpe({ password: cd });
67
- identifier = cipher.encrypt(identifier);
68
- debug('publishing serviceOutputIdentifier from', timestamp, no, cd);
69
- identifier = `${cd}${identifier}`;
70
- resolve(identifier);
71
- }
72
- else {
73
- // 基本的にありえないフロー
74
- reject(new factory.errors.ServiceUnavailable('ServiceOutput identifier not published'));
75
- }
76
- }
77
- });
78
- });
33
+ const timestamp = moment(params.startDate)
34
+ .valueOf()
35
+ .toString();
36
+ const now = moment();
37
+ const TTL = moment(params.startDate)
38
+ .add(1, 'minute') // ミリ秒でカウントしていくので、予約日時後1分で十分
39
+ .diff(now, 'seconds');
40
+ debug(`TTL:${TTL} seconds`);
41
+ const key = util.format('%s:%s', RedisRepository.REDIS_KEY_PREFIX, timestamp);
42
+ const results = yield this.redisClient.multi()
43
+ .incr(key)
44
+ .expire(key, TTL)
45
+ .exec();
46
+ // tslint:disable-next-line:no-single-line-block-comment
47
+ /* istanbul ignore else: please write tests */
48
+ if (Array.isArray(results) && typeof results[0] === 'number') {
49
+ let identifier = timestamp;
50
+ const no = results[0];
51
+ debug('no incremented.', no);
52
+ identifier = `${identifier}${no}`;
53
+ // checkdigit
54
+ const cd = cdigit.luhn.compute(identifier);
55
+ const cipher = fpe({ password: cd });
56
+ identifier = cipher.encrypt(identifier);
57
+ debug('publishing serviceOutputIdentifier from', timestamp, no, cd);
58
+ identifier = `${cd}${identifier}`;
59
+ return identifier;
60
+ }
61
+ else {
62
+ // 基本的にありえないフロー
63
+ throw new factory.errors.ServiceUnavailable('ServiceOutput identifier not published');
64
+ }
79
65
  });
80
66
  }
81
67
  }
@@ -1,4 +1,4 @@
1
- import * as redis from 'redis';
1
+ import { RedisClientType } from 'redis';
2
2
  interface IPublishResult {
3
3
  transactionNumber: string;
4
4
  }
@@ -8,7 +8,7 @@ interface IPublishResult {
8
8
  export declare class RedisRepository {
9
9
  static REDIS_KEY_PREFIX: string;
10
10
  private readonly redisClient;
11
- constructor(redisClient: redis.RedisClient);
11
+ constructor(redisClient: RedisClientType);
12
12
  /**
13
13
  * タイムスタンプから発行する
14
14
  */