or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client-configuration.mddata-protection.mdindex.mdmessage-publishing.mdpagination.mdpermission-management.mdplatform-applications.mdresource-tagging.mdsms-management.mdsubscription-management.mdtopic-management.md
tile.json

pagination.mddocs/

List Operations and Pagination

SNS provides pagination utilities for efficiently handling large result sets from list operations, including topics, subscriptions, platform applications, and other resources.

Capabilities

Pagination Configuration

Core configuration interface for all SNS pagination operations.

/**
 * Configuration interface for SNS pagination operations
 * Extends the base PaginationConfiguration with SNS-specific settings
 */
interface SNSPaginationConfiguration extends PaginationConfiguration {
  /** SNS client instance */
  client: SNSClient;
  /** Maximum number of items per page (optional) */
  pageSize?: number;
  /** Starting token for pagination (optional) */
  startingToken?: string;
}

interface PaginationConfiguration {
  /** Client instance for making API calls */
  client: any;
  /** Maximum number of pages to retrieve */
  maxItems?: number;
  /** Page size for each API call */
  pageSize?: number;
  /** Starting token for resuming pagination */
  startingToken?: string;
  /** Custom item filter function */
  itemFilter?: (item: any) => boolean;
  /** Custom page filter function */
  pageFilter?: (page: any) => boolean;
}

interface Paginator<TOutput> {
  /** Iterate through all pages */
  [Symbol.asyncIterator](): AsyncIterator<TOutput>;
  /** Convert to array (loads all results into memory) */
  toArray(): Promise<TOutput[]>;
}

List Topics Pagination

Paginate through all SNS topics in the account.

/**
 * Creates a paginator for listing all topics
 * Handles automatic pagination through large topic lists
 */
function paginateListTopics(
  config: SNSPaginationConfiguration,
  input: ListTopicsInput
): Paginator<ListTopicsCommandOutput>;

interface ListTopicsInput {
  /** Token for retrieving next page */
  NextToken?: string;
}

interface ListTopicsCommandOutput {
  /** Array of topics in current page */
  Topics?: Topic[];
  /** Token for next page (if more results exist) */
  NextToken?: string;
}

Usage Examples:

import { SNSClient, paginateListTopics } from "@aws-sdk/client-sns";

const client = new SNSClient({ region: "us-east-1" });

// Basic pagination - iterate through all topics
const paginator = paginateListTopics({ client }, {});

for await (const page of paginator) {
  page.Topics?.forEach(topic => {
    console.log("Topic ARN:", topic.TopicArn);
  });
}

// Load all topics into memory
const allTopicsPages = await paginateListTopics({ client }, {}).toArray();
const allTopics = allTopicsPages.flatMap(page => page.Topics || []);
console.log(`Total topics: ${allTopics.length}`);

// Pagination with custom page size
const customPaginator = paginateListTopics(
  { client, pageSize: 50 },
  {}
);

for await (const page of customPaginator) {
  console.log(`Page contains ${page.Topics?.length} topics`);
  if (page.NextToken) {
    console.log("More pages available");
  }
}

List Subscriptions Pagination

Paginate through all subscriptions in the account.

/**
 * Creates a paginator for listing all subscriptions
 * Handles automatic pagination through large subscription lists
 */
function paginateListSubscriptions(
  config: SNSPaginationConfiguration,
  input: ListSubscriptionsInput
): Paginator<ListSubscriptionsCommandOutput>;

interface ListSubscriptionsInput {
  /** Token for retrieving next page */
  NextToken?: string;
}

interface ListSubscriptionsCommandOutput {
  /** Array of subscriptions in current page */
  Subscriptions?: Subscription[];
  /** Token for next page */
  NextToken?: string;
}

Usage Examples:

// Find all email subscriptions across all topics
const subscriptionsPaginator = paginateListSubscriptions({ client }, {});

const emailSubscriptions = [];
for await (const page of subscriptionsPaginator) {
  const emailSubs = page.Subscriptions?.filter(sub => 
    sub.Protocol === "email"
  ) || [];
  emailSubscriptions.push(...emailSubs);
}

console.log(`Found ${emailSubscriptions.length} email subscriptions`);

List Subscriptions by Topic Pagination

Paginate through subscriptions for a specific topic.

