@pi-r/aws 0.6.6 → 0.7.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/README.md CHANGED
@@ -1,10 +1,7 @@
1
- # @pi-r/aws
1
+ ### @pi-r/aws
2
2
 
3
- ## Documentation
3
+ https://e-mc.readthedocs.io/en/latest/cloud/aws.html
4
4
 
5
- - [E-mc](https://e-mc.readthedocs.io/en/latest/cloud/aws.html)
6
- - [squared](https://squared.readthedocs.io)
7
-
8
- ## LICENSE
5
+ ### LICENSE
9
6
 
10
7
  MIT
package/client/index.d.ts CHANGED
@@ -2,23 +2,14 @@ import type { ICloud, IModule } from '@e-mc/types/lib';
2
2
  import type { BucketWebsiteOptions, CloudDatabase } from '@e-mc/types/lib/cloud';
3
3
  import type { BatchQueryResult, QueryResult } from '@e-mc/types/lib/db';
4
4
 
5
- import type { AWSDatabaseCredential, AWSDatabaseQuery, AWSStorageCredential, BucketCannedACL, ConfigureBucketOptions } from '../types';
5
+ import type { BucketCannedACL } from '@pi-r/aws-lib';
6
+
7
+ import type { AWSDatabaseCredential, AWSDatabaseQuery, AWSStorageCredential, ConfigureBucketOptions } from '../types';
6
8
 
7
- import type { ConfigurationOptions } from 'aws-sdk/lib/core';
8
- import type { ServiceConfigurationOptions } from 'aws-sdk/lib/service';
9
9
  import type { AttributeValue, DocumentClient } from 'aws-sdk/clients/dynamodb';
10
- import type { DynamoDBClientConfig } from '@aws-sdk/client-dynamodb';
11
- import type { CreateBucketRequest, DefaultRetention } from 'aws-sdk/clients/s3';
10
+ import type { CreateBucketRequest } from 'aws-sdk/clients/s3';
12
11
 
13
12
  declare namespace AWS {
14
- function isAccessDefined(credential: Pick<ConfigurationOptions, "accessKeyId" | "secretAccessKey" | "sessionToken">): boolean;
15
- function isEnvDefined(): boolean;
16
- function isSharedCredentialsDefined(): boolean;
17
- function isProviderChainDefined(): boolean;
18
- function isDatabaseDefined(credential: AWSDatabaseCredential, data: CloudDatabase): boolean;
19
- function getPublicReadPolicy(bucket: string, authenticated?: boolean, write?: boolean): string;
20
- function getBucketPublicReadPolicy(bucket: string): string;
21
- function getPrivatePolicy(bucket: string): string;
22
13
  function validateStorage(credential: AWSStorageCredential): boolean;
23
14
  function validateDatabase(credential: AWSDatabaseCredential, data: CloudDatabase): boolean;
24
15
  function createStorageClient(this: IModule, credential: AWSStorageCredential, service?: string, sdk?: string): boolean;
@@ -31,11 +22,7 @@ declare namespace AWS {
31
22
  function deleteObjectsV2(this: IModule, credential: AWSStorageCredential, Bucket: string, recursive?: boolean, service?: string, sdk?: string): Promise<void>;
32
23
  function executeQuery(this: ICloud, credential: AWSDatabaseCredential, data: AWSDatabaseQuery, sessionKey?: string): Promise<QueryResult>;
33
24
  function executeBatchQuery(this: ICloud, credential: AWSDatabaseCredential, batch: AWSDatabaseQuery[], sessionKey?: string): Promise<BatchQueryResult>;
34
- function setDatabaseEndpoint(config: ServiceConfigurationOptions | DynamoDBClientConfig): void;
35
- function checkBucketCannedACL(value: unknown): BucketCannedACL | undefined;
36
- function writeMessageDefaultRetention(this: IModule, bucket: string, retention: DefaultRetention, service?: string): void;
37
25
  function parseAttributeValue(value: unknown): AttributeValue;
38
- function getBucketKey(credential: unknown, Bucket: string, acl: string | undefined, service: string, sdk: string): string;
39
26
  function isNoSuchBucket(err: unknown): boolean;
40
27
  }
41
28
 
package/client/index.js CHANGED
@@ -1,18 +1,18 @@
1
1
  "use strict";
2
- exports.isNoSuchBucket = exports.getBucketKey = exports.parseAttributeValue = exports.writeMessageDefaultRetention = exports.checkBucketCannedACL = exports.setDatabaseEndpoint = exports.executeBatchQuery = exports.executeQuery = exports.deleteObjectsV2 = exports.deleteObjects = exports.setBucketWebsite = exports.setBucketPolicy = exports.createBucketV2 = exports.createBucket = exports.createDatabaseClient = exports.createStorageClient = exports.validateDatabase = exports.validateStorage = exports.getPrivatePolicy = exports.getBucketPublicReadPolicy = exports.getPublicReadPolicy = exports.isDatabaseDefined = exports.isProviderChainDefined = exports.isEnvDefined = exports.isSharedCredentialsDefined = exports.isAccessDefined = void 0;
2
+ exports.CLOUD_UPLOAD_CHUNK = exports.CLOUD_UPLOAD_STREAM = exports.isNoSuchBucket = exports.parseAttributeValue = exports.executeBatchQuery = exports.executeQuery = exports.deleteObjectsV2 = exports.deleteObjects = exports.setBucketWebsite = exports.setBucketTagging = exports.setBucketPolicy = exports.createBucketV2 = exports.createBucket = exports.createDatabaseClient = exports.createStorageClient = exports.validateDatabase = exports.validateStorage = void 0;
3
3
  const aws = require("aws-sdk");
4
- const types_1 = require("@e-mc/types");
5
- const util_1 = require("@e-mc/cloud/util");
6
- const Module = require("@e-mc/module");
7
4
  const Cloud = require("@e-mc/cloud");
5
+ const util_1 = require("@e-mc/cloud/util");
6
+ const types_1 = require("@e-mc/types");
7
+ const aws_lib_1 = require("@pi-r/aws-lib");
8
8
  const ACP_AUTHENTICATEDREAD = {
9
9
  Grants: [{
10
10
  Grantee: { Type: 'Group', URI: 'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' },
11
11
  Permission: 'READ'
12
12
  }]
13
13
  };
14
- async function setCannedAcl(S3, Bucket, ACL, service = 'aws', recursive) {
15
- if (service === 'oci') {
14
+ async function setCannedAcl(S3, Bucket, ACL, service = "aws", recursive) {
15
+ if (service === "oci") {
16
16
  return;
17
17
  }
18
18
  const callback = (err) => {
@@ -30,114 +30,51 @@ async function setCannedAcl(S3, Bucket, ACL, service = 'aws', recursive) {
30
30
  }
31
31
  };
32
32
  let promise;
33
- if (service === 'ibm') {
33
+ if (service === "ibm") {
34
34
  if (ACL === 1) {
35
- ACL = 'public-read';
35
+ ACL = "public-read";
36
36
  }
37
- promise = S3.putBucketAcl(ACL === 'authenticated-read' ? { Bucket, AccessControlPolicy: ACP_AUTHENTICATEDREAD } : { Bucket, ACL }).promise();
37
+ promise = S3.putBucketAcl(ACL === "authenticated-read" ? { Bucket, AccessControlPolicy: ACP_AUTHENTICATEDREAD } : { Bucket, ACL }).promise();
38
38
  }
39
39
  else {
40
40
  switch (ACL) {
41
41
  case 1:
42
- promise = S3.putBucketPolicy({ Bucket, Policy: getBucketPublicReadPolicy(Bucket) }).promise();
42
+ promise = S3.putBucketPolicy({ Bucket, Policy: (0, aws_lib_1.getBucketPublicReadPolicy)(Bucket) }).promise();
43
43
  break;
44
- case 'public-read':
45
- promise = S3.putBucketPolicy({ Bucket, Policy: getPublicReadPolicy(Bucket) }).promise();
44
+ case "public-read":
45
+ promise = S3.putBucketPolicy({ Bucket, Policy: (0, aws_lib_1.getPublicReadPolicy)(Bucket) }).promise();
46
46
  break;
47
- case 'public-read-write':
48
- promise = S3.putBucketPolicy({ Bucket, Policy: getPublicReadPolicy(Bucket, false, true) }).promise();
47
+ case "public-read-write":
48
+ promise = S3.putBucketPolicy({ Bucket, Policy: (0, aws_lib_1.getPublicReadPolicy)(Bucket, false, true) }).promise();
49
49
  break;
50
- case 'authenticated-read':
51
- promise = S3.putBucketPolicy({ Bucket, Policy: getPublicReadPolicy(Bucket, true) }).promise();
50
+ case "authenticated-read":
51
+ promise = S3.putBucketPolicy({ Bucket, Policy: (0, aws_lib_1.getPublicReadPolicy)(Bucket, true) }).promise();
52
52
  break;
53
53
  default:
54
- promise = S3.putBucketPolicy({ Bucket, Policy: getPrivatePolicy(Bucket) }).promise();
54
+ promise = S3.putBucketPolicy({ Bucket, Policy: (0, aws_lib_1.getPrivatePolicy)(Bucket) }).promise();
55
55
  break;
56
56
  }
57
57
  }
58
- return promise.then(() => callback(null)).catch(err => callback(err));
59
- }
60
- function isAccessDefined(credential) {
61
- return !!(credential.accessKeyId && credential.secretAccessKey || credential.sessionToken);
62
- }
63
- exports.isAccessDefined = isAccessDefined;
64
- function isSharedCredentialsDefined() {
65
- return !!process.env.AWS_SDK_LOAD_CONFIG;
66
- }
67
- exports.isSharedCredentialsDefined = isSharedCredentialsDefined;
68
- function isEnvDefined() {
69
- return !!(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY);
70
- }
71
- exports.isEnvDefined = isEnvDefined;
72
- function isProviderChainDefined() {
73
- return isSharedCredentialsDefined() || !!(process.env.AWS_WEB_IDENTITY_TOKEN_FILE || process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI);
74
- }
75
- exports.isProviderChainDefined = isProviderChainDefined;
76
- function isDatabaseDefined(credential, data) {
77
- return !!(data.table && (credential.region || credential.endpoint || process.env.AWS_DEFAULT_REGION || process.env.AWS_REGION));
78
- }
79
- exports.isDatabaseDefined = isDatabaseDefined;
80
- function getPublicReadPolicy(bucket, authenticated, write) {
81
- const Action = ["s3:GetObject", "s3:GetObjectVersion"];
82
- if (write) {
83
- Action.push("s3:PutObject", "s3:DeleteObjectVersion");
84
- }
85
- return JSON.stringify({
86
- "Version": "2012-10-17",
87
- "Statement": [{
88
- "Sid": (authenticated ? "AuthenticatedRead" : "PublicRead") + (write ? "Write" : ""),
89
- "Effect": "Allow",
90
- "Principal": authenticated ? { "AWS": "*" } : "*",
91
- "Action": Action,
92
- "Resource": [`arn:aws:s3:::${bucket}/*`]
93
- }]
94
- });
95
- }
96
- exports.getPublicReadPolicy = getPublicReadPolicy;
97
- function getBucketPublicReadPolicy(bucket) {
98
- return JSON.stringify({
99
- "Version": "2012-10-17",
100
- "Statement": [{
101
- "Sid": "BucketPublicRead",
102
- "Effect": "Allow",
103
- "Principal": "*",
104
- "Action": ["s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads"],
105
- "Resource": [`arn:aws:s3:::${bucket}`]
106
- }]
107
- });
108
- }
109
- exports.getBucketPublicReadPolicy = getBucketPublicReadPolicy;
110
- function getPrivatePolicy(bucket) {
111
- return JSON.stringify({
112
- "Version": "2012-10-17",
113
- "Statement": [{
114
- "Sid": "Private",
115
- "Effect": "Deny",
116
- "Principal": "*",
117
- "Action": "*",
118
- "Resource": [`arn:aws:s3:::${bucket}`, `arn:aws:s3:::${bucket}/*`]
119
- }]
120
- });
58
+ return promise.then(() => callback(null)).catch((err) => callback(err));
121
59
  }
122
- exports.getPrivatePolicy = getPrivatePolicy;
123
60
  function validateStorage(credential) {
124
- return !!(isAccessDefined(credential) || isEnvDefined() || credential.fromPath || credential.profile || isProviderChainDefined());
61
+ return !!((0, aws_lib_1.isAccessDefined)(credential) || (0, aws_lib_1.isEnvDefined)() || credential.fromPath || credential.profile || (0, aws_lib_1.isProviderChainDefined)());
125
62
  }
126
63
  exports.validateStorage = validateStorage;
127
64
  function validateDatabase(credential, data) {
128
- return isDatabaseDefined(credential, data) && validateStorage(credential);
65
+ return (0, aws_lib_1.isDatabaseDefined)(credential, data) && validateStorage(credential);
129
66
  }
130
67
  exports.validateDatabase = validateDatabase;
131
- function createStorageClient(credential, service = 'aws', sdk = 'aws-sdk/clients/s3') {
68
+ function createStorageClient(credential, service = "aws", sdk = "aws-sdk/clients/s3") {
132
69
  try {
133
- if (service === 'aws') {
134
- const { profile, fromPath } = credential;
135
- if (fromPath) {
70
+ if (service === "aws") {
71
+ const { fromPath, profile } = credential;
72
+ if (fromPath && this.canRead(fromPath, { ownPermissionOnly: true })) {
136
73
  const client = new aws.S3();
137
74
  client.config.loadFromPath(fromPath);
138
75
  return client;
139
76
  }
140
- if (profile || isSharedCredentialsDefined() && !isAccessDefined(credential) && !isEnvDefined()) {
77
+ if (profile || (0, aws_lib_1.isSharedCredentialsDefined)() && !(0, aws_lib_1.isAccessDefined)(credential) && !(0, aws_lib_1.isEnvDefined)()) {
141
78
  credential = new aws.SharedIniFileCredentials({ profile });
142
79
  }
143
80
  return new aws.S3(credential);
@@ -154,23 +91,30 @@ function createDatabaseClient(credential) {
154
91
  const { profile, fromPath } = credential;
155
92
  let options;
156
93
  if (fromPath) {
157
- aws.config.loadFromPath(fromPath);
94
+ try {
95
+ options = JSON.parse(this.readFile(fromPath, { encoding: 'utf-8', ownPermissionOnly: true, cache: false }));
96
+ }
97
+ catch {
98
+ }
158
99
  }
159
- else if (profile || isSharedCredentialsDefined() && !isAccessDefined(credential) && !isEnvDefined()) {
100
+ else if (profile || (0, aws_lib_1.isSharedCredentialsDefined)() && !(0, aws_lib_1.isAccessDefined)(credential) && !(0, aws_lib_1.isEnvDefined)()) {
160
101
  options = new aws.SharedIniFileCredentials({ profile });
161
102
  }
162
103
  else {
163
104
  options = credential;
164
105
  }
106
+ if (options?.endpoint && !options.region) {
107
+ (0, aws_lib_1.setDatabaseEndpoint)(options);
108
+ }
165
109
  return new aws.DynamoDB.DocumentClient(options);
166
110
  }
167
111
  exports.createDatabaseClient = createDatabaseClient;
168
- async function createBucket(credential, Bucket, publicRead, service = 'aws', sdk = 'aws-sdk/clients/s3') {
169
- return createBucketV2.call(this, credential, Bucket, publicRead ? 'public-read' : undefined, undefined, service, sdk);
112
+ async function createBucket(credential, Bucket, publicRead, service = "aws", sdk = "aws-sdk/clients/s3") {
113
+ return createBucketV2.call(this, credential, Bucket, publicRead ? "public-read" : undefined, undefined, service, sdk);
170
114
  }
171
115
  exports.createBucket = createBucket;
172
- async function createBucketV2(credential, Bucket, ACL, options, service = 'aws', sdk = 'aws-sdk/clients/s3') {
173
- ACL = ACL === 1 ? 1 : checkBucketCannedACL(ACL);
116
+ async function createBucketV2(credential, Bucket, ACL, options, service = "aws", sdk = "aws-sdk/clients/s3") {
117
+ ACL = ACL === 1 ? 1 : (0, aws_lib_1.checkBucketCannedACL)(ACL);
174
118
  const S3 = createStorageClient.call(this, credential, service, sdk);
175
119
  return S3.headBucket({ Bucket }).promise()
176
120
  .then(async () => {
@@ -182,8 +126,8 @@ async function createBucketV2(credential, Bucket, ACL, options, service = 'aws',
182
126
  .catch(async () => {
183
127
  const input = { ...options, Bucket };
184
128
  const region = credential.region;
185
- if (region && (region !== 'us-east-1' || service !== 'aws')) {
186
- input.CreateBucketConfiguration || (input.CreateBucketConfiguration = { LocationConstraint: region });
129
+ if (region && (region !== "us-east-1" || service !== "aws")) {
130
+ input.CreateBucketConfiguration ||= { LocationConstraint: region };
187
131
  }
188
132
  return S3.createBucket(input).promise()
189
133
  .then(async () => {
@@ -209,11 +153,11 @@ async function createBucketV2(credential, Bucket, ACL, options, service = 'aws',
209
153
  });
210
154
  }
211
155
  exports.createBucketV2 = createBucketV2;
212
- async function setBucketPolicy(credential, Bucket, options, service = 'aws', sdk = 'aws-sdk/clients/s3') {
213
- const ibm = service === 'ibm';
156
+ async function setBucketPolicy(credential, Bucket, options, service = "aws", sdk = "aws-sdk/clients/s3") {
157
+ const ibm = service === "ibm";
214
158
  const S3 = createStorageClient.call(this, credential, service, sdk);
215
159
  options.Bucket = Bucket;
216
- if (ibm && 'ACL' in options && options.ACL === 'authenticated-read') {
160
+ if (ibm && 'ACL' in options && options.ACL === "authenticated-read") {
217
161
  options.AccessControlPolicy = ACP_AUTHENTICATEDREAD;
218
162
  delete options.ACL;
219
163
  }
@@ -222,7 +166,7 @@ async function setBucketPolicy(credential, Bucket, options, service = 'aws', sdk
222
166
  this.formatMessage(64, service, ["Bucket policy configured", Bucket], '', { ...Cloud.LOG_CLOUD_COMMAND });
223
167
  return true;
224
168
  })
225
- .catch(err => {
169
+ .catch((err) => {
226
170
  if (!(0, exports.isNoSuchBucket)(err)) {
227
171
  this.formatFail(64, service, ["Unable to update bucket policy", Bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
228
172
  }
@@ -230,7 +174,30 @@ async function setBucketPolicy(credential, Bucket, options, service = 'aws', sdk
230
174
  });
231
175
  }
232
176
  exports.setBucketPolicy = setBucketPolicy;
233
- async function setBucketWebsite(credential, Bucket, options, service = 'aws', sdk = 'aws-sdk/clients/s3') {
177
+ async function setBucketTagging(credential, Bucket, options, service = "aws", sdk = "aws-sdk/clients/s3") {
178
+ if (!(0, types_1.isPlainObject)(options) || !Array.isArray(options.Tagging?.TagSet)) {
179
+ return false;
180
+ }
181
+ const S3 = createStorageClient.call(this, credential, service, sdk);
182
+ const deleting = options.Tagging.TagSet.length === 0;
183
+ const command = () => {
184
+ this.formatMessage(64, service, [deleting ? "Tags deleted" : "Tags created", Bucket], null, { ...Cloud.LOG_CLOUD_COMMAND });
185
+ return true;
186
+ };
187
+ const error = (err) => {
188
+ if (!(0, exports.isNoSuchBucket)(err)) {
189
+ this.formatFail(64, service, ["Unable to update bucket tagging", Bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
190
+ }
191
+ return false;
192
+ };
193
+ if (deleting) {
194
+ return S3.deleteBucketTagging({ Bucket, ExpectedBucketOwner: options.ExpectedBucketOwner }).promise().then(command).catch((err) => error(err));
195
+ }
196
+ options.Bucket = Bucket;
197
+ return S3.putBucketTagging(options).promise().then(command).catch((err) => error(err));
198
+ }
199
+ exports.setBucketTagging = setBucketTagging;
200
+ async function setBucketWebsite(credential, Bucket, options, service = "aws", sdk = "aws-sdk/clients/s3") {
234
201
  const S3 = createStorageClient.call(this, credential, service, sdk);
235
202
  const WebsiteConfiguration = {};
236
203
  const { indexPage: Suffix, errorPage: Key } = options;
@@ -242,22 +209,22 @@ async function setBucketWebsite(credential, Bucket, options, service = 'aws', sd
242
209
  }
243
210
  return S3.putBucketWebsite({ Bucket, WebsiteConfiguration }).promise()
244
211
  .then(() => {
245
- this.formatMessage(64, service, ["Bucket configured", Bucket], WebsiteConfiguration, { ...Cloud.LOG_CLOUD_COMMAND });
212
+ this.formatMessage(64, service, ["Bucket website configured", Bucket], WebsiteConfiguration, { ...Cloud.LOG_CLOUD_COMMAND });
246
213
  return true;
247
214
  })
248
- .catch(err => {
215
+ .catch((err) => {
249
216
  if (!(0, exports.isNoSuchBucket)(err)) {
250
- this.formatFail(64, service, ["Unable to configure bucket", Bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
217
+ this.formatFail(64, service, ["Unable to set bucket website", Bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
251
218
  }
252
219
  return false;
253
220
  });
254
221
  }
255
222
  exports.setBucketWebsite = setBucketWebsite;
256
- async function deleteObjects(credential, Bucket, service = 'aws', sdk = 'aws-sdk/clients/s3') {
223
+ async function deleteObjects(credential, Bucket, service = "aws", sdk = "aws-sdk/clients/s3") {
257
224
  return deleteObjectsV2.call(this, credential, Bucket, true, service, sdk);
258
225
  }
259
226
  exports.deleteObjects = deleteObjects;
260
- async function deleteObjectsV2(credential, Bucket, recursive = true, service = 'aws', sdk = 'aws-sdk/clients/s3') {
227
+ async function deleteObjectsV2(credential, Bucket, recursive = true, service = "aws", sdk = "aws-sdk/clients/s3") {
261
228
  const S3 = createStorageClient.call(this, credential, service, sdk);
262
229
  return S3.listObjects({ Bucket }).promise()
263
230
  .then(async ({ Contents }) => {
@@ -274,12 +241,12 @@ async function deleteObjectsV2(credential, Bucket, recursive = true, service = '
274
241
  this.formatMessage(64, service, ["Bucket emptied" + ` (${recursive ? 'recursive' : files})`, Bucket], recursive ? files : '', { ...Cloud.LOG_CLOUD_COMMAND });
275
242
  }
276
243
  })
277
- .catch(err => {
244
+ .catch((err) => {
278
245
  this.formatFail(64, service, ["Unable to empty bucket", Bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
279
246
  });
280
247
  }
281
248
  })
282
- .catch(err => {
249
+ .catch((err) => {
283
250
  if (!(0, exports.isNoSuchBucket)(err)) {
284
251
  this.formatFail(64, service, ["Unable to list bucket", Bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
285
252
  }
@@ -291,17 +258,16 @@ async function executeQuery(credential, data, sessionKey) {
291
258
  }
292
259
  exports.executeQuery = executeQuery;
293
260
  async function executeBatchQuery(credential, batch, sessionKey) {
294
- var _a;
295
261
  const length = batch.length;
296
262
  const result = new Array(length);
297
263
  const caching = length > 0 && this.hasCache(batch[0].service, sessionKey);
298
264
  const cacheValue = { value: this.valueOfKey(credential, 'cache'), sessionKey };
299
265
  let client;
300
- const createClient = () => client || (client = createDatabaseClient.call(this, credential));
301
- setDatabaseEndpoint(credential);
266
+ const createClient = () => client ||= createDatabaseClient.call(this, credential);
267
+ (0, aws_lib_1.setDatabaseEndpoint)(credential);
302
268
  for (let i = 0; i < length; ++i) {
303
269
  const item = batch[i];
304
- let { service, table: TableName, id, query, partitionKey, key = partitionKey, limit = 0, update, ignoreCache } = item;
270
+ let { service, table: TableName, id, query, key, limit = 0, update, ignoreCache } = item;
305
271
  const useCache = caching && ignoreCache !== true;
306
272
  const getCache = (value) => {
307
273
  if (ignoreCache !== 1) {
@@ -316,7 +282,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
316
282
  throw (0, util_1.formatError)(item, "Missing database table");
317
283
  }
318
284
  if (useCache) {
319
- queryString = TableName + '_' + Module.asString(key, true) + (id !== undefined ? '_' + Module.asString(id, true) : '');
285
+ queryString = TableName + '_' + Cloud.asString(key, true) + (id !== undefined ? '_' + Cloud.asString(id, true) : '');
320
286
  if (!update && (rows = getCache(queryString))) {
321
287
  result[i] = rows;
322
288
  continue;
@@ -343,7 +309,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
343
309
  if (limit > 0) {
344
310
  query.Limit = limit;
345
311
  }
346
- if (useCache && (rows = getCache(queryString = Module.asString(query, true)))) {
312
+ if (useCache && (rows = getCache(queryString = Cloud.asString(query, true)))) {
347
313
  result[i] = rows;
348
314
  continue;
349
315
  }
@@ -357,14 +323,14 @@ async function executeBatchQuery(credential, batch, sessionKey) {
357
323
  if (!(0, types_1.isPlainObject)(params.RequestItems)) {
358
324
  params.RequestItems = {};
359
325
  }
360
- TableName || (TableName = Object.keys(params.RequestItems)[0]);
326
+ TableName ||= Object.keys(params.RequestItems)[0];
361
327
  if (!TableName) {
362
328
  throw (0, util_1.formatError)(item, "Missing database table");
363
329
  }
364
- const Item = (_a = params.RequestItems)[TableName] || (_a[TableName] = {});
330
+ const Item = params.RequestItems[TableName] ||= {};
365
331
  Item.Keys = query;
366
332
  params = { RequestItems: { [TableName]: Item } };
367
- if (useCache && (rows = getCache(queryString = Module.asString(params, true)))) {
333
+ if (useCache && (rows = getCache(queryString = Cloud.asString(params, true)))) {
368
334
  result[i] = rows;
369
335
  continue;
370
336
  }
@@ -381,7 +347,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
381
347
  else {
382
348
  params = { TableName };
383
349
  }
384
- if (useCache && (rows = getCache(queryString = Module.asString(params, true)))) {
350
+ if (useCache && (rows = getCache(queryString = Cloud.asString(params, true)))) {
385
351
  result[i] = rows;
386
352
  continue;
387
353
  }
@@ -398,42 +364,6 @@ async function executeBatchQuery(credential, batch, sessionKey) {
398
364
  return result;
399
365
  }
400
366
  exports.executeBatchQuery = executeBatchQuery;
401
- function setDatabaseEndpoint(config) {
402
- let region = config.region;
403
- const endpoint = config.endpoint || (config.endpoint = `https://dynamodb.${(0, types_1.isString)(region) ? region : process.env.AWS_DEFAULT_REGION || process.env.AWS_REGION || 'us-east-1'}.amazonaws.com`);
404
- if (!region) {
405
- region = ((0, types_1.isString)(endpoint) && /\bdynamodb\.([^.]+)\./i.exec(endpoint)?.[1] || process.env.AWS_DEFAULT_REGION || process.env.AWS_REGION)?.toLowerCase();
406
- if (region && region !== 'us-east-1') {
407
- config.region = region;
408
- }
409
- }
410
- }
411
- exports.setDatabaseEndpoint = setDatabaseEndpoint;
412
- function checkBucketCannedACL(value) {
413
- switch (value) {
414
- case 'private':
415
- case 'public-read':
416
- case 'public-read-write':
417
- case 'authenticated-read':
418
- return value;
419
- }
420
- }
421
- exports.checkBucketCannedACL = checkBucketCannedACL;
422
- function writeMessageDefaultRetention(bucket, retention, service = 'aws') {
423
- const { Years = 0, Days = 0, Mode } = retention;
424
- const status = [];
425
- if (Years > 0) {
426
- status.push(Years.toString() + 'Y');
427
- }
428
- if (Days > 0) {
429
- status.push(Days.toString() + 'D');
430
- }
431
- if (Mode) {
432
- status.push(status.length ? `(${Mode})` : Mode);
433
- }
434
- this.formatMessage(64, service, ["Bucket configured" + ' (Retention Policy)', bucket], status.join(' '), { ...Cloud.LOG_CLOUD_COMMAND });
435
- }
436
- exports.writeMessageDefaultRetention = writeMessageDefaultRetention;
437
367
  function parseAttributeValue(value) {
438
368
  switch (typeof value) {
439
369
  case 'string':
@@ -464,7 +394,7 @@ function parseAttributeValue(value) {
464
394
  return { NULL: true };
465
395
  }
466
396
  exports.parseAttributeValue = parseAttributeValue;
467
- const getBucketKey = (credential, Bucket, acl, service, sdk) => Module.asString(credential, true) + Bucket + '_' + (acl || '') + service + sdk;
468
- exports.getBucketKey = getBucketKey;
469
397
  const isNoSuchBucket = (err) => err instanceof Error && err.code === 'NoSuchBucket';
470
398
  exports.isNoSuchBucket = isNoSuchBucket;
399
+ exports.CLOUD_UPLOAD_STREAM = true;
400
+ exports.CLOUD_UPLOAD_CHUNK = true;
package/download/index.js CHANGED
@@ -1,25 +1,26 @@
1
1
  "use strict";
2
2
  const types_1 = require("@e-mc/types");
3
- const Module = require("@e-mc/module");
4
3
  const Cloud = require("@e-mc/cloud");
5
4
  const client_1 = require("@pi-r/aws");
6
- module.exports = function download(credential, service = 'aws', sdk = 'aws-sdk/clients/s3') {
5
+ module.exports = function download(credential, service = "aws", sdk = "aws-sdk/clients/s3") {
7
6
  const s3 = client_1.createStorageClient.call(this, credential, service, sdk);
8
7
  return (data, callback) => {
9
8
  const { bucket: Bucket, download: target } = data;
10
- const Key = target.filename;
9
+ const Key = target.keyname || target.filename;
11
10
  if (!Bucket || !Key) {
12
11
  callback((0, types_1.errorValue)('Missing property', !Bucket ? 'Bucket' : 'Key'));
13
12
  return;
14
13
  }
15
- const location = Module.joinPath(Bucket, Key);
16
- const params = { Bucket, Key, VersionId: target.versionId };
17
- s3.getObject(params, (err, result) => {
14
+ const location = Cloud.joinPath(Bucket, Key);
15
+ s3.getObject({ ...target.options, Bucket, Key, VersionId: target.versionId }, (err, result) => {
18
16
  if (!err) {
19
17
  callback(null, result.Body);
20
- const deleteObject = target.deleteObject;
18
+ let deleteObject = target.deleteObject;
21
19
  if (deleteObject) {
22
- s3.deleteObject((0, types_1.isPlainObject)(deleteObject) ? Object.assign(deleteObject, params) : params, error => {
20
+ if (!(0, types_1.isPlainObject)(deleteObject)) {
21
+ deleteObject = undefined;
22
+ }
23
+ s3.deleteObject({ ...deleteObject, Bucket, Key, VersionId: target.versionId }, error => {
23
24
  if (!error) {
24
25
  this.formatMessage(64, service, "Delete success", location, { ...Cloud.LOG_CLOUD_DELETE });
25
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-r/aws",
3
- "version": "0.6.6",
3
+ "version": "0.7.0",
4
4
  "description": "AWS V2 cloud functions for E-mc.",
5
5
  "main": "client/index.js",
6
6
  "types": "client/index.d.ts",
@@ -21,9 +21,10 @@
21
21
  "license": "MIT",
22
22
  "homepage": "https://github.com/anpham6/pi-r#readme",
23
23
  "dependencies": {
24
- "@e-mc/cloud": "^0.8.7",
25
- "@e-mc/module": "^0.8.7",
26
- "@e-mc/types": "^0.8.7",
27
- "aws-sdk": "^2.1591.0"
24
+ "@e-mc/cloud": "^0.9.0",
25
+ "@e-mc/module": "^0.9.0",
26
+ "@e-mc/types": "^0.9.0",
27
+ "@pi-r/aws-lib": "^0.7.0",
28
+ "aws-sdk": "^2.1607.0"
28
29
  }
29
30
  }
package/types/index.d.ts CHANGED
@@ -1,13 +1,10 @@
1
-
2
- import type { CloudDatabase } from '@e-mc/types/lib/cloud';
1
+ import type { CloudDatabase, CloudStorage } from '@e-mc/types/lib/cloud';
3
2
 
4
3
  import type { ConfigurationOptions } from 'aws-sdk/lib/core';
5
4
  import type { ServiceConfigurationOptions } from 'aws-sdk/lib/service';
6
5
  import type { DocumentClient } from 'aws-sdk/clients/dynamodb';
7
6
  import type { PutBucketAclRequest, PutBucketPolicyRequest, PutPublicAccessBlockRequest } from 'aws-sdk/clients/s3';
8
7
 
9
- export type BucketCannedACL = "authenticated-read" | "private" | "public-read" | "public-read-write";
10
- export type ObjectCannedACL = BucketCannedACL | "aws-exec-read" | "bucket-owner-full-control" | "bucket-owner-read";
11
8
  export type ConfigureBucketOptions = PutBucketAclRequest | PutBucketPolicyRequest | PutPublicAccessBlockRequest;
12
9
  export type Key = DocumentClient.Key;
13
10
  export type GetItemInput = DocumentClient.GetItemInput;
@@ -16,14 +13,17 @@ export type UpdateItemInput = DocumentClient.UpdateItemInput;
16
13
  export type ScanInput = DocumentClient.ScanInput;
17
14
  export type BatchGetItemInput = DocumentClient.BatchGetItemInput;
18
15
 
16
+ export type AWSStorage = CloudStorage<AWSStorageCredential, "aws">;
17
+
19
18
  export interface AWSStorageCredential extends ConfigurationOptions {
20
19
  profile?: string;
21
20
  fromPath?: string;
22
21
  }
23
22
 
24
- export interface AWSDatabaseQuery extends CloudDatabase<QueryInput | Key[], PlainObject, UpdateItemInput, BatchGetItemInput | ScanInput> {
23
+ export interface AWSDatabaseQuery extends CloudDatabase<QueryInput | Key[], Record<string, unknown>, UpdateItemInput, BatchGetItemInput | ScanInput, AWSDatabaseCredential> {
24
+ source: "cloud";
25
+ service: "aws";
25
26
  key?: string | Key;
26
- partitionKey?: string | Key;
27
27
  }
28
28
 
29
- export interface AWSDatabaseCredential extends AWSStorageCredential, ServiceConfigurationOptions {}
29
+ export interface AWSDatabaseCredential extends AWSStorageCredential, ServiceConfigurationOptions {}
package/upload/index.js CHANGED
@@ -1,22 +1,24 @@
1
1
  "use strict";
2
2
  const path = require("path");
3
+ const fs = require("fs");
4
+ const stream = require("stream");
5
+ const aws = require("aws-sdk");
6
+ const Cloud = require("@e-mc/cloud");
3
7
  const util_1 = require("@e-mc/cloud/util");
4
8
  const types_1 = require("@e-mc/types");
5
- const Module = require("@e-mc/module");
6
- const Cloud = require("@e-mc/cloud");
9
+ const aws_lib_1 = require("@pi-r/aws-lib");
7
10
  const client_1 = require("@pi-r/aws");
8
11
  const BUCKET_SESSION = new Set();
9
12
  const BUCKET_RESPONSE = {};
10
- module.exports = function upload(credential, service = 'aws', sdk = 'aws-sdk/clients/s3') {
13
+ module.exports = function upload(credential, service = "aws", sdk = "aws-sdk/clients/s3") {
11
14
  const s3 = client_1.createStorageClient.call(this, credential, service, sdk);
12
15
  return async (data, callback) => {
13
- var _a;
14
16
  const { bucket: Bucket, localUri } = data;
15
- const { pathname = '', fileGroup, contentType, metadata, tags, endpoint, active, publicRead, acl, admin = {}, overwrite, options } = data.upload;
17
+ const { pathname = '', flags = 0, fileGroup, contentType, metadata, tags: Tags, endpoint, active, publicRead, acl, admin = {}, overwrite, options } = data.upload;
16
18
  let filename = data.upload.filename || path.basename(localUri), bucketKey;
17
19
  const cleanup = () => {
18
20
  BUCKET_SESSION.delete(service + Bucket);
19
- if (bucketKey && bucketKey in BUCKET_RESPONSE) {
21
+ if (bucketKey) {
20
22
  delete BUCKET_RESPONSE[bucketKey];
21
23
  }
22
24
  };
@@ -27,25 +29,73 @@ module.exports = function upload(credential, service = 'aws', sdk = 'aws-sdk/cli
27
29
  const addLog = (err) => err instanceof Error && this.addLog(this.statusType.WARN, err, service, Bucket);
28
30
  const configBucket = admin.configBucket;
29
31
  if (!BUCKET_SESSION.has(service + Bucket)) {
30
- const bucketAcl = admin.publicRead ? 'public-read' : admin.acl;
31
- const response = BUCKET_RESPONSE[_a = bucketKey = (0, client_1.getBucketKey)(credential, Bucket, bucketAcl, service, sdk)] || (BUCKET_RESPONSE[_a] = client_1.createBucketV2.call(this, credential, Bucket, bucketAcl, configBucket?.create, service, sdk));
32
+ const bucketAcl = admin.publicRead ? "public-read" : admin.acl;
33
+ const response = BUCKET_RESPONSE[bucketKey = (0, aws_lib_1.getBucketKey)(credential, Bucket, bucketAcl, service, sdk)] ||= client_1.createBucketV2.call(this, credential, Bucket, bucketAcl, configBucket?.create, service, sdk);
32
34
  if (!await response) {
33
35
  errorResponse(null);
34
36
  return;
35
37
  }
36
38
  BUCKET_SESSION.add(service + Bucket);
37
39
  }
38
- if (service !== 'ibm' && service !== 'oci') {
39
- const DefaultRetention = configBucket?.retentionPolicy;
40
- if ((0, types_1.isPlainObject)(DefaultRetention)) {
41
- s3.putObjectLockConfiguration({ Bucket, ObjectLockConfiguration: { ObjectLockEnabled: 'Enabled', Rule: { DefaultRetention } }, ExpectedBucketOwner: options?.ExpectedBucketOwner, RequestPayer: options?.RequestPayer }, err => {
42
- if (!err) {
43
- client_1.writeMessageDefaultRetention.call(this, Bucket, DefaultRetention, service);
44
- }
45
- else {
46
- addLog(err);
47
- }
48
- });
40
+ if (configBucket && service !== "oci") {
41
+ const { cors: CORSConfiguration, lifecycle: LifecycleConfiguration, retentionPolicy: DefaultRetention } = configBucket;
42
+ const ExpectedBucketOwner = options?.ExpectedBucketOwner;
43
+ const commandMessage = (feature, message) => this.formatMessage(64, service, ["Bucket configured" + ` (${feature})`, Bucket], message || ExpectedBucketOwner, { ...Cloud[message === 'delete' ? 'LOG_CLOUD_WARN' : 'LOG_CLOUD_COMMAND'] });
44
+ if (service !== "ibm") {
45
+ if ((0, types_1.isPlainObject)(DefaultRetention)) {
46
+ s3.putObjectLockConfiguration({ Bucket, ObjectLockConfiguration: { ObjectLockEnabled: 'Enabled', Rule: { DefaultRetention } }, ExpectedBucketOwner, RequestPayer: options?.RequestPayer }, err => {
47
+ if (!err) {
48
+ this.formatMessage(64, service, ["Bucket configured" + ' (Retention Policy)', Bucket], (0, aws_lib_1.formatDefaultRetention)(DefaultRetention), { ...Cloud.LOG_CLOUD_COMMAND });
49
+ }
50
+ else {
51
+ addLog(err);
52
+ }
53
+ });
54
+ }
55
+ }
56
+ if (CORSConfiguration && Array.isArray(CORSConfiguration.CORSRules)) {
57
+ if (CORSConfiguration.CORSRules.length === 0) {
58
+ s3.deleteBucketCors({ Bucket, ExpectedBucketOwner }, err => {
59
+ if (!err) {
60
+ commandMessage('CORS', 'delete');
61
+ }
62
+ else {
63
+ addLog(err);
64
+ }
65
+ });
66
+ }
67
+ else {
68
+ s3.putBucketCors({ Bucket, CORSConfiguration, ExpectedBucketOwner }, err => {
69
+ if (!err) {
70
+ commandMessage('CORS');
71
+ }
72
+ else {
73
+ addLog(err);
74
+ }
75
+ });
76
+ }
77
+ }
78
+ if (LifecycleConfiguration && Array.isArray(LifecycleConfiguration.Rules)) {
79
+ if (LifecycleConfiguration.Rules.length === 0) {
80
+ s3.deleteBucketLifecycle({ Bucket, ExpectedBucketOwner }, err => {
81
+ if (!err) {
82
+ commandMessage('Lifecycle', 'delete');
83
+ }
84
+ else {
85
+ addLog(err);
86
+ }
87
+ });
88
+ }
89
+ else {
90
+ s3.putBucketLifecycleConfiguration({ Bucket, LifecycleConfiguration, ExpectedBucketOwner }, err => {
91
+ if (!err) {
92
+ commandMessage('Lifecycle');
93
+ }
94
+ else {
95
+ addLog(err);
96
+ }
97
+ });
98
+ }
49
99
  }
50
100
  }
51
101
  if (!overwrite) {
@@ -59,7 +109,7 @@ module.exports = function upload(credential, service = 'aws', sdk = 'aws-sdk/cli
59
109
  break;
60
110
  }
61
111
  }
62
- exists = await s3.headObject({ Bucket, Key: pathname ? Module.joinPath(pathname, filename) : filename }).promise()
112
+ exists = await s3.headObject({ Bucket, Key: pathname ? Cloud.joinPath(pathname, filename) : filename }).promise()
63
113
  .then(() => true)
64
114
  .catch((err) => {
65
115
  if (err instanceof Error && err.code !== 'NotFound') {
@@ -75,14 +125,39 @@ module.exports = function upload(credential, service = 'aws', sdk = 'aws-sdk/cli
75
125
  if (pathname) {
76
126
  await s3.putObject({ Bucket, Key: pathname, Body: Buffer.from(''), ContentLength: 0 }).promise().catch(() => { });
77
127
  }
128
+ const partSize = flags & 4 ? (0, types_1.alignSize)(data.upload.chunkSize, 1024) : 0;
78
129
  const Key = [filename];
79
130
  const Body = [data.buffer];
131
+ const Stream = [];
80
132
  const ContentType = [contentType];
133
+ const getPartSize = () => partSize > 0 ? Math.max(partSize, aws.S3.ManagedUpload.minPartSize) : undefined;
134
+ if (Body[0].length === 0) {
135
+ try {
136
+ if ((flags & 2) || partSize > 0) {
137
+ Stream[0] = fs.createReadStream(localUri, { highWaterMark: getPartSize(), signal: this.signal });
138
+ }
139
+ else {
140
+ Body[0] = fs.readFileSync(localUri);
141
+ }
142
+ }
143
+ catch (err) {
144
+ errorResponse(err);
145
+ return;
146
+ }
147
+ }
148
+ else if ((flags & 2) || partSize > 0) {
149
+ Stream[0] = stream.Readable.from(Body[0], { highWaterMark: getPartSize() });
150
+ }
81
151
  if (fileGroup) {
82
- const [key, body, type] = (0, util_1.createKeyAndBody)(filename, fileGroup, addLog);
152
+ const [key, body, type] = (0, util_1.createKeyAndBody)(filename, fileGroup, 0, addLog, flags);
83
153
  Key.push(...key);
84
- Body.push(...body);
85
154
  ContentType.push(...type);
155
+ if (flags & 2) {
156
+ Stream.push(...body);
157
+ }
158
+ else {
159
+ Body.push(...body);
160
+ }
86
161
  }
87
162
  for (let i = 0; i < Key.length; ++i) {
88
163
  const first = i === 0;
@@ -92,12 +167,13 @@ module.exports = function upload(credential, service = 'aws', sdk = 'aws-sdk/cli
92
167
  }
93
168
  return;
94
169
  }
95
- const params = { ...options, Bucket, Key: pathname + Key[i], Body: Body[i] };
170
+ const params = { ...options, Bucket, Key: pathname + Key[i], Body: Stream[i] || Body[i] };
96
171
  const readable = publicRead || active && publicRead !== false && !acl;
172
+ let tags, length = -1;
97
173
  if (first) {
98
- params.ContentType || (params.ContentType = ContentType[i]);
174
+ params.ContentType ||= ContentType[i];
99
175
  if (readable) {
100
- params.ACL = 'public-read';
176
+ params.ACL = "public-read";
101
177
  }
102
178
  else if (acl) {
103
179
  params.ACL = acl;
@@ -105,19 +181,30 @@ module.exports = function upload(credential, service = 'aws', sdk = 'aws-sdk/cli
105
181
  if (metadata) {
106
182
  params.Metadata = metadata;
107
183
  }
184
+ if (service !== "oci") {
185
+ if ((0, types_1.isPlainObject)(Tags) && (length = Object.keys(Tags).length) > 0) {
186
+ tags = [];
187
+ for (const name in Tags) {
188
+ tags.push({ Key: name, Value: Tags[name] });
189
+ }
190
+ }
191
+ else if (Tags === false) {
192
+ length = 0;
193
+ }
194
+ }
108
195
  }
109
196
  else {
110
197
  params.ContentType = ContentType[i];
111
198
  if (!params.ACL) {
112
199
  if (readable) {
113
- params.ACL = 'public-read';
200
+ params.ACL = "public-read";
114
201
  }
115
202
  else if (acl) {
116
203
  params.ACL = acl;
117
204
  }
118
205
  }
119
206
  }
120
- s3.upload(params, (err, result) => {
207
+ new aws.S3.ManagedUpload({ service: s3, params, tags, partSize: getPartSize(), queueSize: partSize > 0 ? data.upload.chunkLimit : undefined }).send((err, result) => {
121
208
  if (err) {
122
209
  if (first) {
123
210
  errorResponse(err);
@@ -127,28 +214,10 @@ module.exports = function upload(credential, service = 'aws', sdk = 'aws-sdk/cli
127
214
  }
128
215
  return;
129
216
  }
130
- const url = endpoint ? Module.joinPath(endpoint, result.Key) : result.Location;
217
+ const url = endpoint ? Cloud.joinPath(endpoint, result.Key) : result.Location;
131
218
  this.formatMessage(64, service, "Upload success", url, { ...Cloud.LOG_CLOUD_UPLOAD });
132
- if (!first) {
133
- return;
134
- }
135
- if (service !== 'oci') {
136
- let length = -1;
137
- if ((0, types_1.isPlainObject)(tags) && (length = Object.keys(tags).length) > 0) {
138
- const TagSet = [];
139
- for (const name in tags) {
140
- TagSet.push({ Key: name, Value: tags[name] });
141
- }
142
- s3.putObjectTagging({ Bucket, Key: result.Key, Tagging: { TagSet }, ExpectedBucketOwner: params.ExpectedBucketOwner, RequestPayer: params.RequestPayer }, error => {
143
- if (!error) {
144
- this.formatMessage(64, service, ["Tags created", Bucket], result.Key, { ...Cloud.LOG_CLOUD_COMMAND });
145
- }
146
- else {
147
- addLog(error);
148
- }
149
- });
150
- }
151
- else if (tags === false || length === 0) {
219
+ if (first) {
220
+ if (length === 0) {
152
221
  s3.deleteObjectTagging({ Bucket, Key: result.Key, ExpectedBucketOwner: params.ExpectedBucketOwner }, error => {
153
222
  if (!error) {
154
223
  this.formatMessage(64, service, ["Tags deleted", Bucket], result.Key, { ...Cloud.LOG_CLOUD_COMMAND });
@@ -158,9 +227,9 @@ module.exports = function upload(credential, service = 'aws', sdk = 'aws-sdk/cli
158
227
  }
159
228
  });
160
229
  }
230
+ cleanup();
231
+ callback(null, url);
161
232
  }
162
- cleanup();
163
- callback(null, url);
164
233
  });
165
234
  }
166
235
  };