bunsane 0.2.0 → 0.2.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/CLAUDE.md ADDED
@@ -0,0 +1,152 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ BunSane is an experimental TypeScript API framework for Bun using Entity-Component-System (ECS) architecture with PostgreSQL storage. It provides GraphQL Yoga integration with automatic schema generation from decorated services.
8
+
9
+ **Stack**: Bun, TypeScript, PostgreSQL, GraphQL Yoga, GQLoom, Zod 4.x, Pino logging
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ # Install dependencies
15
+ bun install
16
+
17
+ # Build (includes studio + typecheck)
18
+ bun run build
19
+
20
+ # Typecheck only
21
+ tsc --noEmit
22
+
23
+ # Tests (requires PostgreSQL)
24
+ bun test # Unit + integration + GraphQL tests
25
+ bun run test:unit # Unit tests only
26
+ bun run test:integration # Integration tests only
27
+ bun run test:graphql # GraphQL schema tests only
28
+
29
+ # Tests with PGlite (no PostgreSQL required)
30
+ bun run test:pglite # All tests with PGlite
31
+ bun run test:pglite:unit # Unit tests only with PGlite
32
+
33
+ # E2E and stress tests
34
+ bun run test:e2e # HTTP tests (no DB needed)
35
+ bun run test:stress # Performance benchmarks
36
+
37
+ # Run single test file
38
+ bun test path/to/file.test.ts
39
+
40
+ # Run tests matching pattern
41
+ bun test --grep "pattern"
42
+ ```
43
+
44
+ ## Architecture
45
+
46
+ ### ECS Model
47
+ - **Entities**: Generic containers with UUID, stored in `entities` table
48
+ - **Components**: Data containers attached to entities, stored in `components` table with JSONB data
49
+ - **Services**: Business logic with GraphQL operations
50
+
51
+ ### Core Classes
52
+
53
+ **Entity** (`core/Entity.ts`):
54
+ ```typescript
55
+ const entity = Entity.Create();
56
+ entity.add(Position, { x: 0, y: 0 }); // Add component
57
+ await entity.set(Position, { x: 10 }); // Update component
58
+ const pos = await entity.get(Position); // Get component data
59
+ await entity.save();
60
+ ```
61
+
62
+ **BaseComponent** (`core/components/BaseComponent.ts`):
63
+ ```typescript
64
+ @Component
65
+ class Position extends BaseComponent {
66
+ @CompData() x: number = 0;
67
+ @CompData({ indexed: true }) y: number = 0; // Creates DB index
68
+ }
69
+ ```
70
+
71
+ **BaseArcheType** (`core/ArcheType.ts`):
72
+ - Predefined entity templates with required components
73
+ - Auto-generates GraphQL types and CRUD operations
74
+
75
+ **Query** (`query/Query.ts`):
76
+ ```typescript
77
+ const entities = await new Query()
78
+ .with(Position) // Require component
79
+ .with(Velocity, { filters: [...] }) // With filters
80
+ .populate() // Load all components
81
+ .limit(10)
82
+ .exec();
83
+ ```
84
+
85
+ **BaseService** (`service/Service.ts`):
86
+ ```typescript
87
+ class UserService extends BaseService {
88
+ @GraphQLOperation({ type: "Query", input: { id: t.id() }, output: User })
89
+ async getUser(input: { id: string }, ctx: GraphQLContext) { ... }
90
+ }
91
+ ```
92
+
93
+ ### GraphQL
94
+
95
+ - Schema generated automatically from decorated services and archetypes
96
+ - Input types use Schema DSL (`gql/schema/index.ts`) via `t.` API
97
+ - Operations: `@GraphQLOperation`, `@GraphQLSubscription`
98
+ - Archetypes: `@ArcheTypeFunction` for computed fields
99
+
100
+ ### Database
101
+
102
+ - PostgreSQL with Bun's native SQL driver (`Bun.SQL`)
103
+ - Auto-migrations on startup for base tables
104
+ - Component data stored as JSONB
105
+ - Indexed fields create GIN indexes automatically
106
+
107
+ ### Caching
108
+
109
+ - Multi-level cache: L1 (memory) + L2 (Redis)
110
+ - `CacheManager.initialize(config)` is async - always await it
111
+ - Strategies: write-through, write-invalidate
112
+ - Cross-instance invalidation via Redis pub/sub
113
+
114
+ ### File Uploads
115
+
116
+ - `UploadManager` handles file validation and storage
117
+ - `S3StorageProvider` for S3-compatible storage (AWS, MinIO, R2)
118
+ - REST: `handleUpload(req)` from `upload/RestUpload.ts`
119
+ - GraphQL: `@Upload()` decorator
120
+
121
+ ## Critical Rules
122
+
123
+ ### Import Style
124
+ **ALWAYS use relative imports** (`./`, `../`) for internal modules. Never use bare imports like `from "core/Logger"` - this breaks consumer typechecking.
125
+
126
+ ### Architecture Decisions
127
+ - No Dependency Injection - uses singletons + global exports
128
+ - Singleton access: `CacheManager.getInstance()`, `EntityManager.instance`, etc.
129
+ - Services registered via `ServiceRegistry.register()`
130
+
131
+ ### Test Database Setup
132
+ - Tests require `.env.test` with PostgreSQL config (or use PGlite mode)
133
+ - `bunfig.toml` preloads `tests/setup.ts`
134
+ - PGlite: `CREATE INDEX CONCURRENTLY` must check `process.env.USE_PGLITE`
135
+ - PGlite JSONB: pass JS objects directly, never `JSON.stringify() + ::jsonb`
136
+
137
+ ## Directory Structure
138
+
139
+ ```
140
+ core/ # ECS core: Entity, Component, ArcheType, App
141
+ cache/ # CacheManager, Redis, Memory caches
142
+ components/ # BaseComponent, decorators, registry
143
+ middleware/ # HTTP middleware (AccessLog, RequestId, SecurityHeaders)
144
+ database/ # DatabaseHelper, SQL utilities
145
+ gql/ # GraphQL generation, Schema DSL
146
+ schema/ # t.* Schema DSL for inputs
147
+ query/ # Fluent Query builder, FilterBuilder
148
+ service/ # BaseService, ServiceRegistry
149
+ upload/ # File uploads, S3StorageProvider
150
+ scheduler/ # Cron-style task scheduling
151
+ tests/ # Unit, integration, GraphQL, E2E, stress tests
152
+ ```
package/gql/index.ts CHANGED
@@ -114,6 +114,15 @@ const maskError = (error: any, message: string): GraphQLError => {
114
114
  }
115
115
  }
116
116
 
117
+ // Pass through known application-level GraphQL error codes
118
+ const isGQLError = (e: any): e is GraphQLError =>
119
+ e instanceof GraphQLError ||
120
+ (e !== null && typeof e === 'object' && 'extensions' in e && 'message' in e && typeof e.message === 'string');
121
+ const knownCodes = ['FORBIDDEN', 'NOT_FOUND', 'BAD_USER_INPUT', 'BAD_REQUEST'];
122
+ if (isGQLError(error) && knownCodes.includes(error.extensions?.code as string)) {
123
+ return error instanceof GraphQLError ? error : new GraphQLError(error.message, { extensions: error.extensions });
124
+ }
125
+
117
126
  if (process.env.NODE_ENV === 'production') {
118
127
  logger.error("GraphQL Error:", error);
119
128
  // Mask sensitive error details in production
@@ -124,7 +133,7 @@ const maskError = (error: any, message: string): GraphQLError => {
124
133
  });
125
134
  }
126
135
  // In development, return the original error
127
- return error instanceof GraphQLError ? error : new GraphQLError(message, { originalError: error });
136
+ return isGQLError(error) ? (error instanceof GraphQLError ? error : new GraphQLError(error.message, { extensions: error.extensions })) : new GraphQLError(message, { originalError: error });
128
137
  };
129
138
 
130
139
  export interface YogaInstanceOptions {
@@ -255,21 +255,20 @@ export class SchemaGeneratorVisitor extends GraphVisitor {
255
255
  }
256
256
  }
257
257
 
258
- // Handle output exactly like V1
259
- const outputType = this.extractOutputType(output);
258
+ // Handle output
259
+ const outputType = this.extractOutputType(output, name);
260
260
  fieldDef += `: ${outputType}`;
261
-
261
+
262
262
  return fieldDef;
263
263
  }
264
-
264
+
265
265
  /**
266
- * Extract output type exactly like V1
266
+ * Extract output type from operation metadata
267
267
  */
268
- private extractOutputType(output: any): string {
268
+ private extractOutputType(output: any, operationName: string): string {
269
269
  if (typeof output === 'string') {
270
270
  return output;
271
271
  } else if (Array.isArray(output)) {
272
- // Handle array of archetypes: [serviceAreaArcheType]
273
272
  const archetypeInstance = output[0];
274
273
  const typeName = this.getArchetypeTypeName(archetypeInstance);
275
274
  if (typeName) {
@@ -279,7 +278,6 @@ export class SchemaGeneratorVisitor extends GraphVisitor {
279
278
  return '[Any]';
280
279
  }
281
280
  } else if (output instanceof BaseArcheType) {
282
- // Handle single archetype instance: serviceAreaArcheType
283
281
  const typeName = this.getArchetypeTypeName(output);
284
282
  if (typeName) {
285
283
  return typeName;
@@ -288,11 +286,16 @@ export class SchemaGeneratorVisitor extends GraphVisitor {
288
286
  return 'Any';
289
287
  }
290
288
  } else if (typeof output === 'object' && output !== null) {
291
- // Legacy object output format - V1 would generate an output type
292
- // For now, return String as fallback (V1 would create outputName+"Output")
293
- return 'String';
289
+ // Inline object output generate a named GraphQL output type
290
+ const capitalizedName = operationName.charAt(0).toUpperCase() + operationName.slice(1);
291
+ const outputTypeName = `${capitalizedName}Output`;
292
+ if (!this.definedTypes.has(outputTypeName)) {
293
+ const outputTypeDef = `type ${outputTypeName} {\n${Object.entries(output).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
294
+ this.typeDefs += outputTypeDef;
295
+ this.definedTypes.add(outputTypeName);
296
+ }
297
+ return outputTypeName;
294
298
  } else {
295
- // Default case when output is not specified - assume String (like V1)
296
299
  return 'String';
297
300
  }
