openpets 1.0.4 → 1.0.6

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 (96) hide show
  1. package/dist/data/api.json +3172 -0
  2. package/dist/src/core/ai-client-base/index.d.ts +47 -0
  3. package/dist/src/core/ai-client-base/index.d.ts.map +1 -0
  4. package/dist/src/core/ai-client-base/index.js +168 -0
  5. package/dist/src/core/ai-client-base/index.js.map +1 -0
  6. package/dist/src/core/browser.d.ts +10 -0
  7. package/dist/src/core/browser.d.ts.map +1 -0
  8. package/{browser.ts → dist/src/core/browser.js} +4 -4
  9. package/dist/src/core/browser.js.map +1 -0
  10. package/dist/src/core/build-pet.d.ts +2 -0
  11. package/dist/src/core/build-pet.d.ts.map +1 -0
  12. package/dist/src/core/build-pet.js +364 -0
  13. package/dist/src/core/build-pet.js.map +1 -0
  14. package/dist/src/core/cli.d.ts +3 -0
  15. package/dist/src/core/cli.d.ts.map +1 -0
  16. package/dist/src/core/cli.js +244 -0
  17. package/dist/src/core/cli.js.map +1 -0
  18. package/dist/src/core/config-manager.d.ts +13 -0
  19. package/dist/src/core/config-manager.d.ts.map +1 -0
  20. package/dist/src/core/config-manager.js +59 -0
  21. package/dist/src/core/config-manager.js.map +1 -0
  22. package/dist/src/core/deploy-pet.d.ts +2 -0
  23. package/dist/src/core/deploy-pet.d.ts.map +1 -0
  24. package/dist/src/core/deploy-pet.js +66 -0
  25. package/dist/src/core/deploy-pet.js.map +1 -0
  26. package/dist/src/core/index.d.ts +11 -0
  27. package/dist/src/core/index.d.ts.map +1 -0
  28. package/dist/src/core/index.js +11 -0
  29. package/dist/src/core/index.js.map +1 -0
  30. package/dist/src/core/local-cache.d.ts +69 -0
  31. package/dist/src/core/local-cache.d.ts.map +1 -0
  32. package/dist/src/core/local-cache.js +212 -0
  33. package/dist/src/core/local-cache.js.map +1 -0
  34. package/dist/src/core/logger.d.ts.map +1 -0
  35. package/{logger.js → dist/src/core/logger.js} +8 -9
  36. package/dist/src/core/logger.js.map +1 -0
  37. package/dist/src/core/mcp-factory.d.ts +12 -0
  38. package/dist/src/core/mcp-factory.d.ts.map +1 -0
  39. package/dist/src/core/mcp-factory.js +143 -0
  40. package/dist/src/core/mcp-factory.js.map +1 -0
  41. package/dist/src/core/mcp-server.d.ts +3 -0
  42. package/dist/src/core/mcp-server.d.ts.map +1 -0
  43. package/dist/src/core/mcp-server.js +55 -0
  44. package/dist/src/core/mcp-server.js.map +1 -0
  45. package/dist/src/core/migrate-plugin.d.ts +15 -0
  46. package/dist/src/core/migrate-plugin.d.ts.map +1 -0
  47. package/dist/src/core/migrate-plugin.js +181 -0
  48. package/dist/src/core/migrate-plugin.js.map +1 -0
  49. package/dist/src/core/pets-registry.d.ts +47 -0
  50. package/dist/src/core/pets-registry.d.ts.map +1 -0
  51. package/dist/src/core/pets-registry.js +109 -0
  52. package/dist/src/core/pets-registry.js.map +1 -0
  53. package/dist/src/core/plugin-factory.d.ts +58 -0
  54. package/dist/src/core/plugin-factory.d.ts.map +1 -0
  55. package/dist/src/core/plugin-factory.js +212 -0
  56. package/dist/src/core/plugin-factory.js.map +1 -0
  57. package/dist/src/core/prompt-utils.d.ts +14 -0
  58. package/dist/src/core/prompt-utils.d.ts.map +1 -0
  59. package/dist/src/core/prompt-utils.js +106 -0
  60. package/dist/src/core/prompt-utils.js.map +1 -0
  61. package/dist/src/core/schema-helpers.d.ts +33 -0
  62. package/dist/src/core/schema-helpers.d.ts.map +1 -0
  63. package/dist/src/core/schema-helpers.js +46 -0
  64. package/dist/src/core/schema-helpers.js.map +1 -0
  65. package/dist/src/core/search-pets.d.ts +29 -0
  66. package/dist/src/core/search-pets.d.ts.map +1 -0
  67. package/dist/src/core/search-pets.js +196 -0
  68. package/dist/src/core/search-pets.js.map +1 -0
  69. package/dist/src/core/types.d.ts +63 -0
  70. package/dist/src/core/types.d.ts.map +1 -0
  71. package/dist/src/core/types.js +2 -0
  72. package/dist/src/core/types.js.map +1 -0
  73. package/dist/src/core/validate-pet.d.ts +40 -0
  74. package/dist/src/core/validate-pet.d.ts.map +1 -0
  75. package/dist/src/core/validate-pet.js +650 -0
  76. package/dist/src/core/validate-pet.js.map +1 -0
  77. package/package.json +7 -11
  78. package/ai-client-base/index.ts +0 -117
  79. package/build-pet.ts +0 -429
  80. package/cli.ts +0 -179
  81. package/config-manager.ts +0 -82
  82. package/deploy-pet.ts +0 -91
  83. package/index.ts +0 -10
  84. package/local-cache.ts +0 -280
  85. package/logger.ts +0 -143
  86. package/mcp-factory.ts +0 -180
  87. package/mcp-server.ts +0 -69
  88. package/migrate-plugin.ts +0 -220
  89. package/pets-registry.ts +0 -160
  90. package/plugin-factory.ts +0 -309
  91. package/prompt-utils.ts +0 -130
  92. package/schema-helpers.ts +0 -59
  93. package/search-pets.ts +0 -267
  94. package/types.ts +0 -68
  95. package/validate-pet.ts +0 -594
  96. /package/{logger.d.ts → dist/src/core/logger.d.ts} +0 -0
