@vltpkg/vsr 0.2.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.
Files changed (83) hide show
  1. package/.editorconfig +13 -0
  2. package/.prettierrc +7 -0
  3. package/CONTRIBUTING.md +228 -0
  4. package/LICENSE.md +110 -0
  5. package/README.md +373 -0
  6. package/bin/vsr.ts +29 -0
  7. package/config.ts +124 -0
  8. package/debug-npm.js +19 -0
  9. package/drizzle.config.js +33 -0
  10. package/package.json +80 -0
  11. package/pnpm-workspace.yaml +5 -0
  12. package/src/api.ts +2246 -0
  13. package/src/assets/public/images/bg.png +0 -0
  14. package/src/assets/public/images/clients/logo-bun.png +0 -0
  15. package/src/assets/public/images/clients/logo-deno.png +0 -0
  16. package/src/assets/public/images/clients/logo-npm.png +0 -0
  17. package/src/assets/public/images/clients/logo-pnpm.png +0 -0
  18. package/src/assets/public/images/clients/logo-vlt.png +0 -0
  19. package/src/assets/public/images/clients/logo-yarn.png +0 -0
  20. package/src/assets/public/images/favicon/apple-touch-icon.png +0 -0
  21. package/src/assets/public/images/favicon/favicon-96x96.png +0 -0
  22. package/src/assets/public/images/favicon/favicon.ico +0 -0
  23. package/src/assets/public/images/favicon/favicon.svg +3 -0
  24. package/src/assets/public/images/favicon/site.webmanifest +21 -0
  25. package/src/assets/public/images/favicon/web-app-manifest-192x192.png +0 -0
  26. package/src/assets/public/images/favicon/web-app-manifest-512x512.png +0 -0
  27. package/src/assets/public/styles/styles.css +219 -0
  28. package/src/db/client.ts +544 -0
  29. package/src/db/migrations/0000_faulty_ricochet.sql +14 -0
  30. package/src/db/migrations/0000_initial.sql +29 -0
  31. package/src/db/migrations/0001_uuid_validation.sql +35 -0
  32. package/src/db/migrations/0001_wealthy_magdalene.sql +7 -0
  33. package/src/db/migrations/drop.sql +3 -0
  34. package/src/db/migrations/meta/0000_snapshot.json +104 -0
  35. package/src/db/migrations/meta/0001_snapshot.json +155 -0
  36. package/src/db/migrations/meta/_journal.json +20 -0
  37. package/src/db/schema.ts +41 -0
  38. package/src/index.ts +709 -0
  39. package/src/routes/access.ts +263 -0
  40. package/src/routes/auth.ts +93 -0
  41. package/src/routes/index.ts +135 -0
  42. package/src/routes/packages.ts +924 -0
  43. package/src/routes/search.ts +50 -0
  44. package/src/routes/static.ts +53 -0
  45. package/src/routes/tokens.ts +102 -0
  46. package/src/routes/users.ts +14 -0
  47. package/src/utils/auth.ts +145 -0
  48. package/src/utils/cache.ts +466 -0
  49. package/src/utils/database.ts +44 -0
  50. package/src/utils/packages.ts +337 -0
  51. package/src/utils/response.ts +100 -0
  52. package/src/utils/routes.ts +47 -0
  53. package/src/utils/spa.ts +14 -0
  54. package/src/utils/tracing.ts +63 -0
  55. package/src/utils/upstream.ts +131 -0
  56. package/test/README.md +91 -0
  57. package/test/access.test.js +760 -0
  58. package/test/cloudflare-waituntil.test.js +141 -0
  59. package/test/db.test.js +447 -0
  60. package/test/dist-tag.test.js +415 -0
  61. package/test/e2e.test.js +904 -0
  62. package/test/hono-context.test.js +250 -0
  63. package/test/integrity-validation.test.js +183 -0
  64. package/test/json-response.test.js +76 -0
  65. package/test/manifest-slimming.test.js +449 -0
  66. package/test/packument-consistency.test.js +351 -0
  67. package/test/packument-version-range.test.js +144 -0
  68. package/test/performance.test.js +162 -0
  69. package/test/route-with-waituntil.test.js +298 -0
  70. package/test/run-tests.js +151 -0
  71. package/test/setup-cache-tests.js +190 -0
  72. package/test/setup.js +64 -0
  73. package/test/stale-while-revalidate.test.js +273 -0
  74. package/test/static-assets.test.js +85 -0
  75. package/test/upstream-routing.test.js +86 -0
  76. package/test/utils/test-helpers.js +84 -0
  77. package/test/waituntil-correct.test.js +208 -0
  78. package/test/waituntil-demo.test.js +138 -0
  79. package/test/waituntil-readme.md +113 -0
  80. package/tsconfig.json +37 -0
  81. package/types.ts +446 -0
  82. package/vitest.config.js +95 -0
  83. package/wrangler.json +58 -0
