bunsane 0.1.2 → 0.1.4

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 (49) hide show
  1. package/TODO.md +1 -1
  2. package/bun.lock +156 -150
  3. package/core/App.ts +188 -31
  4. package/core/ArcheType.ts +1044 -26
  5. package/core/ComponentRegistry.ts +172 -29
  6. package/core/Components.ts +102 -24
  7. package/core/Decorators.ts +0 -1
  8. package/core/Entity.ts +55 -7
  9. package/core/EntityInterface.ts +4 -0
  10. package/core/EntityManager.ts +4 -4
  11. package/core/Query.ts +169 -3
  12. package/core/RequestLoaders.ts +101 -12
  13. package/core/SchedulerManager.ts +3 -4
  14. package/core/metadata/definitions/ArcheType.ts +9 -0
  15. package/core/metadata/definitions/Component.ts +16 -0
  16. package/core/metadata/definitions/gqlObject.ts +10 -0
  17. package/core/metadata/getMetadataStorage.ts +14 -0
  18. package/core/metadata/index.ts +17 -0
  19. package/core/metadata/metadata-storage.ts +81 -0
  20. package/database/DatabaseHelper.ts +22 -20
  21. package/database/index.ts +6 -1
  22. package/database/sqlHelpers.ts +0 -2
  23. package/gql/ArchetypeOperations.ts +281 -0
  24. package/gql/Generator.ts +252 -62
  25. package/gql/helpers.ts +5 -5
  26. package/gql/index.ts +19 -17
  27. package/gql/types.ts +58 -11
  28. package/index.ts +93 -82
  29. package/package.json +39 -37
  30. package/plugins/index.ts +13 -0
  31. package/scheduler/index.ts +87 -0
  32. package/service/Service.ts +4 -0
  33. package/service/ServiceRegistry.ts +5 -1
  34. package/service/index.ts +1 -1
  35. package/swagger/decorators.ts +65 -0
  36. package/swagger/generator.ts +100 -0
  37. package/swagger/index.ts +2 -0
  38. package/tests/bench/insert.bench.ts +1 -0
  39. package/tests/bench/relations.bench.ts +1 -0
  40. package/tests/bench/sorting.bench.ts +1 -0
  41. package/tests/component-hooks-simple.test.ts +117 -0
  42. package/tests/component-hooks.test.ts +83 -31
  43. package/tests/component.test.ts +1 -0
  44. package/tests/hooks.test.ts +1 -0
  45. package/tests/query.test.ts +46 -4
  46. package/tests/relations.test.ts +1 -0
  47. package/types/app.types.ts +0 -0
  48. package/upload/index.ts +0 -2
  49. package/core/processors/ImageProcessor.ts +0 -423
