@aspan-corporation/ac-shared 1.2.24 → 1.2.26
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/lib/services/s3.d.ts +7 -0
- package/lib/services/s3.js +23 -2
- package/lib/services/ssm.d.ts +6 -4
- package/lib/services/ssm.js +12 -1
- package/lib/utils/processMeta.d.ts +13 -1
- package/lib/utils/processMeta.js +27 -4
- package/package.json +1 -1
package/lib/services/s3.d.ts
CHANGED
|
@@ -22,6 +22,13 @@ export declare class S3Service {
|
|
|
22
22
|
putObject(putObjectCommandInput: PutObjectCommandInput): Promise<PutObjectCommandOutput>;
|
|
23
23
|
headObject(headObjectCommandInput: HeadObjectCommandInput): Promise<import("@aws-sdk/client-s3").HeadObjectCommandOutput>;
|
|
24
24
|
checkIfObjectExists(headObjectCommandInput: HeadObjectCommandInput): Promise<boolean>;
|
|
25
|
+
checkIfObjectExistsAndNonEmpty(headObjectCommandInput: HeadObjectCommandInput): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Returns true for both 404 (NotFound) and 403 responses.
|
|
28
|
+
* Some S3 bucket policies return 403 instead of 404 for missing keys
|
|
29
|
+
* to avoid revealing whether a key exists — treat both as "not found".
|
|
30
|
+
*/
|
|
31
|
+
private isNotFound;
|
|
25
32
|
deleteObject(deleteObjectCommandInput: DeleteObjectCommandInput): Promise<DeleteObjectCommandOutput>;
|
|
26
33
|
deleteObjects(deleteObjectsCommandInput: DeleteObjectsCommandInput): Promise<DeleteObjectsCommandOutput>;
|
|
27
34
|
}
|
package/lib/services/s3.js
CHANGED
|
@@ -75,12 +75,33 @@ export class S3Service {
|
|
|
75
75
|
return true;
|
|
76
76
|
}
|
|
77
77
|
catch (error) {
|
|
78
|
-
if (error
|
|
78
|
+
if (this.isNotFound(error))
|
|
79
79
|
return false;
|
|
80
|
-
}
|
|
81
80
|
throw error;
|
|
82
81
|
}
|
|
83
82
|
}
|
|
83
|
+
async checkIfObjectExistsAndNonEmpty(headObjectCommandInput) {
|
|
84
|
+
this.logger.debug("checkIfObjectExistsAndNonEmpty", { headObjectCommandInput });
|
|
85
|
+
try {
|
|
86
|
+
const head = await this.headObject(headObjectCommandInput);
|
|
87
|
+
return (head.ContentLength ?? 0) > 0;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (this.isNotFound(error))
|
|
91
|
+
return false;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns true for both 404 (NotFound) and 403 responses.
|
|
97
|
+
* Some S3 bucket policies return 403 instead of 404 for missing keys
|
|
98
|
+
* to avoid revealing whether a key exists — treat both as "not found".
|
|
99
|
+
*/
|
|
100
|
+
isNotFound(error) {
|
|
101
|
+
return (error.name === "NotFound" ||
|
|
102
|
+
error.$metadata?.httpStatusCode === 404 ||
|
|
103
|
+
error.$metadata?.httpStatusCode === 403);
|
|
104
|
+
}
|
|
84
105
|
async deleteObject(deleteObjectCommandInput) {
|
|
85
106
|
this.logger.debug("deleteObject", { deleteObjectCommandInput });
|
|
86
107
|
return await this.client.send(new DeleteObjectCommand(deleteObjectCommandInput));
|
package/lib/services/ssm.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { GetParameterCommandInput, GetParameterCommandOutput, PutParameterCommandInput, PutParameterCommandOutput, SSMClient } from "@aws-sdk/client-ssm";
|
|
1
|
+
import { GetParameterCommandInput, GetParameterCommandOutput, GetParametersByPathCommandInput, PutParameterCommandInput, PutParameterCommandOutput, SSMClient, type Parameter } from "@aws-sdk/client-ssm";
|
|
2
2
|
import { AssumeRoleCommandOutput } from "@aws-sdk/client-sts";
|
|
3
3
|
import { Logger } from "@aws-lambda-powertools/logger";
|
|
4
4
|
export declare class SSMService {
|
|
5
|
-
logger
|
|
5
|
+
logger?: Logger;
|
|
6
6
|
client: SSMClient;
|
|
7
7
|
constructor({ logger, client, assumeRoleCommandOutput, region }: {
|
|
8
|
-
logger
|
|
8
|
+
logger?: Logger;
|
|
9
9
|
client?: SSMClient;
|
|
10
10
|
assumeRoleCommandOutput?: AssumeRoleCommandOutput;
|
|
11
11
|
region?: string;
|
|
12
12
|
});
|
|
13
13
|
getParameter(params: GetParameterCommandInput): Promise<GetParameterCommandOutput>;
|
|
14
14
|
putParameter(params: PutParameterCommandInput): Promise<PutParameterCommandOutput>;
|
|
15
|
+
/** Fetches all parameters under the given path, handling pagination automatically. */
|
|
16
|
+
getParametersByPath(params: GetParametersByPathCommandInput): Promise<Parameter[]>;
|
|
15
17
|
}
|
|
16
|
-
export type { GetParameterCommandInput, GetParameterCommandOutput, PutParameterCommandInput, PutParameterCommandOutput } from "@aws-sdk/client-ssm";
|
|
18
|
+
export type { GetParameterCommandInput, GetParameterCommandOutput, GetParametersByPathCommandInput, Parameter, PutParameterCommandInput, PutParameterCommandOutput } from "@aws-sdk/client-ssm";
|
package/lib/services/ssm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GetParameterCommand, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
1
|
+
import { GetParameterCommand, GetParametersByPathCommand, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { getObjectWithAssumeRoleCommandOutputAttribute } from "../utils/index.js";
|
|
4
4
|
export class SSMService {
|
|
@@ -23,4 +23,15 @@ export class SSMService {
|
|
|
23
23
|
async putParameter(params) {
|
|
24
24
|
return await this.client.send(new PutParameterCommand(params));
|
|
25
25
|
}
|
|
26
|
+
/** Fetches all parameters under the given path, handling pagination automatically. */
|
|
27
|
+
async getParametersByPath(params) {
|
|
28
|
+
const results = [];
|
|
29
|
+
let nextToken;
|
|
30
|
+
do {
|
|
31
|
+
const response = await this.client.send(new GetParametersByPathCommand({ ...params, NextToken: nextToken }));
|
|
32
|
+
results.push(...(response.Parameters ?? []));
|
|
33
|
+
nextToken = response.NextToken;
|
|
34
|
+
} while (nextToken);
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
26
37
|
}
|
|
@@ -10,6 +10,7 @@ type ExtractMetadataParams = {
|
|
|
10
10
|
locationService: LocationService;
|
|
11
11
|
dynamoDBService: DynamoDBService;
|
|
12
12
|
metaTableName: string;
|
|
13
|
+
placeIndexName: string;
|
|
13
14
|
size: number;
|
|
14
15
|
};
|
|
15
16
|
/**
|
|
@@ -19,5 +20,16 @@ type ExtractMetadataParams = {
|
|
|
19
20
|
* 4. add all old tags that are not present in new tags,
|
|
20
21
|
*
|
|
21
22
|
*/
|
|
22
|
-
export declare const processMeta: ({ id, meta, metaTableName, size, logger, locationService, dynamoDBService }: ExtractMetadataParams) => Promise<void>;
|
|
23
|
+
export declare const processMeta: ({ id, meta, metaTableName, placeIndexName, size, logger, locationService, dynamoDBService }: ExtractMetadataParams) => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Parent folder of an S3 key, used as the `folder` GSI partition key on the meta table.
|
|
26
|
+
*
|
|
27
|
+
* "2024/08/15/photo.jpg" → "2024/08/15/"
|
|
28
|
+
* "2024/08/15/" → "2024/08/"
|
|
29
|
+
* "2024/" → "/"
|
|
30
|
+
* "" → "/"
|
|
31
|
+
*
|
|
32
|
+
* Root sentinel "/" lets us partition top-level entries under a single PK.
|
|
33
|
+
*/
|
|
34
|
+
export declare const deriveFolder: (id: string) => string;
|
|
23
35
|
export {};
|
package/lib/utils/processMeta.js
CHANGED
|
@@ -7,7 +7,7 @@ import { getKeyExtension } from "./thumbsKey.js";
|
|
|
7
7
|
* 4. add all old tags that are not present in new tags,
|
|
8
8
|
*
|
|
9
9
|
*/
|
|
10
|
-
export const processMeta = async ({ id, meta, metaTableName, size, logger, locationService, dynamoDBService }) => {
|
|
10
|
+
export const processMeta = async ({ id, meta, metaTableName, placeIndexName, size, logger, locationService, dynamoDBService }) => {
|
|
11
11
|
// expand location data
|
|
12
12
|
const latitude = Number(meta.find((tag) => tag.key === "latitude")?.value);
|
|
13
13
|
const longitude = Number(meta.find((tag) => tag.key === "longitude")?.value);
|
|
@@ -15,7 +15,7 @@ export const processMeta = async ({ id, meta, metaTableName, size, logger, locat
|
|
|
15
15
|
if (!Number.isNaN(longitude) && !Number.isNaN(latitude)) {
|
|
16
16
|
try {
|
|
17
17
|
const res = await locationService.searchPlaceIndexForPositionCommand({
|
|
18
|
-
IndexName:
|
|
18
|
+
IndexName: placeIndexName,
|
|
19
19
|
Position: [longitude, latitude]
|
|
20
20
|
});
|
|
21
21
|
if (res?.Results && res?.Results?.length > 0) {
|
|
@@ -85,9 +85,13 @@ export const processMeta = async ({ id, meta, metaTableName, size, logger, locat
|
|
|
85
85
|
Key: {
|
|
86
86
|
id
|
|
87
87
|
},
|
|
88
|
-
UpdateExpression: "set tags = :tags",
|
|
88
|
+
UpdateExpression: "set tags = :tags, #folder = :folder",
|
|
89
|
+
ExpressionAttributeNames: {
|
|
90
|
+
"#folder": "folder"
|
|
91
|
+
},
|
|
89
92
|
ExpressionAttributeValues: {
|
|
90
|
-
":tags": reconciledTags
|
|
93
|
+
":tags": reconciledTags,
|
|
94
|
+
":folder": deriveFolder(id)
|
|
91
95
|
},
|
|
92
96
|
ReturnValues: "ALL_NEW"
|
|
93
97
|
});
|
|
@@ -102,6 +106,25 @@ const reconcileTags = ({ oldTags = [], newTags = [] }, logger) => oldTags.reduce
|
|
|
102
106
|
return [...acc, cur];
|
|
103
107
|
}
|
|
104
108
|
}, newTags);
|
|
109
|
+
/**
|
|
110
|
+
* Parent folder of an S3 key, used as the `folder` GSI partition key on the meta table.
|
|
111
|
+
*
|
|
112
|
+
* "2024/08/15/photo.jpg" → "2024/08/15/"
|
|
113
|
+
* "2024/08/15/" → "2024/08/"
|
|
114
|
+
* "2024/" → "/"
|
|
115
|
+
* "" → "/"
|
|
116
|
+
*
|
|
117
|
+
* Root sentinel "/" lets us partition top-level entries under a single PK.
|
|
118
|
+
*/
|
|
119
|
+
export const deriveFolder = (id) => {
|
|
120
|
+
if (!id)
|
|
121
|
+
return "/";
|
|
122
|
+
const trimmed = id.endsWith("/") ? id.slice(0, -1) : id;
|
|
123
|
+
const lastSlash = trimmed.lastIndexOf("/");
|
|
124
|
+
if (lastSlash < 0)
|
|
125
|
+
return "/";
|
|
126
|
+
return trimmed.slice(0, lastSlash + 1);
|
|
127
|
+
};
|
|
105
128
|
const SUBSTRING_ANSI_DATES_BEGIN_WITH = "20";
|
|
106
129
|
const extractMetaFromKey = (key) => {
|
|
107
130
|
if (!key)
|