/**
 * Creates a paginator for listing subscriptions for a specific topic
 * Useful for topic-specific subscription management
 */
function paginateListSubscriptionsByTopic(
  config: SNSPaginationConfiguration,
  input: ListSubscriptionsByTopicInput
): Paginator<ListSubscriptionsByTopicCommandOutput>;

interface ListSubscriptionsByTopicInput {
  /** ARN of the topic */
  TopicArn: string;
  /** Token for retrieving next page */
  NextToken?: string;
}

interface ListSubscriptionsByTopicCommandOutput {
  /** Array of subscriptions for the topic */
  Subscriptions?: Subscription[];
  /** Token for next page */
  NextToken?: string;
}

Usage Example:

// Get all subscriptions for a specific topic
const topicSubscriptionsPaginator = paginateListSubscriptionsByTopic(
  { client },
  { TopicArn: "arn:aws:sns:us-east-1:123456789012:MyTopic" }
);

const subscriptionsByProtocol = new Map<string, number>();

for await (const page of topicSubscriptionsPaginator) {
  page.Subscriptions?.forEach(sub => {
    const protocol = sub.Protocol || 'unknown';
    subscriptionsByProtocol.set(
      protocol,
      (subscriptionsByProtocol.get(protocol) || 0) + 1
    );
  });
}

console.log("Subscriptions by protocol:", Object.fromEntries(subscriptionsByProtocol));

List Platform Applications Pagination

Paginate through platform applications for mobile push notifications.

/**
 * Creates a paginator for listing platform applications
 * Handles pagination through mobile platform apps
 */
function paginateListPlatformApplications(
  config: SNSPaginationConfiguration,
  input: ListPlatformApplicationsInput
): Paginator<ListPlatformApplicationsCommandOutput>;

interface ListPlatformApplicationsInput {
  /** Token for retrieving next page */
  NextToken?: string;
}

interface ListPlatformApplicationsCommandOutput {
  /** Array of platform applications */
  PlatformApplications?: PlatformApplication[];
  /** Token for next page */
  NextToken?: string;
}

Usage Example:

// List all platform applications by type
const platformAppsPaginator = paginateListPlatformApplications({ client }, {});

const appsByPlatform = new Map<string, PlatformApplication[]>();

for await (const page of platformAppsPaginator) {
  page.PlatformApplications?.forEach(app => {
    const platform = app.Attributes?.Platform || 'unknown';
    if (!appsByPlatform.has(platform)) {
      appsByPlatform.set(platform, []);
    }
    appsByPlatform.get(platform)!.push(app);
  });
}

appsByPlatform.forEach((apps, platform) => {
  console.log(`${platform}: ${apps.length} applications`);
});

List Endpoints by Platform Application Pagination

Paginate through endpoints for a specific platform application.

/**
 * Creates a paginator for listing endpoints by platform application
 * Handles pagination through device endpoints
 */
function paginateListEndpointsByPlatformApplication(
  config: SNSPaginationConfiguration,
  input: ListEndpointsByPlatformApplicationInput
): Paginator<ListEndpointsByPlatformApplicationCommandOutput>;

interface ListEndpointsByPlatformApplicationInput {
  /** ARN of the platform application */
  PlatformApplicationArn: string;
  /** Token for retrieving next page */
  NextToken?: string;
}

interface ListEndpointsByPlatformApplicationCommandOutput {
  /** Array of endpoints */
  Endpoints?: Endpoint[];
  /** Token for next page */
  NextToken?: string;
}

Usage Example:

// Count enabled vs disabled endpoints
const endpointsPaginator = paginateListEndpointsByPlatformApplication(
  { client },
  { PlatformApplicationArn: "arn:aws:sns:us-east-1:123456789012:app/APNS/MyApp" }
);

let enabledCount = 0;
let disabledCount = 0;

for await (const page of endpointsPaginator) {
  page.Endpoints?.forEach(endpoint => {
    if (endpoint.Attributes?.Enabled === "true") {
      enabledCount++;
    } else {
      disabledCount++;
    }
  });
}

console.log(`Endpoints - Enabled: ${enabledCount}, Disabled: ${disabledCount}`);

SMS-Related Pagination

Pagination utilities for SMS management operations.

/**
 * Creates a paginator for listing opted-out phone numbers
 */
