@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,405 @@
1
+ import Dexie, { type Table } from 'dexie';
2
+
3
+ import { getEntityMetadata } from './metadata/Entity';
4
+ import { AggregationService } from './services/AggregationService';
5
+ import { BaseEntity } from './services/BaseEntity';
6
+ import { CloudSyncService } from './services/CloudSyncService';
7
+ import { MigrationManager } from './services/MigrationManager';
8
+ import { RelationLoader } from './services/RelationLoader';
9
+ import { SchemaBuilder } from './services/SchemaBuilder';
10
+ import type {
11
+ AggregationOptions,
12
+ AggregationResult,
13
+ CloudSyncConfig,
14
+ DatabaseConfig,
15
+ EntityConstructor,
16
+ Migration,
17
+ } from './types';
18
+ import { logger } from './utils/logger';
19
+
20
+ export class Database extends Dexie {
21
+ private entitySchemas = new Map<string, EntityConstructor>();
22
+ private migrationManager: MigrationManager;
23
+ private relationLoader: RelationLoader;
24
+ private aggregationService: AggregationService;
25
+ private cloudSyncService: CloudSyncService;
26
+
27
+ migrationMetadata!: Table<{
28
+ key: string;
29
+ value: string | number;
30
+ updatedAt: number;
31
+ }>;
32
+
33
+ constructor(config: DatabaseConfig) {
34
+ super(config.name);
35
+
36
+ this.migrationManager = new MigrationManager(config.name, config.version);
37
+ this.relationLoader = new RelationLoader(this);
38
+ this.aggregationService = new AggregationService(this);
39
+ this.cloudSyncService = new CloudSyncService(this);
40
+
41
+ const currentSchema = SchemaBuilder.buildSchema(config.entities);
42
+
43
+ const schemaHash = SchemaBuilder.generateSchemaHash(currentSchema);
44
+ const storedHash = localStorage.getItem(`${config.name}_schema_hash`);
45
+
46
+ let version = config.version;
47
+
48
+ if (storedHash && storedHash !== schemaHash) {
49
+ logger.info('Schema changed, incrementing version...');
50
+
51
+ version = config.version + 1;
52
+ localStorage.setItem(`${config.name}_schema_hash`, schemaHash);
53
+
54
+ if (config.migrations && config.migrations.length > 0) {
55
+ logger.info('Running migrations due to schema change...');
56
+
57
+ this.migrationManager.autoRunMigrations(config.migrations, this);
58
+ } else {
59
+ if (config.onSchemaChangeStrategy === 'all') {
60
+ logger.info('Auto-resetting entire database due to schema change...');
61
+
62
+ this.migrationManager.autoResetDatabase(this);
63
+ } else if (config.onSchemaChangeStrategy === 'selective') {
64
+ logger.info('Auto-resetting only changed tables...');
65
+
66
+ this.migrationManager.autoSelectiveReset(config.entities, this);
67
+ }
68
+ }
69
+ }
70
+
71
+ this.version(version).stores(currentSchema);
72
+ this.migrationManager.setDatabase(this);
73
+
74
+ config.entities.forEach(entity => {
75
+ const metadata = getEntityMetadata(entity);
76
+ const tableName = metadata?.tableName || entity.name.toLowerCase() + 's';
77
+ this.entitySchemas.set(tableName, entity);
78
+ });
79
+
80
+ if (config.cloudSync) {
81
+ this.cloudSyncService.initializeCloudSync(config.cloudSync);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Get repository for entity (TypeORM style)
87
+ *
88
+ * @example
89
+ * const users = db.getRepository(User);
90
+ * const id = await users.add({ name: 'Ann' } as User);
91
+ */
92
+ getRepository<
93
+ T extends BaseEntity,
94
+ TKey extends Extract<
95
+ NonNullable<T['id']>,
96
+ string | number
97
+ > = Extract<NonNullable<T['id']>, string | number>
98
+ >(entityClass: EntityConstructor<T>): Table<T, TKey> {
99
+ const fromRegistry = Array.from(this.entitySchemas.entries()).find(
100
+ ([, ctor]) => ctor === entityClass,
101
+ )?.[0];
102
+
103
+ const metadata = getEntityMetadata(entityClass);
104
+ let tableName = fromRegistry
105
+ || metadata?.tableName
106
+ || entityClass.name.toLowerCase() + 's';
107
+
108
+ if (!fromRegistry && !metadata?.tableName) {
109
+ const n = entityClass.name;
110
+ if (/Entity$/i.test(n)) {
111
+ const base = n.replace(/Entity$/i, '');
112
+ tableName = base.toLowerCase() + 's';
113
+ }
114
+ }
115
+
116
+ return this.table(tableName) as Table<T, TKey>;
117
+ }
118
+
119
+ /**
120
+ * Perform aggregation on entity data
121
+ *
122
+ * @example
123
+ * const result = await db.aggregate({
124
+ * entityClass: Post,
125
+ * options: { where: { category: 'tech' } },
126
+ * });
127
+ */
128
+ async aggregate<T extends BaseEntity>(params: {
129
+ entityClass: EntityConstructor<T>;
130
+ options: AggregationOptions<T>;
131
+ }): Promise<AggregationResult> {
132
+ return this.aggregationService.aggregate(params.entityClass, params.options);
133
+ }
134
+
135
+ /**
136
+ * Create database with entity registration
137
+ *
138
+ * @example
139
+ * const db = Database.createDatabase({
140
+ * name: 'app-db',
141
+ * version: 1,
142
+ * entities: [User, Post],
143
+ * });
144
+ */
145
+ static createDatabase(params: {
146
+ name: string;
147
+ version: number;
148
+ entities: EntityConstructor[];
149
+ config?: Partial<DatabaseConfig>;
150
+ }): Database {
151
+ const db = new Database({
152
+ name: params.name,
153
+ version: params.version,
154
+ entities: params.entities,
155
+ onSchemaChangeStrategy: params.config?.onSchemaChangeStrategy,
156
+ migrations: params.config?.migrations,
157
+ cloudSync: params.config?.cloudSync,
158
+ });
159
+
160
+ return db as Database;
161
+ }
162
+
163
+ /**
164
+ * Clear all data from database
165
+ *
166
+ * @example
167
+ * await db.clearAllData();
168
+ */
169
+ async clearAllData(): Promise<void> {
170
+ const tableNames = this.tables.map(table => table.name);
171
+
172
+ await Promise.all(tableNames.map(name => this.table(name).clear()));
173
+ }
174
+
175
+ /**
176
+ * Reset database when schema changes
177
+ *
178
+ * @example
179
+ * await db.resetDatabase();
180
+ */
181
+ async resetDatabase(): Promise<void> {
182
+ await this.migrationManager.resetDatabase(this);
183
+ }
184
+
185
+ /**
186
+ * Check if schema has changed and reset if needed
187
+ *
188
+ * @example
189
+ * const changed = await db.checkSchemaChanges();
190
+ */
191
+ async checkSchemaChanges(): Promise<boolean> {
192
+ return this.migrationManager.checkSchemaChanges(
193
+ Array.from(this.entitySchemas.values()),
194
+ this,
195
+ );
196
+ }
197
+
198
+ /**
199
+ * Manually perform selective reset for changed tables only
200
+ *
201
+ * @example
202
+ * await db.performSelectiveReset();
203
+ */
204
+ async performSelectiveReset(): Promise<void> {
205
+ await this.migrationManager.performSelectiveReset(
206
+ Array.from(this.entitySchemas.values()),
207
+ this,
208
+ );
209
+ }
210
+
211
+ /**
212
+ * Run migrations for schema changes
213
+ *
214
+ * @example
215
+ * await db.runMigrations(migrations);
216
+ */
217
+ async runMigrations(migrations: Migration[]): Promise<void> {
218
+ await this.migrationManager.runMigrations(migrations, this);
219
+ }
220
+
221
+ /**
222
+ * Get typed table for entity
223
+ *
224
+ * @example
225
+ * const posts = db.getTypedTable(Post);
226
+ */
227
+ getTypedTable<
228
+ T extends BaseEntity,
229
+ TKey extends Extract<
230
+ NonNullable<T['id']>,
231
+ string | number
232
+ > = Extract<NonNullable<T['id']>, string | number>
233
+ >(entityClass: EntityConstructor<T>): Table<T, TKey> {
234
+ const metadata = getEntityMetadata(entityClass);
235
+ const tableName =
236
+ metadata?.tableName || entityClass.name.toLowerCase() + 's';
237
+
238
+ return this.table(tableName) as Table<T, TKey>;
239
+ }
240
+
241
+ /**
242
+ * Get table with proper typing for specific entity
243
+ *
244
+ * @example
245
+ * const users = db.getTableForEntity(User);
246
+ */
247
+ getTableForEntity<
248
+ T extends BaseEntity,
249
+ TKey extends Extract<
250
+ NonNullable<T['id']>,
251
+ string | number
252
+ > = Extract<NonNullable<T['id']>, string | number>
253
+ >(entityClass: EntityConstructor<T>): Table<T, TKey> {
254
+ return this.getTypedTable<T, TKey>(entityClass);
255
+ }
256
+
257
+ /**
258
+ * Get all entities
259
+ *
260
+ * @example
261
+ * const all = db.getEntities();
262
+ */
263
+ getEntities(): EntityConstructor[] {
264
+ return Array.from(this.entitySchemas.values());
265
+ }
266
+
267
+ /**
268
+ * Get entity by table name
269
+ *
270
+ * @example
271
+ * const UserClass = db.getEntity('users');
272
+ */
273
+ getEntity(tableName: string): EntityConstructor | undefined {
274
+ return this.entitySchemas.get(tableName);
275
+ }
276
+
277
+ /**
278
+ * Load relations for an entity
279
+ *
280
+ * @example
281
+ * const userWithRelations = await db.loadRelations({
282
+ * entity: user,
283
+ * entityClass: User,
284
+ * relationNames: ['posts'],
285
+ * });
286
+ */
287
+ async loadRelations<T extends BaseEntity>(params: {
288
+ entity: T;
289
+ entityClass: EntityConstructor<T>;
290
+ relationNames?: string[];
291
+ }): Promise<T> {
292
+ return this.relationLoader.loadRelations(
293
+ params.entity,
294
+ params.entityClass,
295
+ params.relationNames,
296
+ );
297
+ }
298
+
299
+ /**
300
+ * Load a specific relation by name
301
+ *
302
+ * @example
303
+ * const posts = await db.loadRelationByName({
304
+ * entity: { id: userId },
305
+ * entityClass: User,
306
+ * relationName: 'posts',
307
+ * });
308
+ */
309
+ async loadRelationByName<
310
+ T extends BaseEntity,
311
+ K extends keyof T
312
+ >(params: {
313
+ entity: T | { id: string | number };
314
+ entityClass: EntityConstructor<T>;
315
+ relationName: K;
316
+ }): Promise<T[K]> {
317
+ return this.relationLoader.loadRelationByName<T, K>(
318
+ params.entity as T,
319
+ params.entityClass,
320
+ params.relationName,
321
+ );
322
+ }
323
+
324
+ /**
325
+ * Save entity with relations
326
+ *
327
+ * @example
328
+ * const saved = await db.saveWithRelations({
329
+ * entity: user,
330
+ * entityClass: User,
331
+ * });
332
+ */
333
+ async saveWithRelations<T extends BaseEntity>(params: {
334
+ entity: T;
335
+ entityClass: EntityConstructor<T>;
336
+ }): Promise<T> {
337
+ return this.relationLoader.saveWithRelations(params.entity, params.entityClass);
338
+ }
339
+
340
+ /**
341
+ * Delete entity with cascade handling for relations
342
+ *
343
+ * @example
344
+ * await db.deleteWithRelations({
345
+ * entity: user,
346
+ * entityClass: User,
347
+ * });
348
+ */
349
+ async deleteWithRelations<T extends BaseEntity>(params: {
350
+ entity: T;
351
+ entityClass: EntityConstructor<T>;
352
+ }): Promise<void> {
353
+ return this.relationLoader
354
+ .deleteWithRelations(params.entity, params.entityClass);
355
+ }
356
+
357
+ /**
358
+ * Manual sync with cloud
359
+ */
360
+ async sync(): Promise<void> {
361
+ return this.cloudSyncService.sync();
362
+ }
363
+
364
+ /**
365
+ * Get sync status
366
+ */
367
+ getSyncStatus(): { enabled: boolean; lastSync?: Date; isOnline?: boolean } {
368
+ return this.cloudSyncService.getSyncStatus();
369
+ }
370
+
371
+ /**
372
+ * Enable cloud sync (if not already enabled)
373
+ */
374
+ async enableCloudSync(config: CloudSyncConfig): Promise<void> {
375
+ return this.cloudSyncService.enableCloudSync(config);
376
+ }
377
+
378
+ /**
379
+ * Disable cloud sync
380
+ */
381
+ disableCloudSync(): void {
382
+ this.cloudSyncService.disableCloudSync();
383
+ }
384
+
385
+ /**
386
+ * Check if cloud sync is enabled
387
+ */
388
+ isCloudSyncEnabled(): boolean {
389
+ return this.cloudSyncService.isCloudSyncEnabled();
390
+ }
391
+
392
+ /**
393
+ * Get cloud sync configuration
394
+ */
395
+ getCloudSyncConfig(): CloudSyncConfig | undefined {
396
+ return this.cloudSyncService.getCloudSyncConfig();
397
+ }
398
+
399
+ /**
400
+ * Force sync specific tables
401
+ */
402
+ async syncTables(tableNames: string[]): Promise<void> {
403
+ return this.cloudSyncService.syncTables(tableNames);
404
+ }
405
+ }
@@ -0,0 +1,9 @@
1
+ export class ValidationError extends Error {
2
+ public errors: string[];
3
+
4
+ constructor(message: string, errors: string[] = []) {
5
+ super(message);
6
+ this.name = 'ValidationError';
7
+ this.errors = errors;
8
+ }
9
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ export { Database } from './Database';
2
+ export { ValidationError } from './errors/ValidationError';
3
+ export {
4
+ Entity, ManyToMany,
5
+ OneToMany, OneToOne, Relation,
6
+ } from './metadata';
7
+ export { BaseEntity } from './services/BaseEntity';
8
+ export { newEntity } from './services/EntityFactory';
9
+ export { defineEntity } from './services/EntityRegistry';
10
+ export { EntitySchema } from './services/EntitySchema';
11
+ export type {
12
+ CloudSyncConfig, DatabaseConfig,EntityConstructor, EntityInstance,
13
+ } from './types';
@@ -0,0 +1,74 @@
1
+ type ClassConstructor<T = unknown> = abstract new (..._args: never[]) => T;
2
+ import {
3
+ getDefinedColumns,
4
+ getDefinedCompoundIndexes,
5
+ } from '../services/EntityRegistry';
6
+ import type {
7
+ ColumnOptions, CompoundIndexOptions,
8
+ } from '../types';
9
+
10
+ const COLUMNS_METADATA_KEY = Symbol('columns');
11
+ const COMPOUND_INDEXES_METADATA_KEY = Symbol('compoundIndexes');
12
+
13
+ export interface ColumnMetadata extends ColumnOptions {
14
+ propertyKey: string;
15
+ }
16
+
17
+ export function getColumnMetadata(
18
+ target: object | ClassConstructor,
19
+ ): Record<string, ColumnMetadata> {
20
+ const ctor: ClassConstructor =
21
+ (typeof target === 'function')
22
+ ? (target as ClassConstructor)
23
+ : (target as { constructor: ClassConstructor }).constructor;
24
+ const defined = getDefinedColumns(ctor as ClassConstructor);
25
+
26
+ if (defined) {
27
+ const out: Record<string, ColumnMetadata> = {};
28
+
29
+ for (const [key, cfg] of Object.entries(defined)) {
30
+ out[key] = { propertyKey: key, ...cfg } as ColumnMetadata;
31
+ }
32
+
33
+ const hasPrimary = Object.values(out).some(c => c.primaryKey);
34
+ if (!hasPrimary) {
35
+ if (!out.id) {
36
+ out.id = { propertyKey: 'id', primaryKey: true, autoIncrement: true } as ColumnMetadata;
37
+ } else {
38
+ out.id.primaryKey = true;
39
+ if (out.id.autoIncrement === undefined) {
40
+ out.id.autoIncrement = true;
41
+ }
42
+ }
43
+ }
44
+
45
+ return out;
46
+ }
47
+
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ const reflectGet = (Reflect as any)?.getMetadata?.bind(Reflect);
50
+
51
+ return reflectGet ? (reflectGet(COLUMNS_METADATA_KEY, ctor) || {}) : {};
52
+ }
53
+
54
+ export function getCompoundIndexMetadata(
55
+ target: object | ClassConstructor,
56
+ ): CompoundIndexOptions[] {
57
+ const ctor: ClassConstructor =
58
+ (typeof target === 'function')
59
+ ? (target as ClassConstructor)
60
+ : (target as { constructor: ClassConstructor }).constructor;
61
+ const defined = getDefinedCompoundIndexes(ctor as ClassConstructor);
62
+
63
+ if (defined) {
64
+ return defined
65
+ .map(ci => ({ name: ci.name, unique: ci.unique, columns: ci.columns }));
66
+ }
67
+
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ const reflectGet = (Reflect as any)?.getMetadata?.bind(Reflect);
70
+
71
+ return reflectGet ? (reflectGet(COMPOUND_INDEXES_METADATA_KEY, ctor) || []) : [];
72
+ }
73
+
74
+
@@ -0,0 +1,42 @@
1
+ import { z } from 'zod';
2
+
3
+ type ClassConstructor<T = unknown> = abstract new (..._args: never[]) => T;
4
+ import { getDefinedEntityMeta } from '../services/EntityRegistry';
5
+ import type { EntityOptions } from '../types';
6
+
7
+ const ENTITY_METADATA_KEY = Symbol('entity');
8
+
9
+ export interface EntityMetadata {
10
+ tableName: string;
11
+ schema?: z.ZodSchema<unknown>;
12
+ timestamps?: boolean;
13
+ }
14
+
15
+ export function Entity(_options: EntityOptions = {}):
16
+ (_target: ClassConstructor) => void {
17
+ return function(_target: ClassConstructor): void {};
18
+ }
19
+
20
+ export function getEntityMetadata(target: ClassConstructor):
21
+ EntityMetadata | undefined {
22
+ const defined = getDefinedEntityMeta(target);
23
+
24
+ if (defined) {
25
+ return {
26
+ tableName: defined.tableName,
27
+ schema: defined.schema,
28
+ timestamps: defined.timestamps,
29
+ };
30
+ }
31
+
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ const reflectGet = (Reflect as any)?.getMetadata?.bind(Reflect);
34
+
35
+ if (reflectGet) {
36
+ return reflectGet(ENTITY_METADATA_KEY, target);
37
+ }
38
+
39
+ return undefined;
40
+ }
41
+
42
+
@@ -0,0 +1,121 @@
1
+ type ClassConstructor<T = unknown> = abstract new (..._args: never[]) => T;
2
+ type LegacyArgs = [Record<'constructor', ClassConstructor>, string];
3
+ type FieldContext = {
4
+ kind: 'field';
5
+ name: string;
6
+ addInitializer(
7
+ _init: (_this: { constructor: ClassConstructor }) => void
8
+ ): void;
9
+ };
10
+ type StandardArgs = [unknown, FieldContext];
11
+ function isLegacyArgs(args: unknown[]): args is LegacyArgs {
12
+ return Array.isArray(args) && typeof (args as unknown[])[1] === 'string';
13
+ }
14
+ function isFieldContext(args: unknown[]): args is StandardArgs {
15
+ const ctx = (args as unknown[])[1] as FieldContext | undefined;
16
+
17
+ return !!ctx && typeof ctx === 'object' && ctx.kind === 'field' && typeof ctx.name === 'string';
18
+ }
19
+ import { getDefinedRelations } from '../services/EntityRegistry';
20
+ import type {
21
+ EntityConstructor,RelationMetadata, RelationOptions,
22
+ } from '../types';
23
+
24
+ const RELATIONS_METADATA_KEY = Symbol('relations');
25
+
26
+ export function Relation(options: RelationOptions): (..._args: unknown[]) => void {
27
+ return function (...args: unknown[]): void {
28
+ const apply = (ctor: ClassConstructor, propertyKey: string) => {
29
+ const existingRelations = getRelationMetadata(ctor) || {};
30
+ const relationMetadata: RelationMetadata = { propertyKey, ...options };
31
+
32
+ existingRelations[propertyKey] = relationMetadata;
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ const reflectDefine = (Reflect as any)?.defineMetadata?.bind(Reflect);
35
+
36
+ if (reflectDefine) {
37
+ reflectDefine(RELATIONS_METADATA_KEY, existingRelations, ctor);
38
+ }
39
+ };
40
+
41
+ if (isLegacyArgs(args)) {
42
+ const [target, propertyKey] = args;
43
+
44
+ apply(target.constructor, propertyKey);
45
+
46
+ return;
47
+ }
48
+
49
+ if (isFieldContext(args)) {
50
+ const [, context] = args;
51
+ const propertyKey = context.name;
52
+
53
+ context.addInitializer(function (_this: { constructor: ClassConstructor }) {
54
+ apply(_this.constructor, propertyKey);
55
+ });
56
+ }
57
+ };
58
+ }
59
+
60
+ export function getRelationMetadata(
61
+ entityClass: ClassConstructor,
62
+ ): Record<string, RelationMetadata> | undefined {
63
+ const defined = getDefinedRelations(entityClass);
64
+
65
+ if (defined) {
66
+ const out: Record<string, RelationMetadata> = {};
67
+ for (const [key, rel] of Object.entries(defined)) {
68
+ out[key] = { propertyKey: key, ...rel } as RelationMetadata;
69
+ }
70
+
71
+ return out;
72
+ }
73
+
74
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
+ const reflectGet = (Reflect as any)?.getMetadata?.bind(Reflect);
76
+
77
+ return reflectGet
78
+ ? (reflectGet(RELATIONS_METADATA_KEY, entityClass) || {})
79
+ : {};
80
+ }
81
+
82
+ export function OneToOne(params: {
83
+ target: EntityConstructor | string;
84
+ foreignKey?: string;
85
+ options?: Partial<RelationOptions>;
86
+ }): (..._args: unknown[]) => void {
87
+ return Relation({
88
+ type: 'one-to-one',
89
+ target: params.target,
90
+ foreignKey: params.foreignKey,
91
+ ...params.options,
92
+ });
93
+ }
94
+
95
+ export function OneToMany(params: {
96
+ target: EntityConstructor | string;
97
+ foreignKey: string;
98
+ options?: Partial<RelationOptions>;
99
+ }): (..._args: unknown[]) => void {
100
+ return Relation({
101
+ type: 'one-to-many',
102
+ target: params.target,
103
+ foreignKey: params.foreignKey,
104
+ ...params.options,
105
+ });
106
+ }
107
+
108
+ export function ManyToMany(params: {
109
+ target: EntityConstructor | string;
110
+ joinTable: string;
111
+ options?: Partial<RelationOptions>;
112
+ }): (..._args: unknown[]) => void {
113
+ return Relation({
114
+ type: 'many-to-many',
115
+ target: params.target,
116
+ joinTable: params.joinTable,
117
+ ...params.options,
118
+ });
119
+ }
120
+
121
+
@@ -0,0 +1,5 @@
1
+ export * from './Column';
2
+ export * from './Entity';
3
+ export * from './Relation';
4
+
5
+