1- import { GetResourceCommandOutput , ListResourcesOutput , ResourceNotFoundException } from '@aws-sdk/client-cloudcontrol' ;
1+ import { GetResourceCommandOutput , ResourceNotFoundException } from '@aws-sdk/client-cloudcontrol' ;
22import { DateTime } from 'luxon' ;
33import { SchemaRetriever } from '../schema/SchemaRetriever' ;
44import { CfnExternal } from '../server/CfnExternal' ;
@@ -21,6 +21,7 @@ export type ResourceState = {
2121type ResourceList = {
2222 typeName : string ;
2323 resourceIdentifiers : string [ ] ;
24+ nextToken ?: string ;
2425 createdTimestamp : DateTime ;
2526 lastUpdatedTimestamp : DateTime ;
2627} ;
@@ -79,19 +80,65 @@ export class ResourceStateManager implements SettingsConfigurable, Closeable {
7980 return value ;
8081 }
8182
82- public async listResources ( typeName : string , updateFromLive ?: boolean ) : Promise < ResourceList | undefined > {
83- const cachedResourceList = this . resourceListMap . get ( typeName ) ;
84- if ( cachedResourceList && ! updateFromLive ) {
85- return cachedResourceList ;
86- }
87- const resourceList = await this . retrieveResourceList ( typeName ) ;
88- if ( ! resourceList ) {
83+ public async listResources ( typeName : string , nextToken ?: string ) : Promise < ResourceList | undefined > {
84+ const cached = this . resourceListMap . get ( typeName ) ;
85+
86+ if ( ! nextToken ) {
87+ // Initial request - fetch first page and cache it
88+ const resourceList = await this . retrieveResourceList ( typeName ) ;
89+ if ( resourceList ) {
90+ this . resourceListMap . set ( typeName , resourceList ) ;
91+ return resourceList ;
92+ }
8993 return ;
9094 }
9195
92- this . resourceListMap . set ( typeName , resourceList ) ;
96+ // Pagination request - fetch next page and append to cache
97+ const resourceListNextPage = await this . retrieveResourceList ( typeName , nextToken ) ;
98+ if ( resourceListNextPage && cached ) {
99+ // Deduplicate efficiently using Set for O(1) lookup
100+ const cachedSet = new Set ( cached . resourceIdentifiers ) ;
101+ const newIdentifiers = resourceListNextPage . resourceIdentifiers . filter ( ( id ) => ! cachedSet . has ( id ) ) ;
102+ cached . resourceIdentifiers . push ( ...newIdentifiers ) ;
103+ cached . nextToken = resourceListNextPage . nextToken ;
104+ cached . lastUpdatedTimestamp = DateTime . now ( ) ;
105+ return cached ;
106+ }
93107
94- return resourceList ;
108+ return resourceListNextPage ;
109+ }
110+
111+ public async searchResourceByIdentifier (
112+ typeName : string ,
113+ identifier : string ,
114+ ) : Promise < { found : boolean ; resourceList ?: ResourceList } > {
115+ const resource = await this . getResource ( typeName , identifier ) ;
116+ if ( ! resource ) {
117+ return { found : false } ;
118+ }
119+
120+ // Add to cache
121+ const cached = this . resourceListMap . get ( typeName ) ;
122+ if ( cached && ! cached . resourceIdentifiers . includes ( identifier ) ) {
123+ cached . resourceIdentifiers . push ( identifier ) ;
124+ cached . lastUpdatedTimestamp = DateTime . now ( ) ;
125+ return { found : true , resourceList : cached } ;
126+ }
127+
128+ // Create new cache entry if doesn't exist
129+ if ( ! cached ) {
130+ const newList : ResourceList = {
131+ typeName,
132+ resourceIdentifiers : [ identifier ] ,
133+ nextToken : undefined ,
134+ createdTimestamp : DateTime . now ( ) ,
135+ lastUpdatedTimestamp : DateTime . now ( ) ,
136+ } ;
137+ this . resourceListMap . set ( typeName , newList ) ;
138+ return { found : true , resourceList : newList } ;
139+ }
140+
141+ return { found : true , resourceList : cached } ;
95142 }
96143
97144 public getResourceTypes ( ) : string [ ] {
@@ -113,40 +160,42 @@ export class ResourceStateManager implements SettingsConfigurable, Closeable {
113160 return resourceIdToStateMap ?. get ( identifier ) ;
114161 }
115162
116- private async retrieveResourceList ( typeName : string ) : Promise < ResourceList | undefined > {
117- let output : ListResourcesOutput | undefined = undefined ;
118-
163+ private async retrieveResourceList ( typeName : string , nextToken ?: string ) : Promise < ResourceList | undefined > {
119164 try {
120- output = await this . ccapiService . listResources ( typeName ) ;
165+ const output = await this . ccapiService . listResources ( typeName , { nextToken } ) ;
166+
167+ const identifiers =
168+ output . ResourceDescriptions ?. map ( ( desc ) => desc . Identifier ) . filter (
169+ ( id ) : id is string => id !== undefined ,
170+ ) ?? [ ] ;
171+
172+ const now = DateTime . now ( ) ;
173+
174+ return {
175+ typeName : typeName ,
176+ resourceIdentifiers : identifiers ,
177+ createdTimestamp : now ,
178+ lastUpdatedTimestamp : now ,
179+ nextToken : output . NextToken ,
180+ } ;
121181 } catch ( error ) {
122182 log . error ( error , `CCAPI ListResource failed for type ${ typeName } ` ) ;
123183 return ;
124184 }
125-
126- if ( ! output ?. ResourceDescriptions ) {
127- return ;
128- }
129-
130- const now = DateTime . now ( ) ;
131-
132- return {
133- typeName : typeName ,
134- resourceIdentifiers : output . ResourceDescriptions . map ( ( desc ) => desc . Identifier ) . filter (
135- ( id ) => id !== undefined ,
136- ) ,
137- createdTimestamp : now ,
138- lastUpdatedTimestamp : now ,
139- } ;
140185 }
141186
142187 public async refreshResourceList ( resourceTypes : string [ ] ) : Promise < RefreshResourcesResult > {
143188 if ( this . isRefreshing ) {
144189 // return cached resource list
145190 return {
146- resources : resourceTypes . map ( ( resourceType ) => ( {
147- typeName : resourceType ,
148- resourceIdentifiers : this . resourceListMap . get ( resourceType ) ?. resourceIdentifiers ?? [ ] ,
149- } ) ) ,
191+ resources : resourceTypes . map ( ( resourceType ) => {
192+ const cached = this . resourceListMap . get ( resourceType ) ;
193+ return {
194+ typeName : resourceType ,
195+ resourceIdentifiers : cached ?. resourceIdentifiers ?? [ ] ,
196+ nextToken : cached ?. nextToken ,
197+ } ;
198+ } ) ,
150199 refreshFailed : false ,
151200 } ;
152201 }
@@ -158,32 +207,30 @@ export class ResourceStateManager implements SettingsConfigurable, Closeable {
158207 try {
159208 this . isRefreshing = true ;
160209 const result : ListResourcesResult = { resources : [ ] } ;
161- const now = DateTime . now ( ) ;
162210 let anyRefreshFailed = false ;
163211
164212 for ( const resourceType of resourceTypes ) {
165- const storedResourceList = this . resourceListMap . get ( resourceType ) ;
213+ // Clear cache and fetch first page only
214+ this . resourceListMap . delete ( resourceType ) ;
166215
167- const newResourceList = await this . retrieveResourceList ( resourceType ) ;
168- if ( ! newResourceList ) {
169- // Failed to update this resource type
216+ const response = await this . retrieveResourceList ( resourceType ) ;
217+ if ( ! response ) {
218+ anyRefreshFailed = true ;
170219 result . resources . push ( {
171220 typeName : resourceType ,
172- resourceIdentifiers : storedResourceList ?. resourceIdentifiers ?? [ ] ,
221+ resourceIdentifiers : [ ] ,
222+ nextToken : undefined ,
173223 } ) ;
174- anyRefreshFailed = true ;
175224 continue ;
176225 }
177226
178- this . resourceListMap . set ( resourceType , {
179- ...newResourceList ,
180- createdTimestamp : storedResourceList ?. createdTimestamp ?? now ,
181- lastUpdatedTimestamp : now ,
182- } ) ;
227+ // Cache the first page
228+ this . resourceListMap . set ( resourceType , response ) ;
183229
184230 result . resources . push ( {
185231 typeName : resourceType ,
186- resourceIdentifiers : newResourceList . resourceIdentifiers ,
232+ resourceIdentifiers : response . resourceIdentifiers ,
233+ nextToken : response . nextToken ,
187234 } ) ;
188235 }
189236 return { ...result , refreshFailed : anyRefreshFailed } ;
0 commit comments