package/index.ts CHANGED
@@ -1,90 +1,101 @@
1
- import App from "./core/App";
2
- import ServiceRegistry from "./service/ServiceRegistry";
3
- import BaseService from "./service/Service";
4
- import { Component, CompData, BaseComponent } from "./core/Components";
5
- import { Entity } from "./core/Entity";
6
- import ArcheType from "./core/ArcheType";
7
- import Query from "./core/Query";
8
- import {logger} from "./core/Logger";
9
- import { handleGraphQLError, responseError } from "./core/ErrorHandler";
10
- import { type Plugin } from "graphql-yoga";
11
- import { BatchLoader } from "core/BatchLoader";
12
- import { createRequestContextPlugin } from "./core/RequestContext";
13
- import type { RequestLoaders } from "./core/RequestLoaders";
14
- // Hook system exports
15
- import EntityHookManager from "./core/EntityHookManager";
16
- import {
17
- EntityHook,
18
- ComponentHook,
19
- LifecycleHook,
20
- ComponentTargetHook,
21
- registerDecoratedHooks
22
- } from "./core/decorators/EntityHooks";
23
- import type {
24
- EntityHookCallback,
25
- ComponentHookCallback,
26
- LifecycleHookCallback,
27
- HookOptions,
28
- ComponentTargetConfig
29
- } from "./core/EntityHookManager";
30
- import type {
31
- EntityLifecycleEvent,
32
- EntityCreatedEvent,
33
- EntityUpdatedEvent,
34
- EntityDeletedEvent,
35
- ComponentLifecycleEvent,
36
- ComponentAddedEvent,
37
- ComponentUpdatedEvent,
38
- ComponentRemovedEvent
39
- } from "./core/events/EntityLifecycleEvents";
40
- import { ScheduledTask } from "core/decorators/ScheduledTask";
41
- import { ScheduleInterval } from "types/scheduler.types";
1
+ // import App from "./core/App";
2
+ // import ServiceRegistry from "./service/ServiceRegistry";
3
+ // import BaseService from "./service/Service";
4
+ // import { Component, CompData, BaseComponent } from "./core/Components";
5
+ // import { Entity } from "./core/Entity";
6
+ // import type { IEntity } from "./core/EntityInterface";
7
+ // import BaseArcheType, { ArcheType} from "./core/ArcheType";
8
+ // import Query from "./core/Query";
9
+ // import {logger} from "./core/Logger";
10
+ // import { handleGraphQLError, responseError } from "./core/ErrorHandler";
11
+ // import { type Plugin } from "graphql-yoga";
12
+ // import { BatchLoader } from "./core/BatchLoader";
13
+ // import { createRequestContextPlugin } from "./core/RequestContext";
14
+ // import type { RequestLoaders } from "./core/RequestLoaders";
15
+ // import BasePlugin from "./plugins";
16
+ // // Hook system exports
17
+ // import EntityHookManager from "./core/EntityHookManager";
18
+ // import {
19
+ // EntityHook,
20
+ // ComponentHook,
21
+ // LifecycleHook,
22
+ // ComponentTargetHook,
23
+ // registerDecoratedHooks
24
+ // } from "./core/decorators/EntityHooks";
25
+ // import type {
26
+ // EntityHookCallback,
27
+ // ComponentHookCallback,
28
+ // LifecycleHookCallback,
29
+ // HookOptions,
30
+ // ComponentTargetConfig
31
+ // } from "./core/EntityHookManager";
32
+ // import type {
33
+ // EntityLifecycleEvent,
34
+ // EntityCreatedEvent,
35
+ // EntityUpdatedEvent,
36
+ // EntityDeletedEvent,
37
+ // ComponentLifecycleEvent,
38
+ // ComponentAddedEvent,
39
+ // ComponentUpdatedEvent,
40
+ // ComponentRemovedEvent
41
+ // } from "./core/events/EntityLifecycleEvents";
42
+ // import { ScheduledTask } from "./core/decorators/ScheduledTask";
43
+ // import { ScheduleInterval } from "./types/scheduler.types";
44
+ // // Swagger exports
45
+ // import { ApiDocs, ApiTags } from "./swagger";
42
46
 
