@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.
- package/.vscode/extensions.json +5 -0
- package/README.md +1280 -0
- package/angular-demo-app/README.md +84 -0
- package/angular-demo-app/angular.json +109 -0
- package/angular-demo-app/package-lock.json +14215 -0
- package/angular-demo-app/package.json +41 -0
- package/angular-demo-app/src/app/app.component.ts +481 -0
- package/angular-demo-app/src/app/app.routes.ts +8 -0
- package/angular-demo-app/src/app/components/actions.component.ts +202 -0
- package/angular-demo-app/src/app/components/cloud-sync-demo.component.ts +296 -0
- package/angular-demo-app/src/app/components/live-query-demo.component.ts +307 -0
- package/angular-demo-app/src/app/components/main-info.component.ts +148 -0
- package/angular-demo-app/src/app/components/posts-live-query-demo.component.ts +336 -0
- package/angular-demo-app/src/app/components/typescript-demo.component.ts +268 -0
- package/angular-demo-app/src/entities/post-tag.entity.ts +25 -0
- package/angular-demo-app/src/entities/post.entity.ts +49 -0
- package/angular-demo-app/src/entities/profile.entity.ts +42 -0
- package/angular-demo-app/src/entities/tag.entity.ts +36 -0
- package/angular-demo-app/src/entities/user.entity.ts +59 -0
- package/angular-demo-app/src/favicon.ico +1 -0
- package/angular-demo-app/src/index.html +16 -0
- package/angular-demo-app/src/main.ts +13 -0
- package/angular-demo-app/src/services/app-logic.service.ts +449 -0
- package/angular-demo-app/src/services/cloud-sync.service.ts +95 -0
- package/angular-demo-app/src/services/database.service.ts +26 -0
- package/angular-demo-app/src/services/live-query.service.ts +63 -0
- package/angular-demo-app/src/services/posts-live-query.service.ts +86 -0
- package/angular-demo-app/src/services/typescript-demo.service.ts +59 -0
- package/angular-demo-app/src/styles.scss +50 -0
- package/angular-demo-app/tsconfig.app.json +13 -0
- package/angular-demo-app/tsconfig.json +34 -0
- package/angular-demo-app/tsconfig.spec.json +13 -0
- package/dist/Database.d.ts +206 -0
- package/dist/Database.js +288 -0
- package/dist/decorators/Column.d.ts +79 -0
- package/dist/decorators/Column.js +236 -0
- package/dist/decorators/Entity.d.ts +32 -0
- package/dist/decorators/Entity.js +44 -0
- package/dist/decorators/Relation.d.ts +70 -0
- package/dist/decorators/Relation.js +120 -0
- package/dist/decorators/index.d.ts +3 -0
- package/dist/decorators/index.js +3 -0
- package/dist/errors/ValidationError.d.ts +4 -0
- package/dist/errors/ValidationError.js +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/metadata/Column.d.ts +8 -0
- package/dist/metadata/Column.js +44 -0
- package/dist/metadata/Entity.d.ts +11 -0
- package/dist/metadata/Entity.js +21 -0
- package/dist/metadata/Relation.d.ts +20 -0
- package/dist/metadata/Relation.js +74 -0
- package/dist/metadata/index.d.ts +3 -0
- package/dist/metadata/index.js +3 -0
- package/dist/services/AggregationService.d.ts +38 -0
- package/dist/services/AggregationService.js +229 -0
- package/dist/services/BaseEntity.d.ts +32 -0
- package/dist/services/BaseEntity.js +62 -0
- package/dist/services/CloudSyncService.d.ts +100 -0
- package/dist/services/CloudSyncService.js +196 -0
- package/dist/services/DecoratorUtils.d.ts +12 -0
- package/dist/services/DecoratorUtils.js +10 -0
- package/dist/services/EntityFactory.d.ts +25 -0
- package/dist/services/EntityFactory.js +27 -0
- package/dist/services/EntityRegistry.d.ts +61 -0
- package/dist/services/EntityRegistry.js +56 -0
- package/dist/services/EntitySchema.d.ts +56 -0
- package/dist/services/EntitySchema.js +125 -0
- package/dist/services/MigrationManager.d.ts +70 -0
- package/dist/services/MigrationManager.js +181 -0
- package/dist/services/RelationLoader.d.ts +66 -0
- package/dist/services/RelationLoader.js +310 -0
- package/dist/services/SchemaBuilder.d.ts +68 -0
- package/dist/services/SchemaBuilder.js +191 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.js +7 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.js +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +16 -0
- package/eslint.config.js +49 -0
- package/homepage/favicon.svg +36 -0
- package/homepage/index.html +1725 -0
- package/package.json +78 -0
- package/react-demo-app/README.md +61 -0
- package/react-demo-app/eslint.config.js +60 -0
- package/react-demo-app/index.html +13 -0
- package/react-demo-app/package-lock.json +4955 -0
- package/react-demo-app/package.json +39 -0
- package/react-demo-app/src/App.tsx +172 -0
- package/react-demo-app/src/assets/react.svg +1 -0
- package/react-demo-app/src/components/Actions.tsx +171 -0
- package/react-demo-app/src/components/CloudSyncDemo.tsx +191 -0
- package/react-demo-app/src/components/LiveQueryDemo.tsx +122 -0
- package/react-demo-app/src/components/MainInfo.tsx +75 -0
- package/react-demo-app/src/components/PostsLiveQueryDemo.tsx +185 -0
- package/react-demo-app/src/components/TypeScriptDemo.tsx +190 -0
- package/react-demo-app/src/database/Database.ts +30 -0
- package/react-demo-app/src/entities/Post.ts +48 -0
- package/react-demo-app/src/entities/PostTag.ts +26 -0
- package/react-demo-app/src/entities/Profile.ts +41 -0
- package/react-demo-app/src/entities/Tag.ts +35 -0
- package/react-demo-app/src/entities/User.ts +61 -0
- package/react-demo-app/src/hooks/useAppLogic.ts +565 -0
- package/react-demo-app/src/hooks/useCloudSyncDemo.ts +84 -0
- package/react-demo-app/src/hooks/useLiveQueryDemo.ts +68 -0
- package/react-demo-app/src/hooks/usePostsLiveQueryDemo.ts +64 -0
- package/react-demo-app/src/hooks/useTypeScriptDemo.ts +43 -0
- package/react-demo-app/src/index.css +26 -0
- package/react-demo-app/src/main.tsx +18 -0
- package/react-demo-app/src/migrations/001-add-user-email-index.ts +17 -0
- package/react-demo-app/src/migrations/002-add-post-category.ts +37 -0
- package/react-demo-app/src/migrations/index.ts +8 -0
- package/react-demo-app/src/vite-env.d.ts +1 -0
- package/react-demo-app/tsconfig.app.json +22 -0
- package/react-demo-app/tsconfig.json +6 -0
- package/react-demo-app/vite.config.ts +10 -0
- package/src/Database.ts +405 -0
- package/src/errors/ValidationError.ts +9 -0
- package/src/index.ts +13 -0
- package/src/metadata/Column.ts +74 -0
- package/src/metadata/Entity.ts +42 -0
- package/src/metadata/Relation.ts +121 -0
- package/src/metadata/index.ts +5 -0
- package/src/services/AggregationService.ts +348 -0
- package/src/services/BaseEntity.ts +77 -0
- package/src/services/CloudSyncService.ts +248 -0
- package/src/services/EntityFactory.ts +35 -0
- package/src/services/EntityRegistry.ts +109 -0
- package/src/services/EntitySchema.ts +154 -0
- package/src/services/MigrationManager.ts +276 -0
- package/src/services/RelationLoader.ts +532 -0
- package/src/services/SchemaBuilder.ts +237 -0
- package/src/services/index.ts +7 -0
- package/src/types.d.ts +1 -0
- package/src/types.ts +169 -0
- package/src/utils/logger.ts +40 -0
- package/svelte-demo-app/README.md +61 -0
- package/svelte-demo-app/package-lock.json +3000 -0
- package/svelte-demo-app/package.json +30 -0
- package/svelte-demo-app/src/app.d.ts +12 -0
- package/svelte-demo-app/src/app.html +13 -0
- package/svelte-demo-app/src/components/Actions.svelte +121 -0
- package/svelte-demo-app/src/components/CloudSyncDemo.svelte +333 -0
- package/svelte-demo-app/src/components/LiveQueryDemo.svelte +191 -0
- package/svelte-demo-app/src/components/MainInfo.svelte +133 -0
- package/svelte-demo-app/src/components/PostsLiveQueryDemo.svelte +330 -0
- package/svelte-demo-app/src/components/TypeScriptDemo.svelte +251 -0
- package/svelte-demo-app/src/database/Database.ts +29 -0
- package/svelte-demo-app/src/entities/Post.ts +46 -0
- package/svelte-demo-app/src/entities/PostTag.ts +22 -0
- package/svelte-demo-app/src/entities/Profile.ts +39 -0
- package/svelte-demo-app/src/entities/Tag.ts +33 -0
- package/svelte-demo-app/src/entities/User.ts +62 -0
- package/svelte-demo-app/src/lib/database/Database.ts +30 -0
- package/svelte-demo-app/src/lib/entities/Post.ts +47 -0
- package/svelte-demo-app/src/lib/entities/PostTag.ts +23 -0
- package/svelte-demo-app/src/lib/entities/Profile.ts +40 -0
- package/svelte-demo-app/src/lib/entities/Tag.ts +34 -0
- package/svelte-demo-app/src/lib/entities/User.ts +59 -0
- package/svelte-demo-app/src/lib/index.ts +7 -0
- package/svelte-demo-app/src/lib/migrations/001-add-user-email-index.ts +17 -0
- package/svelte-demo-app/src/lib/migrations/002-add-post-category.ts +37 -0
- package/svelte-demo-app/src/lib/migrations/index.ts +8 -0
- package/svelte-demo-app/src/migrations/001-add-user-email-index.ts +17 -0
- package/svelte-demo-app/src/migrations/002-add-post-category.ts +37 -0
- package/svelte-demo-app/src/migrations/index.ts +8 -0
- package/svelte-demo-app/src/routes/+layout.js +3 -0
- package/svelte-demo-app/src/routes/+layout.svelte +228 -0
- package/svelte-demo-app/src/routes/+page.js +3 -0
- package/svelte-demo-app/src/routes/+page.svelte +1305 -0
- package/svelte-demo-app/src/stores/appStore.js +603 -0
- package/svelte-demo-app/svelte.config.js +18 -0
- package/svelte-demo-app/tsconfig.json +14 -0
- package/svelte-demo-app/vite.config.ts +6 -0
- package/tests/aggregation.e2e.test.ts +87 -0
- package/tests/base-entity.e2e.test.ts +47 -0
- package/tests/database-api.e2e.test.ts +177 -0
- package/tests/decorators.e2e.test.ts +40 -0
- package/tests/entity-schema.e2e.test.ts +58 -0
- package/tests/relation-loader-table-names.test.ts +192 -0
- package/tests/relations.e2e.test.ts +178 -0
- package/tests/zod-runtime.e2e.test.ts +69 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +21 -0
- package/vitest.setup.ts +27 -0
- package/vue-demo-app/README.md +61 -0
- package/vue-demo-app/index.html +13 -0
- package/vue-demo-app/package-lock.json +1537 -0
- package/vue-demo-app/package.json +27 -0
- package/vue-demo-app/src/App.vue +100 -0
- package/vue-demo-app/src/components/Actions.vue +135 -0
- package/vue-demo-app/src/components/CloudSyncDemo.vue +139 -0
- package/vue-demo-app/src/components/LiveQueryDemo.vue +122 -0
- package/vue-demo-app/src/components/MainInfo.vue +80 -0
- package/vue-demo-app/src/components/PostsLiveQueryDemo.vue +136 -0
- package/vue-demo-app/src/components/TypeScriptDemo.vue +133 -0
- package/vue-demo-app/src/database/Database.ts +29 -0
- package/vue-demo-app/src/entities/Post.ts +48 -0
- package/vue-demo-app/src/entities/PostTag.ts +24 -0
- package/vue-demo-app/src/entities/Profile.ts +41 -0
- package/vue-demo-app/src/entities/Tag.ts +35 -0
- package/vue-demo-app/src/entities/User.ts +61 -0
- package/vue-demo-app/src/main.ts +29 -0
- package/vue-demo-app/src/migrations/001-add-user-email-index.ts +23 -0
- package/vue-demo-app/src/migrations/002-add-post-category.ts +46 -0
- package/vue-demo-app/src/migrations/index.ts +14 -0
- package/vue-demo-app/src/services/useAppLogic.ts +565 -0
- package/vue-demo-app/src/services/useCloudSyncDemo.ts +84 -0
- package/vue-demo-app/src/services/useLiveQueryDemo.ts +82 -0
- package/vue-demo-app/src/services/usePostsLiveQueryDemo.ts +77 -0
- package/vue-demo-app/src/services/useTypeScriptDemo.ts +56 -0
- package/vue-demo-app/src/vite-env.d.ts +1 -0
- package/vue-demo-app/tsconfig.json +25 -0
- package/vue-demo-app/tsconfig.node.json +10 -0
- package/vue-demo-app/vite.config.ts +16 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { BehaviorSubject } from 'rxjs';
|
|
3
|
+
|
|
4
|
+
@Injectable({
|
|
5
|
+
providedIn: 'root',
|
|
6
|
+
})
|
|
7
|
+
export class CloudSyncService {
|
|
8
|
+
private syncStatusSubject = new BehaviorSubject<{
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
isOnline?: boolean;
|
|
11
|
+
lastSync?: Date;
|
|
12
|
+
}>({
|
|
13
|
+
enabled: false,
|
|
14
|
+
isOnline: undefined,
|
|
15
|
+
lastSync: undefined,
|
|
16
|
+
});
|
|
17
|
+
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
|
18
|
+
|
|
19
|
+
public syncStatus$ = this.syncStatusSubject.asObservable();
|
|
20
|
+
public isLoading$ = this.isLoadingSubject.asObservable();
|
|
21
|
+
|
|
22
|
+
async handleManualSync(): Promise<void> {
|
|
23
|
+
this.isLoadingSubject.next(true);
|
|
24
|
+
this.syncStatusSubject.next({
|
|
25
|
+
...this.syncStatusSubject.value,
|
|
26
|
+
lastSync: new Date(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Simulate sync process
|
|
31
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
32
|
+
this.syncStatusSubject.next({
|
|
33
|
+
...this.syncStatusSubject.value,
|
|
34
|
+
lastSync: new Date(),
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Sync error:', error);
|
|
38
|
+
} finally {
|
|
39
|
+
this.isLoadingSubject.next(false);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async handleEnableCloudSync(): Promise<void> {
|
|
44
|
+
this.isLoadingSubject.next(true);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Simulate enabling cloud sync
|
|
48
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
49
|
+
this.syncStatusSubject.next({
|
|
50
|
+
enabled: true,
|
|
51
|
+
isOnline: true,
|
|
52
|
+
lastSync: new Date(),
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Enable cloud sync error:', error);
|
|
56
|
+
} finally {
|
|
57
|
+
this.isLoadingSubject.next(false);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async handleDisableCloudSync(): Promise<void> {
|
|
62
|
+
this.isLoadingSubject.next(true);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Simulate disabling cloud sync
|
|
66
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
67
|
+
this.syncStatusSubject.next({
|
|
68
|
+
enabled: false,
|
|
69
|
+
isOnline: false,
|
|
70
|
+
lastSync: this.syncStatusSubject.value.lastSync,
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Disable cloud sync error:', error);
|
|
74
|
+
} finally {
|
|
75
|
+
this.isLoadingSubject.next(false);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async handleSyncTables(): Promise<void> {
|
|
80
|
+
this.isLoadingSubject.next(true);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// Simulate table sync
|
|
84
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
85
|
+
this.syncStatusSubject.next({
|
|
86
|
+
...this.syncStatusSubject.value,
|
|
87
|
+
lastSync: new Date(),
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Table sync error:', error);
|
|
91
|
+
} finally {
|
|
92
|
+
this.isLoadingSubject.next(false);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { Database } from 'idb-orm';
|
|
3
|
+
|
|
4
|
+
import { PostEntity } from '../entities/post.entity';
|
|
5
|
+
import { PostTagEntity } from '../entities/post-tag.entity';
|
|
6
|
+
import { ProfileEntity } from '../entities/profile.entity';
|
|
7
|
+
import { TagEntity } from '../entities/tag.entity';
|
|
8
|
+
import { UserEntity } from '../entities/user.entity';
|
|
9
|
+
|
|
10
|
+
@Injectable({
|
|
11
|
+
providedIn: 'root',
|
|
12
|
+
})
|
|
13
|
+
export class DatabaseService {
|
|
14
|
+
public db: Database;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
this.db = Database.createDatabase({
|
|
18
|
+
name: 'DexieORMDemo',
|
|
19
|
+
version: 6,
|
|
20
|
+
entities: [UserEntity, PostEntity, ProfileEntity, TagEntity, PostTagEntity],
|
|
21
|
+
config: {
|
|
22
|
+
migrations: [],
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { BehaviorSubject } from 'rxjs';
|
|
3
|
+
|
|
4
|
+
import { ProfileEntity } from '../entities/profile.entity';
|
|
5
|
+
import { UserEntity } from '../entities/user.entity';
|
|
6
|
+
import { DatabaseService } from './database.service';
|
|
7
|
+
|
|
8
|
+
@Injectable({
|
|
9
|
+
providedIn: 'root',
|
|
10
|
+
})
|
|
11
|
+
export class LiveQueryService {
|
|
12
|
+
private _usersWithProfilesSubject =
|
|
13
|
+
new BehaviorSubject<(UserEntity & { profile?: ProfileEntity })[]>([]);
|
|
14
|
+
private allUsersSubject = new BehaviorSubject<UserEntity[]>([]);
|
|
15
|
+
private userStatsSubject = new BehaviorSubject<{
|
|
16
|
+
totalUsers: number;
|
|
17
|
+
adultUsers: number;
|
|
18
|
+
firstUser: string;
|
|
19
|
+
}>({ totalUsers: 0, adultUsers: 0, firstUser: '' });
|
|
20
|
+
|
|
21
|
+
public usersWithProfiles$ = this._usersWithProfilesSubject.asObservable();
|
|
22
|
+
public allUsers$ = this.allUsersSubject.asObservable();
|
|
23
|
+
public userStats$ = this.userStatsSubject.asObservable();
|
|
24
|
+
|
|
25
|
+
constructor(private _dbService: DatabaseService) {
|
|
26
|
+
this.loadData();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private async loadData(): Promise<void> {
|
|
30
|
+
try {
|
|
31
|
+
// Load users with profiles
|
|
32
|
+
const users = await this._dbService.db.getRepository(UserEntity).toArray();
|
|
33
|
+
const profiles = await this._dbService.
|
|
34
|
+
db.getRepository(ProfileEntity).toArray();
|
|
35
|
+
|
|
36
|
+
const usersWithProfiles = users.map(user => {
|
|
37
|
+
const profile = profiles.find(p => p.userId === user.id);
|
|
38
|
+
|
|
39
|
+
return Object.assign(user, { profile });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this._usersWithProfilesSubject.next(usersWithProfiles);
|
|
43
|
+
this.allUsersSubject.next(users);
|
|
44
|
+
|
|
45
|
+
// Calculate stats
|
|
46
|
+
const totalUsers = users.length;
|
|
47
|
+
const adultUsers = users.filter(u => u.age >= 18).length;
|
|
48
|
+
const firstUser = users[0]?.name || '';
|
|
49
|
+
|
|
50
|
+
this.userStatsSubject.next({
|
|
51
|
+
totalUsers,
|
|
52
|
+
adultUsers,
|
|
53
|
+
firstUser,
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('Error loading live query data:', error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
refresh(): void {
|
|
61
|
+
this.loadData();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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 { DatabaseService } from './database.service';
|
|
7
|
+
|
|
8
|
+
@Injectable({
|
|
9
|
+
providedIn: 'root',
|
|
10
|
+
})
|
|
11
|
+
export class PostsLiveQueryService {
|
|
12
|
+
private allPostsSubject = new BehaviorSubject<PostEntity[]>([]);
|
|
13
|
+
private topPostsSubject = new BehaviorSubject<PostEntity[]>([]);
|
|
14
|
+
private publishedPostsSubject = new BehaviorSubject<PostEntity[]>([]);
|
|
15
|
+
private postStatsSubject = new BehaviorSubject<{
|
|
16
|
+
total: number;
|
|
17
|
+
published: number;
|
|
18
|
+
totalLikes: number;
|
|
19
|
+
}>({ total: 0, published: 0, totalLikes: 0 });
|
|
20
|
+
|
|
21
|
+
public allPosts$ = this.allPostsSubject.asObservable();
|
|
22
|
+
public topPosts$ = this.topPostsSubject.asObservable();
|
|
23
|
+
public publishedPosts$ = this.publishedPostsSubject.asObservable();
|
|
24
|
+
public postStats$ = this.postStatsSubject.asObservable();
|
|
25
|
+
|
|
26
|
+
constructor(private _dbService: DatabaseService) {
|
|
27
|
+
this.loadData();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async loadData(): Promise<void> {
|
|
31
|
+
try {
|
|
32
|
+
const allPosts = await this._dbService.db.getRepository(PostEntity).toArray();
|
|
33
|
+
const topPosts = await this._dbService.db.getRepository(PostEntity)
|
|
34
|
+
.orderBy('likes')
|
|
35
|
+
.reverse()
|
|
36
|
+
.limit(3)
|
|
37
|
+
.toArray();
|
|
38
|
+
const publishedPosts = await this._dbService.db.getRepository(PostEntity)
|
|
39
|
+
.where('published').equals(1)
|
|
40
|
+
.toArray();
|
|
41
|
+
|
|
42
|
+
this.allPostsSubject.next(allPosts);
|
|
43
|
+
this.topPostsSubject.next(topPosts);
|
|
44
|
+
this.publishedPostsSubject.next(publishedPosts);
|
|
45
|
+
|
|
46
|
+
// Calculate stats
|
|
47
|
+
const totalPosts = allPosts.length;
|
|
48
|
+
const published = publishedPosts.length;
|
|
49
|
+
const totalLikes = allPosts.reduce((sum, post) => sum + (post.likes || 0), 0);
|
|
50
|
+
|
|
51
|
+
this.postStatsSubject.next({
|
|
52
|
+
total: totalPosts,
|
|
53
|
+
published,
|
|
54
|
+
totalLikes,
|
|
55
|
+
});
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('Error loading posts live query data:', error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async addSamplePost(): Promise<void> {
|
|
62
|
+
try {
|
|
63
|
+
const post = newEntity(PostEntity, {
|
|
64
|
+
title: `Sample Post ${Date.now()}`,
|
|
65
|
+
content: 'This is a sample post created by the demo app.',
|
|
66
|
+
published: Math.random() > 0.5,
|
|
67
|
+
likes: Math.floor(Math.random() * 100),
|
|
68
|
+
createdAt: Date.now(),
|
|
69
|
+
authorId: 1, // Assuming user with ID 1 exists
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await this._dbService.db.saveWithRelations({
|
|
73
|
+
entity: post,
|
|
74
|
+
entityClass: PostEntity,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.loadData();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error('Error adding sample post:', error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
refresh(): void {
|
|
84
|
+
this.loadData();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { BehaviorSubject } from 'rxjs';
|
|
3
|
+
|
|
4
|
+
import { PostEntity } from '../entities/post.entity';
|
|
5
|
+
import { ProfileEntity } from '../entities/profile.entity';
|
|
6
|
+
import { UserEntity } from '../entities/user.entity';
|
|
7
|
+
import { DatabaseService } from './database.service';
|
|
8
|
+
|
|
9
|
+
@Injectable({
|
|
10
|
+
providedIn: 'root',
|
|
11
|
+
})
|
|
12
|
+
export class TypeScriptDemoService {
|
|
13
|
+
private usersSubject = new BehaviorSubject<UserEntity[]>([]);
|
|
14
|
+
private postsSubject = new BehaviorSubject<PostEntity[]>([]);
|
|
15
|
+
private profilesSubject = new BehaviorSubject<ProfileEntity[]>([]);
|
|
16
|
+
|
|
17
|
+
public users$ = this.usersSubject.asObservable();
|
|
18
|
+
public posts$ = this.postsSubject.asObservable();
|
|
19
|
+
public profiles$ = this.profilesSubject.asObservable();
|
|
20
|
+
|
|
21
|
+
constructor(private _dbService: DatabaseService) {
|
|
22
|
+
this.loadData();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private async loadData(): Promise<void> {
|
|
26
|
+
try {
|
|
27
|
+
const [users, posts, profiles] = await Promise.all([
|
|
28
|
+
this._dbService.db.getRepository(UserEntity).toArray(),
|
|
29
|
+
this._dbService.db.getRepository(PostEntity).toArray(),
|
|
30
|
+
this._dbService.db.getRepository(ProfileEntity).toArray(),
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
this.usersSubject.next(users);
|
|
34
|
+
this.postsSubject.next(posts);
|
|
35
|
+
this.profilesSubject.next(profiles);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Error loading TypeScript demo data:', error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onUserClick(user: UserEntity): void {
|
|
42
|
+
console.log('User clicked:', user);
|
|
43
|
+
alert(`User clicked: ${user.name} (${user.email})`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onPostClick(post: PostEntity): void {
|
|
47
|
+
console.log('Post clicked:', post);
|
|
48
|
+
alert(`Post clicked: ${post.title} - ${post.published ? 'Published' : 'Draft'}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onProfileClick(profile: ProfileEntity): void {
|
|
52
|
+
console.log('Profile clicked:', profile);
|
|
53
|
+
alert(`Profile clicked: ${profile.bio}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
refresh(): void {
|
|
57
|
+
this.loadData();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* You can add global styles to this file, and also import other style files */
|
|
2
|
+
|
|
3
|
+
/* Reset and base styles */
|
|
4
|
+
* {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
html, body {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
overflow-x: hidden;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
font-family: Roboto, "Helvetica Neue", sans-serif;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#root {
|
|
21
|
+
width: 100%;
|
|
22
|
+
min-height: 100vh;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Angular Material overrides */
|
|
26
|
+
.mat-mdc-card {
|
|
27
|
+
margin-bottom: 16px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.mat-mdc-button {
|
|
31
|
+
text-transform: none !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Custom scrollbar */
|
|
35
|
+
::-webkit-scrollbar {
|
|
36
|
+
width: 8px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
::-webkit-scrollbar-track {
|
|
40
|
+
background: #f1f1f1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
::-webkit-scrollbar-thumb {
|
|
44
|
+
background: #c1c1c1;
|
|
45
|
+
border-radius: 4px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
::-webkit-scrollbar-thumb:hover {
|
|
49
|
+
background: #a8a8a8;
|
|
50
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compileOnSave": false,
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": "./",
|
|
5
|
+
"outDir": "./dist/out-tsc",
|
|
6
|
+
"forceConsistentCasingInFileNames": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noImplicitOverride": true,
|
|
9
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
10
|
+
"noImplicitReturns": true,
|
|
11
|
+
"noFallthroughCasesInSwitch": true,
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"declaration": false,
|
|
14
|
+
"downlevelIteration": true,
|
|
15
|
+
"experimentalDecorators": true,
|
|
16
|
+
"moduleResolution": "node",
|
|
17
|
+
"importHelpers": true,
|
|
18
|
+
"target": "ES2022",
|
|
19
|
+
"module": "ES2022",
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
"useDefineForClassFields": false,
|
|
22
|
+
"allowSyntheticDefaultImports": true,
|
|
23
|
+
"lib": [
|
|
24
|
+
"ES2022",
|
|
25
|
+
"dom"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"angularCompilerOptions": {
|
|
29
|
+
"enableI18nLegacyMessageIdFormat": false,
|
|
30
|
+
"strictInjectionParameters": true,
|
|
31
|
+
"strictInputAccessModifiers": true,
|
|
32
|
+
"strictTemplates": true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import Dexie, { type Table } from 'dexie';
|
|
2
|
+
import { BaseEntity } from './services/BaseEntity';
|
|
3
|
+
import type { AggregationOptions, AggregationResult, CloudSyncConfig, DatabaseConfig, EntityConstructor, Migration } from './types';
|
|
4
|
+
export declare class Database extends Dexie {
|
|
5
|
+
private entitySchemas;
|
|
6
|
+
private migrationManager;
|
|
7
|
+
private relationLoader;
|
|
8
|
+
private aggregationService;
|
|
9
|
+
private cloudSyncService;
|
|
10
|
+
migrationMetadata: Table<{
|
|
11
|
+
key: string;
|
|
12
|
+
value: string | number;
|
|
13
|
+
updatedAt: number;
|
|
14
|
+
}>;
|
|
15
|
+
constructor(config: DatabaseConfig);
|
|
16
|
+
/**
|
|
17
|
+
* Get repository for entity (TypeORM style)
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const users = db.getRepository(User);
|
|
21
|
+
* const id = await users.add({ name: 'Ann' } as User);
|
|
22
|
+
*/
|
|
23
|
+
getRepository<T extends BaseEntity, TKey extends Extract<NonNullable<T['id']>, string | number> = Extract<NonNullable<T['id']>, string | number>>(entityClass: EntityConstructor<T>): Table<T, TKey>;
|
|
24
|
+
/**
|
|
25
|
+
* Perform aggregation on entity data
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const result = await db.aggregate({
|
|
29
|
+
* entityClass: Post,
|
|
30
|
+
* options: { where: { category: 'tech' } },
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
aggregate<T extends BaseEntity>(params: {
|
|
34
|
+
entityClass: EntityConstructor<T>;
|
|
35
|
+
options: AggregationOptions<T>;
|
|
36
|
+
}): Promise<AggregationResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Create database with entity registration
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const db = Database.createDatabase({
|
|
42
|
+
* name: 'app-db',
|
|
43
|
+
* version: 1,
|
|
44
|
+
* entities: [User, Post],
|
|
45
|
+
* });
|
|
46
|
+
*/
|
|
47
|
+
static createDatabase(params: {
|
|
48
|
+
name: string;
|
|
49
|
+
version: number;
|
|
50
|
+
entities: EntityConstructor[];
|
|
51
|
+
config?: Partial<DatabaseConfig>;
|
|
52
|
+
}): Database;
|
|
53
|
+
/**
|
|
54
|
+
* Clear all data from database
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* await db.clearAllData();
|
|
58
|
+
*/
|
|
59
|
+
clearAllData(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Reset database when schema changes
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* await db.resetDatabase();
|
|
65
|
+
*/
|
|
66
|
+
resetDatabase(): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Check if schema has changed and reset if needed
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const changed = await db.checkSchemaChanges();
|
|
72
|
+
*/
|
|
73
|
+
checkSchemaChanges(): Promise<boolean>;
|
|
74
|
+
/**
|
|
75
|
+
* Manually perform selective reset for changed tables only
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* await db.performSelectiveReset();
|
|
79
|
+
*/
|
|
80
|
+
performSelectiveReset(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Run migrations for schema changes
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* await db.runMigrations(migrations);
|
|
86
|
+
*/
|
|
87
|
+
runMigrations(migrations: Migration[]): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Get typed table for entity
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* const posts = db.getTypedTable(Post);
|
|
93
|
+
*/
|
|
94
|
+
getTypedTable<T extends BaseEntity, TKey extends Extract<NonNullable<T['id']>, string | number> = Extract<NonNullable<T['id']>, string | number>>(entityClass: EntityConstructor<T>): Table<T, TKey>;
|
|
95
|
+
/**
|
|
96
|
+
* Get table with proper typing for specific entity
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* const users = db.getTableForEntity(User);
|
|
100
|
+
*/
|
|
101
|
+
getTableForEntity<T extends BaseEntity, TKey extends Extract<NonNullable<T['id']>, string | number> = Extract<NonNullable<T['id']>, string | number>>(entityClass: EntityConstructor<T>): Table<T, TKey>;
|
|
102
|
+
/**
|
|
103
|
+
* Get all entities
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* const all = db.getEntities();
|
|
107
|
+
*/
|
|
108
|
+
getEntities(): EntityConstructor[];
|
|
109
|
+
/**
|
|
110
|
+
* Get entity by table name
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* const UserClass = db.getEntity('users');
|
|
114
|
+
*/
|
|
115
|
+
getEntity(tableName: string): EntityConstructor | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Load relations for an entity
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* const userWithRelations = await db.loadRelations({
|
|
121
|
+
* entity: user,
|
|
122
|
+
* entityClass: User,
|
|
123
|
+
* relationNames: ['posts'],
|
|
124
|
+
* });
|
|
125
|
+
*/
|
|
126
|
+
loadRelations<T extends BaseEntity>(params: {
|
|
127
|
+
entity: T;
|
|
128
|
+
entityClass: EntityConstructor<T>;
|
|
129
|
+
relationNames?: string[];
|
|
130
|
+
}): Promise<T>;
|
|
131
|
+
/**
|
|
132
|
+
* Load a specific relation by name
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* const posts = await db.loadRelationByName({
|
|
136
|
+
* entity: { id: userId },
|
|
137
|
+
* entityClass: User,
|
|
138
|
+
* relationName: 'posts',
|
|
139
|
+
* });
|
|
140
|
+
*/
|
|
141
|
+
loadRelationByName<T extends BaseEntity, K extends keyof T>(params: {
|
|
142
|
+
entity: T | {
|
|
143
|
+
id: string | number;
|
|
144
|
+
};
|
|
145
|
+
entityClass: EntityConstructor<T>;
|
|
146
|
+
relationName: K;
|
|
147
|
+
}): Promise<T[K]>;
|
|
148
|
+
/**
|
|
149
|
+
* Save entity with relations
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* const saved = await db.saveWithRelations({
|
|
153
|
+
* entity: user,
|
|
154
|
+
* entityClass: User,
|
|
155
|
+
* });
|
|
156
|
+
*/
|
|
157
|
+
saveWithRelations<T extends BaseEntity>(params: {
|
|
158
|
+
entity: T;
|
|
159
|
+
entityClass: EntityConstructor<T>;
|
|
160
|
+
}): Promise<T>;
|
|
161
|
+
/**
|
|
162
|
+
* Delete entity with cascade handling for relations
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* await db.deleteWithRelations({
|
|
166
|
+
* entity: user,
|
|
167
|
+
* entityClass: User,
|
|
168
|
+
* });
|
|
169
|
+
*/
|
|
170
|
+
deleteWithRelations<T extends BaseEntity>(params: {
|
|
171
|
+
entity: T;
|
|
172
|
+
entityClass: EntityConstructor<T>;
|
|
173
|
+
}): Promise<void>;
|
|
174
|
+
/**
|
|
175
|
+
* Manual sync with cloud
|
|
176
|
+
*/
|
|
177
|
+
sync(): Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Get sync status
|
|
180
|
+
*/
|
|
181
|
+
getSyncStatus(): {
|
|
182
|
+
enabled: boolean;
|
|
183
|
+
lastSync?: Date;
|
|
184
|
+
isOnline?: boolean;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Enable cloud sync (if not already enabled)
|
|
188
|
+
*/
|
|
189
|
+
enableCloudSync(config: CloudSyncConfig): Promise<void>;
|
|
190
|
+
/**
|
|
191
|
+
* Disable cloud sync
|
|
192
|
+
*/
|
|
193
|
+
disableCloudSync(): void;
|
|
194
|
+
/**
|
|
195
|
+
* Check if cloud sync is enabled
|
|
196
|
+
*/
|
|
197
|
+
isCloudSyncEnabled(): boolean;
|
|
198
|
+
/**
|
|
199
|
+
* Get cloud sync configuration
|
|
200
|
+
*/
|
|
201
|
+
getCloudSyncConfig(): CloudSyncConfig | undefined;
|
|
202
|
+
/**
|
|
203
|
+
* Force sync specific tables
|
|
204
|
+
*/
|
|
205
|
+
syncTables(tableNames: string[]): Promise<void>;
|
|
206
|
+
}
|