298
301
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunsane",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "author": {
5
5
  "name": "yaaruu"
6
6
  },
@@ -1,74 +0,0 @@
1
- ---
2
- name: update-memory
3
- description: Updating project memories with new information
4
- disable-model-invocation: false
5
- allowed-tools: Read, Grep
6
- ---
7
-
8
- # Update Memory Skill
9
-
10
- This skill helps you update Serena project memories with new or changed information.
11
-
12
- ## When to Use
13
-
14
- Use `/update-memory` when you need to:
15
- - Record architectural decisions or patterns discovered during work
16
- - Update project conventions based on new practices
17
- - Document important findings from code exploration
18
- - Add new information to existing memory files
19
- - Create new memory files for undocumented aspects
20
-
21
- ## Instructions
22
-
23
- 1. **List existing memories** first to see what's available:
24
- - Use `mcp__serena__list_memories` to see all memory files
25
-
26
- 2. **Read the relevant memory** (if updating existing):
27
- - Use `mcp__serena__read_memory` with the memory file name
28
- - Understand the current content and structure
29
-
30
- 3. **Determine the action**:
31
- - **Update existing**: Use `mcp__serena__edit_memory` for targeted changes
32
- - **Create new**: Use `mcp__serena__write_memory` for new topics
33
- - **Delete obsolete**: Use `mcp__serena__delete_memory` (only if user confirms)
34
-
35
- 4. **For updates**, use `edit_memory` with:
36
- - `mode: "literal"` for exact string replacement
37
- - `mode: "regex"` for pattern-based changes
38
- - Keep the existing format and style
39
-
40
- 5. **For new memories**, use descriptive kebab-case names:
41
- - `project_overview` - General project info
42
- - `architecture` - System architecture
43
- - `code_style_and_conventions` - Coding standards
44
- - `{feature}-implementation` - Feature-specific docs
45
-
46
- ## Memory Naming Conventions
47
-
48
- | Pattern | Purpose |
49
- |---------|---------|
50
- | `project_overview` | High-level project description |
51
- | `architecture` | Directory structure, core concepts |
52
- | `code_style_and_conventions` | Formatting, naming, patterns |
53
- | `{topic}-decisions` | Architectural decisions on topic |
54
- | `{feature}-implementation` | How a feature works |
55
- | `suggested_commands` | Useful commands for the project |
56
- | `task_completion_checklist` | Steps to verify work is complete |
57
-
58
- ## Example Usage
59
-
60
- ```
61
- User: /update-memory Add that we use Bun for testing
62
- Assistant: [Lists memories, reads code_style_and_conventions, updates with new testing info]
63
-
64
- User: /update-memory Create a memory about the caching system
65
- Assistant: [Creates new memory file: caching-system.md with relevant information]
66
- ```
67
-
68
- ## Important Notes
69
-
70
- - Always read existing content before editing to preserve context
71
- - Use markdown format for memory content
72
- - Keep memories focused and organized
73
- - Ask for confirmation before deleting memories
74
- - After updating, summarize what was changed