43
- export {
44
- App,
45
- ArcheType,
46
- ServiceRegistry,
47
- BaseService,
48
- BaseComponent,
49
- Component,
50
- CompData,
51
- Entity,
52
- BatchLoader,
47
+ // export {
48
+ // App,
49
+ // BaseArcheType,
50
+ // ArcheType,
51
+ // ServiceRegistry,
52
+ // BaseService,
53
+ // BaseComponent,
54
+ // Component,
55
+ // CompData,
56
+ // Entity,
57
+ // type IEntity,
58
+ // BatchLoader,
53
59
 
54
- Query,
60
+ // Query,
55
61
 
56
- // Scheduler exports
57
- ScheduleInterval,
58
- ScheduledTask,
62
+ // // Scheduler exports
63
+ // ScheduleInterval,
64
+ // ScheduledTask,
59
65
 
60
- logger,
66
+ // // Swagger exports
67
+ // ApiDocs,
68
+ // ApiTags,
61
69
 
62
- type Plugin,
70
+ // logger,
63
71
 
64
- responseError,
65
- handleGraphQLError,
72
+ // BasePlugin,
73
+ // type Plugin,
66
74
 
67
- createRequestContextPlugin,
68
- type RequestLoaders,
75
+ // responseError,
76
+ // handleGraphQLError,
69
77
 
70
- // Hook system exports
71
- EntityHookManager,
72
- EntityHook,
73
- ComponentHook,
74
- LifecycleHook,
75
- ComponentTargetHook,
76
- registerDecoratedHooks,
77
- type EntityHookCallback,
78
- type ComponentHookCallback,
79
- type LifecycleHookCallback,
80
- type HookOptions,
81
- type ComponentTargetConfig,
82
- type EntityLifecycleEvent,
83
- type EntityCreatedEvent,
84
- type EntityUpdatedEvent,
85
- type EntityDeletedEvent,
86
- type ComponentLifecycleEvent,
87
- type ComponentAddedEvent,
88
- type ComponentUpdatedEvent,
89
- type ComponentRemovedEvent
90
- };
78
+ // createRequestContextPlugin,
79
+ // type RequestLoaders,
80
+
81
+ // // Hook system exports
82
+ // EntityHookManager,
83
+ // EntityHook,
84
+ // ComponentHook,
85
+ // LifecycleHook,
86
+ // ComponentTargetHook,
87
+ // registerDecoratedHooks,
88
+ // type EntityHookCallback,
89
+ // type ComponentHookCallback,
90
+ // type LifecycleHookCallback,
91
+ // type HookOptions,
92
+ // type ComponentTargetConfig,
93
+ // type EntityLifecycleEvent,
94
+ // type EntityCreatedEvent,
95
+ // type EntityUpdatedEvent,
96
+ // type EntityDeletedEvent,
97
+ // type ComponentLifecycleEvent,
98
+ // type ComponentAddedEvent,
99
+ // type ComponentUpdatedEvent,
100
+ // type ComponentRemovedEvent
101
+ // };
package/package.json CHANGED
@@ -1,37 +1,39 @@
1
- {
2
- "name": "bunsane",
3
- "version": "0.1.2",
4
- "author": {
5
- "name": "yaaruu"
6
- },
7
- "keywords": [
8
- "bun",
9
- "framework",
10
- "entity-component-system",
11
- "ecs",
12
- "graphql",
13
- "typescript"
14
- ],
15
- "module": "index.ts",
16
- "type": "module",
17
- "devDependencies": {
18
- "@types/bun": "latest"
19
- },
20
- "peerDependencies": {
21
- "typescript": "^5"
22
- },
23
- "dependencies": {
24
- "dataloader": "2.2.2",
25
- "graphql": "16.11.0",
26
- "graphql-yoga": "5.15.1",
27
- "pino": "9.9.0",
28
- "pino-pretty": "13.1.1",
29
- "reflect-metadata": "0.2.2",
30
- "zod": "4.1.5"
31
- },
32
- "repository": {
33
- "type": "git",
34
- "url": "https://github.com/yaaruu/bunsane.git"
35
- },
36
- "license": "MIT"
37
- }
1
+ {
2
+ "name": "bunsane",
3
+ "version": "0.1.4",
4
+ "author": {
5
+ "name": "yaaruu"
6
+ },
7
+ "keywords": [
8
+ "bun",
9
+ "framework",
10
+ "entity-component-system",
11
+ "ecs",
12
+ "graphql",
13
+ "typescript"
14
+ ],
15
+ "module": "index.ts",
16
+ "type": "module",
17
+ "devDependencies": {
18
+ "@types/bun": "latest"
19
+ },
20
+ "peerDependencies": {
21
+ "typescript": "^5"
22
+ },
23
+ "dependencies": {
24
+ "@gqloom/core": "^0.12.0",
25
+ "@gqloom/zod": "^0.12.2",
26
+ "dataloader": "2.2.2",
27
+ "graphql": "16.11.0",
28
+ "graphql-yoga": "5.15.1",
29
+ "pino": "9.9.0",
30
+ "pino-pretty": "13.1.1",
31
+ "reflect-metadata": "0.2.2",
32
+ "zod": "4.1.5"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/yaaruu/bunsane.git"
37
+ },
38
+ "license": "MIT"
39
+ }
@@ -0,0 +1,13 @@
1
+ import type App from "core/App";
2
+ import type { ApplicationPhase } from "core/ApplicationLifecycle";
3
+
4
+ abstract class BasePlugin {
5
+ name!: string;
6
+ version!: string;
7
+
8
+ abstract init?(app: App): Promise<void> | void;
9
+ onPhaseChange?(phase: ApplicationPhase, app: App): Promise<void> | void;
10
+ onComponentRegistered?(componentName: string, componentCtor: new () => any, app: App): void;
11
+ }
12
+
13
+ export default BasePlugin;
@@ -0,0 +1,87 @@
1
+ import { ScheduleInterval } from "../types/scheduler.types";
2
+ import type { ScheduledTaskOptions } from "../types/scheduler.types";
3
+ import { SchedulerManager } from "core/SchedulerManager";
4
+ import { logger } from "core/Logger";
5
+ import type { ComponentTargetConfig } from "core/EntityHookManager";
6
+ const loggerInstance = logger.child({ scope: "ScheduledTaskDecorator" });
7
+
8
+ /**
9
+ * Decorator for registering scheduled tasks
10
+ * @param options Task configuration options including interval and component target
11
+ */
12
+ export function ScheduledTask(
13
+ options: ScheduledTaskOptions & {
14
+ interval: ScheduleInterval;
15
+ componentTarget?: ComponentTargetConfig
16
+ }
17
+ ) {
18
+ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
19
+ const originalMethod = descriptor.value;
20
+
21
+ // Generate task ID if not provided
22
+ const taskId = options.id || `${target.constructor.name}.${propertyKey}`;
23
+
24
+ // Store task info for later registration
25
+ if (!target.constructor.__scheduledTasks) {
26
+ target.constructor.__scheduledTasks = [];
27
+ }
28
+
29
+ const taskInfo = {
30
+ id: taskId,
31
+ name: options.name || `${target.constructor.name}.${propertyKey}`,
32
+ componentTarget: options.componentTarget, // Legacy support
33
+ interval: options.interval,
34
+ options: {
35
+ runOnStart: options.runOnStart ?? false,
36
+ timeout: options.timeout ?? 30000,
37
+ enableLogging: options.enableLogging ?? true,
38
+ ...options
39
+ },
40
+ service: null, // Will be set when service is instantiated
41
+ methodName: propertyKey,
42
+ nextExecution: new Date(),
43
+ executionCount: 0,
44
+ isRunning: false,
45
+ enabled: true
46
+ };
47
+
48
+ target.constructor.__scheduledTasks.push(taskInfo);
49
+
50
+ // Return the original descriptor to maintain method functionality
51
+ return descriptor;
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Function to manually register decorated tasks for a service instance
57
+ * This is useful when services are instantiated outside the normal decorator flow
58
+ */
59
+ export function registerScheduledTasks(service: any): void {
60
+ const constructor = service.constructor;
61
+
62
+ if (!constructor.__scheduledTasks) {
63
+ return;
64
+ }
65
+
66
+ const scheduler = SchedulerManager.getInstance();
67
+
68
+ for (const task of constructor.__scheduledTasks) {
69
+ const taskWithService = {
70
+ ...task,
71
+ service: service
72
+ };
73
+
74
+ try {
75
+ scheduler.registerTask(taskWithService);
76
+ if (loggerInstance.isLevelEnabled('info')) {
77
+ loggerInstance.info(`Manually registered scheduled task: ${task.name} (${task.id})`);
78
+ }
79
+ } catch (error) {
80
+ loggerInstance.error(`Failed to manually register scheduled task ${task.name}: ${error instanceof Error ? error.message : String(error)}`);
81
+ }
82
+ }
83
+ }
84
+
85
+ export {
86
+ ScheduleInterval
87
+ }
@@ -1,4 +1,8 @@
1
+ import type { GraphQLObjectTypeMeta, GraphQLOperationMeta } from "gql/Generator";
2
+
1
3
  class BaseService {
4
+ public __graphqlObjectType?: GraphQLObjectTypeMeta[];
5
+ public __graphqlOperations?: GraphQLOperationMeta<any>[];
2
6
  constructor() {
3
7
 
4
8
  }
@@ -19,7 +19,10 @@ class ServiceRegistry {
19
19
  switch(event.detail) {
20
20
  case ApplicationPhase.SYSTEM_REGISTERING: {
21
21
  const servicesArray = Array.from(this.services.values());
22
- const { schema } = generateGraphQLSchema(servicesArray);
22
+ // Disable auto-generated archetype operations to avoid conflicts with manual operations
23
+ const { schema } = generateGraphQLSchema(servicesArray, {
24
+ enableArchetypeOperations: false
25
+ });
23
26
  this.schema = schema;
24
27
  ApplicationLifecycle.setPhase(ApplicationPhase.SYSTEM_READY);
25
28
  break;
@@ -41,6 +44,7 @@ class ServiceRegistry {
41
44
  if(!this.services.has(service.constructor.name)) {
42
45
  this.services.set(service.constructor.name, service);
43
46
  }
47
+ return service;
44
48
  }
45
49
 
46
50
  public getServices(): BaseService[] {
package/service/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import BaseService from "./Service";
2
- import { ServiceRegistry } from "index";
2
+ import ServiceRegistry from "./ServiceRegistry";
3
3
  import { httpEndpoint } from "../rest";
4
4
 
5
5
  export {
@@ -0,0 +1,65 @@
1
+ import type { HTTPMethod } from "../rest";
2
+
3
+ export interface OpenAPIOperation {
4
+ summary?: string;
5
+ description?: string;
6
+ tags?: string[];
7
+ parameters?: OpenAPIParameter[];
8
+ requestBody?: OpenAPIRequestBody;
9
+ responses?: Record<string, OpenAPIResponse>;
10
+ security?: any[];
11
+ }
12
+
13
+ export interface OpenAPIParameter {
14
+ name: string;
15
+ in: 'query' | 'header' | 'path' | 'cookie';
16
+ description?: string;
17
+ required?: boolean;
18
+ schema: any;
19
+ }
20
+
21
+ export interface OpenAPIRequestBody {
22
+ description?: string;
23
+ required?: boolean;
24
+ content: Record<string, { schema: any }>;
25
+ }
26
+
27
+ export interface OpenAPIResponse {
28
+ description: string;
29
+ content?: Record<string, { schema: any }>;
30
+ }
31
+
32
+ export interface SwaggerEndpointMetadata {
33
+ method: HTTPMethod;
34
+ path: string;
35
+ operation: OpenAPIOperation;
36
+ }
37
+
38
+ /**
39
+ * Annotations for OpenAPI/Swagger documentation
40
+ * - @ApiOperation: Describes an API operation (endpoint)
41
+ * - @ApiTags: Assigns tags to group operations in the documentation
42
+ *
43
+ */
44
+
45
+ export function ApiDocs(operation: OpenAPIOperation) {
46
+ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
47
+ // Store the operation on the function itself
48
+ (descriptor.value as any).swaggerOperation = operation;
49
+ }
50
+ }
51
+
52
+ export function ApiTags(...tags: string[]) {
53
+ return function (target: any, propertyKey?: string) {
54
+ if (propertyKey) {
55
+ // Method decorator
56
+ if (!target.constructor.swaggerMethodTags) {
57
+ target.constructor.swaggerMethodTags = {};
58
+ }
59
+ target.constructor.swaggerMethodTags[propertyKey] = tags;
60
+ } else {
61
+ // Class decorator
62
+ target.swaggerClassTags = tags;
63
+ }
64
+ };
65
+ }
@@ -0,0 +1,100 @@
1
+ import type { SwaggerEndpointMetadata } from "./decorators";
2
+ import {logger as MainLogger} from "core/Logger";
3
+ const logger = MainLogger.child({ scope: "OpenAPISpecGenerator" });
4
+ export interface OpenAPISpec {
5
+ openapi: string;
6
+ info: {
7
+ title: string;
8
+ version: string;
9
+ description?: string;
10
+ };
11
+ servers?: Array<{
12
+ url: string;
13
+ description?: string;
14
+ }>;
15
+ paths: Record<string, Record<string, any>>;
16
+ components?: {
17
+ schemas?: Record<string, any>;
18
+ securitySchemes?: Record<string, any>;
19
+ };
20
+ tags?: Array<{
21
+ name: string;
22
+ description?: string;
23
+ }>;
24
+ }
25
+
26
+ export class OpenAPISpecGenerator {
27
+ private spec: OpenAPISpec;
28
+
29
+ constructor(title: string = "API Documentation", version: string = "1.0.0") {
30
+ this.spec = {
31
+ openapi: "3.0.0",
32
+ info: {
33
+ title,
34
+ version
35
+ },
36
+ paths: {},
37
+ components: {
38
+ securitySchemes: {
39
+ BearerAuth: {
40
+ type: "http",
41
+ scheme: "bearer",
42
+ bearerFormat: "JWT"
43
+ }
44
+ },
45
+ }
46
+ };
47
+ }
48
+
49
+ addEndpoint(metadata: SwaggerEndpointMetadata) {
50
+ const { method, path, operation } = metadata;
51
+ logger.trace(`Adding endpoint to OpenAPI spec: [${method}] ${path}`);
52
+ if (!this.spec.paths[path]) {
53
+ this.spec.paths[path] = {};
54
+ }
55
+
56
+ this.spec.paths[path][method.toLowerCase()] = {
57
+ ...operation,
58
+ responses: operation.responses || {
59
+ "200": {
60
+ description: "Success"
61
+ }
62
+ }
63
+ };
64
+ }
65
+
66
+ addServer(url: string, description?: string) {
67
+ if (!this.spec.servers) {
68
+ this.spec.servers = [];
69
+ }
70
+ this.spec.servers.push({ url, description });
71
+ }
72
+
73
+ addSecurityScheme(name: string, scheme: any) {
74
+ if (!this.spec.components) {
75
+ this.spec.components = {};
76
+ }
77
+ if (!this.spec.components.securitySchemes) {
78
+ this.spec.components.securitySchemes = {};
79
+ }
80
+ this.spec.components.securitySchemes[name] = scheme;
81
+ }
82
+
83
+ addSchema(name: string, schema: any) {
84
+ if (!this.spec.components) {
85
+ this.spec.components = {};
86
+ }
87
+ if (!this.spec.components.schemas) {
88
+ this.spec.components.schemas = {};
89
+ }
90
+ this.spec.components.schemas[name] = schema;
91
+ }
92
+
93
+ generate(): OpenAPISpec {
94
+ return this.spec;
95
+ }
96
+
97
+ toJSON(): string {
98
+ return JSON.stringify(this.spec, null, 2);
99
+ }
100
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./decorators";
2
+ export * from "./generator";
@@ -7,6 +7,7 @@ import db from "database";
7
7
  let app;
8
8
  beforeAll(async () => {
9
9
  app = new App();
10
+ app.init();
10
11
  await app.waitForAppReady();
11
12
  });
12
13
 
@@ -9,6 +9,7 @@ let app: App;
9
9
 
10
10
  beforeAll(async () => {
11
11
  app = new App();
12
+ app.init();
12
13
  await app.waitForAppReady();
13
14
  });
14
15
 
@@ -10,6 +10,7 @@ let app: App;
10
10
 
11
11
  beforeAll(async () => {
12
12
  app = new App();
13
+ app.init();
13
14
  await app.waitForAppReady();
14
15
  });
15
16