bunsane 0.1.0 → 0.1.1

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 (85) hide show
  1. package/.github/workflows/deploy-docs.yml +57 -0
  2. package/LICENSE.md +1 -1
  3. package/README.md +2 -28
  4. package/TODO.md +8 -1
  5. package/bun.lock +3 -0
  6. package/config/upload.config.ts +135 -0
  7. package/core/App.ts +119 -4
  8. package/core/ArcheType.ts +122 -0
  9. package/core/BatchLoader.ts +100 -0
  10. package/core/ComponentRegistry.ts +4 -3
  11. package/core/Components.ts +2 -2
  12. package/core/Decorators.ts +15 -8
  13. package/core/Entity.ts +159 -12
  14. package/core/EntityCache.ts +15 -0
  15. package/core/EntityHookManager.ts +855 -0
  16. package/core/EntityManager.ts +12 -2
  17. package/core/ErrorHandler.ts +64 -7
  18. package/core/FileValidator.ts +284 -0
  19. package/core/Query.ts +453 -85
  20. package/core/RequestContext.ts +24 -0
  21. package/core/RequestLoaders.ts +65 -0
  22. package/core/SchedulerManager.ts +710 -0
  23. package/core/UploadManager.ts +261 -0
  24. package/core/components/UploadComponent.ts +206 -0
  25. package/core/decorators/EntityHooks.ts +190 -0
  26. package/core/decorators/ScheduledTask.ts +83 -0
  27. package/core/events/EntityLifecycleEvents.ts +177 -0
  28. package/core/processors/ImageProcessor.ts +423 -0
  29. package/core/storage/LocalStorageProvider.ts +290 -0
  30. package/core/storage/StorageProvider.ts +112 -0
  31. package/database/DatabaseHelper.ts +183 -58
  32. package/database/index.ts +1 -1
  33. package/database/sqlHelpers.ts +7 -0
  34. package/docs/README.md +149 -0
  35. package/docs/_coverpage.md +36 -0
  36. package/docs/_sidebar.md +23 -0
  37. package/docs/api/core.md +568 -0
  38. package/docs/api/hooks.md +554 -0
  39. package/docs/api/index.md +222 -0
  40. package/docs/api/query.md +678 -0
  41. package/docs/api/service.md +744 -0
  42. package/docs/core-concepts/archetypes.md +512 -0
  43. package/docs/core-concepts/components.md +498 -0
  44. package/docs/core-concepts/entity.md +314 -0
  45. package/docs/core-concepts/hooks.md +683 -0
  46. package/docs/core-concepts/query.md +588 -0
  47. package/docs/core-concepts/services.md +647 -0
  48. package/docs/examples/code-examples.md +425 -0
  49. package/docs/getting-started.md +337 -0
  50. package/docs/index.html +97 -0
  51. package/examples/hooks/README.md +228 -0
  52. package/examples/hooks/audit-logger.ts +495 -0
  53. package/gql/Generator.ts +56 -34
  54. package/gql/decorators/Upload.ts +176 -0
  55. package/gql/helpers.ts +67 -0
  56. package/gql/index.ts +55 -31
  57. package/gql/types.ts +1 -1
  58. package/index.ts +79 -11
  59. package/package.json +5 -4
  60. package/rest/Generator.ts +3 -0
  61. package/rest/index.ts +22 -0
  62. package/service/Service.ts +1 -1
  63. package/service/ServiceRegistry.ts +10 -6
  64. package/service/index.ts +12 -1
  65. package/tests/bench/insert.bench.ts +59 -0
  66. package/tests/bench/relations.bench.ts +269 -0
  67. package/tests/bench/sorting.bench.ts +415 -0
  68. package/tests/component-hooks.test.ts +1409 -0
  69. package/tests/component.test.ts +205 -0
  70. package/tests/errorHandling.test.ts +155 -0
  71. package/tests/hooks.test.ts +666 -0
  72. package/tests/query-sorting.test.ts +101 -0
  73. package/tests/relations.test.ts +169 -0
  74. package/tests/scheduler.test.ts +724 -0
  75. package/tsconfig.json +35 -34
  76. package/types/graphql.types.ts +87 -0
  77. package/types/hooks.types.ts +141 -0
  78. package/types/scheduler.types.ts +165 -0
  79. package/types/upload.types.ts +184 -0
  80. package/upload/index.ts +140 -0
  81. package/utils/UploadHelper.ts +305 -0
  82. package/utils/cronParser.ts +366 -0
  83. package/utils/errorMessages.ts +151 -0
  84. package/validate-docs.sh +90 -0
  85. package/core/Events.ts +0 -0
