@sylphx/flow 1.1.0 → 1.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 (45) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/package.json +12 -2
  3. package/src/commands/hook-command.ts +10 -230
  4. package/src/composables/index.ts +0 -1
  5. package/src/config/servers.ts +35 -78
  6. package/src/core/interfaces.ts +0 -33
  7. package/src/domains/index.ts +0 -2
  8. package/src/index.ts +0 -4
  9. package/src/services/mcp-service.ts +0 -16
  10. package/src/targets/claude-code.ts +3 -9
  11. package/src/targets/functional/claude-code-logic.ts +4 -22
  12. package/src/targets/opencode.ts +0 -6
  13. package/src/types/mcp.types.ts +29 -38
  14. package/src/types/target.types.ts +0 -2
  15. package/src/types.ts +0 -1
  16. package/src/commands/codebase-command.ts +0 -168
  17. package/src/commands/knowledge-command.ts +0 -161
  18. package/src/composables/useTargetConfig.ts +0 -45
  19. package/src/core/formatting/bytes.test.ts +0 -115
  20. package/src/core/validation/limit.test.ts +0 -155
  21. package/src/core/validation/query.test.ts +0 -44
  22. package/src/domains/codebase/index.ts +0 -5
  23. package/src/domains/codebase/tools.ts +0 -139
  24. package/src/domains/knowledge/index.ts +0 -10
  25. package/src/domains/knowledge/resources.ts +0 -537
  26. package/src/domains/knowledge/tools.ts +0 -174
  27. package/src/services/search/base-indexer.ts +0 -156
  28. package/src/services/search/codebase-indexer-types.ts +0 -38
  29. package/src/services/search/codebase-indexer.ts +0 -647
  30. package/src/services/search/embeddings-provider.ts +0 -455
  31. package/src/services/search/embeddings.ts +0 -316
  32. package/src/services/search/functional-indexer.ts +0 -323
  33. package/src/services/search/index.ts +0 -27
  34. package/src/services/search/indexer.ts +0 -380
  35. package/src/services/search/knowledge-indexer.ts +0 -422
  36. package/src/services/search/semantic-search.ts +0 -244
  37. package/src/services/search/tfidf.ts +0 -559
  38. package/src/services/search/unified-search-service.ts +0 -888
  39. package/src/services/storage/cache-storage.ts +0 -487
  40. package/src/services/storage/drizzle-storage.ts +0 -581
  41. package/src/services/storage/index.ts +0 -15
  42. package/src/services/storage/lancedb-vector-storage.ts +0 -494
  43. package/src/services/storage/memory-storage.ts +0 -268
  44. package/src/services/storage/separated-storage.ts +0 -467
  45. package/src/services/storage/vector-storage.ts +0 -13
@@ -1,30 +1,26 @@
1
1
  /**
2
- * MCP (Model Context Protocol) configuration types
3
- * Types for configuring and managing MCP servers
2
+ * MCP (Model Context Protocol) Configuration Types
3
+ * Defines configuration formats for different MCP server implementations
4
4
  */
5
5
 
6
- import type { Resolvable } from './common.types.js';
7
-
8
6
  /**
9
- * MCP server configuration for stdio-based servers
10
- * Communicates via standard input/output
7
+ * Base MCP server configuration with stdio transport
11
8
  */
12
- export interface MCPServerConfig {
9
+ export type MCPServerConfig = {
13
10
  type: 'stdio';
14
- command: Resolvable<string>;
15
- args?: Resolvable<string[]>;
11
+ command: string;
12
+ args?: string[];
16
13
  env?: Record<string, string>;
17
- }
14
+ };
18
15
 
19
16
  /**
20
- * MCP server configuration for HTTP-based servers
21
- * Communicates via HTTP requests
17
+ * MCP server configuration with HTTP transport
22
18
  */
23
- export interface MCPServerConfigHTTP {
19
+ export type MCPServerConfigHTTP = {
24
20
  type: 'http';
25
- url: Resolvable<string>;
21
+ url: string;
26
22
  headers?: Record<string, string>;
27
- }
23
+ };
28
24
 
29
25
  /**
30
26
  * Union of all possible MCP server configurations
@@ -32,38 +28,33 @@ export interface MCPServerConfigHTTP {
32
28
  export type MCPServerConfigUnion = MCPServerConfig | MCPServerConfigHTTP;
33
29
 
34
30
  /**
35
- * MCP server configuration flags
36
- * Used to disable specific server features per target
31
+ * OpenCode-specific configuration format
37
32
  */
