@gravito/constellation 1.0.0-alpha.2

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 ADDED
@@ -0,0 +1,334 @@
1
+ ---
2
+ title: Constellation
3
+ ---
4
+
5
+ # Constellation
6
+
7
+ Dynamic and static sitemap generation for Gravito applications.
8
+
9
+ **Constellation** provides a flexible way to generate XML sitemaps for your Gravito application, supporting both dynamic generation (via routes) and static generation (for build time). It includes support for Google Sitemap extensions like Images, Videos, News, and i18n alternates.
10
+
11
+ ## Features
12
+
13
+ - **Dynamic Generation**: Serve `sitemap.xml` directly from your application
14
+ - **Static Generation**: Generate files for static hosting
15
+ - **Auto Scanning**: Automatically scan registered routes
16
+ - **Sitemap Extensions**: Support for Images, Videos, News, and i18n
17
+ - **Sitemap Index**: Generators for large sites (Phase 1/2)
18
+ - **Enterprise Features**:
19
+ - **Cloud Storage**: AWS S3 and Google Cloud Storage support
20
+ - **Shadow Processing**: Atomic switching and versioning for safe deployments
21
+ - **Background Jobs**: Non-blocking generation with progress tracking
22
+ - **Incremental Generation**: Only update changed URLs, not the entire sitemap
23
+ - **301 Redirect Handling**: Comprehensive redirect detection and processing
24
+ - **Progress Tracking**: Real-time progress monitoring via API
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ bun add @gravito/constellation
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ### 1. Dynamic Sitemap (Runtime)
35
+
36
+ Integrate directly into your Gravito app:
37
+
38
+ ```typescript
39
+ // gravito.config.ts or index.ts
40
+ import { OrbitSitemap, routeScanner } from '@gravito/constellation'
41
+
42
+ OrbitSitemap.dynamic({
43
+ baseUrl: 'https://example.com',
44
+ providers: [
45
+ // Automatically scan routes
46
+ routeScanner(core.router, {
47
+ exclude: ['/api/*', '/admin/*'],
48
+ defaultChangefreq: 'daily'
49
+ }),
50
+
51
+ // Custom provider (e.g. from database)
52
+ {
53
+ async getEntries() {
54
+ const posts = await db.query('SELECT slug, updated_at FROM posts')
55
+ return posts.map(post => ({
56
+ url: `/blog/${post.slug}`,
57
+ lastmod: post.updated_at
58
+ }))
59
+ }
60
+ }
61
+ ],
62
+ cacheSeconds: 3600 // Cache for 1 hour
63
+ }).install(core)
64
+ ```
65
+
66
+ ### 2. Static Generation (Build Time)
67
+
68
+ Generate files during build:
69
+
70
+ ```typescript
71
+ import { OrbitSitemap, routeScanner } from '@gravito/constellation'
72
+
73
+ const sitemap = OrbitSitemap.static({
74
+ baseUrl: 'https://example.com',
75
+ outDir: './dist',
76
+ providers: [
77
+ routeScanner(core.router),
78
+ // ...
79
+ ]
80
+ })
81
+
82
+ await sitemap.generate()
83
+ ```
84
+
85
+ ### 3. Manual Usage (SitemapStream)
86
+
87
+ Use the low-level API for custom needs:
88
+
89
+ ```typescript
90
+ import { SitemapStream } from '@gravito/constellation'
91
+
92
+ const sitemap = new SitemapStream({ baseUrl: 'https://example.com' })
93
+
94
+ sitemap.add('/')
95
+ sitemap.add({
96
+ url: '/about',
97
+ changefreq: 'monthly',
98
+ priority: 0.8,
99
+ alternates: [
100
+ { lang: 'en', url: '/about' },
101
+ { lang: 'zh-TW', url: '/zh/about' }
102
+ ],
103
+ images: [
104
+ { loc: '/img/team.jpg', title: 'Our Team' }
105
+ ]
106
+ })
107
+
108
+ console.log(sitemap.toXML())
109
+ ```
110
+
111
+ ## Extensions
112
+
113
+ ### Video Sitemap
114
+ ```typescript
115
+ sitemap.add({
116
+ url: '/video-page',
117
+ videos: [{
118
+ thumbnail_loc: 'https://...',
119
+ title: 'Video Title',
120
+ description: 'Description',
121
+ player_loc: 'https://...',
122
+ duration: 600
123
+ }]
124
+ })
125
+ ```
126
+
127
+ ### News Sitemap
128
+ ```typescript
129
+ sitemap.add({
130
+ url: '/news/article',
131
+ news: {
132
+ publication: { name: 'The Daily', language: 'en' },
133
+ publication_date: '2024-01-01',
134
+ title: 'Article Title'
135
+ }
136
+ })
137
+ ```
138
+
139
+ ## Scaling & Distributed (Advanced)
140
+
141
+ ### Large Scale Sharding
142
+ Orbit Sitemap automatically handles large datasets by splitting them into multiple files (default 50,000 URLs per file).
143
+
144
+ ```typescript
145
+ OrbitSitemap.dynamic({
146
+ // ...
147
+ maxEntriesPerFile: 10000, // Custom split limit
148
+ storage: new RedisSitemapStorage({ ... }) // Store generated files in Redis/S3
149
+ })
150
+ ```
151
+
152
+ ### Async Iterators (Streaming)
153
+ For large datasets, use Async Generators in your providers to stream URLs without loading them all into memory.
154
+
155
+ ```typescript
156
+ {
157
+ async *getEntries() {
158
+ for await (const row of db.cursor('SELECT * FROM massive_table')) {
159
+ yield { url: `/item/${row.id}` }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ ### Distributed Locking
166
+ In a distributed environment (e.g. Kubernetes), use `lock` to prevent concurrent sitemap generation.
167
+
168
+ ```typescript
169
+ OrbitSitemap.dynamic({
170
+ // ...
171
+ lock: new RedisSitemapLock(redisClient),
172
+ storage: new S3SitemapStorage(bucket)
173
+ })
174
+ ```
175
+
176
+ ## Enterprise Features
177
+
178
+ ### Cloud Storage (S3 / GCP)
179
+
180
+ Store sitemaps directly in cloud storage with shadow processing:
181
+
182
+ ```typescript
183
+ import { OrbitSitemap, S3SitemapStorage } from '@gravito/constellation'
184
+
185
+ const sitemap = OrbitSitemap.static({
186
+ baseUrl: 'https://example.com',
187
+ storage: new S3SitemapStorage({
188
+ bucket: 'my-sitemap-bucket',
189
+ region: 'us-east-1',
190
+ shadow: {
191
+ enabled: true,
192
+ mode: 'atomic' // or 'versioned'
193
+ }
194
+ }),
195
+ providers: [...]
196
+ })
197
+ ```
198
+
199
+ ### Shadow Processing
200
+
201
+ Generate sitemaps safely with atomic switching or versioning:
202
+
203
+ ```typescript
204
+ const sitemap = OrbitSitemap.static({
205
+ // ...
206
+ shadow: {
207
+ enabled: true,
208
+ mode: 'atomic' // Atomic switch: generate to temp, then swap
209
+ // or 'versioned' // Versioning: keep old versions, switch when ready
210
+ }
211
+ })
212
+ ```
213
+
214
+ ### Background Generation with Progress Tracking
215
+
216
+ Generate sitemaps asynchronously without blocking:
217
+
218
+ ```typescript
219
+ import { OrbitSitemap, MemoryProgressStorage } from '@gravito/constellation'
220
+
221
+ const sitemap = OrbitSitemap.static({
222
+ // ...
223
+ progressStorage: new MemoryProgressStorage()
224
+ })
225
+
226
+ // Trigger background generation
227
+ const jobId = await sitemap.generateAsync({
228
+ onProgress: (progress) => {
229
+ console.log(`${progress.percentage}% (${progress.processed}/${progress.total})`)
230
+ },
231
+ onComplete: () => {
232
+ console.log('Generation completed!')
233
+ }
234
+ })
235
+
236
+ // Query progress via API
237
+ // GET /admin/sitemap/status/:jobId
238
+ ```
239
+
240
+ ### Incremental Generation
241
+
242
+ Only update changed URLs, perfect for large sites:
243
+
244
+ ```typescript
245
+ import { OrbitSitemap, MemoryChangeTracker } from '@gravito/constellation'
246
+
247
+ const sitemap = OrbitSitemap.static({
248
+ // ...
249
+ incremental: {
250
+ enabled: true,
251
+ changeTracker: new MemoryChangeTracker(),
252
+ autoTrack: true
253
+ }
254
+ })
255
+
256
+ // Full generation (first time)
257
+ await sitemap.generate()
258
+
259
+ // Incremental update (only changed URLs)
260
+ await sitemap.generateIncremental(new Date('2024-01-01'))
261
+ ```
262
+
263
+ ### 301 Redirect Handling
264
+
265
+ Automatically handle URL redirects in your sitemap:
266
+
267
+ ```typescript
268
+ import { OrbitSitemap, MemoryRedirectManager, RedirectDetector } from '@gravito/constellation'
269
+
270
+ const redirectManager = new MemoryRedirectManager()
271
+ const detector = new RedirectDetector({
272
+ baseUrl: 'https://example.com',
273
+ autoDetect: {
274
+ enabled: true,
275
+ timeout: 5000,
276
+ cache: true
277
+ }
278
+ })
279
+
280
+ // Auto-detect redirects
281
+ const redirects = await detector.detectBatch(['/old-page', '/another-old'])
282
+
283
+ // Register redirects
284
+ for (const [from, rule] of redirects) {
285
+ if (rule) {
286
+ await redirectManager.register(rule)
287
+ }
288
+ }
289
+
290
+ const sitemap = OrbitSitemap.static({
291
+ // ...
292
+ redirect: {
293
+ enabled: true,
294
+ manager: redirectManager,
295
+ strategy: 'remove_old_add_new', // or 'keep_relation', 'update_url', 'dual_mark'
296
+ followChains: true,
297
+ maxChainLength: 5
298
+ }
299
+ })
300
+ ```
301
+
302
+ ### API Endpoints
303
+
304
+ Install API endpoints for triggering generation and querying progress:
305
+
306
+ ```typescript
307
+ const sitemap = OrbitSitemap.static({ ... })
308
+
309
+ // Install API endpoints
310
+ sitemap.installApiEndpoints(core, '/admin/sitemap')
311
+
312
+ // Available endpoints:
313
+ // POST /admin/sitemap/generate - Trigger generation
314
+ // GET /admin/sitemap/status/:jobId - Query progress
315
+ // GET /admin/sitemap/history - List generation history
316
+ ```
317
+
318
+ ### Creating Custom Storage/Lock
319
+ Implement `SitemapStorage` and `SitemapLock` interfaces:
320
+
321
+ ```typescript
322
+ import { SitemapStorage, SitemapLock } from '@gravito/constellation'
323
+
324
+ class MyStorage implements SitemapStorage { ... }
325
+ class MyLock implements SitemapLock { ... }
326
+ ```
327
+
328
+ ## Type Reference
329
+
330
+ See `dist/index.d.ts` for full type definitions including `SitemapEntry`, `SitemapImage`, `SitemapVideo`, etc.
331
+
332
+ ## License
333
+
334
+ MIT