function paginateListPhoneNumbersOptedOut(
  config: SNSPaginationConfiguration,
  input: ListPhoneNumbersOptedOutInput
): Paginator<ListPhoneNumbersOptedOutCommandOutput>;

/**
 * Creates a paginator for listing SMS sandbox phone numbers
 */
function paginateListSMSSandboxPhoneNumbers(
  config: SNSPaginationConfiguration,
  input: ListSMSSandboxPhoneNumbersInput
): Paginator<ListSMSSandboxPhoneNumbersCommandOutput>;

/**
 * Creates a paginator for listing origination numbers
 */
function paginateListOriginationNumbers(
  config: SNSPaginationConfiguration,
  input: ListOriginationNumbersRequest
): Paginator<ListOriginationNumbersResult>;

Usage Examples:

// List all opted-out phone numbers
const optedOutPaginator = paginateListPhoneNumbersOptedOut({ client }, {});
const optedOutNumbers = [];

for await (const page of optedOutPaginator) {
  if (page.phoneNumbers) {
    optedOutNumbers.push(...page.phoneNumbers);
  }
}

console.log(`Total opted-out numbers: ${optedOutNumbers.length}`);

// List SMS sandbox phone numbers with status
const sandboxPaginator = paginateListSMSSandboxPhoneNumbers({ client }, {});

for await (const page of sandboxPaginator) {
  page.PhoneNumbers?.forEach(phoneNumber => {
    console.log(`${phoneNumber.PhoneNumber}: ${phoneNumber.Status}`);
  });
}

// List origination numbers by capability
const originationPaginator = paginateListOriginationNumbers({ client }, {});

const numbersByCapability = new Map<string, number>();

for await (const page of originationPaginator) {
  page.PhoneNumbers?.forEach(phoneInfo => {
    phoneInfo.NumberCapabilities?.forEach(capability => {
      numbersByCapability.set(
        capability,
        (numbersByCapability.get(capability) || 0) + 1
      );
    });
  });
}

console.log("Numbers by capability:", Object.fromEntries(numbersByCapability));

Advanced Pagination Patterns

Filtered Pagination

Filter results during pagination to reduce memory usage:

const findTopicsWithPattern = async (namePattern: RegExp) => {
  const matchingTopics = [];
  const paginator = paginateListTopics({ client }, {});
  
  for await (const page of paginator) {
    const matches = page.Topics?.filter(topic => {
      const topicName = topic.TopicArn?.split(':').pop();
      return topicName && namePattern.test(topicName);
    }) || [];
    
    matchingTopics.push(...matches);
    
    // Early termination if we found enough results
    if (matchingTopics.length >= 100) {
      console.log("Found enough matching topics, stopping early");
      break;
    }
  }
  
  return matchingTopics;
};

// Usage
const productionTopics = await findTopicsWithPattern(/prod|production/i);
console.log(`Found ${productionTopics.length} production topics`);

Parallel Pagination

Process multiple pagination operations in parallel:

const gatherAllSNSResources = async () => {
  const [topicsPages, subscriptionsPages, platformAppsPages] = await Promise.all([
    paginateListTopics({ client }, {}).toArray(),
    paginateListSubscriptions({ client }, {}).toArray(),
    paginateListPlatformApplications({ client }, {}).toArray()
  ]);
  
  const allTopics = topicsPages.flatMap(page => page.Topics || []);
  const allSubscriptions = subscriptionsPages.flatMap(page => page.Subscriptions || []);
  const allPlatformApps = platformAppsPages.flatMap(page => page.PlatformApplications || []);
  
  return {
    topics: allTopics,
    subscriptions: allSubscriptions,
    platformApplications: allPlatformApps,
    summary: {
      topicCount: allTopics.length,
      subscriptionCount: allSubscriptions.length,
      platformAppCount: allPlatformApps.length
    }
  };
};

const resources = await gatherAllSNSResources();
console.log("SNS Resources Summary:", resources.summary);

Resume Pagination from Token

Resume pagination from a specific point using tokens:

