SNS provides pagination utilities for efficiently handling large result sets from list operations, including topics, subscriptions, platform applications, and other resources.
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[]>;
}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");
}
}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`);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));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`);
});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}`);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));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`);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 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");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);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);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();