@indexeddb-orm/idb-orm 0.0.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 (216) hide show
  1. package/.vscode/extensions.json +5 -0
  2. package/README.md +1280 -0
  3. package/angular-demo-app/README.md +84 -0
  4. package/angular-demo-app/angular.json +109 -0
  5. package/angular-demo-app/package-lock.json +14215 -0
  6. package/angular-demo-app/package.json +41 -0
  7. package/angular-demo-app/src/app/app.component.ts +481 -0
  8. package/angular-demo-app/src/app/app.routes.ts +8 -0
  9. package/angular-demo-app/src/app/components/actions.component.ts +202 -0
  10. package/angular-demo-app/src/app/components/cloud-sync-demo.component.ts +296 -0
  11. package/angular-demo-app/src/app/components/live-query-demo.component.ts +307 -0
  12. package/angular-demo-app/src/app/components/main-info.component.ts +148 -0
  13. package/angular-demo-app/src/app/components/posts-live-query-demo.component.ts +336 -0
  14. package/angular-demo-app/src/app/components/typescript-demo.component.ts +268 -0
  15. package/angular-demo-app/src/entities/post-tag.entity.ts +25 -0
  16. package/angular-demo-app/src/entities/post.entity.ts +49 -0
  17. package/angular-demo-app/src/entities/profile.entity.ts +42 -0
  18. package/angular-demo-app/src/entities/tag.entity.ts +36 -0
  19. package/angular-demo-app/src/entities/user.entity.ts +59 -0
  20. package/angular-demo-app/src/favicon.ico +1 -0
  21. package/angular-demo-app/src/index.html +16 -0
  22. package/angular-demo-app/src/main.ts +13 -0
  23. package/angular-demo-app/src/services/app-logic.service.ts +449 -0
  24. package/angular-demo-app/src/services/cloud-sync.service.ts +95 -0
  25. package/angular-demo-app/src/services/database.service.ts +26 -0
  26. package/angular-demo-app/src/services/live-query.service.ts +63 -0
  27. package/angular-demo-app/src/services/posts-live-query.service.ts +86 -0
  28. package/angular-demo-app/src/services/typescript-demo.service.ts +59 -0
  29. package/angular-demo-app/src/styles.scss +50 -0
  30. package/angular-demo-app/tsconfig.app.json +13 -0
  31. package/angular-demo-app/tsconfig.json +34 -0
  32. package/angular-demo-app/tsconfig.spec.json +13 -0
  33. package/dist/Database.d.ts +206 -0
  34. package/dist/Database.js +288 -0
  35. package/dist/decorators/Column.d.ts +79 -0
  36. package/dist/decorators/Column.js +236 -0
  37. package/dist/decorators/Entity.d.ts +32 -0
  38. package/dist/decorators/Entity.js +44 -0
  39. package/dist/decorators/Relation.d.ts +70 -0
  40. package/dist/decorators/Relation.js +120 -0
  41. package/dist/decorators/index.d.ts +3 -0
  42. package/dist/decorators/index.js +3 -0
  43. package/dist/errors/ValidationError.d.ts +4 -0
  44. package/dist/errors/ValidationError.js +8 -0
  45. package/dist/index.d.ts +8 -0
  46. package/dist/index.js +7 -0
  47. package/dist/metadata/Column.d.ts +8 -0
  48. package/dist/metadata/Column.js +44 -0
  49. package/dist/metadata/Entity.d.ts +11 -0
  50. package/dist/metadata/Entity.js +21 -0
  51. package/dist/metadata/Relation.d.ts +20 -0
  52. package/dist/metadata/Relation.js +74 -0
  53. package/dist/metadata/index.d.ts +3 -0
  54. package/dist/metadata/index.js +3 -0
  55. package/dist/services/AggregationService.d.ts +38 -0
  56. package/dist/services/AggregationService.js +229 -0
  57. package/dist/services/BaseEntity.d.ts +32 -0
  58. package/dist/services/BaseEntity.js +62 -0
  59. package/dist/services/CloudSyncService.d.ts +100 -0
  60. package/dist/services/CloudSyncService.js +196 -0
  61. package/dist/services/DecoratorUtils.d.ts +12 -0
  62. package/dist/services/DecoratorUtils.js +10 -0
  63. package/dist/services/EntityFactory.d.ts +25 -0
  64. package/dist/services/EntityFactory.js +27 -0
  65. package/dist/services/EntityRegistry.d.ts +61 -0
  66. package/dist/services/EntityRegistry.js +56 -0
  67. package/dist/services/EntitySchema.d.ts +56 -0
  68. package/dist/services/EntitySchema.js +125 -0
  69. package/dist/services/MigrationManager.d.ts +70 -0
  70. package/dist/services/MigrationManager.js +181 -0
  71. package/dist/services/RelationLoader.d.ts +66 -0
  72. package/dist/services/RelationLoader.js +310 -0
  73. package/dist/services/SchemaBuilder.d.ts +68 -0
  74. package/dist/services/SchemaBuilder.js +191 -0
  75. package/dist/services/index.d.ts +7 -0
  76. package/dist/services/index.js +7 -0
  77. package/dist/types.d.ts +152 -0
  78. package/dist/types.js +1 -0
  79. package/dist/utils/logger.d.ts +12 -0
  80. package/dist/utils/logger.js +16 -0
  81. package/eslint.config.js +49 -0
  82. package/homepage/favicon.svg +36 -0
  83. package/homepage/index.html +1725 -0
  84. package/package.json +78 -0
  85. package/react-demo-app/README.md +61 -0
  86. package/react-demo-app/eslint.config.js +60 -0
  87. package/react-demo-app/index.html +13 -0
  88. package/react-demo-app/package-lock.json +4955 -0
  89. package/react-demo-app/package.json +39 -0
  90. package/react-demo-app/src/App.tsx +172 -0
  91. package/react-demo-app/src/assets/react.svg +1 -0
  92. package/react-demo-app/src/components/Actions.tsx +171 -0
  93. package/react-demo-app/src/components/CloudSyncDemo.tsx +191 -0
  94. package/react-demo-app/src/components/LiveQueryDemo.tsx +122 -0
  95. package/react-demo-app/src/components/MainInfo.tsx +75 -0
  96. package/react-demo-app/src/components/PostsLiveQueryDemo.tsx +185 -0
  97. package/react-demo-app/src/components/TypeScriptDemo.tsx +190 -0
  98. package/react-demo-app/src/database/Database.ts +30 -0
  99. package/react-demo-app/src/entities/Post.ts +48 -0
  100. package/react-demo-app/src/entities/PostTag.ts +26 -0
  101. package/react-demo-app/src/entities/Profile.ts +41 -0
  102. package/react-demo-app/src/entities/Tag.ts +35 -0
  103. package/react-demo-app/src/entities/User.ts +61 -0
  104. package/react-demo-app/src/hooks/useAppLogic.ts +565 -0
  105. package/react-demo-app/src/hooks/useCloudSyncDemo.ts +84 -0
  106. package/react-demo-app/src/hooks/useLiveQueryDemo.ts +68 -0
  107. package/react-demo-app/src/hooks/usePostsLiveQueryDemo.ts +64 -0
  108. package/react-demo-app/src/hooks/useTypeScriptDemo.ts +43 -0
  109. package/react-demo-app/src/index.css +26 -0
  110. package/react-demo-app/src/main.tsx +18 -0
  111. package/react-demo-app/src/migrations/001-add-user-email-index.ts +17 -0
  112. package/react-demo-app/src/migrations/002-add-post-category.ts +37 -0
  113. package/react-demo-app/src/migrations/index.ts +8 -0
  114. package/react-demo-app/src/vite-env.d.ts +1 -0
  115. package/react-demo-app/tsconfig.app.json +22 -0
  116. package/react-demo-app/tsconfig.json +6 -0
  117. package/react-demo-app/vite.config.ts +10 -0
  118. package/src/Database.ts +405 -0
  119. package/src/errors/ValidationError.ts +9 -0
  120. package/src/index.ts +13 -0
  121. package/src/metadata/Column.ts +74 -0
  122. package/src/metadata/Entity.ts +42 -0
  123. package/src/metadata/Relation.ts +121 -0
  124. package/src/metadata/index.ts +5 -0
  125. package/src/services/AggregationService.ts +348 -0
  126. package/src/services/BaseEntity.ts +77 -0
  127. package/src/services/CloudSyncService.ts +248 -0
  128. package/src/services/EntityFactory.ts +35 -0
  129. package/src/services/EntityRegistry.ts +109 -0
  130. package/src/services/EntitySchema.ts +154 -0
  131. package/src/services/MigrationManager.ts +276 -0
  132. package/src/services/RelationLoader.ts +532 -0
  133. package/src/services/SchemaBuilder.ts +237 -0
  134. package/src/services/index.ts +7 -0
  135. package/src/types.d.ts +1 -0
  136. package/src/types.ts +169 -0
  137. package/src/utils/logger.ts +40 -0
  138. package/svelte-demo-app/README.md +61 -0
  139. package/svelte-demo-app/package-lock.json +3000 -0
  140. package/svelte-demo-app/package.json +30 -0
  141. package/svelte-demo-app/src/app.d.ts +12 -0
  142. package/svelte-demo-app/src/app.html +13 -0
  143. package/svelte-demo-app/src/components/Actions.svelte +121 -0
  144. package/svelte-demo-app/src/components/CloudSyncDemo.svelte +333 -0
  145. package/svelte-demo-app/src/components/LiveQueryDemo.svelte +191 -0
  146. package/svelte-demo-app/src/components/MainInfo.svelte +133 -0
  147. package/svelte-demo-app/src/components/PostsLiveQueryDemo.svelte +330 -0
  148. package/svelte-demo-app/src/components/TypeScriptDemo.svelte +251 -0
  149. package/svelte-demo-app/src/database/Database.ts +29 -0
  150. package/svelte-demo-app/src/entities/Post.ts +46 -0
  151. package/svelte-demo-app/src/entities/PostTag.ts +22 -0
  152. package/svelte-demo-app/src/entities/Profile.ts +39 -0
  153. package/svelte-demo-app/src/entities/Tag.ts +33 -0
  154. package/svelte-demo-app/src/entities/User.ts +62 -0
  155. package/svelte-demo-app/src/lib/database/Database.ts +30 -0
  156. package/svelte-demo-app/src/lib/entities/Post.ts +47 -0
  157. package/svelte-demo-app/src/lib/entities/PostTag.ts +23 -0
  158. package/svelte-demo-app/src/lib/entities/Profile.ts +40 -0
  159. package/svelte-demo-app/src/lib/entities/Tag.ts +34 -0
  160. package/svelte-demo-app/src/lib/entities/User.ts +59 -0
  161. package/svelte-demo-app/src/lib/index.ts +7 -0
  162. package/svelte-demo-app/src/lib/migrations/001-add-user-email-index.ts +17 -0
  163. package/svelte-demo-app/src/lib/migrations/002-add-post-category.ts +37 -0
  164. package/svelte-demo-app/src/lib/migrations/index.ts +8 -0
  165. package/svelte-demo-app/src/migrations/001-add-user-email-index.ts +17 -0
  166. package/svelte-demo-app/src/migrations/002-add-post-category.ts +37 -0
  167. package/svelte-demo-app/src/migrations/index.ts +8 -0
  168. package/svelte-demo-app/src/routes/+layout.js +3 -0
  169. package/svelte-demo-app/src/routes/+layout.svelte +228 -0
  170. package/svelte-demo-app/src/routes/+page.js +3 -0
  171. package/svelte-demo-app/src/routes/+page.svelte +1305 -0
  172. package/svelte-demo-app/src/stores/appStore.js +603 -0
  173. package/svelte-demo-app/svelte.config.js +18 -0
  174. package/svelte-demo-app/tsconfig.json +14 -0
  175. package/svelte-demo-app/vite.config.ts +6 -0
  176. package/tests/aggregation.e2e.test.ts +87 -0
  177. package/tests/base-entity.e2e.test.ts +47 -0
  178. package/tests/database-api.e2e.test.ts +177 -0
  179. package/tests/decorators.e2e.test.ts +40 -0
  180. package/tests/entity-schema.e2e.test.ts +58 -0
  181. package/tests/relation-loader-table-names.test.ts +192 -0
  182. package/tests/relations.e2e.test.ts +178 -0
  183. package/tests/zod-runtime.e2e.test.ts +69 -0
  184. package/tsconfig.json +21 -0
  185. package/vitest.config.ts +21 -0
  186. package/vitest.setup.ts +27 -0
  187. package/vue-demo-app/README.md +61 -0
  188. package/vue-demo-app/index.html +13 -0
  189. package/vue-demo-app/package-lock.json +1537 -0
  190. package/vue-demo-app/package.json +27 -0
  191. package/vue-demo-app/src/App.vue +100 -0
  192. package/vue-demo-app/src/components/Actions.vue +135 -0
  193. package/vue-demo-app/src/components/CloudSyncDemo.vue +139 -0
  194. package/vue-demo-app/src/components/LiveQueryDemo.vue +122 -0
  195. package/vue-demo-app/src/components/MainInfo.vue +80 -0
  196. package/vue-demo-app/src/components/PostsLiveQueryDemo.vue +136 -0
  197. package/vue-demo-app/src/components/TypeScriptDemo.vue +133 -0
  198. package/vue-demo-app/src/database/Database.ts +29 -0
  199. package/vue-demo-app/src/entities/Post.ts +48 -0
  200. package/vue-demo-app/src/entities/PostTag.ts +24 -0
  201. package/vue-demo-app/src/entities/Profile.ts +41 -0
  202. package/vue-demo-app/src/entities/Tag.ts +35 -0
  203. package/vue-demo-app/src/entities/User.ts +61 -0
  204. package/vue-demo-app/src/main.ts +29 -0
  205. package/vue-demo-app/src/migrations/001-add-user-email-index.ts +23 -0
  206. package/vue-demo-app/src/migrations/002-add-post-category.ts +46 -0
  207. package/vue-demo-app/src/migrations/index.ts +14 -0
  208. package/vue-demo-app/src/services/useAppLogic.ts +565 -0
  209. package/vue-demo-app/src/services/useCloudSyncDemo.ts +84 -0
  210. package/vue-demo-app/src/services/useLiveQueryDemo.ts +82 -0
  211. package/vue-demo-app/src/services/usePostsLiveQueryDemo.ts +77 -0
  212. package/vue-demo-app/src/services/useTypeScriptDemo.ts +56 -0
  213. package/vue-demo-app/src/vite-env.d.ts +1 -0
  214. package/vue-demo-app/tsconfig.json +25 -0
  215. package/vue-demo-app/tsconfig.node.json +10 -0
  216. package/vue-demo-app/vite.config.ts +16 -0