38
- export type MCPServerConfigFlags = {
39
- disableTime?: boolean;
40
- disableKnowledge?: boolean;
41
- disableCodebase?: boolean;
33
+ export type OpenCodeConfig = {
34
+ type: 'local' | 'remote';
35
+ command?: string[];
36
+ url?: string;
37
+ headers?: Record<string, string>;
38
+ environment?: Record<string, string>;
42
39
  };
43
40
 
44
41
  /**
45
- * OpenCode-specific configuration format
42
+ * Type guard for stdio config
46
43
  */
47
- export interface OpenCodeConfig {
48
- $schema?: string;
49
- mcp?: Record<string, MCPServerConfigUnion>;
44
+ export function isStdioConfig(config: MCPServerConfigUnion): config is MCPServerConfig {
45
+ return config.type === 'stdio';
50
46
  }
51
47
 
52
48
  /**
53
- * Type guard: Check if config is stdio-based
54
- */
55
- export const isStdioConfig = (config: MCPServerConfigUnion): config is MCPServerConfig =>
56
- config.type === 'stdio';
57
-
58
- /**
59
- * Type guard: Check if config is HTTP-based
49
+ * Type guard for HTTP config
60
50
  */
61
- export const isHttpConfig = (config: MCPServerConfigUnion): config is MCPServerConfigHTTP =>
62
- config.type === 'http';
51
+ export function isHttpConfig(config: MCPServerConfigUnion): config is MCPServerConfigHTTP {
52
+ return config.type === 'http';
53
+ }
63
54
 
64
55
  /**
65
- * Type guard: Check if config is CLI command-based (stdio)
66
- * Alias for isStdioConfig for backward compatibility
56
+ * Type guard for CLI command config (stdio)
67
57
  */
68
- export const isCLICommandConfig = (config: MCPServerConfigUnion): config is MCPServerConfig =>
69
- isStdioConfig(config);
58
+ export function isCLICommandConfig(config: MCPServerConfigUnion): config is MCPServerConfig {
59
+ return isStdioConfig(config);
60
+ }
@@ -69,8 +69,6 @@ export interface Target {
69
69
  // Configuration
70
70
  /** Target-specific configuration */
71
71
  readonly config: TargetConfig;
72
- /** MCP server configuration for this target */
73
- readonly mcpServerConfig?: MCPServerConfigFlags;
74
72
 
75
73
  // Required transformation methods
76
74
  /** Transform agent content for the target */
package/src/types.ts CHANGED
@@ -24,7 +24,6 @@ export type {
24
24
  } from './types/common.types.js';
25
25
  export type {
26
26
  MCPServerConfig,
27
- MCPServerConfigFlags,
28
27
  MCPServerConfigHTTP,
29
28
  MCPServerConfigUnion,
30
29
  OpenCodeConfig,
@@ -1,168 +0,0 @@
1
- import chalk from 'chalk';
2
- import { Command } from 'commander';
3
- import ora from 'ora';
4
- import { ReindexMonitor } from '../components/reindex-progress.js';
5
- import { CodebaseIndexer } from '../services/search/codebase-indexer.js';
6
- import { getDefaultEmbeddingProvider } from '../services/search/embeddings.js';
7
- import { getSearchService } from '../services/search/unified-search-service.js';
8
- import { CLIError } from '../utils/error-handler.js';
9
-
10
- export const codebaseSearchCommand = new Command('search')
11
- .description('Search codebase files and source code')
12
- .argument('<query>', 'Search query - use natural language, function names, or technical terms')
13
- .option('-l, --limit <number>', 'Maximum number of results to return', '10')
14
- .option('--include-content', 'Include file content snippets in results', true)
15
- .option('--extensions <exts...>', 'Filter by file extensions (e.g., .ts .tsx .js)')
16
- .option('--path <pattern>', 'Filter by path pattern (e.g., src/components)')
17
- .option('--exclude <patterns...>', 'Exclude paths containing these patterns')
18
- .action(async (query, options) => {
19
- try {
20
- console.log('');
21
- console.log(chalk.cyan.bold('▸ Search Codebase'));
22
- console.log(chalk.gray(` Query: "${query}"`));
23
-
24
- const spinner = ora('Searching...').start();
25
- const searchService = getSearchService();
26
- await searchService.initialize();
27
-
28
- const result = await searchService.searchCodebase(query, {
29
- limit: Number.parseInt(String(options.limit), 10) || 10,
30
- include_content: options.includeContent !== false,
31
- file_extensions: options.extensions,
32
- path_filter: options.path,
33
- exclude_paths: options.exclude,
34
- });
35
-
36
- spinner.stop();
37
-
38
- const output = searchService.formatResultsForCLI(result.results, query, result.totalIndexed);
39
- console.log(output);
40
- console.log('');
41
- } catch (error) {
42
- console.error(chalk.red(`\n✗ Error: ${(error as Error).message}\n`));
43
- process.exit(1);
44
- }
45
- });
46
-
47
- export const codebaseReindexCommand = new Command('reindex')
48
- .description('Reindex all codebase files')
49
- .action(async () => {
50
- try {
51
- const indexer = new CodebaseIndexer();
52
-
53
- // Check if API key exists - only use embeddings if key is present
54
- const hasApiKey = !!process.env.OPENAI_API_KEY;
55
- const embeddingProvider = hasApiKey ? await getDefaultEmbeddingProvider() : undefined;
56
- const mode: 'tfidf-only' | 'semantic' = hasApiKey ? 'semantic' : 'tfidf-only';
57
-
58
- // Create monitor for progress display
59
- const monitor = new ReindexMonitor();
60
-
61
- // Start the UI
62
- monitor.start(0); // Will update total when we know file count
63
-
64
- let totalFiles = 0;
65
- let phase: 'tokenizing' | 'calculating' | 'completed' = 'tokenizing';
66
-
67
- // Set initial mode
68
- monitor.updateProgress({ mode });
69
-
70
- const result = await indexer.indexCodebase({
71
- force: true, // Reindex should always force rebuild
72
- embeddingProvider,
73
- onProgress: (progress) => {
74
- // Track total files on first progress update
75
- if (totalFiles === 0 && progress.total > 0) {
76
- totalFiles = progress.total;
77
- }
78
-
79
- // Update Ink UI only (stderr output was interfering with Ink rendering)
80
- monitor.updateProgress({
81
- current: progress.current,
82
- total: progress.total,
83
- fileName: progress.fileName,
84
- status: progress.status,
85
- phase,
86
- });
87
- },
88
- });
89
-
90
- // Handle cache hit - simulate progress to show 100%
91
- if (result.stats.cacheHit) {
92
- monitor.updateProgress({
93
- current: result.stats.totalFiles,
94
- total: result.stats.totalFiles,
95
- fileName: '',
96
- status: 'completed',
97
- phase: 'tokenizing',
98
- });
99
-
100
- // Small delay
101
- await new Promise((resolve) => setTimeout(resolve, 300));
102
- } else {
103
- // Update to calculating phase
104
- phase = 'calculating';
105
- monitor.updateProgress({ phase: 'calculating' });
106
-
107
- // Small delay to show calculating phase
108
- await new Promise((resolve) => setTimeout(resolve, 500));
109
- }
110
-
111
- // Show completion with stats
112
- monitor.updateProgress({
113
- phase: 'completed',
114
- stats: {
115
- documentsProcessed: result.stats.totalFiles,
116
- uniqueTerms: result.tfidfIndex.idf.size,
117
- },
118
- });
119
-
120
- // Give time to see the completion message
121
- await new Promise((resolve) => setTimeout(resolve, 2000));
122
-
123
- monitor.stop();
124
- } catch (error) {
125
- throw new CLIError(`Codebase reindex failed: ${(error as Error).message}`);
126
- }
127
- });
128
-
129
- export const codebaseStatusCommand = new Command('status')
130
- .description('Get codebase search system status')
131
- .action(async () => {
132
- try {
133
- console.log('');
134
- console.log(chalk.cyan.bold('▸ Codebase Status'));
135
-
136
- const searchService = getSearchService();
137
- await searchService.initialize();
138
- const status = await searchService.getStatus();
139
-
140
- if (status.codebase.indexed) {
141
- console.log(chalk.green('\n✓ Indexed and ready'));
142
- console.log(chalk.gray(` Files: ${status.codebase.fileCount}`));
143
- if (status.codebase.indexedAt) {
144
- console.log(
145
- chalk.gray(` Last indexed: ${new Date(status.codebase.indexedAt).toLocaleString()}`)
146
- );
147
- }
148
- } else {
149
- console.log(chalk.yellow('\n⚠ Not indexed'));
150
- console.log(chalk.gray(' Run: sylphx-flow codebase reindex'));
151
- }
152
-
153
- console.log(chalk.cyan('\n▸ Available Commands'));
154
- console.log(chalk.gray(' • codebase search <query>'));
155
- console.log(chalk.gray(' • codebase reindex'));
156
- console.log(chalk.gray(' • codebase status'));
157
- console.log('');
158
- } catch (error) {
159
- console.error(chalk.red(`\n✗ Error: ${(error as Error).message}\n`));
160
- process.exit(1);
161
- }
162
- });
163
-
164
- export const codebaseCommand = new Command('codebase')
165
- .description('Manage codebase indexing and search')
166
- .addCommand(codebaseSearchCommand)
167
- .addCommand(codebaseReindexCommand)
168
- .addCommand(codebaseStatusCommand);
@@ -1,161 +0,0 @@
1
- /**
2
- * Knowledge CLI commands
3
- * Knowledge base search and management functionality
4
- */
5
-
6
- import { Command } from 'commander';
7
- import { getKnowledgeContent } from '../domains/knowledge/resources.js';
8
- import { getSearchService } from '../services/search/unified-search-service.js';
9
- import { CLIError } from '../utils/error-handler.js';
10
-
11
- /**
12
- * Knowledge search command
13
- */
14
- export const knowledgeSearchCommand = new Command('search')
15
- .description('Search knowledge base, documentation, and guides')
16
- .argument('<query>', 'Search query - use natural language, technology names, or topic keywords')
17
- .option('-l, --limit <number>', 'Maximum number of results to return', '10')
18
- .option('--include-content', 'Include full content in results', true)
19
- .action(async (query, options) => {
20
- try {
21
- console.log(`📚 Searching knowledge base for: "${query}"`);
22
-
23
- const searchService = getSearchService();
24
- await searchService.initialize();
25
-
26
- const result = await searchService.searchKnowledge(query, {
27
- limit: Number.parseInt(options.limit, 10) || 10,
28
- include_content: options.includeContent !== false,
29
- });
30
-
31
- const output = searchService.formatResultsForCLI(result.results, query, result.totalIndexed);
32
- console.log(output);
33
- } catch (error) {
34
- throw new CLIError(`Knowledge search failed: ${(error as Error).message}`);
35
- }
36
- });
37
-
38
- /**
39
- * Knowledge get command
40
- */
41
- export const knowledgeGetCommand = new Command('get')
42
- .description('Get specific knowledge document by URI')
43
- .argument('<uri>', 'Knowledge URI to access (e.g., "knowledge://stacks/react-app")')
44
- .action(async (uri) => {
45
- try {
46
- const content = await getKnowledgeContent(uri);
47
- console.log(content);
48
- } catch (error) {
49
- const errorMessage = `Knowledge get failed: ${(error as Error).message}`;
50
-
51
- // Show available URIs
52
- const searchService = getSearchService();
53
- const availableURIs = await searchService.getAvailableKnowledgeURIs();
54
- if (availableURIs.length > 0) {
55
- console.log('\n**Available knowledge URIs:**');
56
- for (const uri of availableURIs) {
57
- console.log(`• ${uri}`);
58
- }
59
- }
60
- throw new CLIError(errorMessage);
61
- }
62
- });
63
-
64
- /**
65
- * Knowledge list command
66
- */
67
- export const knowledgeListCommand = new Command('list')
68
- .description('List all available knowledge resources')
69
- .option('--category <type>', 'Filter by category (stacks, guides, universal, data)')
70
- .action(async (options) => {
71
- try {
72
- const searchService = getSearchService();
73
- await searchService.initialize();
74
- const availableURIs = await searchService.getAvailableKnowledgeURIs();
75
-
76
- if (availableURIs.length === 0) {
77
- console.log('📭 No knowledge documents available');
78
- return;
79
- }
80
-
81
- let filteredURIs = availableURIs;
82
- if (options.category) {
83
- filteredURIs = availableURIs.filter((uri) => uri.includes(`/${options.category}/`));
84
- }
85
-
86
- console.log(`📚 Available Knowledge Resources (${filteredURIs.length} documents):\n`);
87
-
88
- // Group by category
89
- const grouped = filteredURIs.reduce(
90
- (acc, uri) => {
91
- const category = uri.split('/')[2] || 'unknown';
92
- if (!acc[category]) {
93
- acc[category] = [];
94
- }
95
- acc[category].push(uri);
96
- return acc;
97
- },
98
- {} as Record<string, string[]>
99
- );
100
-
101
- for (const [category, uris] of Object.entries(grouped)) {
102
- console.log(`### ${category.charAt(0).toUpperCase() + category.slice(1)}`);
103
- for (const uri of uris) {
104
- const name = uri.split('/').pop() || 'Unknown';
105
- console.log(`• ${name} - ${uri}`);
106
- }
107
- console.log('');
108
- }
109
-
110
- console.log('**Usage:**');
111
- console.log('• sylphx knowledge search <query> - Search knowledge base');
112
- console.log('• sylphx knowledge get <uri> - Get specific document');
113
- } catch (error) {
114
- throw new CLIError(`Knowledge status failed: ${(error as Error).message}`);
115
- }
116
- });
117
-
118
- /**
119
- * Knowledge status command
120
- */
121
- export const knowledgeStatusCommand = new Command('status')
122
- .description('Get knowledge base system status')
123
- .action(async () => {
124
- try {
125
- console.log('\n### 📚 Knowledge Base Status\n');
126
-
127
- const searchService = getSearchService();
128
- await searchService.initialize();
129
- const status = await searchService.getStatus();
130
-
131
- if (status.knowledge.indexed) {
132
- console.log('**Status:** ✓ Ready');
133
- console.log(`**Documents:** ${status.knowledge.documentCount} files`);
134
- } else if (status.knowledge.isIndexing) {
135
- console.log(`**Status:** 🔄 Building index (${status.knowledge.progress || 0}%)`);
136
- console.log('**Note:** Please wait a moment and try again');
137
- } else {
138
- console.log('**Status:** ⚠️ Not initialized');
139
- console.log('**Note:** Will auto-index on first search');
140
- }
141
-
142
- console.log('\n**Available Commands:**');
143
- console.log('• sylphx knowledge search <query> - Search knowledge base');
144
- console.log('• sylphx knowledge get <uri> - Get specific document');
145
- } catch (error) {
146
- throw new CLIError(`Knowledge list failed: ${(error as Error).message}`);
147
- }
148
- });
149
-
150
- /**
151
- * Main knowledge command
152
- */
153
- export const knowledgeCommand = new Command('knowledge').description(
154
- 'Knowledge base search and management commands'
155
- );
156
-
157
- // Add subcommands
158
- knowledgeCommand.addCommand(knowledgeSearchCommand);
159
- knowledgeCommand.addCommand(knowledgeGetCommand);
160
- knowledgeCommand.addCommand(knowledgeListCommand);
161
- knowledgeCommand.addCommand(knowledgeStatusCommand);
@@ -1,45 +0,0 @@
1
- import { getTarget, getDefaultTargetUnsafe } from '../config/targets.js';
2
- import { projectSettings } from '../utils/settings.js';
3
- import type { MCPServerConfigFlags } from '../types.js';
4
-
5
- /**
6
- * Get the current target's MCP server configuration
7
- * Follows the same resolution pattern as targetManager.resolveTarget()
8
- * Returns undefined if no target is set or target has no mcpServerConfig
9
- */
10
- export async function useTargetConfig(): Promise<MCPServerConfigFlags | undefined> {
11
- try {
12
- // Try to get target from project settings first
13
- const settings = await projectSettings.load();
14
- const targetId = settings?.target;
15
-
16
- if (targetId) {
17
- const targetOption = getTarget(targetId);
18
- if (targetOption._tag === 'Some') {
19
- return targetOption.value.mcpServerConfig;
20
- }
21
- }
22
-
23
- // Fall back to default target
24
- const defaultTarget = getDefaultTargetUnsafe();
25
- return defaultTarget.mcpServerConfig;
26
- } catch {
27
- // If no target can be resolved, return undefined
28
- return undefined;
29
- }
30
- }
31
-
32
- /**
33
- * Get a specific target's MCP server configuration by ID
34
- */
35
- export function useTargetConfigById(targetId: string): MCPServerConfigFlags | undefined {
36
- try {
37
- const targetOption = getTarget(targetId);
38
- if (targetOption._tag === 'None') {
39
- return undefined;
40
- }
41
- return targetOption.value.mcpServerConfig;
42
- } catch {
43
- return undefined;
44
- }
45
- }
@@ -1,115 +0,0 @@
1
- /**
2
- * Tests for Byte Formatting Utilities
3
- */
4
-
5
- import { describe, expect, it } from 'bun:test';
6
- import { formatBytes, formatFileSize } from './bytes.js';
7
-
8
- describe('formatBytes', () => {
9
- describe('default options (decimals: 2, longUnits)', () => {
10
- it('formats 0 bytes', () => {
11
- expect(formatBytes(0)).toBe('0 Bytes');
12
- });
13
-
14
- it('formats bytes', () => {
15
- expect(formatBytes(500)).toBe('500 Bytes');
16
- });
17
-
18
- it('formats kilobytes', () => {
19
- expect(formatBytes(1024)).toBe('1 KB');
20
- expect(formatBytes(1536)).toBe('1.5 KB');
21
- });
22
-
23
- it('formats megabytes', () => {
24
- expect(formatBytes(1048576)).toBe('1 MB');
25
- expect(formatBytes(1572864)).toBe('1.5 MB');
26
- });
27
-
28
- it('formats gigabytes', () => {
29
- expect(formatBytes(1073741824)).toBe('1 GB');
30
- expect(formatBytes(1610612736)).toBe('1.5 GB');
31
- });
32
-
33
- it('formats terabytes', () => {
34
- expect(formatBytes(1099511627776)).toBe('1 TB');
35
- });
36
-
37
- it('rounds to 2 decimal places', () => {
38
- expect(formatBytes(1587)).toBe('1.55 KB');
39
- expect(formatBytes(1638400)).toBe('1.56 MB');
40
- });
41
- });
42
-
43
- describe('with decimals option', () => {
44
- it('formats with 0 decimals', () => {
45
- expect(formatBytes(1536, { decimals: 0 })).toBe('2 KB');
46
- expect(formatBytes(1024, { decimals: 0 })).toBe('1 KB');
47
- });
48
-
49
- it('formats with 1 decimal', () => {
50
- expect(formatBytes(1536, { decimals: 1 })).toBe('1.5 KB');
51
- expect(formatBytes(1587, { decimals: 1 })).toBe('1.5 KB');
52
- });
53
-
54
- it('formats with 3 decimals', () => {
55
- expect(formatBytes(1587, { decimals: 3 })).toBe('1.55 KB'); // toFixed trims to 1.550, then trimmed
56
- });
57
- });
58
-
59
- describe('with shortUnits option', () => {
60
- it('uses short unit for 0 bytes', () => {
61
- expect(formatBytes(0, { shortUnits: true })).toBe('0 B');
62
- });
63
-
64
- it('uses short unit for bytes', () => {
65
- expect(formatBytes(500, { shortUnits: true })).toBe('500 B');
66
- });
67
-
68
- it('uses short units for kilobytes', () => {
69
- expect(formatBytes(1024, { shortUnits: true })).toBe('1 KB');
70
- });
71
-
72
- it('uses short units for megabytes', () => {
73
- expect(formatBytes(1048576, { shortUnits: true })).toBe('1 MB');
74
- });
75
- });
76
-
77
- describe('combined options', () => {
78
- it('uses short units with 1 decimal', () => {
79
- expect(formatBytes(1536, { decimals: 1, shortUnits: true })).toBe('1.5 KB');
80
- });
81
-
82
- it('uses short units with 0 decimals', () => {
83
- expect(formatBytes(1536, { decimals: 0, shortUnits: true })).toBe('2 KB');
84
- });
85
- });
86
-
87
- describe('edge cases', () => {
88
- it('handles 1 byte', () => {
89
- expect(formatBytes(1)).toBe('1 Bytes');
90
- });
91
-
92
- it('handles very large numbers', () => {
93
- const result = formatBytes(1099511627776 * 1024); // 1 PB
94
- expect(result).toContain('TB'); // Will show in TB since we only go up to TB
95
- });
96
-
97
- it('handles fractional KB', () => {
98
- expect(formatBytes(1500)).toBe('1.46 KB');
99
- });
100
- });
101
- });
102
-
103
- describe('formatFileSize', () => {
104
- it('formats with 1 decimal and short units', () => {
105
- expect(formatFileSize(0)).toBe('0 B');
106
- expect(formatFileSize(1024)).toBe('1 KB'); // toFixed(1) gives "1.0", trimmed to "1"
107
- expect(formatFileSize(1536)).toBe('1.5 KB');
108
- expect(formatFileSize(1048576)).toBe('1 MB'); // toFixed(1) gives "1.0", trimmed to "1"
109
- });
110
-
111
- it('is an alias for formatBytes with specific options', () => {
112
- const bytes = 1572864;
113
- expect(formatFileSize(bytes)).toBe(formatBytes(bytes, { decimals: 1, shortUnits: true }));
114
- });
115
- });