@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,122 @@
|
|
|
1
|
+
import ArticleIcon from '@mui/icons-material/Article';
|
|
2
|
+
import BarChartIcon from '@mui/icons-material/BarChart';
|
|
3
|
+
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
4
|
+
import FavoriteIcon from '@mui/icons-material/Favorite';
|
|
5
|
+
import PeopleIcon from '@mui/icons-material/People';
|
|
6
|
+
import PersonIcon from '@mui/icons-material/Person';
|
|
7
|
+
import { Box, Paper, Stack, Typography } from '@mui/material';
|
|
8
|
+
|
|
9
|
+
import { useLiveQueryDemo } from '../hooks/useLiveQueryDemo';
|
|
10
|
+
|
|
11
|
+
// Component demonstrating useLiveQuery with your Dexie ORM library
|
|
12
|
+
export function LiveQueryDemo() {
|
|
13
|
+
const {
|
|
14
|
+
allUsers,
|
|
15
|
+
adultUsers,
|
|
16
|
+
userCount,
|
|
17
|
+
firstUser,
|
|
18
|
+
usersWithProfiles,
|
|
19
|
+
allPosts,
|
|
20
|
+
allProfiles,
|
|
21
|
+
} = useLiveQueryDemo();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Paper variant="outlined" sx={{ p: 3, my: 3 }}>
|
|
25
|
+
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr 1fr' }, gridTemplateRows: { xs: 'auto', md: '1fr' }, gap: 2, alignItems: 'stretch' }}>
|
|
26
|
+
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
|
27
|
+
<Paper variant="outlined" sx={{ p: 2, flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
28
|
+
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
29
|
+
<BarChartIcon color="primary" />
|
|
30
|
+
Statistics (number | undefined)
|
|
31
|
+
</Typography>
|
|
32
|
+
<Stack spacing={0.5}>
|
|
33
|
+
<Typography><strong>Total users:</strong> {userCount ?? 'Loading...'}</Typography>
|
|
34
|
+
<Typography><strong>Adult users:</strong> {adultUsers?.length ?? 'Loading...'}</Typography>
|
|
35
|
+
<Typography><strong>First user:</strong> {firstUser?.name ?? 'None'}</Typography>
|
|
36
|
+
<Typography><strong>All users (live):</strong> {allUsers?.length ?? 'Loading...'}</Typography>
|
|
37
|
+
<Typography><strong>Posts:</strong> {allPosts?.length ?? 'Loading...'}</Typography>
|
|
38
|
+
<Typography><strong>Profiles:</strong> {allProfiles?.length ?? 'Loading...'}</Typography>
|
|
39
|
+
</Stack>
|
|
40
|
+
</Paper>
|
|
41
|
+
</Box>
|
|
42
|
+
|
|
43
|
+
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
|
44
|
+
<Paper variant="outlined" sx={{ p: 2, flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
45
|
+
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
46
|
+
<PeopleIcon color="primary" />
|
|
47
|
+
Users with profiles (UserEntity with profile)
|
|
48
|
+
</Typography>
|
|
49
|
+
{usersWithProfiles && usersWithProfiles.length > 0 && (
|
|
50
|
+
<Stack spacing={1} sx={{ maxHeight: 200, overflowY: 'auto' }}>
|
|
51
|
+
{usersWithProfiles.map((user) => (
|
|
52
|
+
<Paper key={user.id} variant="outlined" sx={{ p: 1.5 }}>
|
|
53
|
+
<Typography fontWeight={600}>{user.name}</Typography>
|
|
54
|
+
<Typography color="text.secondary" variant="body2">
|
|
55
|
+
{user.email}
|
|
56
|
+
</Typography>
|
|
57
|
+
{user.profile && (
|
|
58
|
+
<Typography color="text.secondary" variant="body2">
|
|
59
|
+
{user.profile.bio}
|
|
60
|
+
</Typography>
|
|
61
|
+
)}
|
|
62
|
+
</Paper>
|
|
63
|
+
))}
|
|
64
|
+
</Stack>
|
|
65
|
+
)}
|
|
66
|
+
</Paper>
|
|
67
|
+
</Box>
|
|
68
|
+
|
|
69
|
+
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
|
70
|
+
<Paper variant="outlined" sx={{ p: 2, flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
71
|
+
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
72
|
+
<PersonIcon color="primary" />
|
|
73
|
+
All users (live)
|
|
74
|
+
</Typography>
|
|
75
|
+
{allUsers && allUsers.length > 0 && (
|
|
76
|
+
<Stack spacing={1} sx={{ maxHeight: 200, overflowY: 'auto' }}>
|
|
77
|
+
{allUsers.map((u) => (
|
|
78
|
+
<Typography key={u.id} variant="body2">
|
|
79
|
+
<strong>{u.name}</strong> — {u.email}
|
|
80
|
+
</Typography>
|
|
81
|
+
))}
|
|
82
|
+
</Stack>
|
|
83
|
+
)}
|
|
84
|
+
</Paper>
|
|
85
|
+
</Box>
|
|
86
|
+
</Box>
|
|
87
|
+
|
|
88
|
+
<Box mt={3}>
|
|
89
|
+
<Typography variant="h6" gutterBottom>
|
|
90
|
+
<ArticleIcon sx={{ mr: 1, verticalAlign: 'middle' }} /> Posts (PostEntity[])
|
|
91
|
+
</Typography>
|
|
92
|
+
{allPosts && allPosts.length > 0 && (
|
|
93
|
+
<Paper variant="outlined" sx={{ p: 2, maxHeight: 180, overflowY: 'auto' }}>
|
|
94
|
+
<Stack spacing={1}>
|
|
95
|
+
{allPosts.map((post) => (
|
|
96
|
+
<Box key={post.id} sx={{ pb: 1, borderBottom: '1px solid #eee' }}>
|
|
97
|
+
<Typography fontWeight={600}>{post.title}</Typography>
|
|
98
|
+
<Typography color="text.secondary" variant="body2" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
99
|
+
{post.published ? <CheckCircleIcon fontSize="small" color="success" /> : <ArticleIcon fontSize="small" />}
|
|
100
|
+
<FavoriteIcon fontSize="small" color="error" /> {post.likes}
|
|
101
|
+
</Typography>
|
|
102
|
+
</Box>
|
|
103
|
+
))}
|
|
104
|
+
</Stack>
|
|
105
|
+
</Paper>
|
|
106
|
+
)}
|
|
107
|
+
</Box>
|
|
108
|
+
|
|
109
|
+
<Paper variant="outlined" sx={{ mt: 2, p: 2, bgcolor: '#e8f5e8' }}>
|
|
110
|
+
<Typography>
|
|
111
|
+
<strong>Typing test:</strong> All variables are fully typed with TypeScript!
|
|
112
|
+
</Typography>
|
|
113
|
+
<Typography>
|
|
114
|
+
<strong>Reactivity:</strong> Data updates automatically when you add/remove records!
|
|
115
|
+
</Typography>
|
|
116
|
+
<Typography>
|
|
117
|
+
<strong>Compatibility:</strong> Your library works perfectly with useLiveQuery!
|
|
118
|
+
</Typography>
|
|
119
|
+
</Paper>
|
|
120
|
+
</Paper>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import AutorenewIcon from '@mui/icons-material/Autorenew';
|
|
2
|
+
import AssessmentIcon from '@mui/icons-material/Assessment';
|
|
3
|
+
import BarChartIcon from '@mui/icons-material/BarChart';
|
|
4
|
+
import CloudSyncIcon from '@mui/icons-material/CloudSync';
|
|
5
|
+
import CodeIcon from '@mui/icons-material/Code';
|
|
6
|
+
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
|
7
|
+
import PeopleIcon from '@mui/icons-material/People';
|
|
8
|
+
import ScienceIcon from '@mui/icons-material/Science';
|
|
9
|
+
import StorageIcon from '@mui/icons-material/Storage';
|
|
10
|
+
import { Box, Button, Paper, Typography } from '@mui/material';
|
|
11
|
+
|
|
12
|
+
export function MainInfo() {
|
|
13
|
+
return (
|
|
14
|
+
<Paper variant="outlined" sx={{ p: 2, mb: 2 }}>
|
|
15
|
+
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
16
|
+
<AutorenewIcon color="primary" /> Dexie ORM for IndexedDB — Overview
|
|
17
|
+
</Typography>
|
|
18
|
+
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
19
|
+
TypeScript ORM built on the latest Dexie 4 with first-class type safety and Zod runtime validation.
|
|
20
|
+
</Typography>
|
|
21
|
+
<Box sx={{
|
|
22
|
+
display: 'grid',
|
|
23
|
+
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
|
|
24
|
+
gap: 1.25,
|
|
25
|
+
}}>
|
|
26
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
27
|
+
<CodeIcon color="primary" fontSize="small" />
|
|
28
|
+
<Typography variant="body2"><strong>Entity config</strong> via defineEntity()</Typography>
|
|
29
|
+
</Box>
|
|
30
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
31
|
+
<StorageIcon color="primary" fontSize="small" />
|
|
32
|
+
<Typography variant="body2"><strong>Automatic schema</strong> with PK, indexes, unique</Typography>
|
|
33
|
+
</Box>
|
|
34
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
35
|
+
<PeopleIcon color="primary" fontSize="small" />
|
|
36
|
+
<Typography variant="body2"><strong>Relations</strong> with helpers</Typography>
|
|
37
|
+
</Box>
|
|
38
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
39
|
+
<ScienceIcon color="primary" fontSize="small" />
|
|
40
|
+
<Typography variant="body2"><strong>Zod validation</strong> at runtime</Typography>
|
|
41
|
+
</Box>
|
|
42
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
43
|
+
<AssessmentIcon color="primary" fontSize="small" />
|
|
44
|
+
<Typography variant="body2"><strong>Aggregations</strong> (count, avg, sum)</Typography>
|
|
45
|
+
</Box>
|
|
46
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
47
|
+
<BarChartIcon color="primary" fontSize="small" />
|
|
48
|
+
<Typography variant="body2"><strong>Live queries</strong> with useLiveQuery()</Typography>
|
|
49
|
+
</Box>
|
|
50
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
51
|
+
<CodeIcon color="primary" fontSize="small" />
|
|
52
|
+
<Typography variant="body2"><strong>TypeScript</strong> full type safety</Typography>
|
|
53
|
+
</Box>
|
|
54
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
55
|
+
<CloudSyncIcon color="primary" fontSize="small" />
|
|
56
|
+
<Typography variant="body2"><strong>Cloud sync</strong> with Dexie Cloud</Typography>
|
|
57
|
+
</Box>
|
|
58
|
+
</Box>
|
|
59
|
+
|
|
60
|
+
<Box sx={{ mt: 2, pt: 2, borderTop: '1px solid', borderColor: 'divider' }}>
|
|
61
|
+
<Button
|
|
62
|
+
variant="outlined"
|
|
63
|
+
size="small"
|
|
64
|
+
startIcon={<OpenInNewIcon />}
|
|
65
|
+
href="https://github.com/radommaciej/indexed-db-orm/blob/main/README.md"
|
|
66
|
+
target="_blank"
|
|
67
|
+
rel="noopener noreferrer"
|
|
68
|
+
sx={{ textTransform: 'none' }}
|
|
69
|
+
>
|
|
70
|
+
View full documentation
|
|
71
|
+
</Button>
|
|
72
|
+
</Box>
|
|
73
|
+
</Paper>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import BarChartIcon from '@mui/icons-material/BarChart';
|
|
2
|
+
import DataObjectIcon from '@mui/icons-material/DataObject';
|
|
3
|
+
import LightbulbIcon from '@mui/icons-material/Lightbulb';
|
|
4
|
+
import ListIcon from '@mui/icons-material/List';
|
|
5
|
+
import TrendingUpIcon from '@mui/icons-material/TrendingUp';
|
|
6
|
+
import { Box, Button, Paper, Typography } from '@mui/material';
|
|
7
|
+
|
|
8
|
+
import { usePostsLiveQueryDemo } from '../hooks/usePostsLiveQueryDemo';
|
|
9
|
+
|
|
10
|
+
// Component demonstrating advanced use of useLiveQuery with posts
|
|
11
|
+
export function PostsLiveQueryDemo() {
|
|
12
|
+
const {
|
|
13
|
+
allPosts,
|
|
14
|
+
publishedPosts,
|
|
15
|
+
topPosts,
|
|
16
|
+
postStats,
|
|
17
|
+
addSamplePost,
|
|
18
|
+
} = usePostsLiveQueryDemo();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Paper
|
|
22
|
+
sx={{
|
|
23
|
+
border: '2px solid #9c27b0',
|
|
24
|
+
p: 3,
|
|
25
|
+
my: 3,
|
|
26
|
+
borderRadius: 2,
|
|
27
|
+
backgroundColor: '#faf5ff',
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
<Typography variant="h4" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
31
|
+
<DataObjectIcon color="primary" />
|
|
32
|
+
Posts useLiveQuery Demo
|
|
33
|
+
</Typography>
|
|
34
|
+
|
|
35
|
+
<Box sx={{ mb: 2 }}>
|
|
36
|
+
<Button
|
|
37
|
+
variant="contained"
|
|
38
|
+
onClick={addSamplePost}
|
|
39
|
+
sx={{
|
|
40
|
+
backgroundColor: '#9c27b0',
|
|
41
|
+
'&:hover': { backgroundColor: '#7b1fa2' },
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
Add sample post
|
|
45
|
+
</Button>
|
|
46
|
+
</Box>
|
|
47
|
+
|
|
48
|
+
<div
|
|
49
|
+
style={{
|
|
50
|
+
display: 'grid',
|
|
51
|
+
gridTemplateColumns: '1fr 1fr 1fr',
|
|
52
|
+
gap: '20px',
|
|
53
|
+
marginBottom: '20px',
|
|
54
|
+
alignItems: 'stretch',
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
58
|
+
<h3 style={{ display: 'flex', alignItems: 'center', gap: '8px', margin: 0 }}>
|
|
59
|
+
<BarChartIcon color="primary" fontSize="small" />
|
|
60
|
+
Statistics
|
|
61
|
+
</h3>
|
|
62
|
+
{postStats ? (
|
|
63
|
+
<div style={{ flex: 1 }}>
|
|
64
|
+
<p><strong>Total posts:</strong> {postStats.total}</p>
|
|
65
|
+
<p><strong>Published:</strong> {postStats.published}</p>
|
|
66
|
+
<p><strong>Total likes:</strong> {postStats.totalLikes}</p>
|
|
67
|
+
</div>
|
|
68
|
+
) : (
|
|
69
|
+
<p style={{ flex: 1 }}>Loading statistics...</p>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
74
|
+
<h3 style={{ display: 'flex', alignItems: 'center', gap: '8px', margin: 0 }}>
|
|
75
|
+
<TrendingUpIcon color="primary" fontSize="small" />
|
|
76
|
+
Top 3 posts
|
|
77
|
+
</h3>
|
|
78
|
+
{topPosts && topPosts.length > 0 && (
|
|
79
|
+
<div style={{ flex: 1 }}>
|
|
80
|
+
{topPosts.map((post, index) => (
|
|
81
|
+
<div
|
|
82
|
+
key={post.id}
|
|
83
|
+
style={{
|
|
84
|
+
padding: '8px',
|
|
85
|
+
margin: '4px 0',
|
|
86
|
+
backgroundColor: 'white',
|
|
87
|
+
borderRadius: '4px',
|
|
88
|
+
border: '1px solid #ddd',
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<strong>#{index + 1}</strong> {post.title}
|
|
92
|
+
<span style={{ color: '#9c27b0' }}> ❤️ {post.likes}</span>
|
|
93
|
+
</div>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
100
|
+
<h3 style={{ margin: 0 }}>Published posts</h3>
|
|
101
|
+
{publishedPosts && publishedPosts.length > 0 && (
|
|
102
|
+
<div style={{ maxHeight: '150px', overflowY: 'auto', flex: 1 }}>
|
|
103
|
+
{publishedPosts.map((post) => (
|
|
104
|
+
<div
|
|
105
|
+
key={post.id}
|
|
106
|
+
style={{
|
|
107
|
+
padding: '4px 0',
|
|
108
|
+
fontSize: '0.9em',
|
|
109
|
+
borderBottom: '1px solid #eee',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<strong>{post.title}</strong> ❤️ {post.likes}
|
|
113
|
+
</div>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{allPosts && allPosts.length > 0 && (
|
|
121
|
+
<div>
|
|
122
|
+
<h3 style={{ display: 'flex', alignItems: 'center', gap: '8px', margin: 0 }}>
|
|
123
|
+
<ListIcon color="primary" fontSize="small" />
|
|
124
|
+
All posts (newest first)
|
|
125
|
+
</h3>
|
|
126
|
+
<div
|
|
127
|
+
style={{
|
|
128
|
+
maxHeight: '200px',
|
|
129
|
+
overflowY: 'auto',
|
|
130
|
+
backgroundColor: 'white',
|
|
131
|
+
padding: '10px',
|
|
132
|
+
borderRadius: '4px',
|
|
133
|
+
border: '1px solid #ddd',
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
{allPosts.map((post) => (
|
|
137
|
+
<div
|
|
138
|
+
key={post.id}
|
|
139
|
+
style={{
|
|
140
|
+
padding: '8px 0',
|
|
141
|
+
borderBottom: '1px solid #eee',
|
|
142
|
+
fontSize: '0.9em',
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
<div
|
|
146
|
+
style={{
|
|
147
|
+
display: 'flex',
|
|
148
|
+
justifyContent: 'space-between',
|
|
149
|
+
alignItems: 'center',
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
<div>
|
|
153
|
+
<strong>{post.title}</strong>
|
|
154
|
+
<span
|
|
155
|
+
style={{
|
|
156
|
+
marginLeft: '10px',
|
|
157
|
+
padding: '2px 6px',
|
|
158
|
+
backgroundColor: post.published ? '#4caf50' : '#ff9800',
|
|
159
|
+
color: 'white',
|
|
160
|
+
borderRadius: '3px',
|
|
161
|
+
fontSize: '0.8em',
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
{post.published ? 'Published' : 'Draft'}
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
<div style={{ color: '#9c27b0' }}>❤️ {post.likes}</div>
|
|
168
|
+
</div>
|
|
169
|
+
<div
|
|
170
|
+
style={{ color: '#666', fontSize: '0.8em', marginTop: '2px' }}
|
|
171
|
+
>
|
|
172
|
+
{post.content?.substring(0, 100)}...
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
<Typography variant="body2" color="text.secondary" sx={{ mt: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
181
|
+
<LightbulbIcon fontSize="small" /> <strong>Note:</strong> All data updates automatically! Add a post and see how all sections refresh in real time.
|
|
182
|
+
</Typography>
|
|
183
|
+
</Paper>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
|
2
|
+
import CodeIcon from '@mui/icons-material/Code';
|
|
3
|
+
import DescriptionIcon from '@mui/icons-material/Description';
|
|
4
|
+
import PeopleIcon from '@mui/icons-material/People';
|
|
5
|
+
import { Box, Paper, Typography } from '@mui/material';
|
|
6
|
+
|
|
7
|
+
import { useTypeScriptDemo } from '../hooks/useTypeScriptDemo';
|
|
8
|
+
|
|
9
|
+
// Component demonstrating how to inspect typing in the IDE
|
|
10
|
+
export function TypeScriptDemo() {
|
|
11
|
+
const {
|
|
12
|
+
users,
|
|
13
|
+
posts,
|
|
14
|
+
profiles,
|
|
15
|
+
handleUserClick,
|
|
16
|
+
handlePostClick,
|
|
17
|
+
handleProfileClick,
|
|
18
|
+
} = useTypeScriptDemo();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Paper
|
|
22
|
+
sx={{
|
|
23
|
+
border: '2px solid #ff9800',
|
|
24
|
+
p: 3,
|
|
25
|
+
my: 3,
|
|
26
|
+
borderRadius: 2,
|
|
27
|
+
backgroundColor: '#fff8e1',
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
<Typography variant="h4" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
31
|
+
<CodeIcon color="primary" />
|
|
32
|
+
TypeScript Typing Test
|
|
33
|
+
</Typography>
|
|
34
|
+
<Typography color="text.secondary" sx={{ mb: 3 }}>
|
|
35
|
+
<strong>
|
|
36
|
+
Instructions:
|
|
37
|
+
</strong>
|
|
38
|
+
Hover over the variables below in your IDE and
|
|
39
|
+
verify TypeScript shows full types! Click items to
|
|
40
|
+
see auto-complete in the console.
|
|
41
|
+
</Typography>
|
|
42
|
+
|
|
43
|
+
<Box
|
|
44
|
+
sx={{
|
|
45
|
+
display: 'grid',
|
|
46
|
+
gridTemplateColumns: { xs: '1fr', md: '1fr 1fr 1fr' },
|
|
47
|
+
gridTemplateRows: { xs: 'auto', md: '1fr' },
|
|
48
|
+
gap: 3,
|
|
49
|
+
alignItems: 'stretch',
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
|
|
53
|
+
<Typography variant="h5" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
54
|
+
<PeopleIcon color="primary" />
|
|
55
|
+
Users (UserEntity[])
|
|
56
|
+
</Typography>
|
|
57
|
+
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
58
|
+
Typ: <code>UserEntity[] | undefined</code>
|
|
59
|
+
</Typography>
|
|
60
|
+
{users && users.length > 0 && (
|
|
61
|
+
<Paper
|
|
62
|
+
sx={{
|
|
63
|
+
maxHeight: 150,
|
|
64
|
+
overflowY: 'auto',
|
|
65
|
+
p: 1,
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
{users.map((user) => (
|
|
69
|
+
<Box
|
|
70
|
+
key={user.id}
|
|
71
|
+
onClick={() => handleUserClick(user)}
|
|
72
|
+
sx={{
|
|
73
|
+
py: 0.5,
|
|
74
|
+
borderBottom: '1px solid #eee',
|
|
75
|
+
cursor: 'pointer',
|
|
76
|
+
'&:hover': { backgroundColor: 'action.hover' },
|
|
77
|
+
}}
|
|
78
|
+
title="Click to see auto-complete in the console"
|
|
79
|
+
>
|
|
80
|
+
<Typography variant="body2">
|
|
81
|
+
<strong>{user.name}</strong> - {user.email}
|
|
82
|
+
</Typography>
|
|
83
|
+
</Box>
|
|
84
|
+
))}
|
|
85
|
+
</Paper>
|
|
86
|
+
)}
|
|
87
|
+
</Box>
|
|
88
|
+
|
|
89
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
|
|
90
|
+
<Typography variant="h5" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
91
|
+
<DescriptionIcon color="primary" />
|
|
92
|
+
Posts (PostEntity[])
|
|
93
|
+
</Typography>
|
|
94
|
+
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
95
|
+
Typ: <code>PostEntity[] | undefined</code>
|
|
96
|
+
</Typography>
|
|
97
|
+
{posts && posts.length > 0 && (
|
|
98
|
+
<Paper
|
|
99
|
+
sx={{
|
|
100
|
+
maxHeight: 150,
|
|
101
|
+
overflowY: 'auto',
|
|
102
|
+
p: 1,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{posts.map((post) => (
|
|
106
|
+
<Box
|
|
107
|
+
key={post.id}
|
|
108
|
+
onClick={() => handlePostClick(post)}
|
|
109
|
+
sx={{
|
|
110
|
+
py: 0.5,
|
|
111
|
+
borderBottom: '1px solid #eee',
|
|
112
|
+
cursor: 'pointer',
|
|
113
|
+
'&:hover': { backgroundColor: 'action.hover' },
|
|
114
|
+
}}
|
|
115
|
+
title="Click to see auto-complete in the console"
|
|
116
|
+
>
|
|
117
|
+
<Typography variant="body2">
|
|
118
|
+
<strong>{post.title}</strong> - {post.published ? 'Published' : 'Draft'}
|
|
119
|
+
</Typography>
|
|
120
|
+
</Box>
|
|
121
|
+
))}
|
|
122
|
+
</Paper>
|
|
123
|
+
)}
|
|
124
|
+
</Box>
|
|
125
|
+
|
|
126
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
|
|
127
|
+
<Typography variant="h5" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
128
|
+
<AccountCircleIcon color="primary" />
|
|
129
|
+
Profiles (ProfileEntity[])
|
|
130
|
+
</Typography>
|
|
131
|
+
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
132
|
+
Type: <code>ProfileEntity[] | undefined</code>
|
|
133
|
+
</Typography>
|
|
134
|
+
{profiles && profiles.length > 0 && (
|
|
135
|
+
<Paper
|
|
136
|
+
sx={{
|
|
137
|
+
maxHeight: 150,
|
|
138
|
+
overflowY: 'auto',
|
|
139
|
+
p: 1,
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
{profiles.map((profile) => (
|
|
143
|
+
<Box
|
|
144
|
+
key={profile.id}
|
|
145
|
+
onClick={() => handleProfileClick(profile)}
|
|
146
|
+
sx={{
|
|
147
|
+
py: 0.5,
|
|
148
|
+
borderBottom: '1px solid #eee',
|
|
149
|
+
cursor: 'pointer',
|
|
150
|
+
'&:hover': { backgroundColor: 'action.hover' },
|
|
151
|
+
}}
|
|
152
|
+
title="Click to see auto-complete in the console"
|
|
153
|
+
>
|
|
154
|
+
<Typography variant="body2">
|
|
155
|
+
<strong>User {profile.userId}</strong> -{' '}
|
|
156
|
+
{profile.bio?.substring(0, 30)}...
|
|
157
|
+
</Typography>
|
|
158
|
+
</Box>
|
|
159
|
+
))}
|
|
160
|
+
</Paper>
|
|
161
|
+
)}
|
|
162
|
+
</Box>
|
|
163
|
+
</Box>
|
|
164
|
+
|
|
165
|
+
<Paper
|
|
166
|
+
sx={{
|
|
167
|
+
mt: 2,
|
|
168
|
+
p: 2,
|
|
169
|
+
backgroundColor: '#e3f2fd',
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<Typography variant="body2" color="text.secondary">
|
|
173
|
+
<strong>IDE Test:</strong>
|
|
174
|
+
<br />
|
|
175
|
+
1. Hover over variables <code>users</code>, <code>posts</code>,{' '}
|
|
176
|
+
<code>profiles</code>
|
|
177
|
+
<br />
|
|
178
|
+
2. Verify TypeScript shows full types:
|
|
179
|
+
(UserEntity[], PostEntity[], ProfileEntity[])
|
|
180
|
+
<br />
|
|
181
|
+
3. Click items to see auto-complete inside handle* functions
|
|
182
|
+
<br />
|
|
183
|
+
4. Check that IDE suggests properties like <code>
|
|
184
|
+
user.name
|
|
185
|
+
</code>, <code>post.title</code>, <code>profile.bio</code>
|
|
186
|
+
</Typography>
|
|
187
|
+
</Paper>
|
|
188
|
+
</Paper>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Database } from 'idb-orm';
|
|
2
|
+
|
|
3
|
+
import { PostEntity } from '../entities/Post';
|
|
4
|
+
import { PostTagEntity } from '../entities/PostTag';
|
|
5
|
+
import { ProfileEntity } from '../entities/Profile';
|
|
6
|
+
import { TagEntity } from '../entities/Tag';
|
|
7
|
+
import { UserEntity } from '../entities/User';
|
|
8
|
+
import { migrations } from '../migrations';
|
|
9
|
+
|
|
10
|
+
// Create database with entity registration
|
|
11
|
+
export const db = Database.createDatabase({
|
|
12
|
+
name: 'DexieORMDemo',
|
|
13
|
+
version: 6,
|
|
14
|
+
entities: [UserEntity, PostEntity, ProfileEntity, TagEntity, PostTagEntity],
|
|
15
|
+
config: {
|
|
16
|
+
// This will be ignored when migrations are provided
|
|
17
|
+
// onSchemaChangeStrategy: 'selective',
|
|
18
|
+
migrations: migrations, // Migrations take priority over reset strategy
|
|
19
|
+
|
|
20
|
+
// Cloud sync configuration (optional)
|
|
21
|
+
// cloudSync: {
|
|
22
|
+
// databaseUrl: 'https://your-database-url.dexie.cloud', // Replace with your actual URL
|
|
23
|
+
// enableOfflineSupport: true,
|
|
24
|
+
// syncInterval: 30000, // Sync every 30 seconds (optional)
|
|
25
|
+
// tables: ['users', 'posts', 'profiles'] // Specific tables to sync
|
|
26
|
+
// (optional - syncs all if empty)
|
|
27
|
+
// }
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseEntity, defineEntity,
|
|
3
|
+
} from 'idb-orm';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
import type { TagEntity } from './Tag';
|
|
7
|
+
|
|
8
|
+
const PostSchema = z.object({
|
|
9
|
+
id: z.number().optional(),
|
|
10
|
+
title: z.string().min(1, 'Title is required'),
|
|
11
|
+
content: z.string().min(1, 'Content is required'),
|
|
12
|
+
authorId: z.number().positive('Author ID must be positive'),
|
|
13
|
+
published: z.boolean().optional(),
|
|
14
|
+
postTags: z.array(z.string()).optional(),
|
|
15
|
+
likes: z.number().min(0, 'Likes must be non-negative').optional(),
|
|
16
|
+
createdAt: z.number().optional(),
|
|
17
|
+
updatedAt: z.number().optional(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export class PostEntity extends BaseEntity<number> {
|
|
21
|
+
title!: string;
|
|
22
|
+
content!: string;
|
|
23
|
+
authorId!: number;
|
|
24
|
+
published?: boolean;
|
|
25
|
+
postTags?: string[];
|
|
26
|
+
likes?: number;
|
|
27
|
+
createdAt?: number;
|
|
28
|
+
updatedAt?: number;
|
|
29
|
+
tags?: TagEntity[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
defineEntity(PostEntity, {
|
|
33
|
+
tableName: 'posts',
|
|
34
|
+
schema: PostSchema,
|
|
35
|
+
columns: {
|
|
36
|
+
title: { required: true, indexed: true },
|
|
37
|
+
content: { required: true },
|
|
38
|
+
authorId: { required: true, indexed: true },
|
|
39
|
+
published: { default: false, indexed: true },
|
|
40
|
+
postTags: {},
|
|
41
|
+
likes: { default: 0, indexed: true },
|
|
42
|
+
createdAt: { default: () => Date.now(), indexed: true },
|
|
43
|
+
updatedAt: { default: () => Date.now() },
|
|
44
|
+
},
|
|
45
|
+
relations: {
|
|
46
|
+
tags: { type: 'many-to-many', target: 'tags', joinTable: 'post_tags' },
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseEntity, defineEntity,
|
|
3
|
+
} from 'idb-orm';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
const PostTagSchema = z.object({
|
|
7
|
+
id: z.number().optional(),
|
|
8
|
+
sourceId: z.number(),
|
|
9
|
+
targetId: z.number(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export class PostTagEntity extends BaseEntity<number> {
|
|
13
|
+
sourceId!: number;
|
|
14
|
+
targetId!: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
defineEntity(PostTagEntity, {
|
|
18
|
+
tableName: 'post_tags',
|
|
19
|
+
schema: PostTagSchema,
|
|
20
|
+
columns: {
|
|
21
|
+
sourceId: { indexed: true },
|
|
22
|
+
targetId: { indexed: true },
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
|