@@ -0,0 +1,42 @@
1
+ import {
2
+ BaseEntity,
3
+ defineEntity,
4
+ } from 'idb-orm';
5
+ import { z } from 'zod';
6
+
7
+ const ProfileSchema = z.object({
8
+ id: z.number().optional(),
9
+ userId: z.number().positive('User ID must be positive'),
10
+ bio: z.string().optional(),
11
+ avatar: z.string().url().optional(),
12
+ website: z.string().url().optional(),
13
+ location: z.string().optional(),
14
+ createdAt: z.number(),
15
+ updatedAt: z.number(),
16
+ });
17
+
18
+ export type Profile = z.infer<typeof ProfileSchema>;
19
+
20
+ export class ProfileEntity extends BaseEntity<number> {
21
+ userId!: number;
22
+ bio?: string;
23
+ avatar?: string;
24
+ website?: string;
25
+ location?: string;
26
+ createdAt!: number;
27
+ updatedAt!: number;
28
+ }
29
+
30
+ defineEntity(ProfileEntity, {
31
+ tableName: 'profiles',
32
+ schema: ProfileSchema,
33
+ columns: {
34
+ userId: { indexed: true },
35
+ bio: { indexed: true },
36
+ avatar: {},
37
+ website: {},
38
+ location: {},
39
+ createdAt: { indexed: true },
40
+ updatedAt: { indexed: true },
41
+ },
42
+ });
@@ -0,0 +1,36 @@
1
+ import {
2
+ BaseEntity,
3
+ defineEntity,
4
+ } from 'idb-orm';
5
+ import { z } from 'zod';
6
+
7
+ const TagSchema = z.object({
8
+ id: z.number().optional(),
9
+ name: z.string().min(1, 'Tag name is required'),
10
+ color: z.string().regex(/^#[0-9A-F]{6}$/i, 'Color must be a valid hex color'),
11
+ description: z.string().optional(),
12
+ createdAt: z.number(),
13
+ updatedAt: z.number(),
14
+ });
15
+
16
+ export type Tag = z.infer<typeof TagSchema>;
17
+
18
+ export class TagEntity extends BaseEntity<number> {
19
+ name!: string;
20
+ color!: string;
21
+ description?: string;
22
+ createdAt!: number;
23
+ updatedAt!: number;
24
+ }
25
+
26
+ defineEntity(TagEntity, {
27
+ tableName: 'tags',
28
+ schema: TagSchema,
29
+ columns: {
30
+ name: { required: true, unique: true, indexed: true },
31
+ color: { required: true },
32
+ description: {},
33
+ createdAt: { indexed: true },
34
+ updatedAt: { indexed: true },
35
+ },
36
+ });
@@ -0,0 +1,59 @@
1
+ import {
2
+ BaseEntity, defineEntity,
3
+ } from 'idb-orm';
4
+ import { z } from 'zod';
5
+
6
+ import type { PostEntity } from './post.entity';
7
+ import type { ProfileEntity } from './profile.entity';
8
+
9
+ // Zod schema for validation
10
+ export const UserSchema = z.object({
11
+ id: z.number().optional(),
12
+ name: z.string().min(2, 'Name must be at least 2 characters'),
13
+ email: z.string().email('Invalid email format'),
14
+ age: z.number().min(18, 'Must be at least 18 years old'),
15
+ createdAt: z.number(),
16
+ updatedAt: z.number(),
17
+ isActive: z.boolean().default(true),
18
+ tags: z.array(z.string()).default([]),
19
+ metadata: z.record(z.string(), z.unknown()).default({}),
20
+ });
21
+
22
+ export type User = z.infer<typeof UserSchema>;
23
+
24
+ export class UserEntity extends BaseEntity<number> {
25
+ name!: string;
26
+ email!: string;
27
+ age!: number;
28
+ createdAt!: number;
29
+ updatedAt!: number;
30
+ isActive!: boolean;
31
+ tags!: string[];
32
+ metadata!: Record<string, unknown>;
33
+ posts?: PostEntity[];
34
+ profile?: ProfileEntity;
35
+ }
36
+
37
+ defineEntity(UserEntity, {
38
+ tableName: 'users',
39
+ schema: UserSchema,
40
+ columns: {
41
+ name: { required: true, indexed: true },
42
+ email: { required: true, unique: true },
43
+ age: { indexed: true },
44
+ createdAt: { indexed: true },
45
+ updatedAt: { indexed: true },
46
+ isActive: { indexed: true },
47
+ tags: {},
48
+ metadata: { indexed: true },
49
+ },
50
+ compoundIndexes: [
51
+ { columns: ['name', 'email'], unique: false, name: 'name_email_index' },
52
+ { columns: ['age', 'isActive'], unique: false, name: 'age_active_index' },
53
+ { columns: ['email'], unique: true, name: 'unique_email_index' },
54
+ ],
55
+ relations: {
56
+ posts: { type: 'one-to-many', target: 'posts', foreignKey: 'authorId' },
57
+ profile: { type: 'one-to-one', target: 'profiles', foreignKey: 'userId' },
58
+ },
59
+ });
@@ -0,0 +1 @@
1
+ data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAF0lEQVRIx2NgGAWjYBSMglEwCkbBSAcACAABAAGiyb5oAAAAAElFTkSuQmCC
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>IndexedDB ORM Demo App</title>
6
+ <base href="/">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <link rel="icon" type="image/x-icon" href="favicon.ico">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com">
10
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
11
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
12
+ </head>
13
+ <body class="mat-typography">
14
+ <app-root></app-root>
15
+ </body>
16
+ </html>
@@ -0,0 +1,13 @@
1
+ import { bootstrapApplication } from '@angular/platform-browser';
2
+ import { provideAnimations } from '@angular/platform-browser/animations';
3
+ import { provideRouter } from '@angular/router';
4
+
5
+ import { AppComponent } from './app/app.component';
6
+ import { routes } from './app/app.routes';
7
+
8
+ bootstrapApplication(AppComponent, {
9
+ providers: [
10
+ provideRouter(routes),
11
+ provideAnimations(),
12
+ ],
13
+ }).catch(err => console.error(err));
@@ -0,0 +1,449 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { newEntity } from 'idb-orm';
3
+ import { BehaviorSubject } from 'rxjs';
4
+
5
+ import { PostEntity } from '../entities/post.entity';
6
+ import { PostTagEntity } from '../entities/post-tag.entity';
7
+ import { ProfileEntity } from '../entities/profile.entity';
8
+ import { TagEntity } from '../entities/tag.entity';
9
+ import { UserEntity } from '../entities/user.entity';
10
+ import { DatabaseService } from './database.service';
11
+
12
+ @Injectable({
13
+ providedIn: 'root',
14
+ })
15
+ export class AppLogicService {
16
+ private usersSubject = new BehaviorSubject<UserEntity[]>([]);
17
+ private tabIndexSubject = new BehaviorSubject<number>(0);
18
+ private mobileOpenSubject = new BehaviorSubject<boolean>(false);
19
+
20
+ public users$ = this.usersSubject.asObservable();
21
+ public tabIndex$ = this.tabIndexSubject.asObservable();
22
+ public mobileOpen$ = this.mobileOpenSubject.asObservable();
23
+
24
+ constructor(private _dbService: DatabaseService) {
25
+ this.loadUsers();
26
+ }
27
+
28
+ async loadUsers(): Promise<void> {
29
+ try {
30
+ const users = await this._dbService.db.getRepository(UserEntity).toArray();
31
+ this.usersSubject.next(users);
32
+ } catch (error) {
33
+ console.error('Error loading users:', error);
34
+ }
35
+ }
36
+
37
+ async addUser(): Promise<void> {
38
+ try {
39
+ const user = newEntity(UserEntity, {
40
+ name: `Test User ${Date.now()}`,
41
+ email: `user${Date.now()}@example.com`,
42
+ age: Math.floor(Math.random() * 50) + 18,
43
+ isActive: true,
44
+ createdAt: Date.now(),
45
+ updatedAt: Date.now(),
46
+ tags: ['demo'],
47
+ metadata: { source: 'demo' },
48
+ });
49
+
50
+ await this._dbService.db.saveWithRelations({
51
+ entity: user,
52
+ entityClass: UserEntity,
53
+ });
54
+
55
+ await this.loadUsers();
56
+ } catch (error) {
57
+ console.error('Error adding user:', error);
58
+ }
59
+ }
60
+
61
+ async testZodValidation(): Promise<void> {
62
+ try {
63
+ console.log('Testing Zod validation...');
64
+
65
+ // Test 1: Manual validation of an invalid object
66
+ console.log('Test 1: Manual validation of invalid object');
67
+ const invalidUser = newEntity(UserEntity, {
68
+ name: 'A', // Too short (min 2 chars)
69
+ email: 'invalid-email', // Invalid email
70
+ age: 15, // Too young (min 18)
71
+ tags: ['test'],
72
+ metadata: { source: 'validation-test' },
73
+ createdAt: Date.now(),
74
+ updatedAt: Date.now(),
75
+ });
76
+
77
+ console.log('Object to validate:', invalidUser);
78
+
79
+ // Manual call to .validate() on the object
80
+ const validationResult = invalidUser.validate();
81
+ console.log('Validation result:', validationResult);
82
+
83
+ if (!validationResult.isValid) {
84
+ console.log('Validation errors:');
85
+ validationResult.errors.forEach((error, index) => {
86
+ console.log(` ${index + 1}. ${error}`);
87
+ });
88
+ }
89
+
90
+ // Test 2: Manual validation of a valid object
91
+ console.log('\nTest 2: Manual validation of a valid object');
92
+ const validUser = newEntity(UserEntity, {
93
+ name: 'John Doe', // Valid
94
+ email: 'john@example.com', // Valid
95
+ age: 25, // Valid
96
+ tags: ['developer', 'typescript'],
97
+ metadata: { source: 'validation-test', role: 'admin' },
98
+ createdAt: Date.now(),
99
+ updatedAt: Date.now(),
100
+ });
101
+
102
+ console.log('Object to validate:', validUser);
103
+
104
+ const validResult = validUser.validate();
105
+ console.log('Validation result:', validResult);
106
+
107
+ if (validResult.isValid) {
108
+ console.log('Validation passed successfully!');
109
+
110
+ // Add the valid user to the DB
111
+ const userRepository = this._dbService.db.getRepository(UserEntity);
112
+ await userRepository.add(validUser);
113
+ console.log('Valid user added to the database');
114
+ await this.loadUsers();
115
+ }
116
+
117
+ // Test 3: validateOrThrow() - throwing errors
118
+ console.log('\nTest 3: validateOrThrow() - throwing errors');
119
+ try {
120
+ invalidUser.validateOrThrow();
121
+ console.log(
122
+ 'validateOrThrow() did not throw - this should not happen',
123
+ );
124
+ } catch (error) {
125
+ console.log(
126
+ 'validateOrThrow() threw as expected:',
127
+ (error as Error).message,
128
+ );
129
+ }
130
+
131
+ alert(
132
+ `Validation test finished!
133
+ \n\nInvalid object: ${validationResult.errors.length} errors\n` +
134
+ `Valid object: ${validResult.isValid ? 'OK' : 'ERRORS'}\n\nCheck console for details.`,
135
+ );
136
+ } catch (error) {
137
+ console.log('Zod validation caught the error:', error);
138
+ alert(`Zod validation working! Error: ${(error as Error).message}`);
139
+ }
140
+ }
141
+
142
+ async testSchemaMigration(): Promise<void> {
143
+ try {
144
+ // This would test schema migration in a real scenario
145
+ console.log('Schema migration test - would test version upgrades here');
146
+ alert('Schema migration test completed (demo)');
147
+ } catch (error) {
148
+ console.error('Schema migration error:', error);
149
+ }
150
+ }
151
+
152
+ async testRelations(): Promise<void> {
153
+ try {
154
+ // Clear existing test data
155
+ // Get all users and filter by metadata.source in JavaScript
156
+ const allUsers = await this._dbService.db.getRepository(UserEntity).toArray();
157
+ const testUsers = allUsers.filter(user => user.metadata?.['source'] === 'relation-test');
158
+ if (testUsers.length > 0) {
159
+ await this._dbService.db
160
+ .getRepository(UserEntity).bulkDelete(testUsers.map(u => u.id!));
161
+ }
162
+ await this._dbService.db.getRepository(ProfileEntity).where('bio').startsWith('Test profile').delete();
163
+ await this._dbService.db.getRepository(PostEntity).where('title').startsWith('Test post').delete();
164
+ await this._dbService.db.getRepository(TagEntity).where('name').startsWith('test-tag').delete();
165
+
166
+ // Create test user
167
+ const user = new UserEntity();
168
+ Object.assign(user, {
169
+ name: 'John Doe',
170
+ email: `john+${Date.now()}@example.com`,
171
+ age: 30,
172
+ isActive: true,
173
+ metadata: { source: 'relation-test' },
174
+ });
175
+
176
+ const savedUser = await this._dbService.db.saveWithRelations({
177
+ entity: user,
178
+ entityClass: UserEntity,
179
+ });
180
+
181
+ // Create profile
182
+ const profile = new ProfileEntity();
183
+ Object.assign(profile, {
184
+ bio: 'Test profile for John Doe',
185
+ userId: savedUser.id!,
186
+ });
187
+
188
+ await this._dbService.db.saveWithRelations({
189
+ entity: profile,
190
+ entityClass: ProfileEntity,
191
+ });
192
+
193
+ // Create post
194
+ const post = new PostEntity();
195
+ Object.assign(post, {
196
+ title: 'Test post',
197
+ content: 'This is a test post',
198
+ published: true,
199
+ likes: 5,
200
+ createdAt: new Date(),
201
+ userId: savedUser.id!,
202
+ });
203
+
204
+ await this._dbService.db.saveWithRelations({
205
+ entity: post,
206
+ entityClass: PostEntity,
207
+ });
208
+
209
+ // Create tag
210
+ const tag = new TagEntity();
211
+ Object.assign(tag, {
212
+ name: 'test-tag-1',
213
+ color: '#ff0000',
214
+ });
215
+
216
+ await this._dbService.db.saveWithRelations({
217
+ entity: tag,
218
+ entityClass: TagEntity,
219
+ });
220
+
221
+ console.log('Relations test completed successfully');
222
+ alert('Relations test completed! Check console for details.');
223
+ await this.loadUsers();
224
+ } catch (error) {
225
+ console.error('Error testing relations:', error);
226
+ alert('Error testing relations: ' + (error as Error).message);
227
+ }
228
+ }
229
+
230
+ async testTransactions(): Promise<void> {
231
+ try {
232
+ await this._dbService.db.transaction('rw', [this._dbService.db.getRepository(UserEntity), this._dbService.db.getRepository(PostEntity)], async () => {
233
+ const user = newEntity(UserEntity, {
234
+ name: 'Transaction User',
235
+ email: `transaction${Date.now()}@example.com`,
236
+ age: 25,
237
+ isActive: true,
238
+ });
239
+
240
+ const savedUser = await this._dbService.db.saveWithRelations({
241
+ entity: user,
242
+ entityClass: UserEntity,
243
+ });
244
+
245
+ const post = newEntity(PostEntity, {
246
+ title: 'Transaction Post',
247
+ content: 'Created in transaction',
248
+ published: false,
249
+ likes: 0,
250
+ createdAt: Date.now(),
251
+ authorId: savedUser.id!,
252
+ });
253
+
254
+ await this._dbService.db.saveWithRelations({
255
+ entity: post,
256
+ entityClass: PostEntity,
257
+ });
258
+ });
259
+
260
+ console.log('Transaction test completed');
261
+ alert('Transaction test completed!');
262
+ await this.loadUsers();
263
+ } catch (error) {
264
+ console.error('Transaction error:', error);
265
+ alert('Transaction error: ' + (error as Error).message);
266
+ }
267
+ }
268
+
269
+ async testCompoundIndexes(): Promise<void> {
270
+ try {
271
+ console.log('Testing compound indexes...');
272
+
273
+ // Test 1: Query using compound index (name + email)
274
+ console.log('Test 1: Query using compound index (name + email)');
275
+ const usersByNameEmail = await this._dbService.db
276
+ .getRepository(UserEntity)
277
+ .where(['name', 'email'])
278
+ .equals(['John Doe', 'john@example.com'])
279
+ .toArray();
280
+ console.log('Users by name+email:', usersByNameEmail);
281
+
282
+ // Test 2: Query using compound index (age + isActive)
283
+ console.log('Test 2: Query using compound index (age + isActive)');
284
+ const activeAdults = await this._dbService.db
285
+ .getRepository(UserEntity)
286
+ .where(['age', 'isActive'])
287
+ .above([18, 1])
288
+ .toArray();
289
+ console.log('Active adults (age > 18, isActive = true):', activeAdults);
290
+
291
+ // Test 3: Query using unique compound index (email)
292
+ console.log('Test 3: Query using unique compound index (email)');
293
+ const userByEmail = await this._dbService.db
294
+ .getRepository(UserEntity)
295
+ .where('email')
296
+ .equals('john@example.com')
297
+ .first();
298
+ console.log('User by email:', userByEmail);
299
+
300
+ // Test 4: Add some test data to demonstrate compound indexes
301
+ console.log('Test 4: Adding test data for compound indexes');
302
+ const testUser1 = newEntity(UserEntity, {
303
+ name: 'Alice Smith',
304
+ email: 'alice@example.com',
305
+ age: 25,
306
+ isActive: true,
307
+ tags: ['test'],
308
+ metadata: { source: 'compound-test' },
309
+ createdAt: Date.now(),
310
+ updatedAt: Date.now(),
311
+ });
312
+
313
+ const testUser2 = newEntity(UserEntity, {
314
+ name: 'Bob Johnson',
315
+ email: 'bob@example.com',
316
+ age: 17,
317
+ isActive: false,
318
+ tags: ['test'],
319
+ metadata: { source: 'compound-test' },
320
+ createdAt: Date.now(),
321
+ updatedAt: Date.now(),
322
+ });
323
+
324
+ await this._dbService.db.saveWithRelations({
325
+ entity: testUser1,
326
+ entityClass: UserEntity,
327
+ });
328
+
329
+ await this._dbService.db.saveWithRelations({
330
+ entity: testUser2,
331
+ entityClass: UserEntity,
332
+ });
333
+
334
+ // Test 5: Query the new test data
335
+ console.log('Test 5: Querying test data with compound indexes');
336
+ const testActiveAdults = await this._dbService.db
337
+ .getRepository(UserEntity)
338
+ .where(['age', 'isActive'])
339
+ .above([18, 1])
340
+ .toArray();
341
+
342
+ const testInactiveMinors = await this._dbService.db
343
+ .getRepository(UserEntity)
344
+ .where(['age', 'isActive'])
345
+ .below([18, 1])
346
+ .toArray();
347
+
348
+ console.log('Test active adults:', testActiveAdults);
349
+ console.log('Test inactive minors:', testInactiveMinors);
350
+
351
+ alert(
352
+ `Compound index test completed!\n\n` +
353
+ `Name+Email query: ${usersByNameEmail.length} results\n` +
354
+ `Active adults: ${activeAdults.length} results\n` +
355
+ `Email query: ${userByEmail ? 'Found' : 'Not found'}\n` +
356
+ `Test active adults: ${testActiveAdults.length} results\n` +
357
+ `Test inactive minors: ${testInactiveMinors.length} results\n\n` +
358
+ `Check console for detailed results.`,
359
+ );
360
+
361
+ await this.loadUsers();
362
+ } catch (error) {
363
+ console.error('Compound index error:', error);
364
+ alert('Compound index error: ' + (error as Error).message);
365
+ }
366
+ }
367
+
368
+ async testAggregations(): Promise<void> {
369
+ try {
370
+ // Test basic statistics
371
+ const userStats = await this._dbService.db.aggregate({
372
+ entityClass: UserEntity,
373
+ options: {
374
+ count: true,
375
+ avg: ['age'],
376
+ },
377
+ });
378
+
379
+ // Test active users only
380
+ const activeUserStats = await this._dbService.db.aggregate({
381
+ entityClass: UserEntity,
382
+ options: {
383
+ where: { isActive: true },
384
+ count: true,
385
+ avg: ['age'],
386
+ },
387
+ });
388
+
389
+ console.log('Aggregation test results:', {
390
+ totalUsers: userStats.count,
391
+ activeUsers: activeUserStats.count,
392
+ averageAge: userStats.avg?.['age'],
393
+ activeUserAvgAge: activeUserStats.avg?.['age'],
394
+ });
395
+
396
+ alert(`Aggregation test completed!\nTotal users: ${userStats.count}\nActive users: ${activeUserStats.count}\nAverage age: ${userStats.avg?.['age']?.toFixed(1) || 'N/A'}\nActive users avg age: ${activeUserStats.avg?.['age']?.toFixed(1) || 'N/A'}`);
397
+ } catch (error) {
398
+ console.error('Aggregation error:', error);
399
+ alert('Aggregation error: ' + (error as Error).message);
400
+ }
401
+ }
402
+
403
+ async clearAll(): Promise<void> {
404
+ try {
405
+ await this._dbService.db.getRepository(UserEntity).clear();
406
+ await this._dbService.db.getRepository(ProfileEntity).clear();
407
+ await this._dbService.db.getRepository(PostEntity).clear();
408
+ await this._dbService.db.getRepository(TagEntity).clear();
409
+ await this._dbService.db.getRepository(PostTagEntity).clear();
410
+
411
+ await this.loadUsers();
412
+ console.log('All data cleared');
413
+ alert('All data cleared!');
414
+ } catch (error) {
415
+ console.error('Error clearing data:', error);
416
+ alert('Error clearing data: ' + (error as Error).message);
417
+ }
418
+ }
419
+
420
+ async toggleAutoReset(): Promise<void> {
421
+ try {
422
+ // Show current status
423
+ const currentStatus =
424
+ localStorage.getItem('DexieORMDemo_auto_reset') !== 'false';
425
+
426
+ const newStatus = !currentStatus;
427
+ localStorage.setItem('DexieORMDemo_auto_reset', newStatus.toString());
428
+
429
+ alert(
430
+ `Auto-reset ${newStatus ? 'enabled' : 'disabled'}! ${
431
+ newStatus
432
+ ? 'Database will reset on schema changes.'
433
+ : 'Database will not reset on schema changes.'
434
+ }`,
435
+ );
436
+ } catch (error) {
437
+ console.error('Auto-reset toggle error:', error);
438
+ alert(`Auto-reset toggle error: ${(error as Error).message}`);
439
+ }
440
+ }
441
+
442
+ setTabIndex(index: number): void {
443
+ this.tabIndexSubject.next(index);
444
+ }
445
+
446
+ toggleMobileOpen(): void {
447
+ this.mobileOpenSubject.next(!this.mobileOpenSubject.value);
448
+ }
449
+ }