@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 +334 -0
- package/dist/index.cjs +2142 -0
- package/dist/index.mjs +2123 -0
- package/package.json +49 -0
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
|