package/cli.ts DELETED
@@ -1,179 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import { buildPet } from './build-pet.js'
4
- import { deployPet } from './deploy-pet.js'
5
- import { addFolderToHistory } from './config-manager.js'
6
- import { spawn } from 'child_process'
7
- import { resolve } from 'path'
8
- import { readFileSync, existsSync } from 'fs'
9
-
10
- const args = process.argv.slice(2)
11
- const command = args[0]
12
-
13
- const DEBUG = process.env.PETS_DEBUG === 'true'
14
-
15
- function log(...msgs: any[]) {
16
- if (DEBUG) {
17
- console.log('[PETS-CLI]', ...msgs)
18
- }
19
- }
20
-
21
- function displayInstalledPlugins() {
22
- const opencodeJsonPath = resolve(process.cwd(), 'opencode.json')
23
-
24
- log('Looking for opencode.json at:', opencodeJsonPath)
25
-
26
- if (!existsSync(opencodeJsonPath)) {
27
- log('opencode.json not found')
28
- return
29
- }
30
-
31
- try {
32
- const content = readFileSync(opencodeJsonPath, 'utf-8')
33
- log('opencode.json content length:', content.length)
34
-
35
- const config = JSON.parse(content)
36
- log('Parsed opencode.json config:', Object.keys(config))
37
-
38
- if (config.plugin && Array.isArray(config.plugin) && config.plugin.length > 0) {
39
- const plugins = config.plugin.filter((p: string) => typeof p === 'string' && p.trim())
40
- log('Found plugins:', plugins)
41
-
42
- if (plugins.length > 0) {
43
- console.log('\x1b[36m%s\x1b[0m', '📦 Installed plugins:')
44
- console.log('\x1b[2m%s\x1b[0m', ` ${plugins.join(', ')}`)
45
- console.log('')
46
- }
47
- } else {
48
- log('No plugins found in config')
49
- }
50
- } catch (error) {
51
- log('Error reading opencode.json:', error)
52
- }
53
- }
54
-
55
- function killPort(port: number) {
56
- log(`Attempting to kill processes on port ${port}`)
57
- try {
58
- const { execSync } = require('child_process')
59
- execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null || true`, { stdio: 'ignore' })
60
- log(`Successfully killed processes on port ${port}`)
61
- } catch (error) {
62
- log(`No processes to kill on port ${port} or error occurred:`, error)
63
- }
64
- }
65
-
66
- function launchManager() {
67
- const startTime = Date.now()
68
- const uiDir = resolve(__dirname, '../../apps/desktop')
69
- const projectDir = process.cwd()
70
-
71
- addFolderToHistory(projectDir)
72
-
73
- console.log('Launching OpenPets Plugin Manager...')
74
- console.log(`Project directory: ${projectDir}`)
75
- console.log(`UI directory: ${uiDir}`)
76
-
77
- log('Launch details:', {
78
- __dirname,
79
- uiDir,
80
- projectDir,
81
- uiDirExists: existsSync(uiDir),
82
- timestamp: new Date().toISOString()
83
- })
84
-
85
- if (!existsSync(uiDir)) {
86
- console.error(`❌ UI directory not found: ${uiDir}`)
87
- log('Contents of parent directory:', require('fs').readdirSync(resolve(__dirname, '../../apps')))
88
- process.exit(1)
89
- }
90
-
91
- log('Checking for package.json in UI directory')
92
- const packageJsonPath = resolve(uiDir, 'package.json')
93
- if (!existsSync(packageJsonPath)) {
94
- console.error(`❌ package.json not found in UI directory: ${packageJsonPath}`)
95
- process.exit(1)
96
- }
97
-
98
- killPort(1420)
99
-
100
- log('Spawning npm process with tauri:dev')
101
- const child = spawn('npm', ['run', 'tauri:dev'], {
102
- cwd: uiDir,
103
- stdio: 'inherit',
104
- shell: true,
105
- env: {
106
- ...process.env,
107
- OPENPETS_PROJECT_DIR: projectDir,
108
- PETS_DEBUG: DEBUG ? 'true' : 'false'
109
- }
110
- })
111
-
112
- log('Child process spawned with PID:', child.pid)
113
-
114
- child.on('error', (error) => {
115
- console.error('Failed to launch manager:', error)
116
- log('Error details:', {
117
- message: error.message,
118
- stack: error.stack,
119
- elapsed: Date.now() - startTime
120
- })
121
- process.exit(1)
122
- })
123
-
124
- child.on('exit', (code) => {
125
- const elapsed = Date.now() - startTime
126
- log(`Manager process exited with code ${code} after ${elapsed}ms`)
127
- if (code !== 0) {
128
- console.error(`Manager exited with code ${code}`)
129
- process.exit(code || 1)
130
- }
131
- })
132
- }
133
-
134
- log('=== PETS CLI STARTUP ===')
135
- log('Command:', command)
136
- log('Args:', args)
137
- log('CWD:', process.cwd())
138
- log('ENV.OPENPETS_PROJECT_DIR:', process.env.OPENPETS_PROJECT_DIR)
139
- log('ENV.PETS_DEBUG:', process.env.PETS_DEBUG)
140
-
141
- displayInstalledPlugins()
142
-
143
- if (command === 'build') {
144
- const petName = args[1]
145
- log('Building pet:', petName)
146
- buildPet(petName).catch(error => {
147
- console.error('Error building pet:', error)
148
- log('Build error details:', error)
149
- process.exit(1)
150
- })
151
- } else if (command === 'deploy') {
152
- const petName = args[1]
153
- log('Deploying pet:', petName)
154
- deployPet(petName).catch(error => {
155
- console.error('Error deploying pet:', error)
156
- log('Deploy error details:', error)
157
- process.exit(1)
158
- })
159
- } else if (!command) {
160
- log('No command provided, launching manager UI')
161
- launchManager()
162
- } else {
163
- log('Unknown command:', command)
164
- console.error('Usage: pets [command] [options]')
165
- console.error('')
166
- console.error('Commands:')
167
- console.error(' (none) Launch the plugin manager UI (default)')
168
- console.error(' build <pet-name> Build and validate a pet package')
169
- console.error(' deploy <pet-name> Build and deploy a pet package with metadata')
170
- console.error('')
171
- console.error('Examples:')
172
- console.error(' pets # Launch the desktop plugin manager')
173
- console.error(' pets build postgres')
174
- console.error(' pets deploy maps')
175
- console.error('')
176
- console.error('Environment:')
177
- console.error(' PETS_DEBUG=true Enable detailed debug logging')
178
- process.exit(1)
179
- }
package/config-manager.ts DELETED
@@ -1,82 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
2
- import { join } from 'path'
3
- import { homedir } from 'os'
4
-
5
- export interface PetsConfig {
6
- folderHistory: Array<{
7
- path: string
8
- timestamp: number
9
- name?: string
10
- }>
11
- }
12
-
13
- const CONFIG_DIR = join(homedir(), '.pets')
14
- const CONFIG_FILE = join(CONFIG_DIR, 'config.json')
15
- const MAX_HISTORY_ITEMS = 20
16
-
17
- function ensureConfigDir(): void {
18
- if (!existsSync(CONFIG_DIR)) {
19
- mkdirSync(CONFIG_DIR, { recursive: true })
20
- }
21
- }
22
-
23
- export function readConfig(): PetsConfig {
24
- ensureConfigDir()
25
-
26
- if (!existsSync(CONFIG_FILE)) {
27
- return { folderHistory: [] }
28
- }
29
-
30
- try {
31
- const content = readFileSync(CONFIG_FILE, 'utf-8')
32
- return JSON.parse(content)
33
- } catch (error) {
34
- console.error('Error reading config file:', error)
35
- return { folderHistory: [] }
36
- }
37
- }
38
-
39
- export function writeConfig(config: PetsConfig): void {
40
- ensureConfigDir()
41
-
42
- try {
43
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8')
44
- } catch (error) {
45
- console.error('Error writing config file:', error)
46
- }
47
- }
48
-
49
- export function addFolderToHistory(folderPath: string): void {
50
- const config = readConfig()
51
-
52
- const existingIndex = config.folderHistory.findIndex(
53
- entry => entry.path === folderPath
54
- )
55
-
56
- if (existingIndex !== -1) {
57
- config.folderHistory.splice(existingIndex, 1)
58
- }
59
-
60
- const folderName = folderPath.split('/').pop() || folderPath
61
-
62
- config.folderHistory.unshift({
63
- path: folderPath,
64
- timestamp: Date.now(),
65
- name: folderName
66
- })
67
-
68
- if (config.folderHistory.length > MAX_HISTORY_ITEMS) {
69
- config.folderHistory = config.folderHistory.slice(0, MAX_HISTORY_ITEMS)
70
- }
71
-
72
- writeConfig(config)
73
- }
74
-
75
- export function getFolderHistory(): PetsConfig['folderHistory'] {
76
- const config = readConfig()
77
- return config.folderHistory
78
- }
79
-
80
- export function clearFolderHistory(): void {
81
- writeConfig({ folderHistory: [] })
82
- }
package/deploy-pet.ts DELETED
@@ -1,91 +0,0 @@
1
- import { join, dirname } from 'path'
2
- import { fileURLToPath } from 'url'
3
- import { existsSync, readdirSync, readFileSync, writeFileSync, statSync, mkdirSync } from 'fs'
4
- import { buildPet } from './build-pet.js'
5
-
6
- const __filename = fileURLToPath(import.meta.url)
7
- const __dirname = dirname(__filename)
8
- const projectRoot = dirname(dirname(__dirname))
9
-
10
- interface PetData {
11
- name: string
12
- version: string
13
- description: string
14
- source_code_url: string
15
- [key: string]: any
16
- }
17
-
18
- export async function deployPet(petName?: string): Promise<void> {
19
- if (!petName) {
20
- const cwd = process.cwd()
21
- const petsDir = join(projectRoot, 'pets')
22
-
23
- if (cwd.includes('/pets/')) {
24
- petName = cwd.split('/pets/')[1].split('/')[0]
25
- console.log(`📦 Auto-detected pet: ${petName}`)
26
- }
27
-
28
- if (!petName) {
29
- console.error('Usage: pets deploy <pet-name>')
30
- console.error('Example: pets deploy postgres')
31
- console.error('')
32
- console.error('Available pets:')
33
- if (existsSync(petsDir)) {
34
- const pets = readdirSync(petsDir).filter(dir => {
35
- const petPath = join(petsDir, dir)
36
- return statSync(petPath).isDirectory() && dir !== '_TEMPLATE_'
37
- })
38
- pets.forEach(pet => console.error(` - ${pet}`))
39
- }
40
- process.exit(1)
41
- }
42
- }
43
-
44
- console.log(`🚀 Deploying ${petName}...`)
45
-
46
- await buildPet(petName)
47
-
48
- const petDir = join(projectRoot, 'pets', petName)
49
- const packageJsonPath = join(petDir, 'package.json')
50
-
51
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
52
-
53
- const sourceCodeUrl = `https://github.com/raggle-ai/pets/tree/main/pets/${petName}`
54
-
55
- const fieldsToExclude = ['$schema', 'version', 'main', 'types', 'scripts', 'license', 'repository', 'dependencies', 'author', 'utils', 'devDependencies']
56
-
57
- const filteredData = Object.keys(packageJson).reduce((acc, key) => {
58
- if (!fieldsToExclude.includes(key)) {
59
- acc[key] = packageJson[key]
60
- }
61
- return acc
62
- }, {} as Record<string, any>)
63
-
64
- const petData = {
65
- ...filteredData,
66
- name: packageJson.name,
67
- version: packageJson.version,
68
- description: packageJson.description,
69
- source_code_url: sourceCodeUrl,
70
- } as PetData
71
-
72
- const dataDir = join(projectRoot, 'data')
73
- if (!existsSync(dataDir)) {
74
- mkdirSync(dataDir, { recursive: true })
75
- }
76
-
77
- const petsJsonPath = join(dataDir, 'pets.json')
78
- let petsData: Record<string, PetData> = {}
79
-
80
- if (existsSync(petsJsonPath)) {
81
- petsData = JSON.parse(readFileSync(petsJsonPath, 'utf8'))
82
- }
83
-
84
- petsData[packageJson.name] = petData
85
-
86
- writeFileSync(petsJsonPath, JSON.stringify(petsData, null, 2), 'utf8')
87
-
88
- console.log(`✅ ${petName} deployed successfully!`)
89
- console.log(` Source URL: ${sourceCodeUrl}`)
90
- console.log(` Data saved to: ${petsJsonPath}`)
91
- }
package/index.ts DELETED
@@ -1,10 +0,0 @@
1
- export * from './plugin-factory'
2
- export * from './schema-helpers'
3
- export * from './validate-pet'
4
- export * from './pets-registry'
5
- export * from './build-pet'
6
- export * from './types'
7
- export * from './ai-client-base'
8
- export * from './search-pets'
9
- export * from './logger'
10
- export * from './local-cache'
package/local-cache.ts DELETED
@@ -1,280 +0,0 @@
1
- export interface CacheEntry<T> {
2
- data: T
3
- timestamp: number
4
- hits: number
5
- }
6
-
7
- export interface CacheStats {
8
- hits: number
9
- misses: number
10
- size: number
11
- oldestEntry?: number
12
- newestEntry?: number
13
- }
14
-
15
- export interface CacheConfig {
16
- ttl?: number
17
- maxSize?: number
18
- cleanupInterval?: number
19
- }
20
-
21
- export class LocalCache<T = any> {
22
- private cache: Map<string, CacheEntry<T>> = new Map()
23
- private stats = { hits: 0, misses: 0 }
24
- private ttl: number
25
- private maxSize: number
26
- private cleanupInterval: number | null
27
- private cleanupTimer: NodeJS.Timeout | null = null
28
-
29
- constructor(config: CacheConfig = {}) {
30
- this.ttl = config.ttl || 3600000
31
- this.maxSize = config.maxSize || 1000
32
- this.cleanupInterval = config.cleanupInterval || null
33
-
34
- if (this.cleanupInterval) {
35
- this.startCleanup()
36
- }
37
- }
38
-
39
- private startCleanup(): void {
40
- if (this.cleanupInterval) {
41
- this.cleanupTimer = setInterval(() => {
42
- this.cleanup()
43
- }, this.cleanupInterval)
44
- }
45
- }
46
-
47
- private stopCleanup(): void {
48
- if (this.cleanupTimer) {
49
- clearInterval(this.cleanupTimer)
50
- this.cleanupTimer = null
51
- }
52
- }
53
-
54
- get(key: string): T | null {
55
- const entry = this.cache.get(key)
56
-
57
- if (!entry) {
58
- this.stats.misses++
59
- return null
60
- }
61
-
62
- const now = Date.now()
63
- if (now - entry.timestamp > this.ttl) {
64
- this.cache.delete(key)
65
- this.stats.misses++
66
- return null
67
- }
68
-
69
- entry.hits++
70
- this.stats.hits++
71
- return entry.data
72
- }
73
-
74
- set(key: string, data: T): void {
75
- if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
76
- this.evictOldest()
77
- }
78
-
79
- this.cache.set(key, {
80
- data,
81
- timestamp: Date.now(),
82
- hits: 0
83
- })
84
- }
85
-
86
- has(key: string): boolean {
87
- const entry = this.cache.get(key)
88
- if (!entry) return false
89
-
90
- const now = Date.now()
91
- if (now - entry.timestamp > this.ttl) {
92
- this.cache.delete(key)
93
- return false
94
- }
95
-
96
- return true
97
- }
98
-
99
- delete(key: string): boolean {
100
- return this.cache.delete(key)
101
- }
102
-
103
- clear(): void {
104
- this.cache.clear()
105
- this.stats = { hits: 0, misses: 0 }
106
- }
107
-
108
- cleanup(): number {
109
- const now = Date.now()
110
- let removed = 0
111
-
112
- for (const [key, entry] of this.cache.entries()) {
113
- if (now - entry.timestamp > this.ttl) {
114
- this.cache.delete(key)
115
- removed++
116
- }
117
- }
118
-
119
- return removed
120
- }
121
-
122
- private evictOldest(): void {
123
- let oldestKey: string | null = null
124
- let oldestTime = Infinity
125
-
126
- for (const [key, entry] of this.cache.entries()) {
127
- if (entry.timestamp < oldestTime) {
128
- oldestTime = entry.timestamp
129
- oldestKey = key
130
- }
131
- }
132
-
133
- if (oldestKey) {
134
- this.cache.delete(oldestKey)
135
- }
136
- }
137
-
138
- getStats(): CacheStats {
139
- const entries = Array.from(this.cache.values())
140
- const timestamps = entries.map(e => e.timestamp)
141
-
142
- return {
143
- hits: this.stats.hits,
144
- misses: this.stats.misses,
145
- size: this.cache.size,
146
- oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : undefined,
147
- newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : undefined
148
- }
149
- }
150
-
151
- keys(): string[] {
152
- return Array.from(this.cache.keys())
153
- }
154
-
155
- values(): T[] {
156
- return Array.from(this.cache.values()).map(entry => entry.data)
157
- }
158
-
159
- entries(): Array<[string, T]> {
160
- return Array.from(this.cache.entries()).map(([key, entry]) => [key, entry.data])
161
- }
162
-
163
- getHitRate(): number {
164
- const total = this.stats.hits + this.stats.misses
165
- return total === 0 ? 0 : this.stats.hits / total
166
- }
167
-
168
- destroy(): void {
169
- this.stopCleanup()
170
- this.clear()
171
- }
172
- }
173
-
174
- export class SingleValueCache<T = any> {
175
- private data: T | null = null
176
- private timestamp: number = 0
177
- private ttl: number
178
- private hits: number = 0
179
- private misses: number = 0
180
-
181
- constructor(config: { ttl?: number } = {}) {
182
- this.ttl = config.ttl || 3600000
183
- }
184
-
185
- get(): T | null {
186
- if (!this.data) {
187
- this.misses++
188
- return null
189
- }
190
-
191
- const now = Date.now()
192
- if (now - this.timestamp > this.ttl) {
193
- this.data = null
194
- this.misses++
195
- return null
196
- }
197
-
198
- this.hits++
199
- return this.data
200
- }
201
-
202
- set(data: T): void {
203
- this.data = data
204
- this.timestamp = Date.now()
205
- }
206
-
207
- has(): boolean {
208
- if (!this.data) return false
209
-
210
- const now = Date.now()
211
- if (now - this.timestamp > this.ttl) {
212
- this.data = null
213
- return false
214
- }
215
-
216
- return true
217
- }
218
-
219
- clear(): void {
220
- this.data = null
221
- this.timestamp = 0
222
- }
223
-
224
- getAge(): number {
225
- if (!this.data) return -1
226
- return Date.now() - this.timestamp
227
- }
228
-
229
- getStats(): { hits: number; misses: number; hasData: boolean; age: number } {
230
- return {
231
- hits: this.hits,
232
- misses: this.misses,
233
- hasData: this.has(),
234
- age: this.getAge()
235
- }
236
- }
237
-
238
- getHitRate(): number {
239
- const total = this.hits + this.misses
240
- return total === 0 ? 0 : this.hits / total
241
- }
242
- }
243
-
244
- export async function withCache<T>(
245
- cache: LocalCache<T> | SingleValueCache<T>,
246
- key: string | null,
247
- fetcher: () => Promise<T>
248
- ): Promise<T> {
249
- if (cache instanceof SingleValueCache) {
250
- const cached = cache.get()
251
- if (cached !== null) {
252
- return cached
253
- }
254
-
255
- const data = await fetcher()
256
- cache.set(data)
257
- return data
258
- }
259
-
260
- if (key === null) {
261
- throw new Error('Key is required for LocalCache')
262
- }
263
-
264
- const cached = cache.get(key)
265
- if (cached !== null) {
266
- return cached
267
- }
268
-
269
- const data = await fetcher()
270
- cache.set(key, data)
271
- return data
272
- }
273
-
274
- export function createCache<T = any>(config: CacheConfig = {}): LocalCache<T> {
275
- return new LocalCache<T>(config)
276
- }
277
-
278
- export function createSingleValueCache<T = any>(config: { ttl?: number } = {}): SingleValueCache<T> {
279
- return new SingleValueCache<T>(config)
280
- }