bunsane 0.1.2 → 0.1.3

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 (48) 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/sqlHelpers.ts +0 -2
  22. package/gql/ArchetypeOperations.ts +281 -0
  23. package/gql/Generator.ts +252 -62
  24. package/gql/helpers.ts +5 -5
  25. package/gql/index.ts +19 -17
  26. package/gql/types.ts +58 -11
  27. package/index.ts +93 -82
  28. package/package.json +39 -37
  29. package/plugins/index.ts +13 -0
  30. package/scheduler/index.ts +87 -0
  31. package/service/Service.ts +4 -0
  32. package/service/ServiceRegistry.ts +5 -1
  33. package/service/index.ts +1 -1
  34. package/swagger/decorators.ts +65 -0
  35. package/swagger/generator.ts +100 -0
  36. package/swagger/index.ts +2 -0
  37. package/tests/bench/insert.bench.ts +1 -0
  38. package/tests/bench/relations.bench.ts +1 -0
  39. package/tests/bench/sorting.bench.ts +1 -0
  40. package/tests/component-hooks-simple.test.ts +117 -0
  41. package/tests/component-hooks.test.ts +83 -31
  42. package/tests/component.test.ts +1 -0
  43. package/tests/hooks.test.ts +1 -0
  44. package/tests/query.test.ts +46 -4
  45. package/tests/relations.test.ts +1 -0
  46. package/types/app.types.ts +0 -0
  47. package/upload/index.ts +0 -2
  48. package/core/processors/ImageProcessor.ts +0 -423
