@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,29 @@
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
+ });
@@ -0,0 +1,46 @@
1
+ import { BaseEntity, defineEntity } from 'idb-orm';
2
+ import { z } from 'zod';
3
+
4
+ import type { TagEntity } from './Tag';
5
+
6
+ const PostSchema = z.object({
7
+ id: z.number().optional(),
8
+ title: z.string().min(1, 'Title is required'),
9
+ content: z.string().min(1, 'Content is required'),
10
+ authorId: z.number().positive('Author ID must be positive'),
11
+ published: z.boolean().optional(),
12
+ postTags: z.array(z.string()).optional(),
13
+ likes: z.number().min(0, 'Likes must be non-negative').optional(),
14
+ createdAt: z.number().optional(),
15
+ updatedAt: z.number().optional(),
16
+ });
17
+
18
+ export class PostEntity extends BaseEntity<number> {
19
+ title!: string;
20
+ content!: string;
21
+ authorId!: number;
22
+ published?: boolean;
23
+ postTags?: string[];
24
+ likes?: number;
25
+ createdAt?: number;
26
+ updatedAt?: number;
27
+ tags?: TagEntity[];
28
+ }
29
+
30
+ defineEntity(PostEntity, {
31
+ tableName: 'posts',
32
+ schema: PostSchema,
33
+ columns: {
34
+ title: { required: true, indexed: true },
35
+ content: { required: true },
36
+ authorId: { required: true, indexed: true },
37
+ published: { default: false, indexed: true },
38
+ postTags: {},
39
+ likes: { default: 0, indexed: true },
40
+ createdAt: { default: () => Date.now(), indexed: true },
41
+ updatedAt: { default: () => Date.now() },
42
+ },
43
+ relations: {
44
+ tags: { type: 'many-to-many', target: 'tags', joinTable: 'post_tags' },
45
+ },
46
+ });
@@ -0,0 +1,22 @@
1
+ import { BaseEntity, defineEntity } from 'idb-orm';
2
+ import { z } from 'zod';
3
+
4
+ const PostTagSchema = z.object({
5
+ id: z.number().optional(),
6
+ sourceId: z.number(),
7
+ targetId: z.number(),
8
+ });
9
+
10
+ export class PostTagEntity extends BaseEntity<number> {
11
+ sourceId!: number;
12
+ targetId!: number;
13
+ }
14
+
15
+ defineEntity(PostTagEntity, {
16
+ tableName: 'post_tags',
17
+ schema: PostTagSchema,
18
+ columns: {
19
+ sourceId: { indexed: true },
20
+ targetId: { indexed: true },
21
+ },
22
+ });
@@ -0,0 +1,39 @@
1
+ import { BaseEntity, defineEntity } from 'idb-orm';
2
+ import { z } from 'zod';
3
+
4
+ const ProfileSchema = z.object({
5
+ id: z.number().optional(),
6
+ userId: z.number().positive('User ID must be positive'),
7
+ bio: z.string().optional(),
8
+ avatar: z.string().url().optional(),
9
+ website: z.string().url().optional(),
10
+ location: z.string().optional(),
11
+ createdAt: z.number(),
12
+ updatedAt: z.number(),
13
+ });
14
+
15
+ export type Profile = z.infer<typeof ProfileSchema>;
16
+
17
+ export class ProfileEntity extends BaseEntity<number> {
18
+ userId!: number;
19
+ bio?: string;
20
+ avatar?: string;
21
+ website?: string;
22
+ location?: string;
23
+ createdAt!: number;
24
+ updatedAt!: number;
25
+ }
26
+
27
+ defineEntity(ProfileEntity, {
28
+ tableName: 'profiles',
29
+ schema: ProfileSchema,
30
+ columns: {
31
+ userId: { indexed: true },
32
+ bio: { indexed: true },
33
+ avatar: {},
34
+ website: {},
35
+ location: {},
36
+ createdAt: { indexed: true },
37
+ updatedAt: { indexed: true },
38
+ },
39
+ });
@@ -0,0 +1,33 @@
1
+ import { BaseEntity, defineEntity } from 'idb-orm';
2
+ import { z } from 'zod';
3
+
4
+ const TagSchema = z.object({
5
+ id: z.number().optional(),
6
+ name: z.string().min(1, 'Tag name is required'),
7
+ color: z.string().regex(/^#[0-9A-F]{6}$/i, 'Color must be a valid hex color'),
8
+ description: z.string().optional(),
9
+ createdAt: z.number(),
10
+ updatedAt: z.number(),
11
+ });
12
+
13
+ export type Tag = z.infer<typeof TagSchema>;
14
+
15
+ export class TagEntity extends BaseEntity<number> {
16
+ name!: string;
17
+ color!: string;
18
+ description?: string;
19
+ createdAt!: number;
20
+ updatedAt!: number;
21
+ }
22
+
23
+ defineEntity(TagEntity, {
24
+ tableName: 'tags',
25
+ schema: TagSchema,
26
+ columns: {
27
+ name: { required: true, unique: true, indexed: true },
28
+ color: { required: true },
29
+ description: {},
30
+ createdAt: { indexed: true },
31
+ updatedAt: { indexed: true },
32
+ },
33
+ });
@@ -0,0 +1,62 @@
1
+ import {
2
+ BaseEntity,
3
+ defineEntity,
4
+ } from 'idb-orm';
5
+ import { z } from 'zod';
6
+
7
+ import type { PostEntity } from './Post';
8
+ import type { ProfileEntity } from './Profile';
9
+
10
+ // Zod schema for validation
11
+ export const UserSchema = z.object({
12
+ id: z.number().optional(),
13
+ name: z.string().min(2, 'Name must be at least 2 characters'),
14
+ email: z.string().email('Invalid email format'),
15
+ age: z.number().min(18, 'Must be at least 18 years old'),
16
+ createdAt: z.number(),
17
+ updatedAt: z.number(),
18
+ isActive: z.boolean().default(true),
19
+ tags: z.array(z.string()).default([]),
20
+ metadata: z.record(z.string(), z.unknown()).default({}),
21
+ // Note:
22
+ // 'posts' and 'profile' relations are not in schema - they're loaded separately
23
+ });
24
+
25
+ export type User = z.infer<typeof UserSchema>;
26
+
27
+ export class UserEntity extends BaseEntity<number> {
28
+ name!: string;
29
+ email!: string;
30
+ age!: number;
31
+ createdAt!: number;
32
+ updatedAt!: number;
33
+ isActive!: boolean;
34
+ tags!: string[];
35
+ metadata!: Record<string, unknown>;
36
+ posts?: PostEntity[];
37
+ profile?: ProfileEntity;
38
+ }
39
+
40
+ defineEntity(UserEntity, {
41
+ tableName: 'users',
42
+ schema: UserSchema,
43
+ columns: {
44
+ name: { required: true, indexed: true },
45
+ email: { required: true, unique: true },
46
+ age: { indexed: true },
47
+ createdAt: { indexed: true },
48
+ updatedAt: { indexed: true },
49
+ isActive: { indexed: true },
50
+ tags: {},
51
+ metadata: { indexed: true },
52
+ },
53
+ compoundIndexes: [
54
+ { columns: ['name', 'email'], unique: false, name: 'name_email_index' },
55
+ { columns: ['age', 'isActive'], unique: false, name: 'age_active_index' },
56
+ { columns: ['email'], unique: true, name: 'unique_email_index' },
57
+ ],
58
+ relations: {
59
+ posts: { type: 'one-to-many', target: 'posts', foreignKey: 'authorId' },
60
+ profile: { type: 'one-to-one', target: 'profiles', foreignKey: 'userId' },
61
+ },
62
+ });
@@ -0,0 +1,30 @@
1
+ import { browser } from '$app/environment';
2
+ import { Database } from '../../../../src/Database';
3
+
4
+ import { PostEntity } from '../entities/Post';
5
+ import { PostTagEntity } from '../entities/PostTag';
6
+ import { ProfileEntity } from '../entities/Profile';
7
+ import { TagEntity } from '../entities/Tag';
8
+ import { UserEntity } from '../entities/User';
9
+ import { migrations } from '../migrations';
10
+
11
+ // Create database with entity registration - only in browser
12
+ export const db = browser ? Database.createDatabase({
13
+ name: 'DexieORMDemo',
14
+ version: 6,
15
+ entities: [UserEntity, PostEntity, ProfileEntity, TagEntity, PostTagEntity],
16
+ config: {
17
+ // This will be ignored when migrations are provided
18
+ // onSchemaChangeStrategy: 'selective',
19
+ migrations: migrations, // Migrations take priority over reset strategy
20
+
21
+ // Cloud sync configuration (optional)
22
+ // cloudSync: {
23
+ // databaseUrl: 'https://your-database-url.dexie.cloud', // Replace with your actual URL
24
+ // enableOfflineSupport: true,
25
+ // syncInterval: 30000, // Sync every 30 seconds (optional)
26
+ // tables: ['users', 'posts', 'profiles'] // Specific tables to sync
27
+ // (optional - syncs all if empty)
28
+ // }
29
+ },
30
+ }) : null;
@@ -0,0 +1,47 @@
1
+ import { BaseEntity } from '../../../../src/services/BaseEntity';
2
+ import { defineEntity } from '../../../../src/services/EntityRegistry';
3
+ import { z } from 'zod';
4
+
5
+ import type { TagEntity } from './Tag';
6
+
7
+ const PostSchema = z.object({
8
+ id: z.number().optional(),
9
+ title: z.string().min(1, 'Title is required'),
10
+ content: z.string().min(1, 'Content is required'),
11
+ authorId: z.number().positive('Author ID must be positive'),
12
+ published: z.boolean().optional(),
13
+ postTags: z.array(z.string()).optional(),
14
+ likes: z.number().min(0, 'Likes must be non-negative').optional(),
15
+ createdAt: z.number().optional(),
16
+ updatedAt: z.number().optional(),
17
+ });
18
+
19
+ export class PostEntity extends BaseEntity<number> {
20
+ title!: string;
21
+ content!: string;
22
+ authorId!: number;
23
+ published?: boolean;
24
+ postTags?: string[];
25
+ likes?: number;
26
+ createdAt?: number;
27
+ updatedAt?: number;
28
+ tags?: TagEntity[];
29
+ }
30
+
31
+ defineEntity(PostEntity, {
32
+ tableName: 'posts',
33
+ schema: PostSchema,
34
+ columns: {
35
+ title: { required: true, indexed: true },
36
+ content: { required: true },
37
+ authorId: { required: true, indexed: true },
38
+ published: { default: false, indexed: true },
39
+ postTags: {},
40
+ likes: { default: 0, indexed: true },
41
+ createdAt: { default: () => Date.now(), indexed: true },
42
+ updatedAt: { default: () => Date.now() },
43
+ },
44
+ relations: {
45
+ tags: { type: 'many-to-many', target: 'tags', joinTable: 'post_tags' },
46
+ },
47
+ });
@@ -0,0 +1,23 @@
1
+ import { BaseEntity } from '../../../../src/services/BaseEntity';
2
+ import { defineEntity } from '../../../../src/services/EntityRegistry';
3
+ import { z } from 'zod';
4
+
5
+ const PostTagSchema = z.object({
6
+ id: z.number().optional(),
7
+ sourceId: z.number(),
8
+ targetId: z.number(),
9
+ });
10
+
11
+ export class PostTagEntity extends BaseEntity<number> {
12
+ sourceId!: number;
13
+ targetId!: number;
14
+ }
15
+
16
+ defineEntity(PostTagEntity, {
17
+ tableName: 'post_tags',
18
+ schema: PostTagSchema,
19
+ columns: {
20
+ sourceId: { indexed: true },
21
+ targetId: { indexed: true },
22
+ },
23
+ });
@@ -0,0 +1,40 @@
1
+ import { BaseEntity } from '../../../../src/services/BaseEntity';
2
+ import { defineEntity } from '../../../../src/services/EntityRegistry';
3
+ import { z } from 'zod';
4
+
5
+ const ProfileSchema = z.object({
6
+ id: z.number().optional(),
7
+ userId: z.number().positive('User ID must be positive'),
8
+ bio: z.string().optional(),
9
+ avatar: z.string().url().optional(),
10
+ website: z.string().url().optional(),
11
+ location: z.string().optional(),
12
+ createdAt: z.number(),
13
+ updatedAt: z.number(),
14
+ });
15
+
16
+ export type Profile = z.infer<typeof ProfileSchema>;
17
+
18
+ export class ProfileEntity extends BaseEntity<number> {
19
+ userId!: number;
20
+ bio?: string;
21
+ avatar?: string;
22
+ website?: string;
23
+ location?: string;
24
+ createdAt!: number;
25
+ updatedAt!: number;
26
+ }
27
+
28
+ defineEntity(ProfileEntity, {
29
+ tableName: 'profiles',
30
+ schema: ProfileSchema,
31
+ columns: {
32
+ userId: { indexed: true },
33
+ bio: { indexed: true },
34
+ avatar: {},
35
+ website: {},
36
+ location: {},
37
+ createdAt: { indexed: true },
38
+ updatedAt: { indexed: true },
39
+ },
40
+ });
@@ -0,0 +1,34 @@
1
+ import { BaseEntity } from '../../../../src/services/BaseEntity';
2
+ import { defineEntity } from '../../../../src/services/EntityRegistry';
3
+ import { z } from 'zod';
4
+
5
+ const TagSchema = z.object({
6
+ id: z.number().optional(),
7
+ name: z.string().min(1, 'Tag name is required'),
8
+ color: z.string().regex(/^#[0-9A-F]{6}$/i, 'Color must be a valid hex color'),
9
+ description: z.string().optional(),
10
+ createdAt: z.number(),
11
+ updatedAt: z.number(),
12
+ });
13
+
14
+ export type Tag = z.infer<typeof TagSchema>;
15
+
16
+ export class TagEntity extends BaseEntity<number> {
17
+ name!: string;
18
+ color!: string;
19
+ description?: string;
20
+ createdAt!: number;
21
+ updatedAt!: number;
22
+ }
23
+
24
+ defineEntity(TagEntity, {
25
+ tableName: 'tags',
26
+ schema: TagSchema,
27
+ columns: {
28
+ name: { required: true, unique: true, indexed: true },
29
+ color: { required: true },
30
+ description: {},
31
+ createdAt: { indexed: true },
32
+ updatedAt: { indexed: true },
33
+ },
34
+ });
@@ -0,0 +1,59 @@
1
+ import { BaseEntity } from '../../../../src/services/BaseEntity';
2
+ import { defineEntity } from '../../../../src/services/EntityRegistry';
3
+ import { z } from 'zod';
4
+
5
+ import type { PostEntity } from './Post';
6
+ import type { ProfileEntity } from './Profile';
7
+
8
+ // Zod schema for validation
9
+ export const UserSchema = z.object({
10
+ id: z.number().optional(),
11
+ name: z.string().min(2, 'Name must be at least 2 characters'),
12
+ email: z.string().email('Invalid email format'),
13
+ age: z.number().min(18, 'Must be at least 18 years old'),
14
+ createdAt: z.number(),
15
+ updatedAt: z.number(),
16
+ isActive: z.boolean().default(true),
17
+ tags: z.array(z.string()).default([]),
18
+ metadata: z.record(z.string(), z.unknown()).default({}),
19
+ // Note: 'posts' and 'profile' relations are not in schema - they're loaded separately
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,7 @@
1
+ // Export all entities and database
2
+ export { db } from './database/Database';
3
+ export { UserEntity, UserSchema, type User } from './entities/User';
4
+ export { PostEntity } from './entities/Post';
5
+ export { ProfileEntity, ProfileSchema, type Profile } from './entities/Profile';
6
+ export { TagEntity, TagSchema, type Tag } from './entities/Tag';
7
+ export { PostTagEntity } from './entities/PostTag';
@@ -0,0 +1,17 @@
1
+ import type { Migration } from '../../../src/types';
2
+
3
+ export const addUserEmailIndex: Migration = {
4
+ version: 1,
5
+ name: 'add-user-email-index',
6
+ up: async () => {
7
+ console.log('Adding email index to users table...');
8
+ // This migration would add an index to the email field
9
+ // In a real scenario, you might need to recreate the table with the new index
10
+ console.log('Email index added successfully');
11
+ },
12
+ down: async () => {
13
+ console.log('Removing email index from users table...');
14
+ // This would remove the index
15
+ console.log('Email index removed successfully');
16
+ },
17
+ };
@@ -0,0 +1,37 @@
1
+ import type { Migration } from '../../../src/types';
2
+
3
+ export const addPostCategory: Migration = {
4
+ version: 2,
5
+ name: 'add-post-category',
6
+ up: async (db) => {
7
+ console.log('Adding category field to posts table...');
8
+
9
+ const posts = await db.table('posts').toArray();
10
+ const updatedPosts = posts.map((post: Record<string, unknown>) => ({
11
+ ...post,
12
+ category: 'general',
13
+ }));
14
+
15
+ const postsRepo = db.table('posts');
16
+ await postsRepo.clear();
17
+ await postsRepo.bulkAdd(updatedPosts);
18
+
19
+ console.log('Category field added to all existing posts');
20
+ },
21
+ down: async (db) => {
22
+ console.log('Removing category field from posts table...');
23
+
24
+ const posts = await db.table('posts').toArray();
25
+ const updatedPosts = posts
26
+ .map(({ _category, ...post }: Record<string, unknown>) => {
27
+
28
+ return post;
29
+ });
30
+
31
+ const postsRepo = db.table('posts');
32
+ await postsRepo.clear();
33
+ await postsRepo.bulkAdd(updatedPosts);
34
+
35
+ console.log('Category field removed from all posts');
36
+ },
37
+ };
@@ -0,0 +1,8 @@
1
+ import type { Migration } from '../../../src/types';
2
+ import { addUserEmailIndex } from './001-add-user-email-index';
3
+ import { addPostCategory } from './002-add-post-category';
4
+
5
+ export const migrations: Migration[] = [
6
+ addUserEmailIndex,
7
+ addPostCategory,
8
+ ];
@@ -0,0 +1,17 @@
1
+ import type { Migration } from '../../../src/types';
2
+
3
+ export const addUserEmailIndex: Migration = {
4
+ version: 1,
5
+ name: 'add-user-email-index',
6
+ up: async () => {
7
+ console.log('Adding email index to users table...');
8
+ // This migration would add an index to the email field
9
+ // In a real scenario, you might need to recreate the table with the new index
10
+ console.log('Email index added successfully');
11
+ },
12
+ down: async () => {
13
+ console.log('Removing email index from users table...');
14
+ // This would remove the index
15
+ console.log('Email index removed successfully');
16
+ },
17
+ };
@@ -0,0 +1,37 @@
1
+ import type { Migration } from '../../../src/types';
2
+
3
+ export const addPostCategory: Migration = {
4
+ version: 2,
5
+ name: 'add-post-category',
6
+ up: async (db) => {
7
+ console.log('Adding category field to posts table...');
8
+
9
+ const posts = await db.table('posts').toArray();
10
+ const updatedPosts = posts.map((post: Record<string, unknown>) => ({
11
+ ...post,
12
+ category: 'general',
13
+ }));
14
+
15
+ const postsRepo = db.table('posts');
16
+ await postsRepo.clear();
17
+ await postsRepo.bulkAdd(updatedPosts);
18
+
19
+ console.log('Category field added to all existing posts');
20
+ },
21
+ down: async (db) => {
22
+ console.log('Removing category field from posts table...');
23
+
24
+ const posts = await db.table('posts').toArray();
25
+ const updatedPosts = posts
26
+ .map(({ _category, ...post }: Record<string, unknown>) => {
27
+
28
+ return post;
29
+ });
30
+
31
+ const postsRepo = db.table('posts');
32
+ await postsRepo.clear();
33
+ await postsRepo.bulkAdd(updatedPosts);
34
+
35
+ console.log('Category field removed from all posts');
36
+ },
37
+ };
@@ -0,0 +1,8 @@
1
+ import type { Migration } from '../../../src/types';
2
+ import { addUserEmailIndex } from './001-add-user-email-index';
3
+ import { addPostCategory } from './002-add-post-category';
4
+
5
+ export const migrations: Migration[] = [
6
+ addUserEmailIndex,
7
+ addPostCategory,
8
+ ];
@@ -0,0 +1,3 @@
1
+ // Disable SSR for this layout to allow IndexedDB to work
2
+ export const ssr = false;
3
+ export const prerender = false;