package/types.ts ADDED
@@ -0,0 +1,446 @@
1
+ // =============================================================================
2
+ // VLT Serverless Registry - Consolidated TypeScript Types
3
+ // =============================================================================
4
+
5
+ import type { Context } from 'hono'
6
+ import type { D1Database } from '@cloudflare/workers-types'
7
+
8
+ // =============================================================================
9
+ // Database Types
10
+ // =============================================================================
11
+
12
+ export interface Package {
13
+ name: string
14
+ tags: string // JSON string containing Record<string, string>
15
+ lastUpdated?: string
16
+ origin: 'local' | 'upstream'
17
+ upstream?: string
18
+ cachedAt?: string
19
+ }
20
+
21
+ export interface ParsedPackage {
22
+ name: string
23
+ tags: Record<string, string>
24
+ lastUpdated?: string
25
+ origin?: 'local' | 'upstream'
26
+ upstream?: string
27
+ cachedAt?: string
28
+ }
29
+
30
+ export interface Version {
31
+ spec: string
32
+ manifest: string // JSON string containing PackageManifest
33
+ publishedAt?: string
34
+ origin: 'local' | 'upstream'
35
+ upstream?: string
36
+ cachedAt?: string
37
+ }
38
+
39
+ export interface ParsedVersion {
40
+ spec: string
41
+ manifest: PackageManifest
42
+ publishedAt?: string
43
+ origin?: 'local' | 'upstream'
44
+ upstream?: string
45
+ cachedAt?: string
46
+ }
47
+
48
+ export interface Token {
49
+ token: string
50
+ uuid: string
51
+ scope: string // JSON string containing TokenScope[]
52
+ }
53
+
54
+ export interface ParsedToken {
55
+ token: string
56
+ uuid: string
57
+ scope: TokenScope[]
58
+ }
59
+
60
+ // =============================================================================
61
+ // Authentication & Authorization Types
62
+ // =============================================================================
63
+
64
+ export interface TokenScope {
65
+ values: string[]
66
+ types: {
67
+ pkg?: { read: boolean; write: boolean }
68
+ user?: { read: boolean; write: boolean }
69
+ }
70
+ }
71
+
72
+ export interface TokenAccess {
73
+ anyUser: boolean
74
+ specificUser: boolean
75
+ anyPackage: boolean
76
+ specificPackage: boolean
77
+ readAccess: boolean
78
+ writeAccess: boolean
79
+ methods: string[]
80
+ }
81
+
82
+ export interface AuthUser {
83
+ uuid: string | null
84
+ scope: TokenScope[] | null
85
+ token: string
86
+ }
87
+
88
+ // =============================================================================
89
+ // Package & Manifest Types
90
+ // =============================================================================
91
+
92
+ export interface PackageManifest {
93
+ name: string
94
+ version: string
95
+ description?: string
96
+ main?: string
97
+ module?: string
98
+ types?: string
99
+ bin?: Record<string, string> | string
100
+ scripts?: Record<string, string>
101
+ dependencies?: Record<string, string>
102
+ devDependencies?: Record<string, string>
103
+ peerDependencies?: Record<string, string>
104
+ optionalDependencies?: Record<string, string>
105
+ peerDependenciesMeta?: Record<string, { optional?: boolean }>
106
+ engines?: Record<string, string>
107
+ os?: string[]
108
+ cpu?: string[]
109
+ keywords?: string[]
110
+ author?: string | { name: string; email?: string; url?: string }
111
+ contributors?: Array<string | { name: string; email?: string; url?: string }>
112
+ license?: string
113
+ repository?: string | { type: string; url: string; directory?: string }
114
+ bugs?: string | { url: string; email?: string }
115
+ homepage?: string
116
+ files?: string[]
117
+ publishConfig?: Record<string, any>
118
+ dist?: {
119
+ tarball: string
120
+ shasum?: string
121
+ integrity?: string
122
+ fileCount?: number
123
+ unpackedSize?: number
124
+ }
125
+ _id?: string
126
+ _rev?: string
127
+ _attachments?: Record<string, any>
128
+ [key: string]: any // Allow additional properties
129
+ }
130
+
131
+ export interface SlimmedManifest {
132
+ name: string
133
+ version: string
134
+ dependencies?: Record<string, string>
135
+ peerDependencies?: Record<string, string>
136
+ optionalDependencies?: Record<string, string>
137
+ peerDependenciesMeta?: Record<string, { optional?: boolean }>
138
+ bin?: Record<string, string> | string
139
+ engines?: Record<string, string>
140
+ dist: {
141
+ tarball: string
142
+ }
143
+ }
144
+
145
+ export interface Packument {
146
+ name: string
147
+ 'dist-tags': Record<string, string>
148
+ versions: Record<string, SlimmedManifest>
149
+ time: Record<string, string> & {
150
+ modified: string
151
+ }
152
+ }
153
+
154
+ export interface PackageSpec {
155
+ name?: string
156
+ pkg?: string
157
+ scope?: string
158
+ }
159
+
160
+ // =============================================================================
161
+ // Upstream & Configuration Types
162
+ // =============================================================================
163
+
164
+ export interface UpstreamConfig {
165
+ type: 'local' | 'npm' | 'vsr' | 'jsr'
166
+ url: string
167
+ allowPublish?: boolean
168
+ }
169
+
170
+ export interface OriginConfig {
171
+ default: string
172
+ upstreams: Record<string, UpstreamConfig>
173
+ }
174
+
175
+ export interface ParsedPackageInfo {
176
+ upstream?: string
177
+ packageName: string
178
+ version?: string
179
+ segments: string[]
180
+ }
181
+
182
+ // =============================================================================
183
+ // Cache Types
184
+ // =============================================================================
185
+
186
+ export interface CacheOptions {
187
+ packumentTtlMinutes?: number
188
+ manifestTtlMinutes?: number
189
+ staleWhileRevalidateMinutes?: number
190
+ forceRefresh?: boolean
191
+ upstream?: string
192
+ }
193
+
194
+ export interface CacheResult<T> {
195
+ data?: T
196
+ package?: T // For package cache results
197
+ version?: T // For version cache results
198
+ fromCache: boolean
199
+ stale?: boolean
200
+ }
201
+
202
+ export interface CacheValidation {
203
+ valid: boolean
204
+ stale: boolean
205
+ data: any
206
+ }
207
+
208
+ export interface QueueMessage {
209
+ type: 'package_refresh' | 'version_refresh'
210
+ packageName?: string
211
+ spec?: string
212
+ upstream: string
213
+ timestamp: number
214
+ options: {
215
+ packumentTtlMinutes?: number
216
+ manifestTtlMinutes?: number
217
+ upstream?: string
218
+ }
219
+ }
220
+
221
+ // =============================================================================
222
+ // Request Context Types
223
+ // =============================================================================
224
+
225
+ export interface RequestContext {
226
+ protocol?: string
227
+ host?: string
228
+ upstream?: string
229
+ }
230
+
231
+ export interface DatabaseOperations {
232
+ // Package operations
233
+ getPackage(name: string): Promise<ParsedPackage | null>
234
+ upsertPackage(name: string, tags: Record<string, string>, lastUpdated?: string): Promise<any>
235
+ upsertCachedPackage(name: string, tags: Record<string, string>, upstream: string, lastUpdated?: string): Promise<any>
236
+ getCachedPackage(name: string): Promise<ParsedPackage | null>
237
+ isPackageCacheValid(name: string, ttlMinutes?: number): Promise<boolean>
238
+
239
+ // Token operations
240
+ getToken(token: string): Promise<ParsedToken | null>
241
+ validateTokenAccess(authToken: string, targetUuid: string): Promise<boolean>
242
+ upsertToken(token: string, uuid: string, scope: TokenScope[], authToken?: string): Promise<any>
243
+ deleteToken(token: string, authToken?: string): Promise<any>
244
+
245
+ // Version operations
246
+ getVersion(spec: string): Promise<ParsedVersion | null>
247
+ upsertVersion(spec: string, manifest: PackageManifest, publishedAt: string): Promise<any>
248
+ upsertCachedVersion(spec: string, manifest: PackageManifest, upstream: string, publishedAt: string): Promise<any>
249
+ getCachedVersion(spec: string): Promise<ParsedVersion | null>
250
+ isVersionCacheValid(spec: string, ttlMinutes?: number): Promise<boolean>
251
+
252
+ // Search operations
253
+ searchPackages(query: string, scope?: string): Promise<any[]>
254
+ getVersionsByPackage(packageName: string): Promise<ParsedVersion[]>
255
+ }
256
+
257
+ export interface HonoContext extends Context {
258
+ db: DatabaseOperations
259
+ env: {
260
+ CACHE_REFRESH_QUEUE?: any
261
+ [key: string]: any
262
+ }
263
+ waitUntil?: (promise: Promise<any>) => void
264
+ }
265
+
266
+ // =============================================================================
267
+ // API Response Types
268
+ // =============================================================================
269
+
270
+ export interface ApiError {
271
+ error: string
272
+ message?: string
273
+ details?: any
274
+ }
275
+
276
+ export interface TokenCreateRequest {
277
+ uuid?: string
278
+ scope: TokenScope[]
279
+ }
280
+
281
+ export interface TokenCreateResponse {
282
+ token: string
283
+ uuid: string
284
+ scope: TokenScope[]
285
+ }
286
+
287
+ export interface AccessRequest {
288
+ username: string
289
+ permission: 'read-only' | 'read-write'
290
+ }
291
+
292
+ export interface AccessResponse {
293
+ name: string
294
+ collaborators: Record<string, 'read-only' | 'read-write'>
295
+ }
296
+
297
+ // =============================================================================
298
+ // Utility Types
299
+ // =============================================================================
300
+
301
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'
302
+
303
+ export interface ValidationResult {
304
+ valid: boolean
305
+ errors?: string[]
306
+ }
307
+
308
+ export interface FileInfo {
309
+ path: string
310
+ content: Uint8Array | string
311
+ size: number
312
+ }
313
+
314
+ // =============================================================================
315
+ // Constants Types
316
+ // =============================================================================
317
+
318
+ export interface CookieOptions {
319
+ path: string
320
+ httpOnly: boolean
321
+ secure: boolean
322
+ sameSite: 'strict' | 'lax' | 'none'
323
+ }
324
+
325
+ export interface ApiDocsConfig {
326
+ metaData: {
327
+ title: string
328
+ }
329
+ hideModels: boolean
330
+ hideDownloadButton: boolean
331
+ darkMode: boolean
332
+ favicon: string
333
+ defaultHttpClient: {
334
+ targetKey: string
335
+ clientKey: string
336
+ }
337
+ authentication: {
338
+ http: {
339
+ bearer: { token: string }
340
+ basic: { username: string; password: string }
341
+ }
342
+ }
343
+ hiddenClients: Record<string, boolean | string[]>
344
+ spec: {
345
+ content: any
346
+ }
347
+ customCss: string
348
+ }
349
+
350
+ // =============================================================================
351
+ // Authentication Provider Types (WorkOS)
352
+ // =============================================================================
353
+
354
+ export interface WorkOSUser {
355
+ id: string
356
+ email: string
357
+ firstName?: string
358
+ lastName?: string
359
+ profilePictureUrl?: string
360
+ createdAt: string
361
+ updatedAt: string
362
+ }
363
+
364
+ export interface WorkOSAuthResponse {
365
+ authenticated: boolean
366
+ sessionId?: string
367
+ organizationId?: string
368
+ role?: string
369
+ permissions?: string[]
370
+ user?: WorkOSUser
371
+ reason?: string
372
+ }
373
+
374
+ export interface WorkOSAuthResult {
375
+ user: WorkOSUser
376
+ sealedSession: string
377
+ }
378
+
379
+ // =============================================================================
380
+ // Search Types
381
+ // =============================================================================
382
+
383
+ export interface SearchResult {
384
+ name: string
385
+ version?: string
386
+ description?: string
387
+ keywords?: string[]
388
+ lastUpdated?: string
389
+ homepage?: string
390
+ repository?: string
391
+ bugs?: string
392
+ author?: string
393
+ publisher?: string
394
+ maintainers?: string[]
395
+ }
396
+
397
+ export interface SearchResponse {
398
+ objects: Array<{
399
+ package: {
400
+ name: string
401
+ scope: string
402
+ version: string
403
+ description: string
404
+ keywords: string[]
405
+ date: string
406
+ links: {
407
+ npm: string
408
+ homepage?: string
409
+ repository?: string
410
+ bugs?: string
411
+ }
412
+ author?: string
413
+ publisher?: string
414
+ maintainers: string[]
415
+ }
416
+ score: {
417
+ final: number
418
+ detail: {
419
+ quality: number
420
+ popularity: number
421
+ maintenance: number
422
+ }
423
+ }
424
+ searchScore: number
425
+ }>
426
+ total: number
427
+ time: string
428
+ }
429
+
430
+ // =============================================================================
431
+ // Environment Types
432
+ // =============================================================================
433
+
434
+ export interface Environment {
435
+ D1_DATABASE?: D1Database
436
+ CACHE_REFRESH_QUEUE?: any
437
+ WORKOS_API_KEY?: string
438
+ WORKOS_CLIENT_ID?: string
439
+ WORKOS_PROVIDER?: string
440
+ WORKOS_REDIRECT_URI?: string
441
+ WORKOS_COOKIE_PASSWORD?: string
442
+ CF?: {
443
+ connecting_ip?: string
444
+ }
445
+ [key: string]: any
446
+ }
@@ -0,0 +1,95 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ /**
4
+ * Consolidated Vitest configuration
5
+ *
6
+ * This config uses the VITEST_MODE environment variable to determine which tests to run:
7
+ * - No mode: Run all tests except excluded ones (default behavior)
8
+ * - cache: Run waituntil and cloudflare waituntil tests
9
+ * - improved: Run improved waituntil related tests
10
+ * - slim: Run manifest slimming tests
11
+ * - integrity: Run integrity validation tests
12
+ * - json-format: Run JSON response tests
13
+ * - packument: Run packument consistency tests
14
+ */
15
+
16
+ // Get the test mode from environment variable
17
+ const testMode = process.env.VITEST_MODE;
18
+
19
+ // Base configuration shared across all modes
20
+ const baseConfig = {
21
+ globals: true,
22
+ environment: 'node',
23
+ testTimeout: testMode ? 10000 : 30000, // Default 30s for full test suite, 10s for specific tests
24
+ watchExclude: ['node_modules', 'dist', '.git'],
25
+ };
26
+
27
+ // Define test configurations for each mode
28
+ const testConfigs = {
29
+ // Default config for running all tests
30
+ default: {
31
+ ...baseConfig,
32
+ exclude: [
33
+ '**/node_modules/**',
34
+ '**/dist/**',
35
+ '**/test/cache-refresh.test.js',
36
+ '**/test/background-refresh.test.js',
37
+ '**/test/caching-simple.test.js',
38
+ '**/test/waituntil-task-queue.test.js',
39
+ '**/test/background-concepts.test.js',
40
+ '**/test/waituntil.test.js'
41
+ ],
42
+ include: ['test/**/*.test.js'],
43
+ hookTimeout: 30000,
44
+ teardownTimeout: 10000,
45
+ reporters: ['verbose'],
46
+ },
47
+ // Config for cache tests
48
+ cache: {
49
+ ...baseConfig,
50
+ include: [
51
+ 'test/waituntil-demo.test.js',
52
+ 'test/cloudflare-waituntil.test.js'
53
+ ],
54
+ },
55
+ // Config for improved tests
56
+ improved: {
57
+ ...baseConfig,
58
+ include: [
59
+ 'test/waituntil-correct.test.js',
60
+ 'test/hono-context.test.js',
61
+ 'test/route-with-waituntil.test.js'
62
+ ],
63
+ },
64
+ // Config for slim tests
65
+ slim: {
66
+ ...baseConfig,
67
+ include: ['test/manifest-slimming.test.js'],
68
+ testTimeout: 5000,
69
+ },
70
+ // Config for integrity tests
71
+ integrity: {
72
+ ...baseConfig,
73
+ include: ['test/integrity-validation.test.js'],
74
+ },
75
+ // Config for JSON format tests
76
+ 'json-format': {
77
+ ...baseConfig,
78
+ include: ['test/json-response.test.js'],
79
+ },
80
+ // Config for packument tests
81
+ packument: {
82
+ ...baseConfig,
83
+ include: [
84
+ 'test/packument-consistency.test.js',
85
+ 'test/packument-version-range.test.js'
86
+ ],
87
+ },
88
+ };
89
+
90
+ // Select the appropriate config based on the mode
91
+ const selectedConfig = testConfigs[testMode] || testConfigs.default;
92
+
93
+ export default defineConfig({
94
+ test: selectedConfig,
95
+ });
package/wrangler.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "vsr",
3
+ "main": "src/index.ts",
4
+ "compatibility_date": "2024-01-01",
5
+ "d1_databases": [
6
+ {
7
+ "binding": "DB",
8
+ "database_name": "vsr-local-database",
9
+ "database_id": "local",
10
+ "migrations_dir": "src/db/migrations"
11
+ }
12
+ ],
13
+ "version_metadata": {
14
+ "binding": "CF_VERSION_METADATA"
15
+ },
16
+ "compatibility_flags": [
17
+ "nodejs_compat"
18
+ ],
19
+ "workers_dev": true,
20
+ "r2_buckets": [
21
+ {
22
+ "binding": "BUCKET",
23
+ "bucket_name": "vsr-local-bucket"
24
+ }
25
+ ],
26
+ "queues": {
27
+ "producers": [
28
+ {
29
+ "queue": "cache-refresh-queue",
30
+ "binding": "CACHE_REFRESH_QUEUE"
31
+ }
32
+ ],
33
+ "consumers": [
34
+ {
35
+ "queue": "cache-refresh-queue",
36
+ "max_batch_size": 10,
37
+ "max_batch_timeout": 5,
38
+ "max_retries": 3
39
+ }
40
+ ]
41
+ },
42
+ "assets": {
43
+ "directory": "./src/assets/",
44
+ "binding": "ASSETS"
45
+ },
46
+ "dev": {
47
+ "local_protocol": "http",
48
+ "port": 1337
49
+ },
50
+ "placement": {
51
+ "mode": "smart"
52
+ },
53
+ "vars": {
54
+ "SENTRY": {
55
+ "dsn": "https://909b085eb764c00250ad312660c2fdf1@o4506397716054016.ingest.us.sentry.io/4509492612300800"
56
+ }
57
+ }
58
+ }