const resumablePagination = async (startingToken?: string) => {
  const paginator = paginateListTopics(
    { client, startingToken },
    {}
  );
  
  let processedCount = 0;
  let lastToken: string | undefined;
  
  try {
    for await (const page of paginator) {
      // Process current page
      const topics = page.Topics || [];
      processedCount += topics.length;
      lastToken = page.NextToken;
      
      console.log(`Processed ${processedCount} topics so far`);
      
      // Simulate some processing that might fail
      if (Math.random() < 0.1) { // 10% chance of failure
        throw new Error("Simulated processing error");
      }
      
      // Save progress periodically
      if (processedCount % 100 === 0) {
        console.log(`Checkpoint: processed ${processedCount}, token: ${lastToken}`);
      }
    }
    
    console.log(`Completed processing ${processedCount} topics`);
  } catch (error) {
    console.error(`Error after processing ${processedCount} topics`);
    console.log(`Resume with token: ${lastToken}`);
    throw error;
  }
};

// Start fresh
await resumablePagination();

// Resume from a specific token if the previous run failed
// await resumablePagination("previous-token-from-checkpoint");

Rate-Limited Pagination

Implement rate limiting to avoid throttling:

const rateLimitedPagination = async (requestsPerSecond: number = 5) => {
  const delay = 1000 / requestsPerSecond;
  const paginator = paginateListTopics({ client }, {});
  
  for await (const page of paginator) {
    // Process the page
    console.log(`Processing page with ${page.Topics?.length} topics`);
    
    // Rate limiting delay
    await new Promise(resolve => setTimeout(resolve, delay));
  }
};

// Process at 2 requests per second to avoid throttling
await rateLimitedPagination(2);

Batch Processing with Pagination

Process paginated results in batches:

const batchProcessTopics = async (batchSize: number = 10) => {
  const paginator = paginateListTopics({ client }, {});
  let batch: Topic[] = [];
  
  for await (const page of paginator) {
    const topics = page.Topics || [];
    batch.push(...topics);
    
    // Process full batches
    while (batch.length >= batchSize) {
      const currentBatch = batch.splice(0, batchSize);
      await processBatch(currentBatch);
    }
  }
  
  // Process remaining items
  if (batch.length > 0) {
    await processBatch(batch);
  }
};

const processBatch = async (topics: Topic[]) => {
  console.log(`Processing batch of ${topics.length} topics`);
  
  // Example: Get attributes for all topics in parallel
  const attributePromises = topics.map(topic => {
    if (topic.TopicArn) {
      return client.send(new GetTopicAttributesCommand({
        TopicArn: topic.TopicArn
      }));
    }
    return Promise.resolve(null);
  });
  
  const results = await Promise.allSettled(attributePromises);
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled' && result.value) {
      console.log(`Topic ${topics[index].TopicArn}: ${result.value.Attributes?.DisplayName || 'No display name'}`);
    } else if (result.status === 'rejected') {
      console.error(`Failed to get attributes for topic ${topics[index].TopicArn}: ${result.reason}`);
    }
  });
};

await batchProcessTopics(5);

Memory-Efficient Processing

Process large result sets without loading everything into memory:

const memoryEfficientProcessing = async () => {
  const paginator = paginateListSubscriptions({ client }, {});
  
  const stats = {
    totalProcessed: 0,
    byProtocol: new Map<string, number>(),
    errors: 0
  };
  
  for await (const page of paginator) {
    // Process each subscription individually
    for (const subscription of page.Subscriptions || []) {
      try {
        // Update statistics
        stats.totalProcessed++;
        const protocol = subscription.Protocol || 'unknown';
        stats.byProtocol.set(protocol, (stats.byProtocol.get(protocol) || 0) + 1);
        
        // Perform some processing (without storing results)
        await processSubscription(subscription);
        
      } catch (error) {
        stats.errors++;
        console.error(`Error processing subscription ${subscription.SubscriptionArn}: ${error.message}`);
      }
    }
    
    // Log progress periodically
    if (stats.totalProcessed % 1000 === 0) {
      console.log(`Processed ${stats.totalProcessed} subscriptions`);
    }
  }
  
  console.log("Final statistics:", {
    total: stats.totalProcessed,
    protocols: Object.fromEntries(stats.byProtocol),
    errors: stats.errors
  });
};

const processSubscription = async (subscription: Subscription) => {
  // Simulate processing without storing results
  if (subscription.SubscriptionArn && subscription.Protocol) {
    // Example: Log subscription info
    console.log(`${subscription.Protocol}: ${subscription.Endpoint}`);
  }
};

await memoryEfficientProcessing();