@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,288 @@
1
+ import Dexie from 'dexie';
2
+ import { getEntityMetadata } from './metadata/Entity';
3
+ import { AggregationService } from './services/AggregationService';
4
+ import { CloudSyncService } from './services/CloudSyncService';
5
+ import { MigrationManager } from './services/MigrationManager';
6
+ import { RelationLoader } from './services/RelationLoader';
7
+ import { SchemaBuilder } from './services/SchemaBuilder';
8
+ import { logger } from './utils/logger';
9
+ export class Database extends Dexie {
10
+ entitySchemas = new Map();
11
+ migrationManager;
12
+ relationLoader;
13
+ aggregationService;
14
+ cloudSyncService;
15
+ migrationMetadata;
16
+ constructor(config) {
17
+ super(config.name);
18
+ this.migrationManager = new MigrationManager(config.name, config.version);
19
+ this.relationLoader = new RelationLoader(this);
20
+ this.aggregationService = new AggregationService(this);
21
+ this.cloudSyncService = new CloudSyncService(this);
22
+ const currentSchema = SchemaBuilder.buildSchema(config.entities);
23
+ const schemaHash = SchemaBuilder.generateSchemaHash(currentSchema);
24
+ const storedHash = localStorage.getItem(`${config.name}_schema_hash`);
25
+ let version = config.version;
26
+ if (storedHash && storedHash !== schemaHash) {
27
+ logger.info('Schema changed, incrementing version...');
28
+ version = config.version + 1;
29
+ localStorage.setItem(`${config.name}_schema_hash`, schemaHash);
30
+ if (config.migrations && config.migrations.length > 0) {
31
+ logger.info('Running migrations due to schema change...');
32
+ this.migrationManager.autoRunMigrations(config.migrations, this);
33
+ }
34
+ else {
35
+ if (config.onSchemaChangeStrategy === 'all') {
36
+ logger.info('Auto-resetting entire database due to schema change...');
37
+ this.migrationManager.autoResetDatabase(this);
38
+ }
39
+ else if (config.onSchemaChangeStrategy === 'selective') {
40
+ logger.info('Auto-resetting only changed tables...');
41
+ this.migrationManager.autoSelectiveReset(config.entities, this);
42
+ }
43
+ }
44
+ }
45
+ this.version(version).stores(currentSchema);
46
+ this.migrationManager.setDatabase(this);
47
+ config.entities.forEach(entity => {
48
+ const metadata = getEntityMetadata(entity);
49
+ const tableName = metadata?.tableName || entity.name.toLowerCase() + 's';
50
+ this.entitySchemas.set(tableName, entity);
51
+ });
52
+ if (config.cloudSync) {
53
+ this.cloudSyncService.initializeCloudSync(config.cloudSync);
54
+ }
55
+ }
56
+ /**
57
+ * Get repository for entity (TypeORM style)
58
+ *
59
+ * @example
60
+ * const users = db.getRepository(User);
61
+ * const id = await users.add({ name: 'Ann' } as User);
62
+ */
63
+ getRepository(entityClass) {
64
+ const fromRegistry = Array.from(this.entitySchemas.entries()).find(([, ctor]) => ctor === entityClass)?.[0];
65
+ const metadata = getEntityMetadata(entityClass);
66
+ let tableName = fromRegistry
67
+ || metadata?.tableName
68
+ || entityClass.name.toLowerCase() + 's';
69
+ if (!fromRegistry && !metadata?.tableName) {
70
+ const n = entityClass.name;
71
+ if (/Entity$/i.test(n)) {
72
+ const base = n.replace(/Entity$/i, '');
73
+ tableName = base.toLowerCase() + 's';
74
+ }
75
+ }
76
+ return this.table(tableName);
77
+ }
78
+ /**
79
+ * Perform aggregation on entity data
80
+ *
81
+ * @example
82
+ * const result = await db.aggregate({
83
+ * entityClass: Post,
84
+ * options: { where: { category: 'tech' } },
85
+ * });
86
+ */
87
+ async aggregate(params) {
88
+ return this.aggregationService.aggregate(params.entityClass, params.options);
89
+ }
90
+ /**
91
+ * Create database with entity registration
92
+ *
93
+ * @example
94
+ * const db = Database.createDatabase({
95
+ * name: 'app-db',
96
+ * version: 1,
97
+ * entities: [User, Post],
98
+ * });
99
+ */
100
+ static createDatabase(params) {
101
+ const db = new Database({
102
+ name: params.name,
103
+ version: params.version,
104
+ entities: params.entities,
105
+ onSchemaChangeStrategy: params.config?.onSchemaChangeStrategy,
106
+ migrations: params.config?.migrations,
107
+ cloudSync: params.config?.cloudSync,
108
+ });
109
+ return db;
110
+ }
111
+ /**
112
+ * Clear all data from database
113
+ *
114
+ * @example
115
+ * await db.clearAllData();
116
+ */
117
+ async clearAllData() {
118
+ const tableNames = this.tables.map(table => table.name);
119
+ await Promise.all(tableNames.map(name => this.table(name).clear()));
120
+ }
121
+ /**
122
+ * Reset database when schema changes
123
+ *
124
+ * @example
125
+ * await db.resetDatabase();
126
+ */
127
+ async resetDatabase() {
128
+ await this.migrationManager.resetDatabase(this);
129
+ }
130
+ /**
131
+ * Check if schema has changed and reset if needed
132
+ *
133
+ * @example
134
+ * const changed = await db.checkSchemaChanges();
135
+ */
136
+ async checkSchemaChanges() {
137
+ return this.migrationManager.checkSchemaChanges(Array.from(this.entitySchemas.values()), this);
138
+ }
139
+ /**
140
+ * Manually perform selective reset for changed tables only
141
+ *
142
+ * @example
143
+ * await db.performSelectiveReset();
144
+ */
145
+ async performSelectiveReset() {
146
+ await this.migrationManager.performSelectiveReset(Array.from(this.entitySchemas.values()), this);
147
+ }
148
+ /**
149
+ * Run migrations for schema changes
150
+ *
151
+ * @example
152
+ * await db.runMigrations(migrations);
153
+ */
154
+ async runMigrations(migrations) {
155
+ await this.migrationManager.runMigrations(migrations, this);
156
+ }
157
+ /**
158
+ * Get typed table for entity
159
+ *
160
+ * @example
161
+ * const posts = db.getTypedTable(Post);
162
+ */
163
+ getTypedTable(entityClass) {
164
+ const metadata = getEntityMetadata(entityClass);
165
+ const tableName = metadata?.tableName || entityClass.name.toLowerCase() + 's';
166
+ return this.table(tableName);
167
+ }
168
+ /**
169
+ * Get table with proper typing for specific entity
170
+ *
171
+ * @example
172
+ * const users = db.getTableForEntity(User);
173
+ */
174
+ getTableForEntity(entityClass) {
175
+ return this.getTypedTable(entityClass);
176
+ }
177
+ /**
178
+ * Get all entities
179
+ *
180
+ * @example
181
+ * const all = db.getEntities();
182
+ */
183
+ getEntities() {
184
+ return Array.from(this.entitySchemas.values());
185
+ }
186
+ /**
187
+ * Get entity by table name
188
+ *
189
+ * @example
190
+ * const UserClass = db.getEntity('users');
191
+ */
192
+ getEntity(tableName) {
193
+ return this.entitySchemas.get(tableName);
194
+ }
195
+ /**
196
+ * Load relations for an entity
197
+ *
198
+ * @example
199
+ * const userWithRelations = await db.loadRelations({
200
+ * entity: user,
201
+ * entityClass: User,
202
+ * relationNames: ['posts'],
203
+ * });
204
+ */
205
+ async loadRelations(params) {
206
+ return this.relationLoader.loadRelations(params.entity, params.entityClass, params.relationNames);
207
+ }
208
+ /**
209
+ * Load a specific relation by name
210
+ *
211
+ * @example
212
+ * const posts = await db.loadRelationByName({
213
+ * entity: { id: userId },
214
+ * entityClass: User,
215
+ * relationName: 'posts',
216
+ * });
217
+ */
218
+ async loadRelationByName(params) {
219
+ return this.relationLoader.loadRelationByName(params.entity, params.entityClass, params.relationName);
220
+ }
221
+ /**
222
+ * Save entity with relations
223
+ *
224
+ * @example
225
+ * const saved = await db.saveWithRelations({
226
+ * entity: user,
227
+ * entityClass: User,
228
+ * });
229
+ */
230
+ async saveWithRelations(params) {
231
+ return this.relationLoader.saveWithRelations(params.entity, params.entityClass);
232
+ }
233
+ /**
234
+ * Delete entity with cascade handling for relations
235
+ *
236
+ * @example
237
+ * await db.deleteWithRelations({
238
+ * entity: user,
239
+ * entityClass: User,
240
+ * });
241
+ */
242
+ async deleteWithRelations(params) {
243
+ return this.relationLoader
244
+ .deleteWithRelations(params.entity, params.entityClass);
245
+ }
246
+ /**
247
+ * Manual sync with cloud
248
+ */
249
+ async sync() {
250
+ return this.cloudSyncService.sync();
251
+ }
252
+ /**
253
+ * Get sync status
254
+ */
255
+ getSyncStatus() {
256
+ return this.cloudSyncService.getSyncStatus();
257
+ }
258
+ /**
259
+ * Enable cloud sync (if not already enabled)
260
+ */
261
+ async enableCloudSync(config) {
262
+ return this.cloudSyncService.enableCloudSync(config);
263
+ }
264
+ /**
265
+ * Disable cloud sync
266
+ */
267
+ disableCloudSync() {
268
+ this.cloudSyncService.disableCloudSync();
269
+ }
270
+ /**
271
+ * Check if cloud sync is enabled
272
+ */
273
+ isCloudSyncEnabled() {
274
+ return this.cloudSyncService.isCloudSyncEnabled();
275
+ }
276
+ /**
277
+ * Get cloud sync configuration
278
+ */
279
+ getCloudSyncConfig() {
280
+ return this.cloudSyncService.getCloudSyncConfig();
281
+ }
282
+ /**
283
+ * Force sync specific tables
284
+ */
285
+ async syncTables(tableNames) {
286
+ return this.cloudSyncService.syncTables(tableNames);
287
+ }
288
+ }
@@ -0,0 +1,79 @@
1
+ import type { ClassConstructor } from '../services/DecoratorUtils';
2
+ import type { ColumnOptions, CompoundIndexOptions } from '../types';
3
+ export interface ColumnMetadata extends ColumnOptions {
4
+ propertyKey: string;
5
+ }
6
+ export declare abstract class BaseDecorator {
7
+ static getMetadata<T>(key: symbol, target: object | ClassConstructor): T | undefined;
8
+ static setMetadata<T>(key: symbol, value: T, target: object | ClassConstructor): void;
9
+ }
10
+ /**
11
+ * Column decorator: marks a class field as a persisted column with options.
12
+ * @param options - Column options (required, default, indexed, unique, etc.)
13
+ * @returns Property decorator function
14
+ *
15
+ * @example
16
+ * class User {
17
+ * @Column({ required: true })
18
+ * name!: string;
19
+ * }
20
+ */
21
+ export declare function Column(options?: ColumnOptions): (...args: unknown[]) => void;
22
+ /**
23
+ * Primary key decorator: marks a field as primary key.
24
+ * @param params - Options (autoIncrement: defaults to true)
25
+ * @returns Property decorator function
26
+ *
27
+ * @example
28
+ * class User {
29
+ * @PrimaryKey()
30
+ * id!: number;
31
+ * }
32
+ */
33
+ export declare function PrimaryKey(params?: {
34
+ autoIncrement?: boolean;
35
+ }): (...args: unknown[]) => void;
36
+ /**
37
+ * Index decorator: marks a field as indexed.
38
+ * @returns Property decorator function
39
+ *
40
+ * @example
41
+ * class Post { @Index() @Column() authorId?: number }
42
+ */
43
+ export declare function Index(): (...args: unknown[]) => void;
44
+ /**
45
+ * Unique decorator: marks a field as unique.
46
+ * @returns Property decorator function
47
+ *
48
+ * @example
49
+ * class User { @Unique() @Column() email!: string }
50
+ */
51
+ export declare function Unique(): (...args: unknown[]) => void;
52
+ /**
53
+ * CompoundIndex decorator: defines a compound index on multiple fields.
54
+ * @param options - name (optional), unique (optional), columns (required)
55
+ * @returns Class decorator function
56
+ *
57
+ * @example
58
+ * @CompoundIndex({ columns: ['firstName', 'lastName'] })
59
+ * class User { /* fields *\/ }
60
+ */
61
+ export declare function CompoundIndex(options: CompoundIndexOptions): (target: ClassConstructor) => void;
62
+ /**
63
+ * Retrieve column metadata for a class.
64
+ * @param target - The entity class (constructor) or prototype
65
+ * @returns Record mapping propertyKey -> ColumnMetadata
66
+ *
67
+ * @example
68
+ * const cols = getColumnMetadata(User);
69
+ */
70
+ export declare function getColumnMetadata(target: object | ClassConstructor): Record<string, ColumnMetadata>;
71
+ /**
72
+ * Retrieve compound index metadata for a class.
73
+ * @param target - The entity class (constructor) or prototype
74
+ * @returns Array of compound index options
75
+ *
76
+ * @example
77
+ * const indexes = getCompoundIndexMetadata(User);
78
+ */
79
+ export declare function getCompoundIndexMetadata(target: object | ClassConstructor): CompoundIndexOptions[];
@@ -0,0 +1,236 @@
1
+ // reflect-metadata no longer required
2
+ import { isFieldContext, isLegacyArgs, } from '../services/DecoratorUtils';
3
+ import { getDefinedColumns, getDefinedCompoundIndexes, } from '../services/EntityRegistry';
4
+ const COLUMNS_METADATA_KEY = Symbol('columns');
5
+ const COMPOUND_INDEXES_METADATA_KEY = Symbol('compoundIndexes');
6
+ export class BaseDecorator {
7
+ static getMetadata(key, target) {
8
+ // Access reflect-metadata only if available
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ const reflectGet = Reflect?.getMetadata?.bind(Reflect);
11
+ return reflectGet ? reflectGet(key, target) : undefined;
12
+ }
13
+ static setMetadata(key, value, target) {
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ const reflectDefine = Reflect?.defineMetadata?.bind(Reflect);
16
+ if (reflectDefine) {
17
+ reflectDefine(key, value, target);
18
+ }
19
+ }
20
+ }
21
+ /**
22
+ * Column decorator: marks a class field as a persisted column with options.
23
+ * @param options - Column options (required, default, indexed, unique, etc.)
24
+ * @returns Property decorator function
25
+ *
26
+ * @example
27
+ * class User {
28
+ * @Column({ required: true })
29
+ * name!: string;
30
+ * }
31
+ */
32
+ export function Column(options = {}) {
33
+ return function (...args) {
34
+ // Legacy TS decorators
35
+ if (isLegacyArgs(args)) {
36
+ const [target, propertyKey] = args;
37
+ const columns = BaseDecorator.getMetadata(COLUMNS_METADATA_KEY, target.constructor) || {};
38
+ columns[propertyKey] = { propertyKey, ...options };
39
+ BaseDecorator.setMetadata(COLUMNS_METADATA_KEY, columns, target.constructor);
40
+ return;
41
+ }
42
+ // Standard decorators (TC39)
43
+ if (isFieldContext(args)) {
44
+ const [, context] = args;
45
+ const propertyKey = context.name;
46
+ context.addInitializer(function () {
47
+ const ctor = this.constructor;
48
+ const columns = BaseDecorator.getMetadata(COLUMNS_METADATA_KEY, ctor) || {};
49
+ columns[propertyKey] = { propertyKey, ...options };
50
+ BaseDecorator.setMetadata(COLUMNS_METADATA_KEY, columns, ctor);
51
+ });
52
+ }
53
+ };
54
+ }
55
+ /**
56
+ * Primary key decorator: marks a field as primary key.
57
+ * @param params - Options (autoIncrement: defaults to true)
58
+ * @returns Property decorator function
59
+ *
60
+ * @example
61
+ * class User {
62
+ * @PrimaryKey()
63
+ * id!: number;
64
+ * }
65
+ */
66
+ export function PrimaryKey(params = {}) {
67
+ return function (...args) {
68
+ const apply = (ctor, propertyKey) => {
69
+ const columns = BaseDecorator.getMetadata(COLUMNS_METADATA_KEY, ctor) || {};
70
+ columns[propertyKey] = {
71
+ propertyKey,
72
+ primaryKey: true,
73
+ autoIncrement: params.autoIncrement ?? true,
74
+ };
75
+ BaseDecorator.setMetadata(COLUMNS_METADATA_KEY, columns, ctor);
76
+ };
77
+ if (isLegacyArgs(args)) {
78
+ const [target, propertyKey] = args;
79
+ apply(target.constructor, propertyKey);
80
+ return;
81
+ }
82
+ if (isFieldContext(args)) {
83
+ const [, context] = args;
84
+ const propertyKey = context.name;
85
+ context.addInitializer(function () {
86
+ apply(this.constructor, propertyKey);
87
+ });
88
+ }
89
+ };
90
+ }
91
+ /**
92
+ * Index decorator: marks a field as indexed.
93
+ * @returns Property decorator function
94
+ *
95
+ * @example
96
+ * class Post { @Index() @Column() authorId?: number }
97
+ */
98
+ export function Index() {
99
+ return function (...args) {
100
+ const apply = (ctor, propertyKey) => {
101
+ const columns = BaseDecorator.getMetadata(COLUMNS_METADATA_KEY, ctor) || {};
102
+ if (columns[propertyKey]) {
103
+ columns[propertyKey].indexed = true;
104
+ }
105
+ else {
106
+ columns[propertyKey] = { propertyKey, indexed: true };
107
+ }
108
+ BaseDecorator.setMetadata(COLUMNS_METADATA_KEY, columns, ctor);
109
+ };
110
+ if (isLegacyArgs(args)) {
111
+ const [target, propertyKey] = args;
112
+ apply(target.constructor, propertyKey);
113
+ return;
114
+ }
115
+ if (isFieldContext(args)) {
116
+ const [, context] = args;
117
+ const propertyKey = context.name;
118
+ context.addInitializer(function () {
119
+ apply(this.constructor, propertyKey);
120
+ });
121
+ }
122
+ };
123
+ }
124
+ /**
125
+ * Unique decorator: marks a field as unique.
126
+ * @returns Property decorator function
127
+ *
128
+ * @example
129
+ * class User { @Unique() @Column() email!: string }
130
+ */
131
+ export function Unique() {
132
+ return function (...args) {
133
+ const apply = (ctor, propertyKey) => {
134
+ const columns = BaseDecorator.getMetadata(COLUMNS_METADATA_KEY, ctor) || {};
135
+ if (columns[propertyKey]) {
136
+ columns[propertyKey].unique = true;
137
+ }
138
+ else {
139
+ columns[propertyKey] = { propertyKey, unique: true };
140
+ }
141
+ BaseDecorator.setMetadata(COLUMNS_METADATA_KEY, columns, ctor);
142
+ };
143
+ if (isLegacyArgs(args)) {
144
+ const [target, propertyKey] = args;
145
+ apply(target.constructor, propertyKey);
146
+ return;
147
+ }
148
+ if (isFieldContext(args)) {
149
+ const [, context] = args;
150
+ const propertyKey = context.name;
151
+ context.addInitializer(function () {
152
+ apply(this.constructor, propertyKey);
153
+ });
154
+ }
155
+ };
156
+ }
157
+ /**
158
+ * CompoundIndex decorator: defines a compound index on multiple fields.
159
+ * @param options - name (optional), unique (optional), columns (required)
160
+ * @returns Class decorator function
161
+ *
162
+ * @example
163
+ * @CompoundIndex({ columns: ['firstName', 'lastName'] })
164
+ * class User { /* fields *\/ }
165
+ */
166
+ export function CompoundIndex(options) {
167
+ return function (target) {
168
+ const compoundIndexes = BaseDecorator.getMetadata(COMPOUND_INDEXES_METADATA_KEY, target) || [];
169
+ compoundIndexes.push({
170
+ name: options.name || options.columns.join('_'),
171
+ unique: options.unique || false,
172
+ columns: options.columns,
173
+ });
174
+ BaseDecorator
175
+ .setMetadata(COMPOUND_INDEXES_METADATA_KEY, compoundIndexes, target);
176
+ };
177
+ }
178
+ /**
179
+ * Retrieve column metadata for a class.
180
+ * @param target - The entity class (constructor) or prototype
181
+ * @returns Record mapping propertyKey -> ColumnMetadata
182
+ *
183
+ * @example
184
+ * const cols = getColumnMetadata(User);
185
+ */
186
+ export function getColumnMetadata(target) {
187
+ const ctor = (typeof target === 'function')
188
+ ? target
189
+ : target.constructor;
190
+ const defined = getDefinedColumns(ctor);
191
+ if (defined) {
192
+ const out = {};
193
+ for (const [key, cfg] of Object.entries(defined)) {
194
+ out[key] = { propertyKey: key, ...cfg };
195
+ }
196
+ // If no explicit primary key was defined via defineEntity,
197
+ // assume conventional auto-increment primary key on `id`.
198
+ const hasPrimary = Object.values(out).some(c => c.primaryKey);
199
+ if (!hasPrimary) {
200
+ if (!out.id) {
201
+ out.id = { propertyKey: 'id', primaryKey: true, autoIncrement: true };
202
+ }
203
+ else {
204
+ out.id.primaryKey = true;
205
+ if (out.id.autoIncrement === undefined) {
206
+ out.id.autoIncrement = true;
207
+ }
208
+ }
209
+ }
210
+ return out;
211
+ }
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ const reflectGet = Reflect?.getMetadata?.bind(Reflect);
214
+ return reflectGet ? (reflectGet(COLUMNS_METADATA_KEY, ctor) || {}) : {};
215
+ }
216
+ /**
217
+ * Retrieve compound index metadata for a class.
218
+ * @param target - The entity class (constructor) or prototype
219
+ * @returns Array of compound index options
220
+ *
221
+ * @example
222
+ * const indexes = getCompoundIndexMetadata(User);
223
+ */
224
+ export function getCompoundIndexMetadata(target) {
225
+ const ctor = (typeof target === 'function')
226
+ ? target
227
+ : target.constructor;
228
+ const defined = getDefinedCompoundIndexes(ctor);
229
+ if (defined) {
230
+ return defined
231
+ .map(ci => ({ name: ci.name, unique: ci.unique, columns: ci.columns }));
232
+ }
233
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
234
+ const reflectGet = Reflect?.getMetadata?.bind(Reflect);
235
+ return reflectGet ? (reflectGet(COMPOUND_INDEXES_METADATA_KEY, ctor) || []) : [];
236
+ }
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ import type { ClassConstructor } from '../services/DecoratorUtils';
3
+ import type { EntityOptions } from '../types';
4
+ export interface EntityMetadata {
5
+ tableName: string;
6
+ schema?: z.ZodSchema<unknown>;
7
+ timestamps?: boolean;
8
+ }
9
+ /**
10
+ * Entity decorator: marks a class as a persisted entity and attaches metadata.
11
+ *
12
+ * @param options - Optional entity options:
13
+ * - tableName: custom table name (defaults to className + 's')
14
+ * - schema: zod schema used for validation
15
+ * - timestamps: enable automatic timestamp fields (future use)
16
+ * @returns Class decorator function
17
+ *
18
+ * @example
19
+ * @Entity({ tableName: 'users', schema: userSchema })
20
+ * class User { /* ... *\/ }
21
+ */
22
+ export declare function Entity(_options?: EntityOptions): (_target: ClassConstructor) => void;
23
+ /**
24
+ * Get entity metadata previously set by @Entity.
25
+ * @param target - The entity class (constructor)
26
+ * @returns EntityMetadata or undefined if not set
27
+ *
28
+ * @example
29
+ * const meta = getEntityMetadata(User);
30
+ * console.log(meta?.tableName);
31
+ */
32
+ export declare function getEntityMetadata(target: ClassConstructor): EntityMetadata | undefined;
@@ -0,0 +1,44 @@
1
+ import { getDefinedEntityMeta } from '../services/EntityRegistry';
2
+ const ENTITY_METADATA_KEY = Symbol('entity');
3
+ /**
4
+ * Entity decorator: marks a class as a persisted entity and attaches metadata.
5
+ *
6
+ * @param options - Optional entity options:
7
+ * - tableName: custom table name (defaults to className + 's')
8
+ * - schema: zod schema used for validation
9
+ * - timestamps: enable automatic timestamp fields (future use)
10
+ * @returns Class decorator function
11
+ *
12
+ * @example
13
+ * @Entity({ tableName: 'users', schema: userSchema })
14
+ * class User { /* ... *\/ }
15
+ */
16
+ export function Entity(_options = {}) {
17
+ // No-op decorator retained for backwards compatibility
18
+ return function (_target) { };
19
+ }
20
+ /**
21
+ * Get entity metadata previously set by @Entity.
22
+ * @param target - The entity class (constructor)
23
+ * @returns EntityMetadata or undefined if not set
24
+ *
25
+ * @example
26
+ * const meta = getEntityMetadata(User);
27
+ * console.log(meta?.tableName);
28
+ */
29
+ export function getEntityMetadata(target) {
30
+ const defined = getDefinedEntityMeta(target);
31
+ if (defined) {
32
+ return {
33
+ tableName: defined.tableName,
34
+ schema: defined.schema,
35
+ timestamps: defined.timestamps,
36
+ };
37
+ }
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ const reflectGet = Reflect?.getMetadata?.bind(Reflect);
40
+ if (reflectGet) {
41
+ return reflectGet(ENTITY_METADATA_KEY, target);
42
+ }
43
+ return undefined;
44
+ }