@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,27 @@
1
+ /**
2
+ * Create a new entity instance and validate it in a single call.
3
+ *
4
+ * This helper instantiates the provided entity class, assigns the given
5
+ * partial data, and immediately validates it using the entity's
6
+ * `init` method (which calls `validateOrThrow` under the hood).
7
+ *
8
+ * - Preferred construction style matches the project convention:
9
+ * empty constructor followed by property assignment and validation.
10
+ * - If the entity defines a Zod schema, invalid data will cause a
11
+ * ValidationError to be thrown.
12
+ *
13
+ * @typeParam T - Concrete entity type extending BaseEntity
14
+ * @param EntityClass - Entity constructor (must have a zero-arg constructor)
15
+ * @param data - Partial entity data to assign before validation
16
+ * @returns A fully initialized and validated entity instance
17
+ * @throws {import('../errors/ValidationError').ValidationError}
18
+ * If validation fails according to the entity schema
19
+ *
20
+ * @example
21
+ * const user = newEntity(User, { name: 'Alice' });
22
+ * // user is validated; throws if required fields are missing or invalid
23
+ */
24
+ export function newEntity(EntityClass, data) {
25
+ const instance = new EntityClass();
26
+ return instance.init(data);
27
+ }
@@ -0,0 +1,61 @@
1
+ import type { z } from 'zod';
2
+ type ClassConstructor<T = unknown> = abstract new (..._args: never[]) => T;
3
+ export type ColumnConfig = {
4
+ required?: boolean;
5
+ unique?: boolean;
6
+ indexed?: boolean;
7
+ default?: unknown | (() => unknown);
8
+ };
9
+ export type RelationConfig = {
10
+ type: 'one-to-one' | 'one-to-many' | 'many-to-many';
11
+ target: ClassConstructor | string;
12
+ foreignKey?: string;
13
+ joinTable?: string;
14
+ cascade?: boolean;
15
+ eager?: boolean;
16
+ };
17
+ export type DefineEntityOptions = {
18
+ tableName?: string;
19
+ schema?: z.ZodSchema<unknown>;
20
+ timestamps?: boolean;
21
+ columns?: Record<string, ColumnConfig>;
22
+ compoundIndexes?: {
23
+ name?: string;
24
+ unique?: boolean;
25
+ columns: string[];
26
+ }[];
27
+ relations?: Record<string, RelationConfig>;
28
+ };
29
+ type StoredEntityMeta = Required<Pick<DefineEntityOptions, 'tableName'>> & Omit<DefineEntityOptions, 'tableName'>;
30
+ /**
31
+ * Define entity metadata without decorators.
32
+ *
33
+ * Registers table name, schema, columns, indexes and relations for a given class
34
+ * so the ORM can work without TypeScript decorators.
35
+ *
36
+ * @param EntityClass - The entity constructor/class
37
+ * @param options - Metadata describing table, schema, columns, indexes and relations
38
+ * @returns void
39
+ *
40
+ * @example
41
+ * defineEntity(UserEntity, {
42
+ * tableName: 'users',
43
+ * columns: {
44
+ * name: { required: true, indexed: true },
45
+ * email: { required: true, unique: true },
46
+ * },
47
+ * relations: {
48
+ * posts: { type: 'one-to-many', target: PostEntity, foreignKey: 'authorId' }
49
+ * }
50
+ * });
51
+ */
52
+ export declare function defineEntity<T>(EntityClass: ClassConstructor<T>, options: DefineEntityOptions): void;
53
+ export declare function getDefinedEntityMeta(ctor: ClassConstructor): StoredEntityMeta | undefined;
54
+ export declare function getDefinedColumns(ctor: ClassConstructor): Record<string, ColumnConfig> | undefined;
55
+ export declare function getDefinedCompoundIndexes(ctor: ClassConstructor): {
56
+ name: string;
57
+ unique: boolean;
58
+ columns: string[];
59
+ }[] | undefined;
60
+ export declare function getDefinedRelations(ctor: ClassConstructor): Record<string, RelationConfig> | undefined;
61
+ export {};
@@ -0,0 +1,56 @@
1
+ const entityMeta = new Map();
2
+ const columnMeta = new Map();
3
+ const compoundIndexMeta = new Map();
4
+ const relationMeta = new Map();
5
+ /**
6
+ * Define entity metadata without decorators.
7
+ *
8
+ * Registers table name, schema, columns, indexes and relations for a given class
9
+ * so the ORM can work without TypeScript decorators.
10
+ *
11
+ * @param EntityClass - The entity constructor/class
12
+ * @param options - Metadata describing table, schema, columns, indexes and relations
13
+ * @returns void
14
+ *
15
+ * @example
16
+ * defineEntity(UserEntity, {
17
+ * tableName: 'users',
18
+ * columns: {
19
+ * name: { required: true, indexed: true },
20
+ * email: { required: true, unique: true },
21
+ * },
22
+ * relations: {
23
+ * posts: { type: 'one-to-many', target: PostEntity, foreignKey: 'authorId' }
24
+ * }
25
+ * });
26
+ */
27
+ export function defineEntity(EntityClass, options) {
28
+ const tableName = options.tableName || EntityClass.name.toLowerCase() + 's';
29
+ entityMeta.set(EntityClass, { tableName, ...options });
30
+ if (options.columns) {
31
+ columnMeta.set(EntityClass, options.columns);
32
+ }
33
+ if (options.compoundIndexes) {
34
+ const normalized = options.compoundIndexes.map(ci => ({
35
+ name: ci.name || ci.columns.join('_'),
36
+ unique: !!ci.unique,
37
+ columns: ci.columns,
38
+ }));
39
+ compoundIndexMeta.set(EntityClass, normalized);
40
+ }
41
+ if (options.relations) {
42
+ relationMeta.set(EntityClass, options.relations);
43
+ }
44
+ }
45
+ export function getDefinedEntityMeta(ctor) {
46
+ return entityMeta.get(ctor);
47
+ }
48
+ export function getDefinedColumns(ctor) {
49
+ return columnMeta.get(ctor);
50
+ }
51
+ export function getDefinedCompoundIndexes(ctor) {
52
+ return compoundIndexMeta.get(ctor);
53
+ }
54
+ export function getDefinedRelations(ctor) {
55
+ return relationMeta.get(ctor);
56
+ }
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod';
2
+ import { BaseEntity } from './BaseEntity';
3
+ export declare class EntitySchema<T extends BaseEntity = BaseEntity> {
4
+ private entityClass;
5
+ private schema?;
6
+ constructor(entityClass: new () => T, schema?: z.ZodSchema<unknown>);
7
+ /**
8
+ * Get the table name for this entity
9
+ * @returns The table name (either from metadata or generated from class name)
10
+ *
11
+ * @example
12
+ * const schema = new EntitySchema(User);
13
+ * const table = schema.getTableName(); // 'users'
14
+ */
15
+ getTableName(): string;
16
+ /**
17
+ * Get the Zod schema for this entity
18
+ * @returns The Zod schema or undefined if not defined
19
+ *
20
+ * @example
21
+ * const schema = new EntitySchema(User, userZodSchema);
22
+ * const zod = schema.getSchema();
23
+ */
24
+ getSchema(): z.ZodSchema<unknown> | undefined;
25
+ /**
26
+ * Get column metadata for this entity
27
+ * @returns Object containing column metadata
28
+ *
29
+ * @example
30
+ * const cols = new EntitySchema(User).getColumns();
31
+ * // cols.name.required === true
32
+ */
33
+ getColumns(): Record<string, unknown>;
34
+ /**
35
+ * Validate data against entity schema
36
+ * @param data - Data to validate
37
+ * @returns Validation result with status and errors
38
+ *
39
+ * @example
40
+ * const { isValid, errors } = new EntitySchema(User, userSchema)
41
+ * .validate({ name: 'Alice' });
42
+ */
43
+ validate(data: Partial<T>): {
44
+ isValid: boolean;
45
+ errors: string[];
46
+ };
47
+ /**
48
+ * Create a new instance of the entity
49
+ * @param data - Optional data to initialize the entity with
50
+ * @returns New entity instance
51
+ *
52
+ * @example
53
+ * const user = new EntitySchema(User).create({ name: 'Bob' });
54
+ */
55
+ create(data?: Partial<T>): T;
56
+ }
@@ -0,0 +1,125 @@
1
+ import { z } from 'zod';
2
+ import { getColumnMetadata } from '../metadata/Column';
3
+ import { getEntityMetadata } from '../metadata/Entity';
4
+ import { getDefinedColumns } from './EntityRegistry';
5
+ export class EntitySchema {
6
+ entityClass;
7
+ schema;
8
+ constructor(entityClass, schema) {
9
+ this.entityClass = entityClass;
10
+ this.schema = schema;
11
+ }
12
+ /**
13
+ * Get the table name for this entity
14
+ * @returns The table name (either from metadata or generated from class name)
15
+ *
16
+ * @example
17
+ * const schema = new EntitySchema(User);
18
+ * const table = schema.getTableName(); // 'users'
19
+ */
20
+ getTableName() {
21
+ const metadata = getEntityMetadata(this.entityClass);
22
+ return metadata?.tableName || this.entityClass.name.toLowerCase() + 's';
23
+ }
24
+ /**
25
+ * Get the Zod schema for this entity
26
+ * @returns The Zod schema or undefined if not defined
27
+ *
28
+ * @example
29
+ * const schema = new EntitySchema(User, userZodSchema);
30
+ * const zod = schema.getSchema();
31
+ */
32
+ getSchema() {
33
+ return this.schema
34
+ || this.entityClass.schema;
35
+ }
36
+ /**
37
+ * Get column metadata for this entity
38
+ * @returns Object containing column metadata
39
+ *
40
+ * @example
41
+ * const cols = new EntitySchema(User).getColumns();
42
+ * // cols.name.required === true
43
+ */
44
+ getColumns() {
45
+ const defined = getDefinedColumns(this.entityClass);
46
+ if (defined) {
47
+ return defined;
48
+ }
49
+ return getColumnMetadata(this.entityClass);
50
+ }
51
+ /**
52
+ * Validate data against entity schema
53
+ * @param data - Data to validate
54
+ * @returns Validation result with status and errors
55
+ *
56
+ * @example
57
+ * const { isValid, errors } = new EntitySchema(User, userSchema)
58
+ * .validate({ name: 'Alice' });
59
+ */
60
+ validate(data) {
61
+ const schema = this.getSchema();
62
+ if (!schema) {
63
+ const columns = this.getColumns();
64
+ const missing = [];
65
+ for (const [propertyKey, options] of Object.entries(columns)) {
66
+ if (!options || !options.required) {
67
+ continue;
68
+ }
69
+ const value = data[propertyKey];
70
+ if (value === undefined || value === null) {
71
+ missing.push(propertyKey);
72
+ }
73
+ }
74
+ if (missing.length > 0) {
75
+ return {
76
+ isValid: false,
77
+ errors: missing.map(key => `${key}: is required`),
78
+ };
79
+ }
80
+ return { isValid: true, errors: [] };
81
+ }
82
+ try {
83
+ schema.parse(data);
84
+ return { isValid: true, errors: [] };
85
+ }
86
+ catch (error) {
87
+ if (error instanceof z.ZodError) {
88
+ const errors = error.issues.map(err => `${err.path.join('.')}: ${err.message}`);
89
+ return { isValid: false, errors };
90
+ }
91
+ return { isValid: false, errors: ['Unknown validation error'] };
92
+ }
93
+ }
94
+ /**
95
+ * Create a new instance of the entity
96
+ * @param data - Optional data to initialize the entity with
97
+ * @returns New entity instance
98
+ *
99
+ * @example
100
+ * const user = new EntitySchema(User).create({ name: 'Bob' });
101
+ */
102
+ create(data) {
103
+ const instance = new this.entityClass();
104
+ const columns = this.getColumns();
105
+ for (const [propertyKey, options] of Object.entries(columns)) {
106
+ const hasValueInData = data
107
+ && data[propertyKey] !== undefined;
108
+ const hasValueInInstance = instance[propertyKey] !== undefined;
109
+ if (!hasValueInData &&
110
+ !hasValueInInstance &&
111
+ options &&
112
+ Object.prototype.hasOwnProperty.call(options, 'default')) {
113
+ const defaultOption = options.default;
114
+ const value = typeof defaultOption === 'function'
115
+ ? defaultOption()
116
+ : defaultOption;
117
+ instance[propertyKey] = value;
118
+ }
119
+ }
120
+ if (data) {
121
+ Object.assign(instance, data);
122
+ }
123
+ return instance;
124
+ }
125
+ }
@@ -0,0 +1,70 @@
1
+ import type Dexie from 'dexie';
2
+ import type { EntityConstructor, Migration } from '../types';
3
+ export declare class MigrationManager {
4
+ private dbName;
5
+ private currentVersion;
6
+ private db;
7
+ constructor(dbName: string, currentVersion: number);
8
+ setDatabase(db: {
9
+ migrationMetadata: {
10
+ get: (_key: string) => Promise<{
11
+ key: string;
12
+ value: string | number;
13
+ updatedAt: number;
14
+ } | undefined>;
15
+ put: (_data: {
16
+ key: string;
17
+ value: string | number;
18
+ updatedAt: number;
19
+ }) => Promise<unknown>;
20
+ };
21
+ }): void;
22
+ private getLastMigrationVersion;
23
+ private setLastMigrationVersion;
24
+ private getSchemaHash;
25
+ private setSchemaHash;
26
+ /**
27
+ * Run database migrations
28
+ * @param migrations - Array of migration objects
29
+ * @param db - Database instance for running migrations
30
+ * @returns Promise that resolves when all migrations are complete
31
+ */
32
+ runMigrations(migrations: Migration[], db: (Dexie & {
33
+ clearAllData: () => Promise<void>;
34
+ })): Promise<void>;
35
+ autoRunMigrations(migrations: Migration[], db: (Dexie & {
36
+ clearAllData: () => Promise<void>;
37
+ })): void;
38
+ /**
39
+ * Perform selective reset for changed tables only
40
+ * @param entities - Array of entity constructors
41
+ * @param db - Database instance for clearing tables
42
+ * @returns Promise that resolves when selective reset is complete
43
+ */
44
+ performSelectiveReset(entities: EntityConstructor[], db: Dexie): Promise<void>;
45
+ autoSelectiveReset(entities: EntityConstructor[], db: Dexie): void;
46
+ /**
47
+ * Reset entire database by clearing all data
48
+ * @param db - Database instance for clearing data
49
+ * @returns Promise that resolves when database is reset
50
+ */
51
+ resetDatabase(db: {
52
+ clearAllData: () => Promise<void>;
53
+ }): Promise<void>;
54
+ /**
55
+ * Automatically reset database asynchronously
56
+ * @param db - Database instance for clearing data
57
+ */
58
+ autoResetDatabase(db: {
59
+ clearAllData: () => Promise<void>;
60
+ }): void;
61
+ /**
62
+ * Check if schema has changed and reset database if needed
63
+ * @param entities - Array of entity constructors
64
+ * @param db - Database instance for clearing data
65
+ * @returns Promise resolving to true if schema changed, false otherwise
66
+ */
67
+ checkSchemaChanges(entities: EntityConstructor[], db: {
68
+ clearAllData: () => Promise<void>;
69
+ }): Promise<boolean>;
70
+ }
@@ -0,0 +1,181 @@
1
+ import { createServiceLogger } from '../utils/logger';
2
+ import { SchemaBuilder } from './SchemaBuilder';
3
+ const logger = createServiceLogger('MigrationManager');
4
+ export class MigrationManager {
5
+ dbName;
6
+ currentVersion;
7
+ db = null;
8
+ constructor(dbName, currentVersion) {
9
+ this.dbName = dbName;
10
+ this.currentVersion = currentVersion;
11
+ }
12
+ setDatabase(db) {
13
+ this.db = db;
14
+ }
15
+ async getLastMigrationVersion() {
16
+ if (!this.db) {
17
+ logger.error('Database not set in MigrationManager');
18
+ throw new Error('MigrationManager database not set');
19
+ }
20
+ const key = `dexie_orm_migration_${this.dbName}_version`;
21
+ const metadata = await this.db.migrationMetadata.get(key);
22
+ return metadata?.value || 0;
23
+ }
24
+ async setLastMigrationVersion(version) {
25
+ if (!this.db) {
26
+ logger.error('Database not set in MigrationManager');
27
+ throw new Error('MigrationManager database not set');
28
+ }
29
+ const key = `dexie_orm_migration_${this.dbName}_version`;
30
+ await this.db.migrationMetadata.put({
31
+ key,
32
+ value: version,
33
+ updatedAt: Date.now(),
34
+ });
35
+ }
36
+ async getSchemaHash() {
37
+ if (!this.db) {
38
+ logger.error('Database not set in MigrationManager');
39
+ throw new Error('MigrationManager database not set');
40
+ }
41
+ const key = `dexie_orm_schema_${this.dbName}_hash`;
42
+ const metadata = await this.db.migrationMetadata.get(key);
43
+ return metadata?.value || null;
44
+ }
45
+ async setSchemaHash(hash) {
46
+ if (!this.db) {
47
+ logger.error('Database not set in MigrationManager');
48
+ throw new Error('MigrationManager database not set');
49
+ }
50
+ const key = `dexie_orm_schema_${this.dbName}_hash`;
51
+ await this.db.migrationMetadata.put({
52
+ key,
53
+ value: hash,
54
+ updatedAt: Date.now(),
55
+ });
56
+ }
57
+ /**
58
+ * Run database migrations
59
+ * @param migrations - Array of migration objects
60
+ * @param db - Database instance for running migrations
61
+ * @returns Promise that resolves when all migrations are complete
62
+ */
63
+ async runMigrations(migrations, db) {
64
+ logger.info('Running migrations...');
65
+ const lastMigrationVersion = await this.getLastMigrationVersion();
66
+ const migrationsToRun = migrations.filter(migration => migration.version > lastMigrationVersion
67
+ && migration.version <= this.currentVersion);
68
+ if (migrationsToRun.length === 0) {
69
+ logger.info('No migrations to run');
70
+ return;
71
+ }
72
+ migrationsToRun.sort((a, b) => a.version - b.version);
73
+ for (const migration of migrationsToRun) {
74
+ logger.info(`Running migration: ${migration.name} (v${migration.version})`);
75
+ try {
76
+ await migration.up(db);
77
+ await this.setLastMigrationVersion(migration.version);
78
+ logger.info(`Migration ${migration.name} completed`);
79
+ }
80
+ catch (error) {
81
+ logger.error(`Migration ${migration.name} failed:`, error);
82
+ throw error;
83
+ }
84
+ }
85
+ logger.info('All migrations completed');
86
+ }
87
+ autoRunMigrations(migrations, db) {
88
+ setTimeout(async () => {
89
+ try {
90
+ await this.runMigrations(migrations, db);
91
+ }
92
+ catch (error) {
93
+ logger.error('Error during auto-migration:', error);
94
+ }
95
+ }, 0);
96
+ }
97
+ /**
98
+ * Perform selective reset for changed tables only
99
+ * @param entities - Array of entity constructors
100
+ * @param db - Database instance for clearing tables
101
+ * @returns Promise that resolves when selective reset is complete
102
+ */
103
+ async performSelectiveReset(entities, db) {
104
+ logger.info('Performing selective reset for changed tables...');
105
+ const currentTableSchemas = SchemaBuilder.buildTableSchemas(entities);
106
+ const storedTableSchemas = SchemaBuilder.getStoredTableSchemas(this.dbName);
107
+ const changes = SchemaBuilder
108
+ .compareSchemas(storedTableSchemas, currentTableSchemas);
109
+ if (changes.length === 0) {
110
+ logger.info('No schema changes detected for selective reset');
111
+ return;
112
+ }
113
+ for (const change of changes) {
114
+ if (change.changeType === 'removed' || change.changeType === 'modified') {
115
+ logger.info(`Clearing ${change.changeType} table: ${change.tableName}`);
116
+ try {
117
+ await db.table(change.tableName).clear();
118
+ }
119
+ catch (error) {
120
+ logger.error(`Could not clear table ${change.tableName}:`, error);
121
+ }
122
+ }
123
+ }
124
+ SchemaBuilder.storeTableSchemas(this.dbName, currentTableSchemas);
125
+ logger.info('Selective reset complete');
126
+ }
127
+ autoSelectiveReset(entities, db) {
128
+ setTimeout(async () => {
129
+ try {
130
+ await this.performSelectiveReset(entities, db);
131
+ }
132
+ catch (error) {
133
+ logger.error('Error during auto-selective reset:', error);
134
+ }
135
+ }, 0);
136
+ }
137
+ /**
138
+ * Reset entire database by clearing all data
139
+ * @param db - Database instance for clearing data
140
+ * @returns Promise that resolves when database is reset
141
+ */
142
+ async resetDatabase(db) {
143
+ logger.info('Resetting database due to schema changes...');
144
+ await db.clearAllData();
145
+ await this.setLastMigrationVersion(0);
146
+ await this.setSchemaHash('');
147
+ logger.info('Database reset complete');
148
+ }
149
+ /**
150
+ * Automatically reset database asynchronously
151
+ * @param db - Database instance for clearing data
152
+ */
153
+ autoResetDatabase(db) {
154
+ setTimeout(async () => {
155
+ try {
156
+ await this.resetDatabase(db);
157
+ }
158
+ catch (error) {
159
+ logger.error('Error during auto-reset:', error);
160
+ }
161
+ }, 0);
162
+ }
163
+ /**
164
+ * Check if schema has changed and reset database if needed
165
+ * @param entities - Array of entity constructors
166
+ * @param db - Database instance for clearing data
167
+ * @returns Promise resolving to true if schema changed, false otherwise
168
+ */
169
+ async checkSchemaChanges(entities, db) {
170
+ const currentSchema = SchemaBuilder.buildSchema(entities);
171
+ const schemaHash = SchemaBuilder.generateSchemaHash(currentSchema);
172
+ const storedHash = await this.getSchemaHash();
173
+ if (storedHash && storedHash !== schemaHash) {
174
+ logger.info('Schema changes detected!');
175
+ await this.resetDatabase(db);
176
+ await this.setSchemaHash(schemaHash);
177
+ return true;
178
+ }
179
+ return false;
180
+ }
181
+ }
@@ -0,0 +1,66 @@
1
+ import type { EntityConstructor } from '../types';
2
+ import { BaseEntity } from './BaseEntity';
3
+ export declare class RelationLoader {
4
+ private db;
5
+ constructor(database: {
6
+ table: (_name: string) => unknown;
7
+ });
8
+ private resolveTableName;
9
+ /**
10
+ * Load relations for an entity
11
+ * @param entity - The entity to load relations for
12
+ * @param entityClass - The entity class
13
+ * @param relationNames - Optional array of specific relation names to load.
14
+ * If not provided, loads all eager relations.
15
+ */
16
+ loadRelations<T extends BaseEntity>(entity: T, entityClass: EntityConstructor<T>, relationNames?: string[]): Promise<T>;
17
+ /**
18
+ * Load a specific relation by name
19
+ * @param entity - The entity to load relation for
20
+ * @param entityClass - The entity class
21
+ * @param relationName - The name of the relation to load
22
+ * @returns Promise resolving to the loaded relation data
23
+ */
24
+ loadRelationByName<T extends BaseEntity, K extends keyof T>(entity: T, entityClass: EntityConstructor<T>, relationName: K): Promise<T[K]>;
25
+ /**
26
+ * Load a specific relation based on metadata
27
+ * @param entity - The entity to load relation for
28
+ * @param relationMeta - The relation metadata containing:
29
+ * type, target, and configuration
30
+ * @returns Promise resolving to the loaded relation data
31
+ */
32
+ loadRelation<T extends BaseEntity>(entity: T, relationMeta: {
33
+ type: string;
34
+ target: string | EntityConstructor;
35
+ foreignKey?: string;
36
+ joinTable?: string;
37
+ }): Promise<unknown>;
38
+ private loadOneToOne;
39
+ private loadOneToMany;
40
+ /**
41
+ * Load many-to-many relation
42
+ */
43
+ private loadManyToMany;
44
+ /**
45
+ * Save entity with all its relations
46
+ * @param entity - The entity to save with relations
47
+ * @param entityClass - The entity class
48
+ * @returns Promise resolving to the saved entity with relations
49
+ */
50
+ saveWithRelations<T extends BaseEntity>(entity: T, entityClass: EntityConstructor<T>): Promise<T>;
51
+ /**
52
+ * Delete entity with cascade handling for relations
53
+ * @param entity - The entity to delete
54
+ * @param entityClass - The entity class
55
+ */
56
+ deleteWithRelations<T extends BaseEntity>(entity: T, entityClass: EntityConstructor<T>): Promise<void>;
57
+ private deleteRelation;
58
+ private deleteOneToOne;
59
+ private deleteOneToMany;
60
+ private deleteManyToMany;
61
+ private saveEntity;
62
+ private saveRelation;
63
+ private saveOneToOne;
64
+ private saveOneToMany;
65
+ private saveManyToMany;
66
+ }