@@ -1,423 +0,0 @@
1
- import type { ImageProcessingOptions } from "../../types/upload.types";
2
- import { logger as MainLogger } from "../Logger";
3
-
4
- const logger = MainLogger.child({ scope: "ImageProcessor" });
5
-
6
- /**
7
- * ImageProcessor - Handle image manipulation and processing
8
- * Note: This is a basic implementation. For production use, consider integrating
9
- * with Sharp, Jimp, or similar image processing libraries.
10
- */
11
- export class ImageProcessor {
12
- /**
13
- * Process image according to configuration
14
- */
15
- public static async processImage(
16
- file: File,
17
- options: ImageProcessingOptions
18
- ): Promise<{
19
- processedFile: File;
20
- metadata: {
21
- width: number;
22
- height: number;
23
- format: string;
24
- size: number;
25
- };
26
- thumbnails?: Array<{
27
- size: string;
28
- file: File;
29
- width: number;
30
- height: number;
31
- }>;
32
- }> {
33
- logger.info(`Processing image: ${file.name}`);
34
-
35
- try {
36
- // Get image metadata
37
- const metadata = await this.getImageMetadata(file);
38
-
39
- let processedFile = file;
40
-
41
- // Resize if needed
42
- if (options.maxDimensions) {
43
- processedFile = await this.resizeImage(
44
- processedFile,
45
- options.maxDimensions.width,
46
- options.maxDimensions.height
47
- );
48
- }
49
-
50
- // Compress if needed
51
- if (options.compress && options.quality) {
52
- processedFile = await this.compressImage(processedFile, options.quality);
53
- }
54
-
55
- // Convert format if needed
56
- if (options.convertTo) {
57
- processedFile = await this.convertFormat(processedFile, options.convertTo);
58
- }
59
-
60
- // Generate thumbnails
61
- let thumbnails: Array<{
62
- size: string;
63
- file: File;
64
- width: number;
65
- height: number;
66
- }> | undefined;
67
-
68
- if (options.generateThumbnails && options.thumbnailSizes) {
69
- thumbnails = await this.generateThumbnails(processedFile, options.thumbnailSizes);
70
- }
71
-
72
- const finalMetadata = await this.getImageMetadata(processedFile);
73
-
74
- logger.info(`Image processing completed for: ${file.name}`);
75
-
76
- return {
77
- processedFile,
78
- metadata: finalMetadata,
79
- thumbnails
80
- };
81
-
82
- } catch (error) {
83
- logger.error(`Image processing failed for ${file.name}: ${error instanceof Error ? error.message : 'Unknown error'}`);
84
- throw error;
85
- }
86
- }
87
-
88
- /**
89
- * Get image metadata (basic implementation)
90
- * In production, use a proper image processing library
91
- */
92
- public static async getImageMetadata(file: File): Promise<{
93
- width: number;
94
- height: number;
95
- format: string;
96
- size: number;
97
- }> {
98
- return new Promise((resolve, reject) => {
99
- const img = new Image();
100
- const url = URL.createObjectURL(file);
101
-
102
- img.onload = () => {
103
- URL.revokeObjectURL(url);
104
- resolve({
105
- width: img.width,
106
- height: img.height,
107
- format: file.type.split('/')[1] || 'unknown',
108
- size: file.size
109
- });
110
- };
111
-
112
- img.onerror = () => {
113
- URL.revokeObjectURL(url);
114
- reject(new Error('Failed to load image for metadata extraction'));
115
- };
116
-
117
- img.src = url;
118
- });
119
- }
120
-
121
- /**
122
- * Resize image (basic canvas implementation)
123
- * For production, use Sharp or similar library
124
- */
125
- public static async resizeImage(
126
- file: File,
127
- maxWidth: number,
128
- maxHeight: number
129
- ): Promise<File> {
130
- return new Promise((resolve, reject) => {
131
- const img = new Image();
132
- const canvas = document.createElement('canvas');
133
- const ctx = canvas.getContext('2d');
134
-
135
- if (!ctx) {
136
- reject(new Error('Canvas context not available'));
137
- return;
138
- }
139
-
140
- img.onload = () => {
141
- // Calculate new dimensions
142
- const { width: newWidth, height: newHeight } = this.calculateDimensions(
143
- img.width,
144
- img.height,
145
- maxWidth,
146
- maxHeight
147
- );
148
-
149
- canvas.width = newWidth;
150
- canvas.height = newHeight;
151
-
152
- // Draw resized image
153
- ctx.drawImage(img, 0, 0, newWidth, newHeight);
154
-
155
- // Convert to file
156
- canvas.toBlob((blob) => {
157
- if (blob) {
158
- const resizedFile = new File([blob], file.name, {
159
- type: file.type,
160
- lastModified: Date.now()
161
- });
162
- resolve(resizedFile);
163
- } else {
164
- reject(new Error('Failed to create resized image blob'));
165
- }
166
- }, file.type);
167
- };
168
-
169
- img.onerror = () => {
170
- reject(new Error('Failed to load image for resizing'));
171
- };
172
-
173
- img.src = URL.createObjectURL(file);
174
- });
175
- }
176
-
177
- /**
178
- * Compress image (basic implementation)
179
- */
180
- public static async compressImage(file: File, quality: number): Promise<File> {
181
- if (!file.type.startsWith('image/')) {
182
- return file;
183
- }
184
-
185
- return new Promise((resolve, reject) => {
186
- const img = new Image();
187
- const canvas = document.createElement('canvas');
188
- const ctx = canvas.getContext('2d');
189
-
190
- if (!ctx) {
191
- reject(new Error('Canvas context not available'));
192
- return;
193
- }
194
-
195
- img.onload = () => {
196
- canvas.width = img.width;
197
- canvas.height = img.height;
198
-
199
- ctx.drawImage(img, 0, 0);
200
-
201
- // Convert with quality
202
- canvas.toBlob((blob) => {
203
- if (blob) {
204
- const compressedFile = new File([blob], file.name, {
205
- type: file.type,
206
- lastModified: Date.now()
207
- });
208
- resolve(compressedFile);
209
- } else {
210
- reject(new Error('Failed to create compressed image blob'));
211
- }
212
- }, file.type, quality / 100);
213
- };
214
-
215
- img.onerror = () => {
216
- reject(new Error('Failed to load image for compression'));
217
- };
218
-
219
- img.src = URL.createObjectURL(file);
220
- });
221
- }
222
-
223
- /**
224
- * Convert image format
225
- */
226
- public static async convertFormat(
227
- file: File,
228
- targetFormat: "jpeg" | "png" | "webp"
229
- ): Promise<File> {
230
- const mimeType = `image/${targetFormat}`;
231
- const extension = targetFormat === 'jpeg' ? 'jpg' : targetFormat;
232
- const newName = file.name.replace(/\.[^/.]+$/, `.${extension}`);
233
-
234
- return new Promise((resolve, reject) => {
235
- const img = new Image();
236
- const canvas = document.createElement('canvas');
237
- const ctx = canvas.getContext('2d');
238
-
239
- if (!ctx) {
240
- reject(new Error('Canvas context not available'));
241
- return;
242
- }
243
-
244
- img.onload = () => {
245
- canvas.width = img.width;
246
- canvas.height = img.height;
247
-
248
- // Set white background for JPEG conversion
249
- if (targetFormat === 'jpeg') {
250
- ctx.fillStyle = '#FFFFFF';
251
- ctx.fillRect(0, 0, canvas.width, canvas.height);
252
- }
253
-
254
- ctx.drawImage(img, 0, 0);
255
-
256
- canvas.toBlob((blob) => {
257
- if (blob) {
258
- const convertedFile = new File([blob], newName, {
259
- type: mimeType,
260
- lastModified: Date.now()
261
- });
262
- resolve(convertedFile);
263
- } else {
264
- reject(new Error('Failed to create converted image blob'));
265
- }
266
- }, mimeType);
267
- };
268
-
269
- img.onerror = () => {
270
- reject(new Error('Failed to load image for format conversion'));
271
- };
272
-
273
- img.src = URL.createObjectURL(file);
274
- });
275
- }
276
-
277
- /**
278
- * Generate thumbnails
279
- */
280
- public static async generateThumbnails(
281
- file: File,
282
- sizes: Array<{ width: number; height: number; suffix: string }>
283
- ): Promise<Array<{
284
- size: string;
285
- file: File;
286
- width: number;
287
- height: number;
288
- }>> {
289
- const thumbnails: Array<{
290
- size: string;
291
- file: File;
292
- width: number;
293
- height: number;
294
- }> = [];
295
-
296
- for (const size of sizes) {
297
- try {
298
- const thumbnail = await this.createThumbnail(file, size.width, size.height, size.suffix);
299
- thumbnails.push({
300
- size: `${size.width}x${size.height}`,
301
- file: thumbnail,
302
- width: size.width,
303
- height: size.height
304
- });
305
- } catch (error) {
306
- logger.warn(`Failed to generate ${size.width}x${size.height} thumbnail: ${error instanceof Error ? error.message : 'Unknown error'}`);
307
- }
308
- }
309
-
310
- return thumbnails;
311
- }
312
-
313
- /**
314
- * Create a single thumbnail
315
- */
316
- private static async createThumbnail(
317
- file: File,
318
- width: number,
319
- height: number,
320
- suffix: string
321
- ): Promise<File> {
322
- return new Promise((resolve, reject) => {
323
- const img = new Image();
324
- const canvas = document.createElement('canvas');
325
- const ctx = canvas.getContext('2d');
326
-
327
- if (!ctx) {
328
- reject(new Error('Canvas context not available'));
329
- return;
330
- }
331
-
332
- img.onload = () => {
333
- // Calculate dimensions maintaining aspect ratio
334
- const { width: newWidth, height: newHeight } = this.calculateDimensions(
335
- img.width,
336
- img.height,
337
- width,
338
- height
339
- );
340
-
341
- canvas.width = newWidth;
342
- canvas.height = newHeight;
343
-
344
- ctx.drawImage(img, 0, 0, newWidth, newHeight);
345
-
346
- const fileName = file.name.replace(/(\.[^.]+)$/, `${suffix}$1`);
347
-
348
- canvas.toBlob((blob) => {
349
- if (blob) {
350
- const thumbnailFile = new File([blob], fileName, {
351
- type: file.type,
352
- lastModified: Date.now()
353
- });
354
- resolve(thumbnailFile);
355
- } else {
356
- reject(new Error('Failed to create thumbnail blob'));
357
- }
358
- }, file.type);
359
- };
360
-
361
- img.onerror = () => {
362
- reject(new Error('Failed to load image for thumbnail creation'));
363
- };
364
-
365
- img.src = URL.createObjectURL(file);
366
- });
367
- }
368
-
369
- /**
370
- * Calculate new dimensions maintaining aspect ratio
371
- */
372
- private static calculateDimensions(
373
- originalWidth: number,
374
- originalHeight: number,
375
- maxWidth: number,
376
- maxHeight: number
377
- ): { width: number; height: number } {
378
- const aspectRatio = originalWidth / originalHeight;
379
-
380
- let newWidth = originalWidth;
381
- let newHeight = originalHeight;
382
-
383
- if (originalWidth > maxWidth) {
384
- newWidth = maxWidth;
385
- newHeight = newWidth / aspectRatio;
386
- }
387
-
388
- if (newHeight > maxHeight) {
389
- newHeight = maxHeight;
390
- newWidth = newHeight * aspectRatio;
391
- }
392
-
393
- return {
394
- width: Math.round(newWidth),
395
- height: Math.round(newHeight)
396
- };
397
- }
398
-
399
- /**
400
- * Validate if file is a processable image
401
- */
402
- public static isProcessableImage(file: File): boolean {
403
- const processableTypes = [
404
- 'image/jpeg',
405
- 'image/png',
406
- 'image/gif',
407
- 'image/webp'
408
- ];
409
-
410
- return processableTypes.includes(file.type);
411
- }
412
-
413
- /**
414
- * Get optimal quality based on file size
415
- */
416
- public static getOptimalQuality(fileSize: number): number {
417
- // Reduce quality for larger files
418
- if (fileSize > 5 * 1024 * 1024) return 70; // >5MB
419
- if (fileSize > 2 * 1024 * 1024) return 80; // >2MB
420
- if (fileSize > 1 * 1024 * 1024) return 85; // >1MB
421
- return 90; // Default quality
422
- }
423
- }