@@ -0,0 +1,205 @@
1
+ import { describe, test, expect, beforeAll } from "bun:test";
2
+ import { BaseComponent, CompData, Component } from "../core/Components";
3
+ import type { ComponentDataType } from "../core/Components";
4
+ import { Entity } from "../core/Entity";
5
+ import App from "../core/App";
6
+ import ComponentRegistry from "../core/ComponentRegistry";
7
+
8
+ // Test component with 'value' attribute (standard assumption)
9
+ @Component
10
+ class ValueComponent extends BaseComponent {
11
+ @CompData()
12
+ value: string = "";
13
+ }
14
+
15
+ // Test component with different attribute name
16
+ @Component
17
+ class CustomAttributeComponent extends BaseComponent {
18
+ @CompData()
19
+ customData: string = "";
20
+ }
21
+
22
+ // Test component with multiple attributes
23
+ @Component
24
+ class MultiAttributeComponent extends BaseComponent {
25
+ @CompData()
26
+ title: string = "";
27
+ @CompData()
28
+ content: string = "";
29
+ @CompData({ indexed: true })
30
+ category: string = "";
31
+ }
32
+
33
+ // Test component with no data attributes
34
+ @Component
35
+ class NoDataComponent extends BaseComponent {
36
+ // No @CompData properties
37
+ }
38
+
39
+ // Test component with numeric value
40
+ @Component
41
+ class NumericComponent extends BaseComponent {
42
+ @CompData()
43
+ count: number = 0;
44
+ }
45
+
46
+ // Test component with boolean value
47
+ @Component
48
+ class BooleanComponent extends BaseComponent {
49
+ @CompData()
50
+ enabled: boolean = false;
51
+ }
52
+
53
+ describe("Component Edge Cases and Attribute Handling", () => {
54
+ describe("Component with standard 'value' attribute", () => {
55
+ test("should correctly identify and return value property", () => {
56
+ const comp = new ValueComponent();
57
+ comp.value = "test value";
58
+
59
+ const props = comp.properties();
60
+ expect(props).toContain("value");
61
+ expect(props).toHaveLength(1);
62
+
63
+ const data = comp.data();
64
+ expect(data.value).toBe("test value");
65
+ });
66
+ });
67
+
68
+ describe("Component with custom attribute name", () => {
69
+ test("should correctly identify and return custom property", () => {
70
+ const comp = new CustomAttributeComponent();
71
+ comp.customData = "custom data";
72
+
73
+ const props = comp.properties();
74
+ expect(props).toContain("customData");
75
+ expect(props).toHaveLength(1);
76
+
77
+ const data = comp.data();
78
+ expect(data.customData).toBe("custom data");
79
+ });
80
+ });
81
+
82
+ describe("Component with multiple attributes", () => {
83
+ test("should correctly identify all data properties", () => {
84
+ const comp = new MultiAttributeComponent();
85
+ comp.title = "Test Title";
86
+ comp.content = "Test Content";
87
+ comp.category = "Test Category";
88
+
89
+ const props = comp.properties();
90
+ expect(props).toContain("title");
91
+ expect(props).toContain("content");
92
+ expect(props).toContain("category");
93
+ expect(props).toHaveLength(3);
94
+
95
+ const indexedProps = comp.indexedProperties();
96
+ expect(indexedProps).toContain("category");
97
+ expect(indexedProps).toHaveLength(1);
98
+ });
99
+
100
+ test("should return all data in data() method", () => {
101
+ const comp = new MultiAttributeComponent();
102
+ comp.title = "Title";
103
+ comp.content = "Content";
104
+ comp.category = "Category";
105
+
106
+ const data = comp.data();
107
+ expect(data.title).toBe("Title");
108
+ expect(data.content).toBe("Content");
109
+ expect(data.category).toBe("Category");
110
+ });
111
+ });
112
+
113
+ describe("Component with no data attributes", () => {
114
+ test("should have empty properties array", () => {
115
+ const comp = new NoDataComponent();
116
+
117
+ const props = comp.properties();
118
+ expect(props).toHaveLength(0);
119
+
120
+ const data = comp.data();
121
+ expect(Object.keys(data)).toHaveLength(0);
122
+ });
123
+ });
124
+
125
+ describe("Component with numeric attribute", () => {
126
+ test("should handle numeric values correctly", () => {
127
+ const comp = new NumericComponent();
128
+ comp.count = 42;
129
+
130
+ const data = comp.data();
131
+ expect(data.count).toBe(42);
132
+ expect(typeof data.count).toBe("number");
133
+ });
134
+ });
135
+
136
+ describe("Component with boolean attribute", () => {
137
+ test("should handle boolean values correctly", () => {
138
+ const comp = new BooleanComponent();
139
+ comp.enabled = true;
140
+
141
+ const data = comp.data();
142
+ expect(data.enabled).toBe(true);
143
+ expect(typeof data.enabled).toBe("boolean");
144
+ });
145
+ });
146
+
147
+ describe("Component update operations", () => {
148
+ test("should handle partial updates for multi-attribute components", () => {
149
+ const comp = new MultiAttributeComponent();
150
+ comp.title = "Initial Title";
151
+ comp.content = "Initial Content";
152
+ comp.category = "Initial Category";
153
+
154
+ // Simulate partial update
155
+ comp.title = "Updated Title";
156
+
157
+ const data = comp.data();
158
+ expect(data.title).toBe("Updated Title");
159
+ expect(data.content).toBe("Initial Content");
160
+ expect(data.category).toBe("Initial Category");
161
+ });
162
+ });
163
+
164
+ describe("Component type safety and data integrity", () => {
165
+ test("should maintain type safety for ComponentDataType", () => {
166
+ const comp = new ValueComponent();
167
+ comp.value = "test";
168
+
169
+ const data: ComponentDataType<ValueComponent> = comp.data();
170
+ expect(data.value).toBe("test");
171
+ // TypeScript should prevent accessing non-existent properties
172
+ // This test ensures the type system works correctly
173
+ });
174
+
175
+ test("should exclude non-data properties from data()", () => {
176
+ const comp = new ValueComponent();
177
+ comp.value = "test";
178
+ // id is not a data property
179
+ comp.id = "some-id";
180
+
181
+ const data = comp.data();
182
+ expect(data.value).toBe("test");
183
+ expect((data as any).id).toBeUndefined();
184
+ });
185
+ });
186
+
187
+ describe("Component registry and type identification", () => {
188
+ test("should generate unique type IDs for different components", () => {
189
+ const comp1 = new ValueComponent();
190
+ const comp2 = new CustomAttributeComponent();
191
+
192
+ expect(comp1.getTypeID()).not.toBe(comp2.getTypeID());
193
+ });
194
+ });
195
+
196
+ describe("Error handling for malformed components", () => {
197
+ test("should handle components with undefined values gracefully", () => {
198
+ const comp = new ValueComponent();
199
+ // value is undefined initially
200
+
201
+ const data = comp.data();
202
+ expect(data.value).toBe(""); // Default value
203
+ });
204
+ });
205
+ });
@@ -0,0 +1,155 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { createUserFriendlyError, handleGraphQLError } from "../core/ErrorHandler";
3
+ import { getErrorMessage, mapZodPathToErrorCode } from "../utils/errorMessages";
4
+ import { GraphQLError } from "graphql";
5
+ import * as z from "zod";
6
+
7
+ describe('Error Handling Phase 1 Tests', () => {
8
+ describe('User-Friendly Error Messages', () => {
9
+ test('should return correct error message for known error code', () => {
10
+ const errorInfo = getErrorMessage('INVALID_EMAIL');
11
+ expect(errorInfo.userMessage).toBe('Please enter a valid email address');
12
+ expect(errorInfo.suggestion).toBe('Check that your email follows the format: name@example.com');
13
+ expect(errorInfo.category).toBe('validation');
14
+ });
15
+
16
+ test('should return fallback message for unknown error code', () => {
17
+ const errorInfo = getErrorMessage('UNKNOWN_CODE');
18
+ expect(errorInfo.userMessage).toBe('An unexpected error occurred');
19
+ expect(errorInfo.category).toBe('system');
20
+ });
21
+
22
+ test('should map Zod paths to error codes correctly', () => {
23
+ expect(mapZodPathToErrorCode(['email'])).toBe('INVALID_EMAIL');
24
+ expect(mapZodPathToErrorCode(['password'])).toBe('TOO_SHORT');
25
+ expect(mapZodPathToErrorCode(['unknownField'])).toBe('INVALID_FORMAT');
26
+ });
27
+ });
28
+
29
+ describe('createUserFriendlyError function', () => {
30
+ test('should create GraphQL error with user-friendly message', () => {
31
+ const error = createUserFriendlyError('INVALID_EMAIL');
32
+
33
+ expect(error).toBeInstanceOf(GraphQLError);
34
+ expect(error.message).toBe('Please enter a valid email address');
35
+ expect(error.extensions).toEqual({
36
+ code: 'INVALID_EMAIL',
37
+ category: 'validation',
38
+ suggestion: 'Check that your email follows the format: name@example.com',
39
+ userFriendly: true
40
+ });
41
+ });
42
+
43
+ test('should allow custom message override', () => {
44
+ const customMessage = 'Custom email error';
45
+ const error = createUserFriendlyError('INVALID_EMAIL', customMessage);
46
+
47
+ expect(error.message).toBe(customMessage);
48
+ expect(error.extensions?.code).toBe('INVALID_EMAIL');
49
+ });
50
+
51
+ test('should merge additional extensions', () => {
52
+ const error = createUserFriendlyError('INVALID_EMAIL', undefined, {
53
+ extensions: { additionalField: 'test' }
54
+ });
55
+
56
+ expect(error.extensions?.additionalField).toBe('test');
57
+ expect(error.extensions?.userFriendly).toBe(true);
58
+ });
59
+ });
60
+
61
+ describe('handleGraphQLError function', () => {
62
+ test('should handle Zod validation errors with user-friendly messages', () => {
63
+ // Create a real Zod error by validating invalid data with a field name
64
+ const userSchema = z.object({
65
+ email: z.string().email()
66
+ });
67
+ let zodError: z.ZodError;
68
+
69
+ try {
70
+ userSchema.parse({ email: 'invalid-email' });
71
+ } catch (error) {
72
+ zodError = error as z.ZodError;
73
+ }
74
+
75
+ expect(() => handleGraphQLError(zodError!)).toThrow(GraphQLError);
76
+
77
+ try {
78
+ handleGraphQLError(zodError!);
79
+ } catch (error: any) {
80
+ expect(error.message).toBe('Please enter a valid email address');
81
+ expect(error.extensions?.code).toBe('VALIDATION_ERROR');
82
+ expect(error.extensions?.category).toBe('validation');
83
+ expect(error.extensions?.userFriendly).toBe(true);
84
+ expect(error.extensions?.validationErrors).toBeDefined();
85
+ expect(error.extensions?.suggestion).toBe('Check that your email follows the format: name@example.com');
86
+ }
87
+ });
88
+
89
+ test('should handle multiple Zod validation errors', () => {
90
+ // Create a schema that will produce multiple validation errors
91
+ const userSchema = z.object({
92
+ email: z.string().email(),
93
+ password: z.string().min(8)
94
+ });
95
+
96
+ let zodError: z.ZodError;
97
+
98
+ try {
99
+ userSchema.parse({
100
+ email: 'invalid-email',
101
+ password: 'short'
102
+ });
103
+ } catch (error) {
104
+ zodError = error as z.ZodError;
105
+ }
106
+
107
+ expect(() => handleGraphQLError(zodError!)).toThrow(GraphQLError);
108
+
109
+ try {
110
+ handleGraphQLError(zodError!);
111
+ } catch (error: any) {
112
+ expect(error.message).toContain('Please enter a valid email address');
113
+ expect(error.extensions?.validationErrors).toHaveLength(2);
114
+ }
115
+ });
116
+
117
+ test('should handle empty Zod errors gracefully', () => {
118
+ const zodError = new z.ZodError([]);
119
+
120
+ expect(() => handleGraphQLError(zodError)).toThrow(GraphQLError);
121
+
122
+ try {
123
+ handleGraphQLError(zodError);
124
+ } catch (error: any) {
125
+ expect(error.message).toBe('Validation failed');
126
+ expect(error.extensions?.code).toBe('VALIDATION_ERROR');
127
+ expect(error.extensions?.userFriendly).toBe(true);
128
+ }
129
+ });
130
+
131
+ test('should re-throw existing GraphQL errors', () => {
132
+ const originalError = new GraphQLError('Original error', {
133
+ extensions: { code: 'ORIGINAL' }
134
+ });
135
+
136
+ expect(() => handleGraphQLError(originalError)).toThrow(originalError);
137
+ });
138
+
139
+ test('should handle unknown errors with user-friendly message', () => {
140
+ const unknownError = new Error('Some unknown error');
141
+
142
+ expect(() => handleGraphQLError(unknownError)).toThrow(GraphQLError);
143
+
144
+ try {
145
+ handleGraphQLError(unknownError);
146
+ } catch (error: any) {
147
+ expect(error.message).toBe('Something went wrong on our end');
148
+ expect(error.extensions?.code).toBe('INTERNAL_ERROR');
149
+ expect(error.extensions?.category).toBe('system');
150
+ expect(error.extensions?.suggestion).toBe('Please try again in a few moments. If the problem persists, contact support');
151
+ expect(error.extensions?.userFriendly).toBe(true);
152
+ }
153
+ });
154
+ });
155
+ });