@gravito/constellation 3.0.1 → 3.1.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 +96 -251
- package/README.zh-TW.md +130 -13
- package/dist/{DiskSitemapStorage-7ZZMGC4K.js → DiskSitemapStorage-VLN5I24C.js} +1 -1
- package/dist/chunk-3IZTXYU7.js +166 -0
- package/dist/index.cjs +1893 -196
- package/dist/index.d.cts +1597 -171
- package/dist/index.d.ts +1597 -171
- package/dist/index.js +1774 -199
- package/package.json +7 -5
- package/dist/chunk-7WHLC3OJ.js +0 -56
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Job } from '@gravito/stream';
|
|
2
2
|
import { GravitoOrbit, PlanetCore } from '@gravito/core';
|
|
3
|
+
import { Transform } from 'node:stream';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Supported change frequency values for sitemap entries.
|
|
@@ -176,11 +177,26 @@ interface SitemapProvider {
|
|
|
176
177
|
* @since 3.0.0
|
|
177
178
|
*/
|
|
178
179
|
interface SitemapStreamOptions {
|
|
179
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* Base domain used to normalize relative URLs.
|
|
182
|
+
* Example: 'https://example.com'
|
|
183
|
+
*/
|
|
180
184
|
baseUrl: string;
|
|
181
185
|
/** Whether to output formatted and indented XML. @default false */
|
|
182
186
|
pretty?: boolean | undefined;
|
|
183
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Options for stream-based write operations.
|
|
190
|
+
*
|
|
191
|
+
* @public
|
|
192
|
+
* @since 3.1.0
|
|
193
|
+
*/
|
|
194
|
+
interface WriteStreamOptions {
|
|
195
|
+
/** Whether to enable gzip compression for the output. @default false */
|
|
196
|
+
compress?: boolean | undefined;
|
|
197
|
+
/** Optional content type override. @default 'application/xml' */
|
|
198
|
+
contentType?: string | undefined;
|
|
199
|
+
}
|
|
184
200
|
/**
|
|
185
201
|
* Persistence layer for storing generated sitemap files.
|
|
186
202
|
*
|
|
@@ -198,6 +214,17 @@ interface SitemapStorage {
|
|
|
198
214
|
* @param content - The raw XML content.
|
|
199
215
|
*/
|
|
200
216
|
write(filename: string, content: string): Promise<void>;
|
|
217
|
+
/**
|
|
218
|
+
* Write sitemap content using a streaming approach to reduce memory usage.
|
|
219
|
+
*
|
|
220
|
+
* If implemented, this method will be preferred over `write()` for large sitemaps.
|
|
221
|
+
*
|
|
222
|
+
* @param filename - The destination filename.
|
|
223
|
+
* @param stream - An async iterable that yields XML string chunks.
|
|
224
|
+
* @param options - Optional write configuration including compression.
|
|
225
|
+
* @since 3.1.0
|
|
226
|
+
*/
|
|
227
|
+
writeStream?(filename: string, stream: AsyncIterable<string>, options?: WriteStreamOptions): Promise<void>;
|
|
201
228
|
/**
|
|
202
229
|
* Read sitemap content from the storage backend.
|
|
203
230
|
*
|
|
@@ -205,6 +232,7 @@ interface SitemapStorage {
|
|
|
205
232
|
* @returns The XML content as a string, or null if not found.
|
|
206
233
|
*/
|
|
207
234
|
read(filename: string): Promise<string | null>;
|
|
235
|
+
readStream?(filename: string): Promise<AsyncIterable<string> | null>;
|
|
208
236
|
/**
|
|
209
237
|
* Check if a sitemap file exists in storage.
|
|
210
238
|
*
|
|
@@ -441,6 +469,33 @@ interface RedirectRule {
|
|
|
441
469
|
/** Timestamp when the redirect rule was created. */
|
|
442
470
|
createdAt?: Date;
|
|
443
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Represents a manifest file that tracks URL-to-shard mappings.
|
|
474
|
+
*
|
|
475
|
+
* @public
|
|
476
|
+
* @since 3.0.1
|
|
477
|
+
*/
|
|
478
|
+
interface ShardManifest {
|
|
479
|
+
version: number;
|
|
480
|
+
generatedAt: Date | string;
|
|
481
|
+
baseUrl: string;
|
|
482
|
+
maxEntriesPerShard: number;
|
|
483
|
+
sort: string;
|
|
484
|
+
shards: ShardInfo[];
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Metadata about a single sitemap shard.
|
|
488
|
+
*
|
|
489
|
+
* @public
|
|
490
|
+
* @since 3.0.1
|
|
491
|
+
*/
|
|
492
|
+
interface ShardInfo {
|
|
493
|
+
filename: string;
|
|
494
|
+
from: string;
|
|
495
|
+
to: string;
|
|
496
|
+
count: number;
|
|
497
|
+
lastmod: Date | string;
|
|
498
|
+
}
|
|
444
499
|
/**
|
|
445
500
|
* Manager for registering and resolving redirect rules.
|
|
446
501
|
*
|
|
@@ -483,6 +538,20 @@ interface RedirectManager {
|
|
|
483
538
|
*/
|
|
484
539
|
resolve(url: string, followChains?: boolean, maxChainLength?: number): Promise<string | null>;
|
|
485
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* Compression configuration options.
|
|
543
|
+
*
|
|
544
|
+
* @public
|
|
545
|
+
* @since 3.1.0
|
|
546
|
+
*/
|
|
547
|
+
interface CompressionOptions {
|
|
548
|
+
/** Whether compression is enabled. @default false */
|
|
549
|
+
enabled: boolean;
|
|
550
|
+
/** Compression format. @default 'gzip' */
|
|
551
|
+
format?: 'gzip' | undefined;
|
|
552
|
+
/** Compression level (1-9, where 1 is fastest and 9 is best compression). @default 6 */
|
|
553
|
+
level?: number | undefined;
|
|
554
|
+
}
|
|
486
555
|
|
|
487
556
|
/**
|
|
488
557
|
* Options for configuring the `MemoryChangeTracker`.
|
|
@@ -505,11 +574,34 @@ interface MemoryChangeTrackerOptions {
|
|
|
505
574
|
*/
|
|
506
575
|
declare class MemoryChangeTracker implements ChangeTracker {
|
|
507
576
|
private changes;
|
|
577
|
+
private urlIndex;
|
|
508
578
|
private maxChanges;
|
|
509
579
|
constructor(options?: MemoryChangeTrackerOptions);
|
|
580
|
+
/**
|
|
581
|
+
* Record a new site structure change in memory.
|
|
582
|
+
*
|
|
583
|
+
* @param change - The change event to record.
|
|
584
|
+
*/
|
|
510
585
|
track(change: SitemapChange): Promise<void>;
|
|
586
|
+
/**
|
|
587
|
+
* Retrieve all changes recorded in memory since a specific time.
|
|
588
|
+
*
|
|
589
|
+
* @param since - Optional start date for the query.
|
|
590
|
+
* @returns An array of change events.
|
|
591
|
+
*/
|
|
511
592
|
getChanges(since?: Date): Promise<SitemapChange[]>;
|
|
593
|
+
/**
|
|
594
|
+
* Retrieve the full change history for a specific URL from memory.
|
|
595
|
+
*
|
|
596
|
+
* @param url - The URL to query history for.
|
|
597
|
+
* @returns An array of change events.
|
|
598
|
+
*/
|
|
512
599
|
getChangesByUrl(url: string): Promise<SitemapChange[]>;
|
|
600
|
+
/**
|
|
601
|
+
* Purge old change records from memory storage.
|
|
602
|
+
*
|
|
603
|
+
* @param since - If provided, only records older than this date will be cleared.
|
|
604
|
+
*/
|
|
513
605
|
clear(since?: Date): Promise<void>;
|
|
514
606
|
}
|
|
515
607
|
/**
|
|
@@ -543,9 +635,31 @@ declare class RedisChangeTracker implements ChangeTracker {
|
|
|
543
635
|
constructor(options: RedisChangeTrackerOptions);
|
|
544
636
|
private getKey;
|
|
545
637
|
private getListKey;
|
|
638
|
+
/**
|
|
639
|
+
* Record a new site structure change in Redis.
|
|
640
|
+
*
|
|
641
|
+
* @param change - The change event to record.
|
|
642
|
+
*/
|
|
546
643
|
track(change: SitemapChange): Promise<void>;
|
|
644
|
+
/**
|
|
645
|
+
* Retrieve all changes recorded in Redis since a specific time.
|
|
646
|
+
*
|
|
647
|
+
* @param since - Optional start date for the query.
|
|
648
|
+
* @returns An array of change events.
|
|
649
|
+
*/
|
|
547
650
|
getChanges(since?: Date): Promise<SitemapChange[]>;
|
|
651
|
+
/**
|
|
652
|
+
* Retrieve the full change history for a specific URL from Redis.
|
|
653
|
+
*
|
|
654
|
+
* @param url - The URL to query history for.
|
|
655
|
+
* @returns An array of change events.
|
|
656
|
+
*/
|
|
548
657
|
getChangesByUrl(url: string): Promise<SitemapChange[]>;
|
|
658
|
+
/**
|
|
659
|
+
* Purge old change records from Redis storage.
|
|
660
|
+
*
|
|
661
|
+
* @param since - If provided, only records older than this date will be cleared.
|
|
662
|
+
*/
|
|
549
663
|
clear(since?: Date): Promise<void>;
|
|
550
664
|
}
|
|
551
665
|
|
|
@@ -587,19 +701,35 @@ declare class DiffCalculator {
|
|
|
587
701
|
private batchSize;
|
|
588
702
|
constructor(options?: DiffCalculatorOptions);
|
|
589
703
|
/**
|
|
590
|
-
*
|
|
704
|
+
* Calculates the difference between two sets of sitemap entries.
|
|
705
|
+
*
|
|
706
|
+
* @param oldEntries - The previous set of sitemap entries.
|
|
707
|
+
* @param newEntries - The current set of sitemap entries.
|
|
708
|
+
* @returns A DiffResult containing added, updated, and removed entries.
|
|
591
709
|
*/
|
|
592
710
|
calculate(oldEntries: SitemapEntry[], newEntries: SitemapEntry[]): DiffResult;
|
|
593
711
|
/**
|
|
594
|
-
*
|
|
712
|
+
* Batch calculates differences for large datasets using async iterables.
|
|
713
|
+
*
|
|
714
|
+
* @param oldEntries - An async iterable of the previous sitemap entries.
|
|
715
|
+
* @param newEntries - An async iterable of the current sitemap entries.
|
|
716
|
+
* @returns A promise resolving to the DiffResult.
|
|
595
717
|
*/
|
|
596
718
|
calculateBatch(oldEntries: AsyncIterable<SitemapEntry>, newEntries: AsyncIterable<SitemapEntry>): Promise<DiffResult>;
|
|
597
719
|
/**
|
|
598
|
-
*
|
|
720
|
+
* Calculates differences based on a sequence of change records.
|
|
721
|
+
*
|
|
722
|
+
* @param baseEntries - The base set of sitemap entries.
|
|
723
|
+
* @param changes - An array of change records to apply to the base set.
|
|
724
|
+
* @returns A DiffResult comparing the base set with the applied changes.
|
|
599
725
|
*/
|
|
600
726
|
calculateFromChanges(baseEntries: SitemapEntry[], changes: SitemapChange[]): DiffResult;
|
|
601
727
|
/**
|
|
602
|
-
*
|
|
728
|
+
* Checks if a sitemap entry has changed by comparing its key properties.
|
|
729
|
+
*
|
|
730
|
+
* @param oldEntry - The previous sitemap entry.
|
|
731
|
+
* @param newEntry - The current sitemap entry.
|
|
732
|
+
* @returns True if the entry has changed, false otherwise.
|
|
603
733
|
*/
|
|
604
734
|
private hasChanged;
|
|
605
735
|
}
|
|
@@ -650,25 +780,34 @@ declare class ShadowProcessor {
|
|
|
650
780
|
private options;
|
|
651
781
|
private shadowId;
|
|
652
782
|
private operations;
|
|
783
|
+
private mutex;
|
|
653
784
|
constructor(options: ShadowProcessorOptions);
|
|
654
785
|
/**
|
|
655
|
-
*
|
|
786
|
+
* Adds a single file write operation to the current shadow session.
|
|
787
|
+
*
|
|
788
|
+
* If shadow processing is disabled, the file is written directly to the
|
|
789
|
+
* final destination in storage. Otherwise, it is written to the shadow staging area.
|
|
790
|
+
*
|
|
791
|
+
* @param operation - The shadow write operation details.
|
|
656
792
|
*/
|
|
657
793
|
addOperation(operation: ShadowOperation): Promise<void>;
|
|
658
794
|
/**
|
|
659
|
-
*
|
|
795
|
+
* Commits all staged shadow operations to the final production location.
|
|
796
|
+
*
|
|
797
|
+
* Depending on the `mode`, this will either perform an atomic swap of all files
|
|
798
|
+
* or commit each file individually (potentially creating new versions).
|
|
660
799
|
*/
|
|
661
800
|
commit(): Promise<void>;
|
|
662
801
|
/**
|
|
663
|
-
*
|
|
802
|
+
* Cancels all staged shadow operations without committing them.
|
|
664
803
|
*/
|
|
665
804
|
rollback(): Promise<void>;
|
|
666
805
|
/**
|
|
667
|
-
*
|
|
806
|
+
* Returns the unique identifier for the current shadow session.
|
|
668
807
|
*/
|
|
669
808
|
getShadowId(): string;
|
|
670
809
|
/**
|
|
671
|
-
*
|
|
810
|
+
* Returns an array of all staged shadow operations.
|
|
672
811
|
*/
|
|
673
812
|
getOperations(): ShadowOperation[];
|
|
674
813
|
}
|
|
@@ -688,6 +827,8 @@ interface SitemapGeneratorOptions extends SitemapStreamOptions {
|
|
|
688
827
|
maxEntriesPerFile?: number;
|
|
689
828
|
/** The name of the main sitemap or sitemap index file. @default 'sitemap.xml' */
|
|
690
829
|
filename?: string;
|
|
830
|
+
/** Whether to generate a shard manifest file. @default false */
|
|
831
|
+
generateManifest?: boolean;
|
|
691
832
|
/** Configuration for staging files before atomic deployment. */
|
|
692
833
|
shadow?: {
|
|
693
834
|
/** Whether shadow processing is enabled. */
|
|
@@ -701,6 +842,8 @@ interface SitemapGeneratorOptions extends SitemapStreamOptions {
|
|
|
701
842
|
total: number;
|
|
702
843
|
percentage: number;
|
|
703
844
|
}) => void;
|
|
845
|
+
/** Compression configuration for reducing sitemap file sizes. @since 3.1.0 */
|
|
846
|
+
compression?: CompressionOptions;
|
|
704
847
|
}
|
|
705
848
|
/**
|
|
706
849
|
* SitemapGenerator is the primary orchestrator for creating sitemaps in Gravito.
|
|
@@ -726,9 +869,28 @@ declare class SitemapGenerator {
|
|
|
726
869
|
private options;
|
|
727
870
|
private shadowProcessor;
|
|
728
871
|
constructor(options: SitemapGeneratorOptions);
|
|
872
|
+
/**
|
|
873
|
+
* Orchestrates the sitemap generation process.
|
|
874
|
+
*
|
|
875
|
+
* This method scans all providers, handles sharding, generates the XML files,
|
|
876
|
+
* and optionally creates a sitemap index and manifest.
|
|
877
|
+
*/
|
|
729
878
|
run(): Promise<void>;
|
|
730
879
|
/**
|
|
731
|
-
*
|
|
880
|
+
* 統一的 sitemap 寫入方法,優先使用串流寫入以降低記憶體使用。
|
|
881
|
+
*
|
|
882
|
+
* @param stream - SitemapStream 實例
|
|
883
|
+
* @param filename - 檔案名稱(不含 .gz)
|
|
884
|
+
* @returns 實際寫入的檔名(可能包含 .gz)
|
|
885
|
+
* @since 3.1.0
|
|
886
|
+
*/
|
|
887
|
+
private writeSitemap;
|
|
888
|
+
/**
|
|
889
|
+
* Normalizes a URL to an absolute URL using the base URL.
|
|
890
|
+
*/
|
|
891
|
+
private normalizeUrl;
|
|
892
|
+
/**
|
|
893
|
+
* Returns the shadow processor instance if enabled.
|
|
732
894
|
*/
|
|
733
895
|
getShadowProcessor(): ShadowProcessor | null;
|
|
734
896
|
}
|
|
@@ -736,25 +898,23 @@ declare class SitemapGenerator {
|
|
|
736
898
|
/**
|
|
737
899
|
* Options for configuring the `IncrementalGenerator`.
|
|
738
900
|
*
|
|
739
|
-
* Extends `SitemapGeneratorOptions` to include change tracking and difference
|
|
740
|
-
* calculation components.
|
|
741
|
-
*
|
|
742
901
|
* @public
|
|
743
902
|
* @since 3.0.0
|
|
744
903
|
*/
|
|
745
904
|
interface IncrementalGeneratorOptions extends SitemapGeneratorOptions {
|
|
746
|
-
/** The change tracker used to
|
|
905
|
+
/** The change tracker used to retrieve and record structural site changes. */
|
|
747
906
|
changeTracker: ChangeTracker;
|
|
748
|
-
/** Optional
|
|
907
|
+
/** Optional diff calculator to identify added, updated, or removed entries. */
|
|
749
908
|
diffCalculator?: DiffCalculator;
|
|
750
|
-
/** Whether to automatically track
|
|
909
|
+
/** Whether to automatically track new entries discovered during full generation. @default true */
|
|
751
910
|
autoTrack?: boolean;
|
|
752
911
|
}
|
|
753
912
|
/**
|
|
754
|
-
* IncrementalGenerator
|
|
913
|
+
* IncrementalGenerator manages partial updates to the sitemap based on detected changes.
|
|
755
914
|
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
915
|
+
* It provides methods for performing both full and incremental generations,
|
|
916
|
+
* using a `ChangeTracker` to determine which parts of the sitemap need updating.
|
|
917
|
+
* This is particularly efficient for large sites where full regeneration is costly.
|
|
758
918
|
*
|
|
759
919
|
* @public
|
|
760
920
|
* @since 3.0.0
|
|
@@ -764,33 +924,51 @@ declare class IncrementalGenerator {
|
|
|
764
924
|
private changeTracker;
|
|
765
925
|
private diffCalculator;
|
|
766
926
|
private generator;
|
|
927
|
+
private mutex;
|
|
767
928
|
constructor(options: IncrementalGeneratorOptions);
|
|
768
929
|
/**
|
|
769
|
-
*
|
|
930
|
+
* Performs a full sitemap generation and optionally records all entries in the change tracker.
|
|
770
931
|
*/
|
|
771
932
|
generateFull(): Promise<void>;
|
|
772
933
|
/**
|
|
773
|
-
*
|
|
934
|
+
* Performs an incremental sitemap update based on changes recorded since a specific time.
|
|
935
|
+
*
|
|
936
|
+
* If the number of changes exceeds a certain threshold (e.g., 30% of total URLs),
|
|
937
|
+
* a full generation is triggered instead to ensure consistency.
|
|
938
|
+
*
|
|
939
|
+
* @param since - Optional start date for the incremental update.
|
|
774
940
|
*/
|
|
775
941
|
generateIncremental(since?: Date): Promise<void>;
|
|
776
942
|
/**
|
|
777
|
-
*
|
|
943
|
+
* Internal implementation of full sitemap generation.
|
|
778
944
|
*/
|
|
779
|
-
|
|
945
|
+
private performFullGeneration;
|
|
780
946
|
/**
|
|
781
|
-
*
|
|
947
|
+
* Internal implementation of incremental sitemap generation.
|
|
782
948
|
*/
|
|
783
|
-
|
|
949
|
+
private performIncrementalGeneration;
|
|
950
|
+
/**
|
|
951
|
+
* Normalizes a URL to an absolute URL using the base URL.
|
|
952
|
+
*/
|
|
953
|
+
private normalizeUrl;
|
|
954
|
+
/**
|
|
955
|
+
* Loads the sitemap shard manifest from storage.
|
|
956
|
+
*/
|
|
957
|
+
private loadManifest;
|
|
958
|
+
/**
|
|
959
|
+
* Identifies which shards are affected by the given set of changes.
|
|
960
|
+
*/
|
|
961
|
+
private getAffectedShards;
|
|
784
962
|
/**
|
|
785
|
-
*
|
|
963
|
+
* Updates the affected shards in storage.
|
|
786
964
|
*/
|
|
787
|
-
private
|
|
965
|
+
private updateShards;
|
|
788
966
|
/**
|
|
789
|
-
*
|
|
967
|
+
* Applies changes to a set of sitemap entries and returns the updated, sorted list.
|
|
790
968
|
*/
|
|
791
|
-
private
|
|
969
|
+
private applyChanges;
|
|
792
970
|
/**
|
|
793
|
-
*
|
|
971
|
+
* Helper to convert an async iterable into an array.
|
|
794
972
|
*/
|
|
795
973
|
private toArray;
|
|
796
974
|
}
|
|
@@ -823,31 +1001,44 @@ declare class ProgressTracker {
|
|
|
823
1001
|
private updateTimer;
|
|
824
1002
|
constructor(options: ProgressTrackerOptions);
|
|
825
1003
|
/**
|
|
826
|
-
*
|
|
1004
|
+
* Initializes progress tracking for a new job.
|
|
1005
|
+
*
|
|
1006
|
+
* @param jobId - Unique identifier for the generation job.
|
|
1007
|
+
* @param total - Total number of entries to be processed.
|
|
827
1008
|
*/
|
|
828
1009
|
init(jobId: string, total: number): Promise<void>;
|
|
829
1010
|
/**
|
|
830
|
-
*
|
|
1011
|
+
* Updates the current progress of the job.
|
|
1012
|
+
*
|
|
1013
|
+
* Updates are debounced and flushed to storage at regular intervals
|
|
1014
|
+
* specified by `updateInterval` to avoid excessive write operations.
|
|
1015
|
+
*
|
|
1016
|
+
* @param processed - Number of entries processed so far.
|
|
1017
|
+
* @param status - Optional new status for the job.
|
|
831
1018
|
*/
|
|
832
1019
|
update(processed: number, status?: SitemapProgress['status']): Promise<void>;
|
|
833
1020
|
/**
|
|
834
|
-
*
|
|
1021
|
+
* Marks the current job as successfully completed.
|
|
835
1022
|
*/
|
|
836
1023
|
complete(): Promise<void>;
|
|
837
1024
|
/**
|
|
838
|
-
*
|
|
1025
|
+
* Marks the current job as failed with an error message.
|
|
1026
|
+
*
|
|
1027
|
+
* @param error - The error message describing why the job failed.
|
|
839
1028
|
*/
|
|
840
1029
|
fail(error: string): Promise<void>;
|
|
841
1030
|
/**
|
|
842
|
-
*
|
|
1031
|
+
* Flushes the current progress state to the storage backend.
|
|
843
1032
|
*/
|
|
844
1033
|
private flush;
|
|
845
1034
|
/**
|
|
846
|
-
*
|
|
1035
|
+
* Stops the periodic update timer.
|
|
847
1036
|
*/
|
|
848
1037
|
private stop;
|
|
849
1038
|
/**
|
|
850
|
-
*
|
|
1039
|
+
* Returns a copy of the current progress state.
|
|
1040
|
+
*
|
|
1041
|
+
* @returns The current SitemapProgress object, or null if no job is active.
|
|
851
1042
|
*/
|
|
852
1043
|
getCurrentProgress(): SitemapProgress | null;
|
|
853
1044
|
}
|
|
@@ -873,9 +1064,29 @@ declare class SitemapIndex {
|
|
|
873
1064
|
private options;
|
|
874
1065
|
private entries;
|
|
875
1066
|
constructor(options: SitemapStreamOptions);
|
|
1067
|
+
/**
|
|
1068
|
+
* Adds a single entry to the sitemap index.
|
|
1069
|
+
*
|
|
1070
|
+
* @param entry - A sitemap filename or a `SitemapIndexEntry` object.
|
|
1071
|
+
* @returns The `SitemapIndex` instance for chaining.
|
|
1072
|
+
*/
|
|
876
1073
|
add(entry: string | SitemapIndexEntry): this;
|
|
1074
|
+
/**
|
|
1075
|
+
* Adds multiple entries to the sitemap index.
|
|
1076
|
+
*
|
|
1077
|
+
* @param entries - An array of sitemap filenames or `SitemapIndexEntry` objects.
|
|
1078
|
+
* @returns The `SitemapIndex` instance for chaining.
|
|
1079
|
+
*/
|
|
877
1080
|
addAll(entries: (string | SitemapIndexEntry)[]): this;
|
|
1081
|
+
/**
|
|
1082
|
+
* Generates the sitemap index XML content.
|
|
1083
|
+
*
|
|
1084
|
+
* @returns The complete XML string for the sitemap index.
|
|
1085
|
+
*/
|
|
878
1086
|
toXML(): string;
|
|
1087
|
+
/**
|
|
1088
|
+
* Escapes special XML characters in a string.
|
|
1089
|
+
*/
|
|
879
1090
|
private escape;
|
|
880
1091
|
}
|
|
881
1092
|
|
|
@@ -902,16 +1113,73 @@ declare class SitemapIndex {
|
|
|
902
1113
|
declare class SitemapStream {
|
|
903
1114
|
private options;
|
|
904
1115
|
private entries;
|
|
1116
|
+
private flags;
|
|
905
1117
|
constructor(options: SitemapStreamOptions);
|
|
1118
|
+
/**
|
|
1119
|
+
* Adds a single entry to the sitemap stream.
|
|
1120
|
+
*
|
|
1121
|
+
* @param entry - A URL string or a complete `SitemapEntry` object.
|
|
1122
|
+
* @returns The `SitemapStream` instance for chaining.
|
|
1123
|
+
*/
|
|
906
1124
|
add(entry: string | SitemapEntry): this;
|
|
1125
|
+
/**
|
|
1126
|
+
* Adds multiple entries to the sitemap stream.
|
|
1127
|
+
*
|
|
1128
|
+
* @param entries - An array of URL strings or `SitemapEntry` objects.
|
|
1129
|
+
* @returns The `SitemapStream` instance for chaining.
|
|
1130
|
+
*/
|
|
907
1131
|
addAll(entries: (string | SitemapEntry)[]): this;
|
|
1132
|
+
/**
|
|
1133
|
+
* Generates the sitemap XML content.
|
|
1134
|
+
*
|
|
1135
|
+
* Automatically includes the necessary XML namespaces for images, videos, news,
|
|
1136
|
+
* and internationalization if the entries contain such metadata.
|
|
1137
|
+
*
|
|
1138
|
+
* @returns The complete XML string for the sitemap.
|
|
1139
|
+
*/
|
|
908
1140
|
toXML(): string;
|
|
1141
|
+
/**
|
|
1142
|
+
* 以 AsyncGenerator 方式產生 XML 內容。
|
|
1143
|
+
* 每次 yield 一個邏輯區塊,適合串流寫入場景,可減少記憶體峰值。
|
|
1144
|
+
*
|
|
1145
|
+
* @returns AsyncGenerator 產生 XML 字串片段
|
|
1146
|
+
*
|
|
1147
|
+
* @example
|
|
1148
|
+
* ```typescript
|
|
1149
|
+
* const stream = new SitemapStream({ baseUrl: 'https://example.com' })
|
|
1150
|
+
* stream.add({ url: '/page1' })
|
|
1151
|
+
* stream.add({ url: '/page2' })
|
|
1152
|
+
*
|
|
1153
|
+
* for await (const chunk of stream.toAsyncIterable()) {
|
|
1154
|
+
* process.stdout.write(chunk)
|
|
1155
|
+
* }
|
|
1156
|
+
* ```
|
|
1157
|
+
*
|
|
1158
|
+
* @since 3.1.0
|
|
1159
|
+
*/
|
|
1160
|
+
toAsyncIterable(): AsyncGenerator<string, void, unknown>;
|
|
1161
|
+
/**
|
|
1162
|
+
* 同步版本的 iterable,供 toXML() 使用。
|
|
1163
|
+
*/
|
|
1164
|
+
private toSyncIterable;
|
|
1165
|
+
/**
|
|
1166
|
+
* 建立 urlset 開標籤與所有必要的 XML 命名空間。
|
|
1167
|
+
*/
|
|
1168
|
+
private buildUrlsetOpenTag;
|
|
1169
|
+
/**
|
|
1170
|
+
* Renders a single sitemap entry into its XML representation.
|
|
1171
|
+
*/
|
|
909
1172
|
private renderUrl;
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
private hasAlternates;
|
|
1173
|
+
/**
|
|
1174
|
+
* Escapes special XML characters in a string.
|
|
1175
|
+
*/
|
|
914
1176
|
private escape;
|
|
1177
|
+
/**
|
|
1178
|
+
* Returns all entries currently in the stream.
|
|
1179
|
+
*
|
|
1180
|
+
* @returns An array of `SitemapEntry` objects.
|
|
1181
|
+
*/
|
|
1182
|
+
getEntries(): SitemapEntry[];
|
|
915
1183
|
}
|
|
916
1184
|
|
|
917
1185
|
/**
|
|
@@ -922,10 +1190,18 @@ type I18nSitemapEntryOptions = Omit<SitemapEntry, 'url' | 'alternates'>;
|
|
|
922
1190
|
/**
|
|
923
1191
|
* Generate fully cross-referenced SitemapEntries for multiple locales.
|
|
924
1192
|
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
927
|
-
*
|
|
928
|
-
*
|
|
1193
|
+
* This helper creates a set of sitemap entries where each entry represents a
|
|
1194
|
+
* specific locale version of a page, and all entries are linked via `xhtml:link`
|
|
1195
|
+
* alternate tags to satisfy Google's internationalization requirements.
|
|
1196
|
+
*
|
|
1197
|
+
* @param path - The path relative to the locale prefix (e.g., '/docs/intro').
|
|
1198
|
+
* @param locales - List of supported language/region codes (e.g., ['en', 'zh-TW']).
|
|
1199
|
+
* @param baseUrl - The domain root (e.g., 'https://example.com').
|
|
1200
|
+
* @param options - Additional SitemapEntry properties like `lastmod` or `priority`.
|
|
1201
|
+
* @returns An array of interconnected `SitemapEntry` objects.
|
|
1202
|
+
*
|
|
1203
|
+
* @public
|
|
1204
|
+
* @since 3.0.0
|
|
929
1205
|
*/
|
|
930
1206
|
declare function generateI18nEntries(path: string, locales: string[], baseUrl?: string, options?: I18nSitemapEntryOptions): SitemapEntry[];
|
|
931
1207
|
|
|
@@ -971,154 +1247,880 @@ declare class GenerateSitemapJob extends Job {
|
|
|
971
1247
|
private totalEntries;
|
|
972
1248
|
private processedEntries;
|
|
973
1249
|
constructor(options: GenerateSitemapJobOptions);
|
|
1250
|
+
/**
|
|
1251
|
+
* Main entry point for the job execution.
|
|
1252
|
+
*
|
|
1253
|
+
* Orchestrates the full lifecycle of sitemap generation, including progress
|
|
1254
|
+
* initialization, generation, shadow commit, and error handling.
|
|
1255
|
+
*/
|
|
974
1256
|
handle(): Promise<void>;
|
|
975
1257
|
/**
|
|
976
|
-
*
|
|
1258
|
+
* Calculates the total number of URL entries from all providers.
|
|
1259
|
+
*
|
|
1260
|
+
* @returns A promise resolving to the total entry count.
|
|
977
1261
|
*/
|
|
978
1262
|
private calculateTotal;
|
|
979
1263
|
/**
|
|
980
|
-
*
|
|
1264
|
+
* Performs sitemap generation while reporting progress to the tracker and callback.
|
|
981
1265
|
*/
|
|
982
1266
|
private generateWithProgress;
|
|
983
1267
|
}
|
|
984
1268
|
|
|
985
1269
|
/**
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
* Useful for small to medium sites where fresh data is critical. Dynamic sitemaps
|
|
989
|
-
* are generated on-the-fly and can be cached at the HTTP level.
|
|
990
|
-
*
|
|
991
|
-
* @public
|
|
992
|
-
* @since 3.0.0
|
|
993
|
-
*/
|
|
994
|
-
interface DynamicSitemapOptions extends SitemapStreamOptions {
|
|
995
|
-
/** The URL path where the sitemap will be exposed. @default '/sitemap.xml' */
|
|
996
|
-
path?: string | undefined;
|
|
997
|
-
/** List of sitemap entry providers to scan for content. */
|
|
998
|
-
providers: SitemapProvider[];
|
|
999
|
-
/** Cache duration in seconds for the HTTP response. @default undefined (no-cache) */
|
|
1000
|
-
cacheSeconds?: number | undefined;
|
|
1001
|
-
/** Persistence backend for cached XML files. Defaults to MemorySitemapStorage. */
|
|
1002
|
-
storage?: SitemapStorage | undefined;
|
|
1003
|
-
/** Optional distributed lock to prevent "cache stampede" during heavy generation. */
|
|
1004
|
-
lock?: SitemapLock | undefined;
|
|
1005
|
-
}
|
|
1006
|
-
/**
|
|
1007
|
-
* Configuration for a statically pre-generated sitemap.
|
|
1270
|
+
* In-memory lock implementation for single-instance sitemap generation.
|
|
1008
1271
|
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1272
|
+
* Provides mutex-style mutual exclusion within a single Node.js process using a Map-based
|
|
1273
|
+
* storage mechanism. Designed for development environments and single-instance deployments
|
|
1274
|
+
* where distributed coordination is not required.
|
|
1011
1275
|
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
/** Local directory where generated XML files will be saved. */
|
|
1017
|
-
outDir: string;
|
|
1018
|
-
/** The name of the root sitemap file. @default 'sitemap.xml' */
|
|
1019
|
-
filename?: string | undefined;
|
|
1020
|
-
/** List of sitemap entry providers to scan for content. */
|
|
1021
|
-
providers: SitemapProvider[];
|
|
1022
|
-
/** Custom storage backend. Defaults to DiskSitemapStorage using `outDir`. */
|
|
1023
|
-
storage?: SitemapStorage | undefined;
|
|
1024
|
-
/** Optional incremental generation settings to only update changed URLs. */
|
|
1025
|
-
incremental?: {
|
|
1026
|
-
/** Whether to enable incremental builds. */
|
|
1027
|
-
enabled: boolean;
|
|
1028
|
-
/** Backend for tracking structural site changes. */
|
|
1029
|
-
changeTracker: ChangeTracker;
|
|
1030
|
-
/** Whether to automatically record new changes during scan. @default false */
|
|
1031
|
-
autoTrack?: boolean;
|
|
1032
|
-
};
|
|
1033
|
-
/** Optional SEO redirect orchestration settings. */
|
|
1034
|
-
redirect?: {
|
|
1035
|
-
/** Whether to automatically handle 301/302 redirects found in sitemap. */
|
|
1036
|
-
enabled: boolean;
|
|
1037
|
-
/** Backend for managing and resolving redirect rules. */
|
|
1038
|
-
manager: RedirectManager;
|
|
1039
|
-
/** Strategy for resolving conflicting or chained redirects. @default 'remove_old_add_new' */
|
|
1040
|
-
strategy?: 'remove_old_add_new' | 'keep_relation' | 'update_url' | 'dual_mark';
|
|
1041
|
-
/** Whether to resolve redirect chains into a single jump. @default false */
|
|
1042
|
-
followChains?: boolean;
|
|
1043
|
-
/** Maximum number of redirect jumps to follow. @default 5 */
|
|
1044
|
-
maxChainLength?: number;
|
|
1045
|
-
};
|
|
1046
|
-
/** Deployment settings for atomic sitemap updates via shadow staging. */
|
|
1047
|
-
shadow?: {
|
|
1048
|
-
/** Whether to enable "shadow" (atomic staging) mode. */
|
|
1049
|
-
enabled: boolean;
|
|
1050
|
-
/** The update strategy: 'atomic' (full swap) or 'versioned' (archived). @default 'atomic' */
|
|
1051
|
-
mode: 'atomic' | 'versioned';
|
|
1052
|
-
};
|
|
1053
|
-
/** Persistence backend for long-running job progress tracking. */
|
|
1054
|
-
progressStorage?: SitemapProgressStorage;
|
|
1055
|
-
}
|
|
1056
|
-
/**
|
|
1057
|
-
* OrbitSitemap is the enterprise SEO orchestration module for Gravito.
|
|
1276
|
+
* **When to use:**
|
|
1277
|
+
* - Development and testing environments
|
|
1278
|
+
* - Single-instance production deployments (e.g., single Docker container)
|
|
1279
|
+
* - Scenarios where Redis infrastructure is unavailable
|
|
1058
1280
|
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1281
|
+
* **When NOT to use:**
|
|
1282
|
+
* - Kubernetes or multi-instance deployments (use {@link RedisLock} instead)
|
|
1283
|
+
* - Horizontally scaled applications
|
|
1284
|
+
* - Any environment requiring cross-process synchronization
|
|
1061
1285
|
*
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1286
|
+
* **Design rationale:**
|
|
1287
|
+
* - Zero external dependencies (no Redis, no database)
|
|
1288
|
+
* - Automatic TTL-based lock expiration to prevent deadlocks
|
|
1289
|
+
* - Synchronous cleanup of expired locks during read operations
|
|
1290
|
+
* - Fast in-process performance with O(1) lock operations
|
|
1065
1291
|
*
|
|
1066
|
-
* @example
|
|
1292
|
+
* @example Basic usage with try-finally pattern
|
|
1067
1293
|
* ```typescript
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1294
|
+
* import { MemoryLock } from '@gravito/constellation'
|
|
1295
|
+
*
|
|
1296
|
+
* const lock = new MemoryLock()
|
|
1297
|
+
*
|
|
1298
|
+
* // Attempt to acquire lock with 60-second TTL
|
|
1299
|
+
* const acquired = await lock.acquire('sitemap-generation', 60000)
|
|
1300
|
+
* if (acquired) {
|
|
1301
|
+
* try {
|
|
1302
|
+
* // Perform exclusive operation
|
|
1303
|
+
* await generateSitemap()
|
|
1304
|
+
* } finally {
|
|
1305
|
+
* // Always release lock to prevent resource leakage
|
|
1306
|
+
* await lock.release('sitemap-generation')
|
|
1307
|
+
* }
|
|
1308
|
+
* } else {
|
|
1309
|
+
* console.log('Another process is generating sitemap, skipping')
|
|
1310
|
+
* }
|
|
1073
1311
|
* ```
|
|
1074
1312
|
*
|
|
1075
|
-
* @example
|
|
1313
|
+
* @example Handling lock contention
|
|
1076
1314
|
* ```typescript
|
|
1077
|
-
* const
|
|
1078
|
-
*
|
|
1079
|
-
*
|
|
1080
|
-
*
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1315
|
+
* const lock = new MemoryLock()
|
|
1316
|
+
*
|
|
1317
|
+
* if (!await lock.acquire('expensive-operation', 30000)) {
|
|
1318
|
+
* return new Response('Service busy, try again later', {
|
|
1319
|
+
* status: 503,
|
|
1320
|
+
* headers: { 'Retry-After': '30' }
|
|
1321
|
+
* })
|
|
1322
|
+
* }
|
|
1083
1323
|
* ```
|
|
1084
1324
|
*
|
|
1085
1325
|
* @public
|
|
1086
|
-
* @since 3.
|
|
1326
|
+
* @since 3.1.0
|
|
1087
1327
|
*/
|
|
1088
|
-
declare class
|
|
1089
|
-
private options;
|
|
1090
|
-
private mode;
|
|
1091
|
-
private constructor();
|
|
1328
|
+
declare class MemoryLock implements SitemapLock {
|
|
1092
1329
|
/**
|
|
1093
|
-
*
|
|
1330
|
+
* Internal map storing resource identifiers to their lock expiration timestamps.
|
|
1094
1331
|
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1332
|
+
* Keys represent unique resource identifiers (e.g., 'sitemap-generation').
|
|
1333
|
+
* Values are Unix timestamps in milliseconds representing when the lock expires.
|
|
1334
|
+
* Expired locks are automatically cleaned up during `acquire()` and `isLocked()` calls.
|
|
1097
1335
|
*/
|
|
1098
|
-
|
|
1336
|
+
private locks;
|
|
1099
1337
|
/**
|
|
1100
|
-
*
|
|
1338
|
+
* Attempts to acquire an exclusive lock on the specified resource.
|
|
1101
1339
|
*
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1340
|
+
* Uses a test-and-set approach: checks if the lock exists and is valid, then atomically
|
|
1341
|
+
* sets the lock if available. Expired locks are treated as available and automatically
|
|
1342
|
+
* replaced during acquisition.
|
|
1343
|
+
*
|
|
1344
|
+
* **Behavior:**
|
|
1345
|
+
* - Returns `true` if lock was successfully acquired
|
|
1346
|
+
* - Returns `false` if resource is already locked by another caller
|
|
1347
|
+
* - Automatically replaces expired locks (acts as self-healing mechanism)
|
|
1348
|
+
* - Lock automatically expires after TTL milliseconds
|
|
1349
|
+
*
|
|
1350
|
+
* **Race condition handling:**
|
|
1351
|
+
* Safe within a single process due to JavaScript's single-threaded event loop.
|
|
1352
|
+
* NOT safe across multiple processes or instances (use RedisLock for that).
|
|
1353
|
+
*
|
|
1354
|
+
* @param resource - Unique identifier for the resource to lock (e.g., 'sitemap-generation', 'blog-index').
|
|
1355
|
+
* Should be consistent across all callers attempting to lock the same resource.
|
|
1356
|
+
* @param ttl - Time-to-live in milliseconds. Lock automatically expires after this duration.
|
|
1357
|
+
* Recommended: 2-5x the expected operation duration to prevent premature expiration.
|
|
1358
|
+
* @returns Promise resolving to `true` if lock acquired, `false` if already locked.
|
|
1359
|
+
*
|
|
1360
|
+
* @example Preventing concurrent sitemap generation
|
|
1361
|
+
* ```typescript
|
|
1362
|
+
* const lock = new MemoryLock()
|
|
1363
|
+
* const acquired = await lock.acquire('sitemap-generation', 60000)
|
|
1364
|
+
*
|
|
1365
|
+
* if (!acquired) {
|
|
1366
|
+
* console.log('Another process is already generating the sitemap')
|
|
1367
|
+
* return new Response('Generation in progress', { status: 503 })
|
|
1368
|
+
* }
|
|
1369
|
+
*
|
|
1370
|
+
* try {
|
|
1371
|
+
* await generateSitemap()
|
|
1372
|
+
* } finally {
|
|
1373
|
+
* await lock.release('sitemap-generation')
|
|
1374
|
+
* }
|
|
1375
|
+
* ```
|
|
1376
|
+
*
|
|
1377
|
+
* @example Setting appropriate TTL
|
|
1378
|
+
* ```typescript
|
|
1379
|
+
* // For fast operations (< 1 second), use short TTL
|
|
1380
|
+
* await lock.acquire('cache-refresh', 5000)
|
|
1381
|
+
*
|
|
1382
|
+
* // For slow operations (minutes), use longer TTL
|
|
1383
|
+
* await lock.acquire('full-reindex', 300000) // 5 minutes
|
|
1384
|
+
* ```
|
|
1104
1385
|
*/
|
|
1105
|
-
|
|
1386
|
+
acquire(resource: string, ttl: number): Promise<boolean>;
|
|
1106
1387
|
/**
|
|
1107
|
-
*
|
|
1388
|
+
* Releases the lock on the specified resource, allowing others to acquire it.
|
|
1108
1389
|
*
|
|
1109
|
-
*
|
|
1390
|
+
* Immediately removes the lock from memory without any ownership validation.
|
|
1391
|
+
* Unlike RedisLock, this does NOT verify that the caller is the lock owner,
|
|
1392
|
+
* so callers must ensure they only release locks they acquired.
|
|
1393
|
+
*
|
|
1394
|
+
* **Best practices:**
|
|
1395
|
+
* - Always call `release()` in a `finally` block to prevent lock leakage
|
|
1396
|
+
* - Only release locks you successfully acquired
|
|
1397
|
+
* - If operation fails, still release the lock to allow retry
|
|
1398
|
+
*
|
|
1399
|
+
* **Idempotency:**
|
|
1400
|
+
* Safe to call multiple times on the same resource. Releasing a non-existent
|
|
1401
|
+
* lock is a no-op.
|
|
1402
|
+
*
|
|
1403
|
+
* @param resource - The resource identifier to unlock. Must match the identifier
|
|
1404
|
+
* used in the corresponding `acquire()` call.
|
|
1405
|
+
*
|
|
1406
|
+
* @example Proper release pattern with try-finally
|
|
1407
|
+
* ```typescript
|
|
1408
|
+
* const acquired = await lock.acquire('sitemap-generation', 60000)
|
|
1409
|
+
* if (!acquired) return
|
|
1410
|
+
*
|
|
1411
|
+
* try {
|
|
1412
|
+
* await generateSitemap()
|
|
1413
|
+
* } finally {
|
|
1414
|
+
* // Always release, even if operation throws
|
|
1415
|
+
* await lock.release('sitemap-generation')
|
|
1416
|
+
* }
|
|
1417
|
+
* ```
|
|
1418
|
+
*
|
|
1419
|
+
* @example Handling operation failures
|
|
1420
|
+
* ```typescript
|
|
1421
|
+
* const acquired = await lock.acquire('data-import', 120000)
|
|
1422
|
+
* if (!acquired) return
|
|
1423
|
+
*
|
|
1424
|
+
* try {
|
|
1425
|
+
* await importData()
|
|
1426
|
+
* } catch (error) {
|
|
1427
|
+
* console.error('Import failed:', error)
|
|
1428
|
+
* // Lock still released in finally block
|
|
1429
|
+
* throw error
|
|
1430
|
+
* } finally {
|
|
1431
|
+
* await lock.release('data-import')
|
|
1432
|
+
* }
|
|
1433
|
+
* ```
|
|
1434
|
+
*/
|
|
1435
|
+
release(resource: string): Promise<void>;
|
|
1436
|
+
/**
|
|
1437
|
+
* Checks whether a resource is currently locked and has not expired.
|
|
1438
|
+
*
|
|
1439
|
+
* Performs automatic cleanup by removing expired locks during the check,
|
|
1440
|
+
* ensuring the internal map doesn't accumulate stale entries over time.
|
|
1441
|
+
*
|
|
1442
|
+
* **Use cases:**
|
|
1443
|
+
* - Pre-flight checks before attempting expensive operations
|
|
1444
|
+
* - Status monitoring and health checks
|
|
1445
|
+
* - Implementing custom retry logic
|
|
1446
|
+
* - Debugging and testing
|
|
1447
|
+
*
|
|
1448
|
+
* **Side effects:**
|
|
1449
|
+
* Automatically deletes expired locks as a garbage collection mechanism.
|
|
1450
|
+
* This is intentional to prevent memory leaks from abandoned locks.
|
|
1451
|
+
*
|
|
1452
|
+
* @param resource - The resource identifier to check for lock status.
|
|
1453
|
+
* @returns Promise resolving to `true` if resource is actively locked (not expired),
|
|
1454
|
+
* `false` if unlocked or lock has expired.
|
|
1455
|
+
*
|
|
1456
|
+
* @example Pre-flight check before starting work
|
|
1457
|
+
* ```typescript
|
|
1458
|
+
* const lock = new MemoryLock()
|
|
1459
|
+
*
|
|
1460
|
+
* if (await lock.isLocked('sitemap-generation')) {
|
|
1461
|
+
* console.log('Sitemap generation already in progress')
|
|
1462
|
+
* return
|
|
1463
|
+
* }
|
|
1464
|
+
*
|
|
1465
|
+
* // Safe to proceed
|
|
1466
|
+
* await lock.acquire('sitemap-generation', 60000)
|
|
1467
|
+
* ```
|
|
1468
|
+
*
|
|
1469
|
+
* @example Health check endpoint
|
|
1470
|
+
* ```typescript
|
|
1471
|
+
* app.get('/health/locks', async (c) => {
|
|
1472
|
+
* const isGenerating = await lock.isLocked('sitemap-generation')
|
|
1473
|
+
* const isIndexing = await lock.isLocked('search-indexing')
|
|
1474
|
+
*
|
|
1475
|
+
* return c.json({
|
|
1476
|
+
* sitemapGeneration: isGenerating ? 'in-progress' : 'idle',
|
|
1477
|
+
* searchIndexing: isIndexing ? 'in-progress' : 'idle'
|
|
1478
|
+
* })
|
|
1479
|
+
* })
|
|
1480
|
+
* ```
|
|
1481
|
+
*
|
|
1482
|
+
* @example Custom retry logic
|
|
1483
|
+
* ```typescript
|
|
1484
|
+
* let attempts = 0
|
|
1485
|
+
* while (attempts < 5) {
|
|
1486
|
+
* if (!await lock.isLocked('resource')) {
|
|
1487
|
+
* const acquired = await lock.acquire('resource', 10000)
|
|
1488
|
+
* if (acquired) break
|
|
1489
|
+
* }
|
|
1490
|
+
* await sleep(1000)
|
|
1491
|
+
* attempts++
|
|
1492
|
+
* }
|
|
1493
|
+
* ```
|
|
1494
|
+
*/
|
|
1495
|
+
isLocked(resource: string): Promise<boolean>;
|
|
1496
|
+
/**
|
|
1497
|
+
* Clears all locks from memory, including both active and expired locks.
|
|
1498
|
+
*
|
|
1499
|
+
* **Use cases:**
|
|
1500
|
+
* - Test cleanup between test cases to ensure isolation
|
|
1501
|
+
* - Application shutdown to release all resources
|
|
1502
|
+
* - Manual intervention during debugging
|
|
1503
|
+
* - Resetting state after catastrophic errors
|
|
1504
|
+
*
|
|
1505
|
+
* **Warning:**
|
|
1506
|
+
* This forcibly releases ALL locks without any ownership validation.
|
|
1507
|
+
* Should not be called during normal operation in production environments.
|
|
1508
|
+
*
|
|
1509
|
+
* @example Test cleanup with beforeEach hook
|
|
1510
|
+
* ```typescript
|
|
1511
|
+
* import { describe, beforeEach, test } from 'vitest'
|
|
1512
|
+
*
|
|
1513
|
+
* const lock = new MemoryLock()
|
|
1514
|
+
*
|
|
1515
|
+
* beforeEach(async () => {
|
|
1516
|
+
* await lock.clear() // Ensure clean state for each test
|
|
1517
|
+
* })
|
|
1518
|
+
*
|
|
1519
|
+
* test('lock acquisition', async () => {
|
|
1520
|
+
* const acquired = await lock.acquire('test-resource', 5000)
|
|
1521
|
+
* expect(acquired).toBe(true)
|
|
1522
|
+
* })
|
|
1523
|
+
* ```
|
|
1524
|
+
*
|
|
1525
|
+
* @example Graceful shutdown handler
|
|
1526
|
+
* ```typescript
|
|
1527
|
+
* process.on('SIGTERM', async () => {
|
|
1528
|
+
* console.log('Shutting down, releasing all locks...')
|
|
1529
|
+
* await lock.clear()
|
|
1530
|
+
* process.exit(0)
|
|
1531
|
+
* })
|
|
1532
|
+
* ```
|
|
1533
|
+
*/
|
|
1534
|
+
clear(): Promise<void>;
|
|
1535
|
+
/**
|
|
1536
|
+
* Returns the number of lock entries currently stored in memory.
|
|
1537
|
+
*
|
|
1538
|
+
* **Important:** This includes BOTH active and expired locks. Expired locks
|
|
1539
|
+
* are only cleaned up during `acquire()` or `isLocked()` calls, so this count
|
|
1540
|
+
* may include stale entries.
|
|
1541
|
+
*
|
|
1542
|
+
* **Use cases:**
|
|
1543
|
+
* - Monitoring memory usage and lock accumulation
|
|
1544
|
+
* - Debugging lock leakage issues
|
|
1545
|
+
* - Testing lock lifecycle behavior
|
|
1546
|
+
* - Detecting abnormal lock retention patterns
|
|
1547
|
+
*
|
|
1548
|
+
* **Not suitable for:**
|
|
1549
|
+
* - Determining number of ACTIVE locks (use `isLocked()` on each resource)
|
|
1550
|
+
* - Production health checks (includes expired locks)
|
|
1551
|
+
*
|
|
1552
|
+
* @returns The total number of lock entries in the internal Map, including expired ones.
|
|
1553
|
+
*
|
|
1554
|
+
* @example Monitoring lock accumulation
|
|
1555
|
+
* ```typescript
|
|
1556
|
+
* const lock = new MemoryLock()
|
|
1557
|
+
*
|
|
1558
|
+
* setInterval(() => {
|
|
1559
|
+
* const count = lock.size()
|
|
1560
|
+
* if (count > 100) {
|
|
1561
|
+
* console.warn(`High lock count detected: ${count}`)
|
|
1562
|
+
* // May indicate lock leakage or missing release() calls
|
|
1563
|
+
* }
|
|
1564
|
+
* }, 60000)
|
|
1565
|
+
* ```
|
|
1566
|
+
*
|
|
1567
|
+
* @example Testing lock cleanup behavior
|
|
1568
|
+
* ```typescript
|
|
1569
|
+
* import { test, expect } from 'vitest'
|
|
1570
|
+
*
|
|
1571
|
+
* test('expired locks are cleaned up', async () => {
|
|
1572
|
+
* const lock = new MemoryLock()
|
|
1573
|
+
*
|
|
1574
|
+
* await lock.acquire('resource', 10)
|
|
1575
|
+
* expect(lock.size()).toBe(1)
|
|
1576
|
+
*
|
|
1577
|
+
* await sleep(20) // Wait for expiration
|
|
1578
|
+
* expect(lock.size()).toBe(1) // Still in map (not cleaned yet)
|
|
1579
|
+
*
|
|
1580
|
+
* await lock.isLocked('resource') // Triggers cleanup
|
|
1581
|
+
* expect(lock.size()).toBe(0) // Now removed
|
|
1582
|
+
* })
|
|
1583
|
+
* ```
|
|
1584
|
+
*/
|
|
1585
|
+
size(): number;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Minimal Redis client interface required for distributed locking operations.
|
|
1590
|
+
*
|
|
1591
|
+
* Designed to be compatible with popular Redis clients (node-redis, ioredis)
|
|
1592
|
+
* while keeping dependencies minimal. Only requires SET NX EX and EVAL commands
|
|
1593
|
+
* for atomic lock operations.
|
|
1594
|
+
*
|
|
1595
|
+
* @public
|
|
1596
|
+
* @since 3.1.0
|
|
1597
|
+
*/
|
|
1598
|
+
interface RedisClient {
|
|
1599
|
+
/**
|
|
1600
|
+
* Atomically sets a key with value only if key doesn't exist (NX) and expires after TTL seconds (EX).
|
|
1601
|
+
*
|
|
1602
|
+
* This is the core primitive for distributed lock acquisition. The combination of
|
|
1603
|
+
* NX (Not eXists) and EX (EXpiration) flags ensures atomicity and auto-release.
|
|
1604
|
+
*
|
|
1605
|
+
* @param key - Redis key for the lock (e.g., 'sitemap:lock:generation')
|
|
1606
|
+
* @param value - Unique lock identifier (UUID) for ownership validation
|
|
1607
|
+
* @param mode - Must be 'EX' for expiration in seconds
|
|
1608
|
+
* @param ttl - Time-to-live in seconds before auto-release
|
|
1609
|
+
* @param flag - Must be 'NX' to set only if not exists
|
|
1610
|
+
* @returns 'OK' if lock acquired, null if key already exists
|
|
1611
|
+
*/
|
|
1612
|
+
set(key: string, value: string, mode: 'EX', ttl: number, flag: 'NX'): Promise<string | null>;
|
|
1613
|
+
/**
|
|
1614
|
+
* Executes a Lua script atomically on the Redis server.
|
|
1615
|
+
*
|
|
1616
|
+
* Used for safe lock release: compares lock owner and deletes in a single atomic operation.
|
|
1617
|
+
* This prevents accidentally releasing locks held by other instances.
|
|
1618
|
+
*
|
|
1619
|
+
* @param script - Lua script source code
|
|
1620
|
+
* @param numKeys - Number of Redis keys referenced in the script
|
|
1621
|
+
* @param args - Keys and arguments for the script (KEYS[], ARGV[])
|
|
1622
|
+
* @returns Script execution result (1 if deleted, 0 if not owner)
|
|
1623
|
+
*/
|
|
1624
|
+
eval(script: string, numKeys: number, ...args: string[]): Promise<number>;
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Configuration options for RedisLock distributed locking.
|
|
1628
|
+
*
|
|
1629
|
+
* @public
|
|
1630
|
+
* @since 3.1.0
|
|
1631
|
+
*/
|
|
1632
|
+
interface RedisLockOptions {
|
|
1633
|
+
/**
|
|
1634
|
+
* Connected Redis client instance.
|
|
1635
|
+
*
|
|
1636
|
+
* Must be already connected and ready for commands.
|
|
1637
|
+
* Supports node-redis, ioredis, or any client implementing RedisClient interface.
|
|
1638
|
+
*/
|
|
1639
|
+
client: RedisClient;
|
|
1640
|
+
/**
|
|
1641
|
+
* Prefix for all lock keys in Redis.
|
|
1642
|
+
*
|
|
1643
|
+
* Recommended to include namespace and entity type for clarity.
|
|
1644
|
+
*
|
|
1645
|
+
* @defaultValue 'sitemap:lock:'
|
|
1646
|
+
* @example 'myapp:sitemap:lock:'
|
|
1647
|
+
*/
|
|
1648
|
+
keyPrefix?: string;
|
|
1649
|
+
/**
|
|
1650
|
+
* Number of retry attempts if lock acquisition fails.
|
|
1651
|
+
*
|
|
1652
|
+
* Set to 0 for fail-fast behavior (no retries).
|
|
1653
|
+
* Recommended: 3-5 retries for transient contention.
|
|
1654
|
+
*
|
|
1655
|
+
* @defaultValue 0
|
|
1656
|
+
*/
|
|
1657
|
+
retryCount?: number;
|
|
1658
|
+
/**
|
|
1659
|
+
* Delay in milliseconds between retry attempts.
|
|
1660
|
+
*
|
|
1661
|
+
* Should be tuned based on expected lock hold time.
|
|
1662
|
+
* Too short: wastes CPU, too long: increases latency.
|
|
1663
|
+
*
|
|
1664
|
+
* @defaultValue 100
|
|
1665
|
+
*/
|
|
1666
|
+
retryDelay?: number;
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Redis-based distributed lock for multi-instance sitemap generation.
|
|
1670
|
+
*
|
|
1671
|
+
* Implements distributed mutual exclusion using Redis atomic operations (SET NX EX)
|
|
1672
|
+
* and Lua scripts for safe ownership-validated release. Designed for production
|
|
1673
|
+
* environments where multiple instances compete for exclusive access to resources.
|
|
1674
|
+
*
|
|
1675
|
+
* **When to use:**
|
|
1676
|
+
* - Kubernetes or Docker Swarm multi-replica deployments
|
|
1677
|
+
* - Horizontally scaled applications (multiple instances/VMs)
|
|
1678
|
+
* - Any environment requiring cross-process/cross-machine synchronization
|
|
1679
|
+
* - Production systems where cache stampede prevention is critical
|
|
1680
|
+
*
|
|
1681
|
+
* **When NOT to use:**
|
|
1682
|
+
* - Single-instance deployments (use {@link MemoryLock} instead)
|
|
1683
|
+
* - Development/testing environments without Redis infrastructure
|
|
1684
|
+
* - Scenarios where eventual consistency is acceptable
|
|
1685
|
+
*
|
|
1686
|
+
* **Design rationale:**
|
|
1687
|
+
* - Atomic operations prevent race conditions at Redis level
|
|
1688
|
+
* - Unique lock ID (UUID) ensures only owner can release lock
|
|
1689
|
+
* - Auto-expiration (TTL) prevents deadlocks from crashed instances
|
|
1690
|
+
* - Retry mechanism handles transient contention gracefully
|
|
1691
|
+
* - Lua scripts provide atomicity for compare-and-delete operations
|
|
1692
|
+
*
|
|
1693
|
+
* **Comparison with RedLock:**
|
|
1694
|
+
* This implementation uses a single Redis instance. For Redis Cluster deployments,
|
|
1695
|
+
* consider implementing the RedLock algorithm (multiple Redis masters) for higher
|
|
1696
|
+
* availability. Current implementation trades off some availability for simplicity.
|
|
1697
|
+
*
|
|
1698
|
+
* @example Basic usage with node-redis
|
|
1699
|
+
* ```typescript
|
|
1700
|
+
* import { RedisLock } from '@gravito/constellation'
|
|
1701
|
+
* import { createClient } from 'redis'
|
|
1702
|
+
*
|
|
1703
|
+
* const redisClient = createClient({ url: 'redis://localhost:6379' })
|
|
1704
|
+
* await redisClient.connect()
|
|
1705
|
+
*
|
|
1706
|
+
* const lock = new RedisLock({
|
|
1707
|
+
* client: redisClient,
|
|
1708
|
+
* keyPrefix: 'sitemap:lock:',
|
|
1709
|
+
* retryCount: 3,
|
|
1710
|
+
* retryDelay: 100
|
|
1711
|
+
* })
|
|
1712
|
+
*
|
|
1713
|
+
* const acquired = await lock.acquire('sitemap-generation', 60000)
|
|
1714
|
+
* if (acquired) {
|
|
1715
|
+
* try {
|
|
1716
|
+
* await generateSitemap()
|
|
1717
|
+
* } finally {
|
|
1718
|
+
* await lock.release('sitemap-generation')
|
|
1719
|
+
* }
|
|
1720
|
+
* }
|
|
1721
|
+
* ```
|
|
1722
|
+
*
|
|
1723
|
+
* @example Production deployment with Kubernetes
|
|
1724
|
+
* ```typescript
|
|
1725
|
+
* // In Kubernetes, multiple pods compete for the lock
|
|
1726
|
+
* const lock = new RedisLock({
|
|
1727
|
+
* client: redisClient,
|
|
1728
|
+
* keyPrefix: `${process.env.K8S_NAMESPACE}:sitemap:lock:`,
|
|
1729
|
+
* retryCount: 5,
|
|
1730
|
+
* retryDelay: 200
|
|
1731
|
+
* })
|
|
1732
|
+
*
|
|
1733
|
+
* // Pod 1 acquires lock
|
|
1734
|
+
* const acquired = await lock.acquire('generation', 300000)
|
|
1735
|
+
* if (!acquired) {
|
|
1736
|
+
* // Pod 2, 3, ... receive 503 and retry later
|
|
1737
|
+
* return new Response('Another pod is generating sitemap', {
|
|
1738
|
+
* status: 503,
|
|
1739
|
+
* headers: { 'Retry-After': '60' }
|
|
1740
|
+
* })
|
|
1741
|
+
* }
|
|
1742
|
+
* ```
|
|
1743
|
+
*
|
|
1744
|
+
* @example Handling Redis connection failures
|
|
1745
|
+
* ```typescript
|
|
1746
|
+
* const lock = new RedisLock({ client: redisClient })
|
|
1747
|
+
*
|
|
1748
|
+
* try {
|
|
1749
|
+
* const acquired = await lock.acquire('resource', 30000)
|
|
1750
|
+
* // If Redis is down, acquire() returns false (logged internally)
|
|
1751
|
+
* if (!acquired) {
|
|
1752
|
+
* console.log('Lock acquisition failed (may be Redis connection issue)')
|
|
1753
|
+
* }
|
|
1754
|
+
* } catch (error) {
|
|
1755
|
+
* // Network errors are caught and logged, not thrown
|
|
1756
|
+
* console.error('Unexpected error:', error)
|
|
1757
|
+
* }
|
|
1758
|
+
* ```
|
|
1759
|
+
*
|
|
1760
|
+
* @public
|
|
1761
|
+
* @since 3.1.0
|
|
1762
|
+
*/
|
|
1763
|
+
declare class RedisLock implements SitemapLock {
|
|
1764
|
+
private options;
|
|
1765
|
+
/**
|
|
1766
|
+
* Unique identifier for this lock instance.
|
|
1767
|
+
*
|
|
1768
|
+
* Generated once during construction and used for all locks acquired by this instance.
|
|
1769
|
+
* Enables ownership validation: only the instance that acquired the lock can release it.
|
|
1770
|
+
*
|
|
1771
|
+
* **Security consideration:**
|
|
1772
|
+
* UUIDs are sufficiently random to prevent lock hijacking across instances.
|
|
1773
|
+
* However, they are stored in plain text in Redis (not encrypted).
|
|
1774
|
+
*/
|
|
1775
|
+
private lockId;
|
|
1776
|
+
/**
|
|
1777
|
+
* Redis key prefix for all locks acquired through this instance.
|
|
1778
|
+
*
|
|
1779
|
+
* Combined with resource name to form full Redis key (e.g., 'sitemap:lock:generation').
|
|
1780
|
+
* Allows namespace isolation and easier debugging in Redis CLI.
|
|
1781
|
+
*/
|
|
1782
|
+
private keyPrefix;
|
|
1783
|
+
/**
|
|
1784
|
+
* Maximum number of retry attempts when lock is held by another instance.
|
|
1785
|
+
*
|
|
1786
|
+
* Set to 0 for fail-fast behavior. Higher values increase acquisition success
|
|
1787
|
+
* rate but also increase latency under contention.
|
|
1788
|
+
*/
|
|
1789
|
+
private retryCount;
|
|
1790
|
+
/**
|
|
1791
|
+
* Delay in milliseconds between consecutive retry attempts.
|
|
1792
|
+
*
|
|
1793
|
+
* Should be tuned based on expected lock hold time. Typical values: 50-500ms.
|
|
1794
|
+
*/
|
|
1795
|
+
private retryDelay;
|
|
1796
|
+
/**
|
|
1797
|
+
* Constructs a new RedisLock instance with the specified configuration.
|
|
1798
|
+
*
|
|
1799
|
+
* @param options - Configuration including Redis client and retry parameters.
|
|
1800
|
+
*
|
|
1801
|
+
* @example With custom retry strategy
|
|
1802
|
+
* ```typescript
|
|
1803
|
+
* const lock = new RedisLock({
|
|
1804
|
+
* client: redisClient,
|
|
1805
|
+
* keyPrefix: 'app:locks:',
|
|
1806
|
+
* retryCount: 10, // More retries for high-contention scenarios
|
|
1807
|
+
* retryDelay: 50 // Shorter delay for low-latency requirements
|
|
1808
|
+
* })
|
|
1809
|
+
* ```
|
|
1810
|
+
*/
|
|
1811
|
+
constructor(options: RedisLockOptions);
|
|
1812
|
+
/**
|
|
1813
|
+
* Attempts to acquire a distributed lock using Redis SET NX EX command.
|
|
1814
|
+
*
|
|
1815
|
+
* Uses atomic Redis operations to ensure only one instance across your entire
|
|
1816
|
+
* infrastructure can hold the lock at any given time. Implements retry logic
|
|
1817
|
+
* with exponential backoff for handling transient contention.
|
|
1818
|
+
*
|
|
1819
|
+
* **Algorithm:**
|
|
1820
|
+
* 1. Convert TTL from milliseconds to seconds (Redis requirement)
|
|
1821
|
+
* 2. Attempt Redis SET key lockId EX ttl NX (atomic operation)
|
|
1822
|
+
* 3. If successful (returns 'OK'), lock acquired
|
|
1823
|
+
* 4. If failed (returns null), retry up to retryCount times with retryDelay
|
|
1824
|
+
* 5. Return true if acquired, false if all attempts exhausted
|
|
1825
|
+
*
|
|
1826
|
+
* **Atomicity guarantee:**
|
|
1827
|
+
* The combination of NX (set if Not eXists) and EX (set EXpiration) in a single
|
|
1828
|
+
* Redis command ensures no race conditions. Either the lock is acquired or it isn't.
|
|
1829
|
+
*
|
|
1830
|
+
* **Error handling:**
|
|
1831
|
+
* Redis connection errors are caught, logged to console, and treated as acquisition
|
|
1832
|
+
* failure (returns false). This fail-safe behavior prevents exceptions from bubbling
|
|
1833
|
+
* up to application code.
|
|
1834
|
+
*
|
|
1835
|
+
* **Performance:**
|
|
1836
|
+
* - Single instance: O(1) Redis operation
|
|
1837
|
+
* - With retry: O(retryCount) worst case
|
|
1838
|
+
* - Network latency: ~1-5ms per attempt (depends on Redis location)
|
|
1839
|
+
*
|
|
1840
|
+
* @param resource - Unique identifier for the resource to lock.
|
|
1841
|
+
* Combined with keyPrefix to form Redis key.
|
|
1842
|
+
* @param ttl - Time-to-live in milliseconds. Lock auto-expires after this duration.
|
|
1843
|
+
* Recommended: 2-5x expected operation time to handle slowdowns.
|
|
1844
|
+
* Minimum: 1000ms (1 second) for practical use.
|
|
1845
|
+
* @returns Promise resolving to `true` if lock acquired, `false` if held by another instance
|
|
1846
|
+
* or Redis connection failed.
|
|
1847
|
+
*
|
|
1848
|
+
* @example Basic acquisition in distributed environment
|
|
1849
|
+
* ```typescript
|
|
1850
|
+
* const lock = new RedisLock({ client: redisClient })
|
|
1851
|
+
* const acquired = await lock.acquire('sitemap-generation', 60000)
|
|
1852
|
+
*
|
|
1853
|
+
* if (!acquired) {
|
|
1854
|
+
* // Another instance (e.g., different Kubernetes pod) holds the lock
|
|
1855
|
+
* console.log('Sitemap generation in progress on another instance')
|
|
1856
|
+
* return new Response('Service busy', {
|
|
1857
|
+
* status: 503,
|
|
1858
|
+
* headers: { 'Retry-After': '30' }
|
|
1859
|
+
* })
|
|
1860
|
+
* }
|
|
1861
|
+
*
|
|
1862
|
+
* try {
|
|
1863
|
+
* await generateSitemap()
|
|
1864
|
+
* } finally {
|
|
1865
|
+
* await lock.release('sitemap-generation')
|
|
1866
|
+
* }
|
|
1867
|
+
* ```
|
|
1868
|
+
*
|
|
1869
|
+
* @example With retry logic for transient contention
|
|
1870
|
+
* ```typescript
|
|
1871
|
+
* const lock = new RedisLock({
|
|
1872
|
+
* client: redisClient,
|
|
1873
|
+
* retryCount: 5,
|
|
1874
|
+
* retryDelay: 200
|
|
1875
|
+
* })
|
|
1876
|
+
*
|
|
1877
|
+
* // Will retry 5 times with 200ms delay between attempts
|
|
1878
|
+
* const acquired = await lock.acquire('data-import', 120000)
|
|
1879
|
+
* ```
|
|
1880
|
+
*
|
|
1881
|
+
* @example Setting appropriate TTL
|
|
1882
|
+
* ```typescript
|
|
1883
|
+
* // Fast operation: short TTL
|
|
1884
|
+
* await lock.acquire('cache-rebuild', 10000) // 10 seconds
|
|
1885
|
+
*
|
|
1886
|
+
* // Slow operation: longer TTL with buffer
|
|
1887
|
+
* await lock.acquire('full-sitemap', 300000) // 5 minutes
|
|
1888
|
+
*
|
|
1889
|
+
* // Very slow operation: generous TTL
|
|
1890
|
+
* await lock.acquire('data-migration', 1800000) // 30 minutes
|
|
1891
|
+
* ```
|
|
1892
|
+
*/
|
|
1893
|
+
acquire(resource: string, ttl: number): Promise<boolean>;
|
|
1894
|
+
/**
|
|
1895
|
+
* Releases the distributed lock using Lua script for atomic ownership validation.
|
|
1896
|
+
*
|
|
1897
|
+
* Uses a Lua script to atomically check if the current instance owns the lock
|
|
1898
|
+
* (by comparing lockId) and delete it if so. This prevents accidentally releasing
|
|
1899
|
+
* locks held by other instances, which could cause data corruption in distributed systems.
|
|
1900
|
+
*
|
|
1901
|
+
* **Lua script logic:**
|
|
1902
|
+
* ```lua
|
|
1903
|
+
* if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
1904
|
+
* return redis.call("del", KEYS[1]) -- Delete only if owner matches
|
|
1905
|
+
* else
|
|
1906
|
+
* return 0 -- Not owner or lock expired, do nothing
|
|
1907
|
+
* end
|
|
1908
|
+
* ```
|
|
1909
|
+
*
|
|
1910
|
+
* **Why Lua scripts?**
|
|
1911
|
+
* - Atomicity: GET + comparison + DEL execute as one atomic operation
|
|
1912
|
+
* - Prevents race conditions: Lock cannot change between check and delete
|
|
1913
|
+
* - Server-side execution: No network round trips between steps
|
|
1914
|
+
*
|
|
1915
|
+
* **Error handling:**
|
|
1916
|
+
* Errors during release (e.g., Redis connection loss) are logged but do NOT throw.
|
|
1917
|
+
* This is intentional: if instance crashed or TTL expired, lock is already released.
|
|
1918
|
+
* Silent failure here prevents cascading errors in finally blocks.
|
|
1919
|
+
*
|
|
1920
|
+
* **Idempotency:**
|
|
1921
|
+
* Safe to call multiple times. Releasing an already-released or expired lock is a no-op.
|
|
1922
|
+
*
|
|
1923
|
+
* @param resource - The resource identifier to unlock. Must match the identifier
|
|
1924
|
+
* used in the corresponding `acquire()` call.
|
|
1925
|
+
*
|
|
1926
|
+
* @example Proper release pattern with try-finally
|
|
1927
|
+
* ```typescript
|
|
1928
|
+
* const acquired = await lock.acquire('sitemap-generation', 60000)
|
|
1929
|
+
* if (!acquired) return
|
|
1930
|
+
*
|
|
1931
|
+
* try {
|
|
1932
|
+
* await generateSitemap()
|
|
1933
|
+
* } finally {
|
|
1934
|
+
* // Always release, even if operation throws
|
|
1935
|
+
* await lock.release('sitemap-generation')
|
|
1936
|
+
* }
|
|
1937
|
+
* ```
|
|
1938
|
+
*
|
|
1939
|
+
* @example Handling operation failures
|
|
1940
|
+
* ```typescript
|
|
1941
|
+
* const acquired = await lock.acquire('data-processing', 120000)
|
|
1942
|
+
* if (!acquired) {
|
|
1943
|
+
* throw new Error('Could not acquire lock')
|
|
1944
|
+
* }
|
|
1945
|
+
*
|
|
1946
|
+
* try {
|
|
1947
|
+
* await processData()
|
|
1948
|
+
* } catch (error) {
|
|
1949
|
+
* console.error('Processing failed:', error)
|
|
1950
|
+
* // Lock still released in finally block
|
|
1951
|
+
* throw error
|
|
1952
|
+
* } finally {
|
|
1953
|
+
* await lock.release('data-processing')
|
|
1954
|
+
* }
|
|
1955
|
+
* ```
|
|
1956
|
+
*
|
|
1957
|
+
* @example Why ownership validation matters
|
|
1958
|
+
* ```typescript
|
|
1959
|
+
* // Instance A acquires lock with 10-second TTL
|
|
1960
|
+
* const lockA = new RedisLock({ client: redisClientA })
|
|
1961
|
+
* await lockA.acquire('task', 10000)
|
|
1962
|
+
*
|
|
1963
|
+
* // ... 11 seconds pass, lock auto-expires ...
|
|
1964
|
+
*
|
|
1965
|
+
* // Instance B acquires the now-expired lock
|
|
1966
|
+
* const lockB = new RedisLock({ client: redisClientB })
|
|
1967
|
+
* await lockB.acquire('task', 10000)
|
|
1968
|
+
*
|
|
1969
|
+
* // Instance A tries to release (after slowdown/GC pause)
|
|
1970
|
+
* await lockA.release('task')
|
|
1971
|
+
* // ✅ Lua script detects lockId mismatch, does NOT delete B's lock
|
|
1972
|
+
* // ❌ Without Lua: Would delete B's lock, causing data corruption
|
|
1973
|
+
* ```
|
|
1974
|
+
*/
|
|
1975
|
+
release(resource: string): Promise<void>;
|
|
1976
|
+
/**
|
|
1977
|
+
* Internal utility for sleeping between retry attempts.
|
|
1978
|
+
*
|
|
1979
|
+
* @param ms - Duration to sleep in milliseconds
|
|
1980
|
+
*/
|
|
1981
|
+
private sleep;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* Configuration for a dynamically generated sitemap that is built upon request.
|
|
1986
|
+
*
|
|
1987
|
+
* Useful for small to medium sites where fresh data is critical. Dynamic sitemaps
|
|
1988
|
+
* are generated on-the-fly and can be cached at the HTTP level.
|
|
1989
|
+
*
|
|
1990
|
+
* @public
|
|
1991
|
+
* @since 3.0.0
|
|
1992
|
+
*/
|
|
1993
|
+
interface DynamicSitemapOptions extends SitemapStreamOptions {
|
|
1994
|
+
/** The URL path where the sitemap will be exposed. @default '/sitemap.xml' */
|
|
1995
|
+
path?: string | undefined;
|
|
1996
|
+
/** List of sitemap entry providers to scan for content. */
|
|
1997
|
+
providers: SitemapProvider[];
|
|
1998
|
+
/** Cache duration in seconds for the HTTP response. @default undefined (no-cache) */
|
|
1999
|
+
cacheSeconds?: number | undefined;
|
|
2000
|
+
/** Persistence backend for cached XML files. Defaults to MemorySitemapStorage. */
|
|
2001
|
+
storage?: SitemapStorage | undefined;
|
|
2002
|
+
/** Optional distributed lock to prevent "cache stampede" during heavy generation. */
|
|
2003
|
+
lock?: SitemapLock | undefined;
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Configuration for a statically pre-generated sitemap.
|
|
2007
|
+
*
|
|
2008
|
+
* Recommended for large sites or high-traffic applications. Static sitemaps
|
|
2009
|
+
* are built (usually as part of a build process) and served as static files.
|
|
2010
|
+
*
|
|
2011
|
+
* @public
|
|
2012
|
+
* @since 3.0.0
|
|
2013
|
+
*/
|
|
2014
|
+
interface StaticSitemapOptions extends SitemapStreamOptions {
|
|
2015
|
+
/** Local directory where generated XML files will be saved. */
|
|
2016
|
+
outDir: string;
|
|
2017
|
+
/** The name of the root sitemap file. @default 'sitemap.xml' */
|
|
2018
|
+
filename?: string | undefined;
|
|
2019
|
+
/** List of sitemap entry providers to scan for content. */
|
|
2020
|
+
providers: SitemapProvider[];
|
|
2021
|
+
/** Custom storage backend. Defaults to DiskSitemapStorage using `outDir`. */
|
|
2022
|
+
storage?: SitemapStorage | undefined;
|
|
2023
|
+
/** Optional incremental generation settings to only update changed URLs. */
|
|
2024
|
+
incremental?: {
|
|
2025
|
+
/** Whether to enable incremental builds. */
|
|
2026
|
+
enabled: boolean;
|
|
2027
|
+
/** Backend for tracking structural site changes. */
|
|
2028
|
+
changeTracker: ChangeTracker;
|
|
2029
|
+
/** Whether to automatically record new changes during scan. @default false */
|
|
2030
|
+
autoTrack?: boolean;
|
|
2031
|
+
};
|
|
2032
|
+
/** Optional SEO redirect orchestration settings. */
|
|
2033
|
+
redirect?: {
|
|
2034
|
+
/** Whether to automatically handle 301/302 redirects found in sitemap. */
|
|
2035
|
+
enabled: boolean;
|
|
2036
|
+
/** Backend for managing and resolving redirect rules. */
|
|
2037
|
+
manager: RedirectManager;
|
|
2038
|
+
/** Strategy for resolving conflicting or chained redirects. @default 'remove_old_add_new' */
|
|
2039
|
+
strategy?: 'remove_old_add_new' | 'keep_relation' | 'update_url' | 'dual_mark';
|
|
2040
|
+
/** Whether to resolve redirect chains into a single jump. @default false */
|
|
2041
|
+
followChains?: boolean;
|
|
2042
|
+
/** Maximum number of redirect jumps to follow. @default 5 */
|
|
2043
|
+
maxChainLength?: number;
|
|
2044
|
+
};
|
|
2045
|
+
/** Deployment settings for atomic sitemap updates via shadow staging. */
|
|
2046
|
+
shadow?: {
|
|
2047
|
+
/** Whether to enable "shadow" (atomic staging) mode. */
|
|
2048
|
+
enabled: boolean;
|
|
2049
|
+
/** The update strategy: 'atomic' (full swap) or 'versioned' (archived). @default 'atomic' */
|
|
2050
|
+
mode: 'atomic' | 'versioned';
|
|
2051
|
+
};
|
|
2052
|
+
/** Persistence backend for long-running job progress tracking. */
|
|
2053
|
+
progressStorage?: SitemapProgressStorage;
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* OrbitSitemap is the enterprise SEO orchestration module for Gravito.
|
|
2057
|
+
*
|
|
2058
|
+
* It provides advanced sitemap generation supporting large-scale indexing,
|
|
2059
|
+
* automatic 301/302 redirect handling, and atomic sitemap deployments.
|
|
2060
|
+
*
|
|
2061
|
+
* It can operate in two modes:
|
|
2062
|
+
* 1. **Dynamic**: Sitemaps are generated on-the-fly and cached.
|
|
2063
|
+
* 2. **Static**: Sitemaps are pre-built (e.g., during CI/CD) and served from disk or cloud storage.
|
|
2064
|
+
*
|
|
2065
|
+
* @example Dynamic Mode
|
|
2066
|
+
* ```typescript
|
|
2067
|
+
* const sitemap = OrbitSitemap.dynamic({
|
|
2068
|
+
* baseUrl: 'https://example.com',
|
|
2069
|
+
* providers: [new PostSitemapProvider()]
|
|
2070
|
+
* });
|
|
2071
|
+
* core.addOrbit(sitemap);
|
|
2072
|
+
* ```
|
|
2073
|
+
*
|
|
2074
|
+
* @example Static Mode
|
|
2075
|
+
* ```typescript
|
|
2076
|
+
* const sitemap = OrbitSitemap.static({
|
|
2077
|
+
* baseUrl: 'https://example.com',
|
|
2078
|
+
* outDir: './public',
|
|
2079
|
+
* shadow: { enabled: true, mode: 'atomic' }
|
|
2080
|
+
* });
|
|
2081
|
+
* await sitemap.generate();
|
|
2082
|
+
* ```
|
|
2083
|
+
*
|
|
2084
|
+
* @public
|
|
2085
|
+
* @since 3.0.0
|
|
2086
|
+
*/
|
|
2087
|
+
declare class OrbitSitemap implements GravitoOrbit {
|
|
2088
|
+
private options;
|
|
2089
|
+
private mode;
|
|
2090
|
+
private constructor();
|
|
2091
|
+
/**
|
|
2092
|
+
* Create a dynamic sitemap configuration.
|
|
2093
|
+
*
|
|
2094
|
+
* @param options - The dynamic sitemap options.
|
|
2095
|
+
* @returns An OrbitSitemap instance configured for dynamic generation.
|
|
2096
|
+
*/
|
|
2097
|
+
static dynamic(options: DynamicSitemapOptions): OrbitSitemap;
|
|
2098
|
+
/**
|
|
2099
|
+
* Create a static sitemap configuration.
|
|
2100
|
+
*
|
|
2101
|
+
* @param options - The static sitemap options.
|
|
2102
|
+
* @returns An OrbitSitemap instance configured for static generation.
|
|
2103
|
+
*/
|
|
2104
|
+
static static(options: StaticSitemapOptions): OrbitSitemap;
|
|
2105
|
+
/**
|
|
2106
|
+
* Installs the sitemap module into PlanetCore.
|
|
2107
|
+
*
|
|
2108
|
+
* @param core - The PlanetCore instance.
|
|
1110
2109
|
*/
|
|
1111
2110
|
install(core: PlanetCore): void;
|
|
2111
|
+
/**
|
|
2112
|
+
* Internal method to set up dynamic sitemap routes.
|
|
2113
|
+
*/
|
|
1112
2114
|
private installDynamic;
|
|
1113
2115
|
/**
|
|
1114
|
-
*
|
|
2116
|
+
* Generates the sitemap (static mode only).
|
|
1115
2117
|
*
|
|
1116
2118
|
* @returns A promise that resolves when generation is complete.
|
|
1117
2119
|
* @throws {Error} If called in dynamic mode.
|
|
1118
2120
|
*/
|
|
1119
2121
|
generate(): Promise<void>;
|
|
1120
2122
|
/**
|
|
1121
|
-
*
|
|
2123
|
+
* Generates incremental sitemap updates (static mode only).
|
|
1122
2124
|
*
|
|
1123
2125
|
* @param since - Only include items modified since this date.
|
|
1124
2126
|
* @returns A promise that resolves when incremental generation is complete.
|
|
@@ -1126,7 +2128,7 @@ declare class OrbitSitemap implements GravitoOrbit {
|
|
|
1126
2128
|
*/
|
|
1127
2129
|
generateIncremental(since?: Date): Promise<void>;
|
|
1128
2130
|
/**
|
|
1129
|
-
*
|
|
2131
|
+
* Generates sitemap asynchronously in the background (static mode only).
|
|
1130
2132
|
*
|
|
1131
2133
|
* @param options - Options for the async generation job.
|
|
1132
2134
|
* @returns A promise resolving to the job ID.
|
|
@@ -1144,7 +2146,7 @@ declare class OrbitSitemap implements GravitoOrbit {
|
|
|
1144
2146
|
onError?: (error: Error) => void;
|
|
1145
2147
|
}): Promise<string>;
|
|
1146
2148
|
/**
|
|
1147
|
-
*
|
|
2149
|
+
* Installs API endpoints for triggering and monitoring sitemap generation.
|
|
1148
2150
|
*
|
|
1149
2151
|
* @param core - The PlanetCore instance.
|
|
1150
2152
|
* @param basePath - The base path for the API endpoints (default: '/admin/sitemap').
|
|
@@ -1193,9 +2195,12 @@ declare class RouteScanner implements SitemapProvider {
|
|
|
1193
2195
|
private options;
|
|
1194
2196
|
constructor(router: any, options?: RouteScannerOptions);
|
|
1195
2197
|
/**
|
|
1196
|
-
*
|
|
2198
|
+
* Scans the router and returns discovered static GET routes as sitemap entries.
|
|
1197
2199
|
*
|
|
1198
|
-
*
|
|
2200
|
+
* This method iterates through all registered routes in the Gravito router,
|
|
2201
|
+
* applying inclusion/exclusion filters and defaulting metadata for matching routes.
|
|
2202
|
+
*
|
|
2203
|
+
* @returns An array of `SitemapEntry` objects.
|
|
1199
2204
|
*/
|
|
1200
2205
|
getEntries(): SitemapEntry[];
|
|
1201
2206
|
private extractRoutes;
|
|
@@ -1305,27 +2310,33 @@ declare class RedirectDetector {
|
|
|
1305
2310
|
private cache;
|
|
1306
2311
|
constructor(options: RedirectDetectorOptions);
|
|
1307
2312
|
/**
|
|
1308
|
-
*
|
|
2313
|
+
* Detects redirects for a single URL using multiple strategies.
|
|
2314
|
+
*
|
|
2315
|
+
* @param url - The URL path to probe for redirects.
|
|
2316
|
+
* @returns A promise resolving to a `RedirectRule` if a redirect is found, or null.
|
|
1309
2317
|
*/
|
|
1310
2318
|
detect(url: string): Promise<RedirectRule | null>;
|
|
1311
2319
|
/**
|
|
1312
|
-
*
|
|
2320
|
+
* Batch detects redirects for multiple URLs with concurrency control.
|
|
2321
|
+
*
|
|
2322
|
+
* @param urls - An array of URL paths to probe.
|
|
2323
|
+
* @returns A promise resolving to a Map of URLs to their respective `RedirectRule` or null.
|
|
1313
2324
|
*/
|
|
1314
2325
|
detectBatch(urls: string[]): Promise<Map<string, RedirectRule | null>>;
|
|
1315
2326
|
/**
|
|
1316
|
-
*
|
|
2327
|
+
* Detects a redirect from the configured database table.
|
|
1317
2328
|
*/
|
|
1318
2329
|
private detectFromDatabase;
|
|
1319
2330
|
/**
|
|
1320
|
-
*
|
|
2331
|
+
* Detects a redirect from a static JSON configuration file.
|
|
1321
2332
|
*/
|
|
1322
2333
|
private detectFromConfig;
|
|
1323
2334
|
/**
|
|
1324
|
-
*
|
|
2335
|
+
* Auto-detects a redirect by sending an HTTP HEAD request.
|
|
1325
2336
|
*/
|
|
1326
2337
|
private detectAuto;
|
|
1327
2338
|
/**
|
|
1328
|
-
*
|
|
2339
|
+
* Caches the detection result for a URL.
|
|
1329
2340
|
*/
|
|
1330
2341
|
private cacheResult;
|
|
1331
2342
|
}
|
|
@@ -1374,23 +2385,26 @@ declare class RedirectHandler {
|
|
|
1374
2385
|
private options;
|
|
1375
2386
|
constructor(options: RedirectHandlerOptions);
|
|
1376
2387
|
/**
|
|
1377
|
-
*
|
|
2388
|
+
* Processes a list of sitemap entries and handles redirects according to the configured strategy.
|
|
2389
|
+
*
|
|
2390
|
+
* @param entries - The original list of sitemap entries.
|
|
2391
|
+
* @returns A promise resolving to the processed list of entries.
|
|
1378
2392
|
*/
|
|
1379
2393
|
processEntries(entries: SitemapEntry[]): Promise<SitemapEntry[]>;
|
|
1380
2394
|
/**
|
|
1381
|
-
*
|
|
2395
|
+
* Strategy 1: Remove old URL and add the new destination URL.
|
|
1382
2396
|
*/
|
|
1383
2397
|
private handleRemoveOldAddNew;
|
|
1384
2398
|
/**
|
|
1385
|
-
*
|
|
2399
|
+
* Strategy 2: Keep the original URL but mark the destination as canonical.
|
|
1386
2400
|
*/
|
|
1387
2401
|
private handleKeepRelation;
|
|
1388
2402
|
/**
|
|
1389
|
-
*
|
|
2403
|
+
* Strategy 3: Silently update the URL to the destination.
|
|
1390
2404
|
*/
|
|
1391
2405
|
private handleUpdateUrl;
|
|
1392
2406
|
/**
|
|
1393
|
-
*
|
|
2407
|
+
* Strategy 4: Include both the original and destination URLs.
|
|
1394
2408
|
*/
|
|
1395
2409
|
private handleDualMark;
|
|
1396
2410
|
}
|
|
@@ -1418,10 +2432,39 @@ declare class MemoryRedirectManager implements RedirectManager {
|
|
|
1418
2432
|
private rules;
|
|
1419
2433
|
private maxRules;
|
|
1420
2434
|
constructor(options?: MemoryRedirectManagerOptions);
|
|
2435
|
+
/**
|
|
2436
|
+
* Registers a single redirect rule in memory.
|
|
2437
|
+
*
|
|
2438
|
+
* @param redirect - The redirect rule to add.
|
|
2439
|
+
*/
|
|
1421
2440
|
register(redirect: RedirectRule): Promise<void>;
|
|
2441
|
+
/**
|
|
2442
|
+
* Registers multiple redirect rules in memory.
|
|
2443
|
+
*
|
|
2444
|
+
* @param redirects - An array of redirect rules.
|
|
2445
|
+
*/
|
|
1422
2446
|
registerBatch(redirects: RedirectRule[]): Promise<void>;
|
|
2447
|
+
/**
|
|
2448
|
+
* Retrieves a specific redirect rule by its source path from memory.
|
|
2449
|
+
*
|
|
2450
|
+
* @param from - The source path.
|
|
2451
|
+
* @returns A promise resolving to the redirect rule, or null if not found.
|
|
2452
|
+
*/
|
|
1423
2453
|
get(from: string): Promise<RedirectRule | null>;
|
|
2454
|
+
/**
|
|
2455
|
+
* Retrieves all registered redirect rules from memory.
|
|
2456
|
+
*
|
|
2457
|
+
* @returns A promise resolving to an array of all redirect rules.
|
|
2458
|
+
*/
|
|
1424
2459
|
getAll(): Promise<RedirectRule[]>;
|
|
2460
|
+
/**
|
|
2461
|
+
* Resolves a URL to its final destination through the redirect table.
|
|
2462
|
+
*
|
|
2463
|
+
* @param url - The URL to resolve.
|
|
2464
|
+
* @param followChains - Whether to recursively resolve chained redirects.
|
|
2465
|
+
* @param maxChainLength - Maximum depth for chain resolution.
|
|
2466
|
+
* @returns A promise resolving to the final destination URL.
|
|
2467
|
+
*/
|
|
1425
2468
|
resolve(url: string, followChains?: boolean, maxChainLength?: number): Promise<string | null>;
|
|
1426
2469
|
}
|
|
1427
2470
|
/**
|
|
@@ -1455,10 +2498,39 @@ declare class RedisRedirectManager implements RedirectManager {
|
|
|
1455
2498
|
constructor(options: RedisRedirectManagerOptions);
|
|
1456
2499
|
private getKey;
|
|
1457
2500
|
private getListKey;
|
|
2501
|
+
/**
|
|
2502
|
+
* Registers a single redirect rule in Redis.
|
|
2503
|
+
*
|
|
2504
|
+
* @param redirect - The redirect rule to add.
|
|
2505
|
+
*/
|
|
1458
2506
|
register(redirect: RedirectRule): Promise<void>;
|
|
2507
|
+
/**
|
|
2508
|
+
* Registers multiple redirect rules in Redis.
|
|
2509
|
+
*
|
|
2510
|
+
* @param redirects - An array of redirect rules.
|
|
2511
|
+
*/
|
|
1459
2512
|
registerBatch(redirects: RedirectRule[]): Promise<void>;
|
|
2513
|
+
/**
|
|
2514
|
+
* Retrieves a specific redirect rule by its source path from Redis.
|
|
2515
|
+
*
|
|
2516
|
+
* @param from - The source path.
|
|
2517
|
+
* @returns A promise resolving to the redirect rule, or null if not found.
|
|
2518
|
+
*/
|
|
1460
2519
|
get(from: string): Promise<RedirectRule | null>;
|
|
2520
|
+
/**
|
|
2521
|
+
* Retrieves all registered redirect rules from Redis.
|
|
2522
|
+
*
|
|
2523
|
+
* @returns A promise resolving to an array of all redirect rules.
|
|
2524
|
+
*/
|
|
1461
2525
|
getAll(): Promise<RedirectRule[]>;
|
|
2526
|
+
/**
|
|
2527
|
+
* Resolves a URL to its final destination through the Redis redirect table.
|
|
2528
|
+
*
|
|
2529
|
+
* @param url - The URL to resolve.
|
|
2530
|
+
* @param followChains - Whether to recursively resolve chained redirects.
|
|
2531
|
+
* @param maxChainLength - Maximum depth for chain resolution.
|
|
2532
|
+
* @returns A promise resolving to the final destination URL.
|
|
2533
|
+
*/
|
|
1462
2534
|
resolve(url: string, followChains?: boolean, maxChainLength?: number): Promise<string | null>;
|
|
1463
2535
|
}
|
|
1464
2536
|
|
|
@@ -1482,9 +2554,51 @@ declare class DiskSitemapStorage implements SitemapStorage {
|
|
|
1482
2554
|
private outDir;
|
|
1483
2555
|
private baseUrl;
|
|
1484
2556
|
constructor(outDir: string, baseUrl: string);
|
|
2557
|
+
/**
|
|
2558
|
+
* Writes sitemap content to a file on the local disk.
|
|
2559
|
+
*
|
|
2560
|
+
* @param filename - The name of the file to write.
|
|
2561
|
+
* @param content - The XML or JSON content.
|
|
2562
|
+
*/
|
|
1485
2563
|
write(filename: string, content: string): Promise<void>;
|
|
2564
|
+
/**
|
|
2565
|
+
* 使用串流方式寫入 sitemap 檔案,可選擇性啟用 gzip 壓縮。
|
|
2566
|
+
* 此方法可大幅降低大型 sitemap 的記憶體峰值。
|
|
2567
|
+
*
|
|
2568
|
+
* @param filename - 檔案名稱
|
|
2569
|
+
* @param stream - XML 內容的 AsyncIterable
|
|
2570
|
+
* @param options - 寫入選項(如壓縮、content type)
|
|
2571
|
+
*
|
|
2572
|
+
* @since 3.1.0
|
|
2573
|
+
*/
|
|
2574
|
+
writeStream(filename: string, stream: AsyncIterable<string>, options?: WriteStreamOptions): Promise<void>;
|
|
2575
|
+
/**
|
|
2576
|
+
* Reads sitemap content from a file on the local disk.
|
|
2577
|
+
*
|
|
2578
|
+
* @param filename - The name of the file to read.
|
|
2579
|
+
* @returns A promise resolving to the file content as a string, or null if not found.
|
|
2580
|
+
*/
|
|
1486
2581
|
read(filename: string): Promise<string | null>;
|
|
2582
|
+
/**
|
|
2583
|
+
* Returns a readable stream for a sitemap file on the local disk.
|
|
2584
|
+
*
|
|
2585
|
+
* @param filename - The name of the file to stream.
|
|
2586
|
+
* @returns A promise resolving to an async iterable of file chunks, or null if not found.
|
|
2587
|
+
*/
|
|
2588
|
+
readStream(filename: string): Promise<AsyncIterable<string> | null>;
|
|
2589
|
+
/**
|
|
2590
|
+
* Checks if a sitemap file exists on the local disk.
|
|
2591
|
+
*
|
|
2592
|
+
* @param filename - The name of the file to check.
|
|
2593
|
+
* @returns A promise resolving to true if the file exists, false otherwise.
|
|
2594
|
+
*/
|
|
1487
2595
|
exists(filename: string): Promise<boolean>;
|
|
2596
|
+
/**
|
|
2597
|
+
* Returns the full public URL for a sitemap file.
|
|
2598
|
+
*
|
|
2599
|
+
* @param filename - The name of the sitemap file.
|
|
2600
|
+
* @returns The public URL as a string.
|
|
2601
|
+
*/
|
|
1488
2602
|
getUrl(filename: string): string;
|
|
1489
2603
|
}
|
|
1490
2604
|
|
|
@@ -1542,26 +2656,125 @@ declare class GCPSitemapStorage implements SitemapStorage {
|
|
|
1542
2656
|
constructor(options: GCPSitemapStorageOptions);
|
|
1543
2657
|
private getStorageClient;
|
|
1544
2658
|
private getKey;
|
|
2659
|
+
/**
|
|
2660
|
+
* Writes sitemap content to a Google Cloud Storage object.
|
|
2661
|
+
*
|
|
2662
|
+
* @param filename - The name of the file to write.
|
|
2663
|
+
* @param content - The XML or JSON content.
|
|
2664
|
+
*/
|
|
1545
2665
|
write(filename: string, content: string): Promise<void>;
|
|
2666
|
+
/**
|
|
2667
|
+
* 使用串流方式寫入 sitemap 至 GCP Cloud Storage,可選擇性啟用 gzip 壓縮。
|
|
2668
|
+
*
|
|
2669
|
+
* @param filename - 檔案名稱
|
|
2670
|
+
* @param stream - XML 內容的 AsyncIterable
|
|
2671
|
+
* @param options - 寫入選項(如壓縮、content type)
|
|
2672
|
+
*
|
|
2673
|
+
* @since 3.1.0
|
|
2674
|
+
*/
|
|
2675
|
+
writeStream(filename: string, stream: AsyncIterable<string>, options?: WriteStreamOptions): Promise<void>;
|
|
2676
|
+
/**
|
|
2677
|
+
* Reads sitemap content from a Google Cloud Storage object.
|
|
2678
|
+
*
|
|
2679
|
+
* @param filename - The name of the file to read.
|
|
2680
|
+
* @returns A promise resolving to the file content as a string, or null if not found.
|
|
2681
|
+
*/
|
|
1546
2682
|
read(filename: string): Promise<string | null>;
|
|
2683
|
+
/**
|
|
2684
|
+
* Returns a readable stream for a Google Cloud Storage object.
|
|
2685
|
+
*
|
|
2686
|
+
* @param filename - The name of the file to stream.
|
|
2687
|
+
* @returns A promise resolving to an async iterable of file chunks, or null if not found.
|
|
2688
|
+
*/
|
|
2689
|
+
readStream(filename: string): Promise<AsyncIterable<string> | null>;
|
|
2690
|
+
/**
|
|
2691
|
+
* Checks if a Google Cloud Storage object exists.
|
|
2692
|
+
*
|
|
2693
|
+
* @param filename - The name of the file to check.
|
|
2694
|
+
* @returns A promise resolving to true if the file exists, false otherwise.
|
|
2695
|
+
*/
|
|
1547
2696
|
exists(filename: string): Promise<boolean>;
|
|
2697
|
+
/**
|
|
2698
|
+
* Returns the full public URL for a Google Cloud Storage object.
|
|
2699
|
+
*
|
|
2700
|
+
* @param filename - The name of the sitemap file.
|
|
2701
|
+
* @returns The public URL as a string.
|
|
2702
|
+
*/
|
|
1548
2703
|
getUrl(filename: string): string;
|
|
2704
|
+
/**
|
|
2705
|
+
* Writes content to a shadow (staged) location in Google Cloud Storage.
|
|
2706
|
+
*
|
|
2707
|
+
* @param filename - The name of the file to write.
|
|
2708
|
+
* @param content - The XML or JSON content.
|
|
2709
|
+
* @param shadowId - Optional unique session identifier.
|
|
2710
|
+
*/
|
|
1549
2711
|
writeShadow(filename: string, content: string, shadowId?: string): Promise<void>;
|
|
2712
|
+
/**
|
|
2713
|
+
* Commits all staged shadow objects in a session to production in Google Cloud Storage.
|
|
2714
|
+
*
|
|
2715
|
+
* @param shadowId - The identifier of the session to commit.
|
|
2716
|
+
*/
|
|
1550
2717
|
commitShadow(shadowId: string): Promise<void>;
|
|
2718
|
+
/**
|
|
2719
|
+
* Lists all archived versions of a specific sitemap in Google Cloud Storage.
|
|
2720
|
+
*
|
|
2721
|
+
* @param filename - The sitemap filename.
|
|
2722
|
+
* @returns A promise resolving to an array of version identifiers.
|
|
2723
|
+
*/
|
|
1551
2724
|
listVersions(filename: string): Promise<string[]>;
|
|
2725
|
+
/**
|
|
2726
|
+
* Reverts a sitemap to a previously archived version in Google Cloud Storage.
|
|
2727
|
+
*
|
|
2728
|
+
* @param filename - The sitemap filename.
|
|
2729
|
+
* @param version - The version identifier to switch to.
|
|
2730
|
+
*/
|
|
1552
2731
|
switchVersion(filename: string, version: string): Promise<void>;
|
|
1553
2732
|
}
|
|
1554
2733
|
|
|
1555
2734
|
/**
|
|
1556
|
-
*
|
|
1557
|
-
*
|
|
2735
|
+
* MemoryProgressStorage is a non-persistent, in-memory implementation of the `SitemapProgressStorage`.
|
|
2736
|
+
*
|
|
2737
|
+
* It is suitable for single-process applications or development environments where
|
|
2738
|
+
* persistence of job progress across application restarts is not required.
|
|
2739
|
+
*
|
|
2740
|
+
* @public
|
|
2741
|
+
* @since 3.0.0
|
|
1558
2742
|
*/
|
|
1559
2743
|
declare class MemoryProgressStorage implements SitemapProgressStorage {
|
|
1560
2744
|
private storage;
|
|
2745
|
+
/**
|
|
2746
|
+
* Retrieves the progress of a specific generation job from memory.
|
|
2747
|
+
*
|
|
2748
|
+
* @param jobId - Unique identifier for the job.
|
|
2749
|
+
* @returns A promise resolving to the `SitemapProgress` object, or null if not found.
|
|
2750
|
+
*/
|
|
1561
2751
|
get(jobId: string): Promise<SitemapProgress | null>;
|
|
2752
|
+
/**
|
|
2753
|
+
* Initializes or overwrites a progress record in memory.
|
|
2754
|
+
*
|
|
2755
|
+
* @param jobId - Unique identifier for the job.
|
|
2756
|
+
* @param progress - The initial or current state of the job progress.
|
|
2757
|
+
*/
|
|
1562
2758
|
set(jobId: string, progress: SitemapProgress): Promise<void>;
|
|
2759
|
+
/**
|
|
2760
|
+
* Updates specific fields of an existing progress record in memory.
|
|
2761
|
+
*
|
|
2762
|
+
* @param jobId - Unique identifier for the job.
|
|
2763
|
+
* @param updates - Object containing the fields to update.
|
|
2764
|
+
*/
|
|
1563
2765
|
update(jobId: string, updates: Partial<SitemapProgress>): Promise<void>;
|
|
2766
|
+
/**
|
|
2767
|
+
* Deletes a progress record from memory.
|
|
2768
|
+
*
|
|
2769
|
+
* @param jobId - Unique identifier for the job to remove.
|
|
2770
|
+
*/
|
|
1564
2771
|
delete(jobId: string): Promise<void>;
|
|
2772
|
+
/**
|
|
2773
|
+
* Lists the most recent sitemap generation jobs from memory.
|
|
2774
|
+
*
|
|
2775
|
+
* @param limit - Maximum number of records to return.
|
|
2776
|
+
* @returns A promise resolving to an array of `SitemapProgress` objects, sorted by start time.
|
|
2777
|
+
*/
|
|
1565
2778
|
list(limit?: number): Promise<SitemapProgress[]>;
|
|
1566
2779
|
}
|
|
1567
2780
|
|
|
@@ -1584,9 +2797,51 @@ declare class MemorySitemapStorage implements SitemapStorage {
|
|
|
1584
2797
|
private baseUrl;
|
|
1585
2798
|
private files;
|
|
1586
2799
|
constructor(baseUrl: string);
|
|
2800
|
+
/**
|
|
2801
|
+
* Writes sitemap content to memory.
|
|
2802
|
+
*
|
|
2803
|
+
* @param filename - The name of the file to store.
|
|
2804
|
+
* @param content - The XML or JSON content.
|
|
2805
|
+
*/
|
|
1587
2806
|
write(filename: string, content: string): Promise<void>;
|
|
2807
|
+
/**
|
|
2808
|
+
* 使用串流方式寫入 sitemap 至記憶體,可選擇性啟用 gzip 壓縮。
|
|
2809
|
+
* 記憶體儲存會收集串流為完整字串。
|
|
2810
|
+
*
|
|
2811
|
+
* @param filename - 檔案名稱
|
|
2812
|
+
* @param stream - XML 內容的 AsyncIterable
|
|
2813
|
+
* @param options - 寫入選項(如壓縮)
|
|
2814
|
+
*
|
|
2815
|
+
* @since 3.1.0
|
|
2816
|
+
*/
|
|
2817
|
+
writeStream(filename: string, stream: AsyncIterable<string>, options?: WriteStreamOptions): Promise<void>;
|
|
2818
|
+
/**
|
|
2819
|
+
* Reads sitemap content from memory.
|
|
2820
|
+
*
|
|
2821
|
+
* @param filename - The name of the file to read.
|
|
2822
|
+
* @returns A promise resolving to the file content as a string, or null if not found.
|
|
2823
|
+
*/
|
|
1588
2824
|
read(filename: string): Promise<string | null>;
|
|
2825
|
+
/**
|
|
2826
|
+
* Returns a readable stream for a sitemap file in memory.
|
|
2827
|
+
*
|
|
2828
|
+
* @param filename - The name of the file to stream.
|
|
2829
|
+
* @returns A promise resolving to an async iterable of file chunks, or null if not found.
|
|
2830
|
+
*/
|
|
2831
|
+
readStream(filename: string): Promise<AsyncIterable<string> | null>;
|
|
2832
|
+
/**
|
|
2833
|
+
* Checks if a sitemap file exists in memory.
|
|
2834
|
+
*
|
|
2835
|
+
* @param filename - The name of the file to check.
|
|
2836
|
+
* @returns A promise resolving to true if the file exists, false otherwise.
|
|
2837
|
+
*/
|
|
1589
2838
|
exists(filename: string): Promise<boolean>;
|
|
2839
|
+
/**
|
|
2840
|
+
* Returns the full public URL for a sitemap file.
|
|
2841
|
+
*
|
|
2842
|
+
* @param filename - The name of the sitemap file.
|
|
2843
|
+
* @returns The public URL as a string.
|
|
2844
|
+
*/
|
|
1590
2845
|
getUrl(filename: string): string;
|
|
1591
2846
|
}
|
|
1592
2847
|
|
|
@@ -1620,10 +2875,39 @@ declare class RedisProgressStorage implements SitemapProgressStorage {
|
|
|
1620
2875
|
constructor(options: RedisProgressStorageOptions);
|
|
1621
2876
|
private getKey;
|
|
1622
2877
|
private getListKey;
|
|
2878
|
+
/**
|
|
2879
|
+
* Retrieves the progress of a specific generation job from Redis.
|
|
2880
|
+
*
|
|
2881
|
+
* @param jobId - Unique identifier for the job.
|
|
2882
|
+
* @returns A promise resolving to the `SitemapProgress` object, or null if not found.
|
|
2883
|
+
*/
|
|
1623
2884
|
get(jobId: string): Promise<SitemapProgress | null>;
|
|
2885
|
+
/**
|
|
2886
|
+
* Initializes or overwrites a progress record in Redis.
|
|
2887
|
+
*
|
|
2888
|
+
* @param jobId - Unique identifier for the job.
|
|
2889
|
+
* @param progress - The initial or current state of the job progress.
|
|
2890
|
+
*/
|
|
1624
2891
|
set(jobId: string, progress: SitemapProgress): Promise<void>;
|
|
2892
|
+
/**
|
|
2893
|
+
* Updates specific fields of an existing progress record in Redis.
|
|
2894
|
+
*
|
|
2895
|
+
* @param jobId - Unique identifier for the job.
|
|
2896
|
+
* @param updates - Object containing the fields to update.
|
|
2897
|
+
*/
|
|
1625
2898
|
update(jobId: string, updates: Partial<SitemapProgress>): Promise<void>;
|
|
2899
|
+
/**
|
|
2900
|
+
* Deletes a progress record from Redis.
|
|
2901
|
+
*
|
|
2902
|
+
* @param jobId - Unique identifier for the job to remove.
|
|
2903
|
+
*/
|
|
1626
2904
|
delete(jobId: string): Promise<void>;
|
|
2905
|
+
/**
|
|
2906
|
+
* Lists the most recent sitemap generation jobs from Redis.
|
|
2907
|
+
*
|
|
2908
|
+
* @param limit - Maximum number of records to return.
|
|
2909
|
+
* @returns A promise resolving to an array of `SitemapProgress` objects, sorted by start time.
|
|
2910
|
+
*/
|
|
1627
2911
|
list(limit?: number): Promise<SitemapProgress[]>;
|
|
1628
2912
|
}
|
|
1629
2913
|
|
|
@@ -1684,14 +2968,156 @@ declare class S3SitemapStorage implements SitemapStorage {
|
|
|
1684
2968
|
constructor(options: S3SitemapStorageOptions);
|
|
1685
2969
|
private getS3Client;
|
|
1686
2970
|
private getKey;
|
|
2971
|
+
/**
|
|
2972
|
+
* Writes sitemap content to an S3 object.
|
|
2973
|
+
*
|
|
2974
|
+
* @param filename - The name of the file to write.
|
|
2975
|
+
* @param content - The XML or JSON content.
|
|
2976
|
+
*/
|
|
1687
2977
|
write(filename: string, content: string): Promise<void>;
|
|
2978
|
+
/**
|
|
2979
|
+
* 使用串流方式寫入 sitemap 至 S3,可選擇性啟用 gzip 壓縮。
|
|
2980
|
+
* S3 需要知道 Content-Length,因此會先收集串流為 Buffer。
|
|
2981
|
+
*
|
|
2982
|
+
* @param filename - 檔案名稱
|
|
2983
|
+
* @param stream - XML 內容的 AsyncIterable
|
|
2984
|
+
* @param options - 寫入選項(如壓縮、content type)
|
|
2985
|
+
*
|
|
2986
|
+
* @since 3.1.0
|
|
2987
|
+
*/
|
|
2988
|
+
writeStream(filename: string, stream: AsyncIterable<string>, options?: WriteStreamOptions): Promise<void>;
|
|
2989
|
+
/**
|
|
2990
|
+
* Reads sitemap content from an S3 object.
|
|
2991
|
+
*
|
|
2992
|
+
* @param filename - The name of the file to read.
|
|
2993
|
+
* @returns A promise resolving to the file content as a string, or null if not found.
|
|
2994
|
+
*/
|
|
1688
2995
|
read(filename: string): Promise<string | null>;
|
|
2996
|
+
/**
|
|
2997
|
+
* Returns a readable stream for an S3 object.
|
|
2998
|
+
*
|
|
2999
|
+
* @param filename - The name of the file to stream.
|
|
3000
|
+
* @returns A promise resolving to an async iterable of file chunks, or null if not found.
|
|
3001
|
+
*/
|
|
3002
|
+
readStream(filename: string): Promise<AsyncIterable<string> | null>;
|
|
3003
|
+
/**
|
|
3004
|
+
* Checks if an S3 object exists.
|
|
3005
|
+
*
|
|
3006
|
+
* @param filename - The name of the file to check.
|
|
3007
|
+
* @returns A promise resolving to true if the file exists, false otherwise.
|
|
3008
|
+
*/
|
|
1689
3009
|
exists(filename: string): Promise<boolean>;
|
|
3010
|
+
/**
|
|
3011
|
+
* Returns the full public URL for an S3 object.
|
|
3012
|
+
*
|
|
3013
|
+
* @param filename - The name of the sitemap file.
|
|
3014
|
+
* @returns The public URL as a string.
|
|
3015
|
+
*/
|
|
1690
3016
|
getUrl(filename: string): string;
|
|
3017
|
+
/**
|
|
3018
|
+
* Writes content to a shadow (staged) location in S3.
|
|
3019
|
+
*
|
|
3020
|
+
* @param filename - The name of the file to write.
|
|
3021
|
+
* @param content - The XML or JSON content.
|
|
3022
|
+
* @param shadowId - Optional unique session identifier.
|
|
3023
|
+
*/
|
|
1691
3024
|
writeShadow(filename: string, content: string, shadowId?: string): Promise<void>;
|
|
3025
|
+
/**
|
|
3026
|
+
* Commits all staged shadow objects in a session to production.
|
|
3027
|
+
*
|
|
3028
|
+
* @param shadowId - The identifier of the session to commit.
|
|
3029
|
+
*/
|
|
1692
3030
|
commitShadow(shadowId: string): Promise<void>;
|
|
3031
|
+
/**
|
|
3032
|
+
* Lists all archived versions of a specific sitemap in S3.
|
|
3033
|
+
*
|
|
3034
|
+
* @param filename - The sitemap filename.
|
|
3035
|
+
* @returns A promise resolving to an array of version identifiers.
|
|
3036
|
+
*/
|
|
1693
3037
|
listVersions(filename: string): Promise<string[]>;
|
|
3038
|
+
/**
|
|
3039
|
+
* Reverts a sitemap to a previously archived version in S3.
|
|
3040
|
+
*
|
|
3041
|
+
* @param filename - The sitemap filename.
|
|
3042
|
+
* @param version - The version identifier to switch to.
|
|
3043
|
+
*/
|
|
1694
3044
|
switchVersion(filename: string, version: string): Promise<void>;
|
|
1695
3045
|
}
|
|
1696
3046
|
|
|
1697
|
-
|
|
3047
|
+
/**
|
|
3048
|
+
* @gravito/constellation - Compression utilities
|
|
3049
|
+
* @module utils/Compression
|
|
3050
|
+
* @since 3.1.0
|
|
3051
|
+
*/
|
|
3052
|
+
|
|
3053
|
+
/**
|
|
3054
|
+
* Compression configuration.
|
|
3055
|
+
*/
|
|
3056
|
+
interface CompressionConfig {
|
|
3057
|
+
/** Compression format. @default 'gzip' */
|
|
3058
|
+
format?: 'gzip' | undefined;
|
|
3059
|
+
/** Compression level (1-9). @default 6 */
|
|
3060
|
+
level?: number | undefined;
|
|
3061
|
+
}
|
|
3062
|
+
/**
|
|
3063
|
+
* 將 AsyncIterable<string> 壓縮為 Buffer。
|
|
3064
|
+
* 適用於需要完整壓縮結果的場景(如 S3 Upload)。
|
|
3065
|
+
*
|
|
3066
|
+
* @param source - 輸入的字串串流
|
|
3067
|
+
* @param config - 壓縮設定
|
|
3068
|
+
* @returns 壓縮後的 Buffer
|
|
3069
|
+
*
|
|
3070
|
+
* @example
|
|
3071
|
+
* ```typescript
|
|
3072
|
+
* const source = (async function*() {
|
|
3073
|
+
* yield '<?xml version="1.0"?>'
|
|
3074
|
+
* yield '<urlset>...</urlset>'
|
|
3075
|
+
* })()
|
|
3076
|
+
* const compressed = await compressToBuffer(source)
|
|
3077
|
+
* ```
|
|
3078
|
+
*/
|
|
3079
|
+
declare function compressToBuffer(source: AsyncIterable<string>, config?: CompressionConfig): Promise<Buffer>;
|
|
3080
|
+
/**
|
|
3081
|
+
* 回傳壓縮串流 Transform。
|
|
3082
|
+
* 適用於可直接 pipe 的場景(如 Disk write)。
|
|
3083
|
+
*
|
|
3084
|
+
* @param config - 壓縮設定
|
|
3085
|
+
* @returns Transform stream
|
|
3086
|
+
*
|
|
3087
|
+
* @example
|
|
3088
|
+
* ```typescript
|
|
3089
|
+
* const readable = Readable.from(source)
|
|
3090
|
+
* const gzip = createCompressionStream({ level: 9 })
|
|
3091
|
+
* const writeStream = createWriteStream('output.xml.gz')
|
|
3092
|
+
* await pipeline(readable, gzip, writeStream)
|
|
3093
|
+
* ```
|
|
3094
|
+
*/
|
|
3095
|
+
declare function createCompressionStream(config?: CompressionConfig): Transform;
|
|
3096
|
+
/**
|
|
3097
|
+
* 將檔名轉換為 gzip 格式(加上 .gz 副檔名)。
|
|
3098
|
+
*
|
|
3099
|
+
* @param filename - 原始檔名
|
|
3100
|
+
* @returns 帶有 .gz 副檔名的檔名
|
|
3101
|
+
*
|
|
3102
|
+
* @example
|
|
3103
|
+
* ```typescript
|
|
3104
|
+
* toGzipFilename('sitemap.xml') // 'sitemap.xml.gz'
|
|
3105
|
+
* toGzipFilename('sitemap.xml.gz') // 'sitemap.xml.gz' (不重複添加)
|
|
3106
|
+
* ```
|
|
3107
|
+
*/
|
|
3108
|
+
declare function toGzipFilename(filename: string): string;
|
|
3109
|
+
/**
|
|
3110
|
+
* 移除檔名的 .gz 副檔名。
|
|
3111
|
+
*
|
|
3112
|
+
* @param filename - gzip 檔名
|
|
3113
|
+
* @returns 移除 .gz 後的檔名
|
|
3114
|
+
*
|
|
3115
|
+
* @example
|
|
3116
|
+
* ```typescript
|
|
3117
|
+
* fromGzipFilename('sitemap.xml.gz') // 'sitemap.xml'
|
|
3118
|
+
* fromGzipFilename('sitemap.xml') // 'sitemap.xml'
|
|
3119
|
+
* ```
|
|
3120
|
+
*/
|
|
3121
|
+
declare function fromGzipFilename(filename: string): string;
|
|
3122
|
+
|
|
3123
|
+
export { type AlternateUrl, type ChangeFreq, type ChangeTracker, type ChangeType, type CompressionConfig, type CompressionOptions, DiffCalculator, DiskSitemapStorage, type DynamicSitemapOptions, GCPSitemapStorage, type GCPSitemapStorageOptions, GenerateSitemapJob, type GenerateSitemapJobOptions, type I18nSitemapEntryOptions, IncrementalGenerator, type IncrementalGeneratorOptions, MemoryChangeTracker, MemoryLock, MemoryProgressStorage, MemoryRedirectManager, type MemoryRedirectManagerOptions, MemorySitemapStorage, OrbitSitemap, ProgressTracker, type ProgressTrackerOptions, RedirectDetector, type RedirectDetectorOptions, RedirectHandler, type RedirectHandlerOptions, type RedirectHandlingStrategy, type RedirectManager, type RedirectRule, RedisChangeTracker, type RedisClient, RedisLock, type RedisLockOptions, RedisProgressStorage, type RedisProgressStorageOptions, RedisRedirectManager, type RedisRedirectManagerOptions, RouteScanner, type RouteScannerOptions, S3SitemapStorage, type S3SitemapStorageOptions, ShadowProcessor, type ShadowProcessorOptions, type ShardInfo, type ShardManifest, type SitemapCache, type SitemapChange, type SitemapEntry, SitemapGenerator, type SitemapGeneratorOptions, type SitemapImage, SitemapIndex, type SitemapIndexEntry, type SitemapLock, type SitemapNews, type SitemapProgress, type SitemapProgressStorage, type SitemapProvider, type SitemapStorage, SitemapStream, type SitemapStreamOptions, type SitemapVideo, type StaticSitemapOptions, type WriteStreamOptions, compressToBuffer, createCompressionStream, fromGzipFilename, generateI18nEntries, routeScanner, toGzipFilename };
|