@indexeddb-orm/idb-orm 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/extensions.json +5 -0
- package/README.md +1280 -0
- package/angular-demo-app/README.md +84 -0
- package/angular-demo-app/angular.json +109 -0
- package/angular-demo-app/package-lock.json +14215 -0
- package/angular-demo-app/package.json +41 -0
- package/angular-demo-app/src/app/app.component.ts +481 -0
- package/angular-demo-app/src/app/app.routes.ts +8 -0
- package/angular-demo-app/src/app/components/actions.component.ts +202 -0
- package/angular-demo-app/src/app/components/cloud-sync-demo.component.ts +296 -0
- package/angular-demo-app/src/app/components/live-query-demo.component.ts +307 -0
- package/angular-demo-app/src/app/components/main-info.component.ts +148 -0
- package/angular-demo-app/src/app/components/posts-live-query-demo.component.ts +336 -0
- package/angular-demo-app/src/app/components/typescript-demo.component.ts +268 -0
- package/angular-demo-app/src/entities/post-tag.entity.ts +25 -0
- package/angular-demo-app/src/entities/post.entity.ts +49 -0
- package/angular-demo-app/src/entities/profile.entity.ts +42 -0
- package/angular-demo-app/src/entities/tag.entity.ts +36 -0
- package/angular-demo-app/src/entities/user.entity.ts +59 -0
- package/angular-demo-app/src/favicon.ico +1 -0
- package/angular-demo-app/src/index.html +16 -0
- package/angular-demo-app/src/main.ts +13 -0
- package/angular-demo-app/src/services/app-logic.service.ts +449 -0
- package/angular-demo-app/src/services/cloud-sync.service.ts +95 -0
- package/angular-demo-app/src/services/database.service.ts +26 -0
- package/angular-demo-app/src/services/live-query.service.ts +63 -0
- package/angular-demo-app/src/services/posts-live-query.service.ts +86 -0
- package/angular-demo-app/src/services/typescript-demo.service.ts +59 -0
- package/angular-demo-app/src/styles.scss +50 -0
- package/angular-demo-app/tsconfig.app.json +13 -0
- package/angular-demo-app/tsconfig.json +34 -0
- package/angular-demo-app/tsconfig.spec.json +13 -0
- package/dist/Database.d.ts +206 -0
- package/dist/Database.js +288 -0
- package/dist/decorators/Column.d.ts +79 -0
- package/dist/decorators/Column.js +236 -0
- package/dist/decorators/Entity.d.ts +32 -0
- package/dist/decorators/Entity.js +44 -0
- package/dist/decorators/Relation.d.ts +70 -0
- package/dist/decorators/Relation.js +120 -0
- package/dist/decorators/index.d.ts +3 -0
- package/dist/decorators/index.js +3 -0
- package/dist/errors/ValidationError.d.ts +4 -0
- package/dist/errors/ValidationError.js +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/metadata/Column.d.ts +8 -0
- package/dist/metadata/Column.js +44 -0
- package/dist/metadata/Entity.d.ts +11 -0
- package/dist/metadata/Entity.js +21 -0
- package/dist/metadata/Relation.d.ts +20 -0
- package/dist/metadata/Relation.js +74 -0
- package/dist/metadata/index.d.ts +3 -0
- package/dist/metadata/index.js +3 -0
- package/dist/services/AggregationService.d.ts +38 -0
- package/dist/services/AggregationService.js +229 -0
- package/dist/services/BaseEntity.d.ts +32 -0
- package/dist/services/BaseEntity.js +62 -0
- package/dist/services/CloudSyncService.d.ts +100 -0
- package/dist/services/CloudSyncService.js +196 -0
- package/dist/services/DecoratorUtils.d.ts +12 -0
- package/dist/services/DecoratorUtils.js +10 -0
- package/dist/services/EntityFactory.d.ts +25 -0
- package/dist/services/EntityFactory.js +27 -0
- package/dist/services/EntityRegistry.d.ts +61 -0
- package/dist/services/EntityRegistry.js +56 -0
- package/dist/services/EntitySchema.d.ts +56 -0
- package/dist/services/EntitySchema.js +125 -0
- package/dist/services/MigrationManager.d.ts +70 -0
- package/dist/services/MigrationManager.js +181 -0
- package/dist/services/RelationLoader.d.ts +66 -0
- package/dist/services/RelationLoader.js +310 -0
- package/dist/services/SchemaBuilder.d.ts +68 -0
- package/dist/services/SchemaBuilder.js +191 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.js +7 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.js +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +16 -0
- package/eslint.config.js +49 -0
- package/homepage/favicon.svg +36 -0
- package/homepage/index.html +1725 -0
- package/package.json +78 -0
- package/react-demo-app/README.md +61 -0
- package/react-demo-app/eslint.config.js +60 -0
- package/react-demo-app/index.html +13 -0
- package/react-demo-app/package-lock.json +4955 -0
- package/react-demo-app/package.json +39 -0
- package/react-demo-app/src/App.tsx +172 -0
- package/react-demo-app/src/assets/react.svg +1 -0
- package/react-demo-app/src/components/Actions.tsx +171 -0
- package/react-demo-app/src/components/CloudSyncDemo.tsx +191 -0
- package/react-demo-app/src/components/LiveQueryDemo.tsx +122 -0
- package/react-demo-app/src/components/MainInfo.tsx +75 -0
- package/react-demo-app/src/components/PostsLiveQueryDemo.tsx +185 -0
- package/react-demo-app/src/components/TypeScriptDemo.tsx +190 -0
- package/react-demo-app/src/database/Database.ts +30 -0
- package/react-demo-app/src/entities/Post.ts +48 -0
- package/react-demo-app/src/entities/PostTag.ts +26 -0
- package/react-demo-app/src/entities/Profile.ts +41 -0
- package/react-demo-app/src/entities/Tag.ts +35 -0
- package/react-demo-app/src/entities/User.ts +61 -0
- package/react-demo-app/src/hooks/useAppLogic.ts +565 -0
- package/react-demo-app/src/hooks/useCloudSyncDemo.ts +84 -0
- package/react-demo-app/src/hooks/useLiveQueryDemo.ts +68 -0
- package/react-demo-app/src/hooks/usePostsLiveQueryDemo.ts +64 -0
- package/react-demo-app/src/hooks/useTypeScriptDemo.ts +43 -0
- package/react-demo-app/src/index.css +26 -0
- package/react-demo-app/src/main.tsx +18 -0
- package/react-demo-app/src/migrations/001-add-user-email-index.ts +17 -0
- package/react-demo-app/src/migrations/002-add-post-category.ts +37 -0
- package/react-demo-app/src/migrations/index.ts +8 -0
- package/react-demo-app/src/vite-env.d.ts +1 -0
- package/react-demo-app/tsconfig.app.json +22 -0
- package/react-demo-app/tsconfig.json +6 -0
- package/react-demo-app/vite.config.ts +10 -0
- package/src/Database.ts +405 -0
- package/src/errors/ValidationError.ts +9 -0
- package/src/index.ts +13 -0
- package/src/metadata/Column.ts +74 -0
- package/src/metadata/Entity.ts +42 -0
- package/src/metadata/Relation.ts +121 -0
- package/src/metadata/index.ts +5 -0
- package/src/services/AggregationService.ts +348 -0
- package/src/services/BaseEntity.ts +77 -0
- package/src/services/CloudSyncService.ts +248 -0
- package/src/services/EntityFactory.ts +35 -0
- package/src/services/EntityRegistry.ts +109 -0
- package/src/services/EntitySchema.ts +154 -0
- package/src/services/MigrationManager.ts +276 -0
- package/src/services/RelationLoader.ts +532 -0
- package/src/services/SchemaBuilder.ts +237 -0
- package/src/services/index.ts +7 -0
- package/src/types.d.ts +1 -0
- package/src/types.ts +169 -0
- package/src/utils/logger.ts +40 -0
- package/svelte-demo-app/README.md +61 -0
- package/svelte-demo-app/package-lock.json +3000 -0
- package/svelte-demo-app/package.json +30 -0
- package/svelte-demo-app/src/app.d.ts +12 -0
- package/svelte-demo-app/src/app.html +13 -0
- package/svelte-demo-app/src/components/Actions.svelte +121 -0
- package/svelte-demo-app/src/components/CloudSyncDemo.svelte +333 -0
- package/svelte-demo-app/src/components/LiveQueryDemo.svelte +191 -0
- package/svelte-demo-app/src/components/MainInfo.svelte +133 -0
- package/svelte-demo-app/src/components/PostsLiveQueryDemo.svelte +330 -0
- package/svelte-demo-app/src/components/TypeScriptDemo.svelte +251 -0
- package/svelte-demo-app/src/database/Database.ts +29 -0
- package/svelte-demo-app/src/entities/Post.ts +46 -0
- package/svelte-demo-app/src/entities/PostTag.ts +22 -0
- package/svelte-demo-app/src/entities/Profile.ts +39 -0
- package/svelte-demo-app/src/entities/Tag.ts +33 -0
- package/svelte-demo-app/src/entities/User.ts +62 -0
- package/svelte-demo-app/src/lib/database/Database.ts +30 -0
- package/svelte-demo-app/src/lib/entities/Post.ts +47 -0
- package/svelte-demo-app/src/lib/entities/PostTag.ts +23 -0
- package/svelte-demo-app/src/lib/entities/Profile.ts +40 -0
- package/svelte-demo-app/src/lib/entities/Tag.ts +34 -0
- package/svelte-demo-app/src/lib/entities/User.ts +59 -0
- package/svelte-demo-app/src/lib/index.ts +7 -0
- package/svelte-demo-app/src/lib/migrations/001-add-user-email-index.ts +17 -0
- package/svelte-demo-app/src/lib/migrations/002-add-post-category.ts +37 -0
- package/svelte-demo-app/src/lib/migrations/index.ts +8 -0
- package/svelte-demo-app/src/migrations/001-add-user-email-index.ts +17 -0
- package/svelte-demo-app/src/migrations/002-add-post-category.ts +37 -0
- package/svelte-demo-app/src/migrations/index.ts +8 -0
- package/svelte-demo-app/src/routes/+layout.js +3 -0
- package/svelte-demo-app/src/routes/+layout.svelte +228 -0
- package/svelte-demo-app/src/routes/+page.js +3 -0
- package/svelte-demo-app/src/routes/+page.svelte +1305 -0
- package/svelte-demo-app/src/stores/appStore.js +603 -0
- package/svelte-demo-app/svelte.config.js +18 -0
- package/svelte-demo-app/tsconfig.json +14 -0
- package/svelte-demo-app/vite.config.ts +6 -0
- package/tests/aggregation.e2e.test.ts +87 -0
- package/tests/base-entity.e2e.test.ts +47 -0
- package/tests/database-api.e2e.test.ts +177 -0
- package/tests/decorators.e2e.test.ts +40 -0
- package/tests/entity-schema.e2e.test.ts +58 -0
- package/tests/relation-loader-table-names.test.ts +192 -0
- package/tests/relations.e2e.test.ts +178 -0
- package/tests/zod-runtime.e2e.test.ts +69 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +21 -0
- package/vitest.setup.ts +27 -0
- package/vue-demo-app/README.md +61 -0
- package/vue-demo-app/index.html +13 -0
- package/vue-demo-app/package-lock.json +1537 -0
- package/vue-demo-app/package.json +27 -0
- package/vue-demo-app/src/App.vue +100 -0
- package/vue-demo-app/src/components/Actions.vue +135 -0
- package/vue-demo-app/src/components/CloudSyncDemo.vue +139 -0
- package/vue-demo-app/src/components/LiveQueryDemo.vue +122 -0
- package/vue-demo-app/src/components/MainInfo.vue +80 -0
- package/vue-demo-app/src/components/PostsLiveQueryDemo.vue +136 -0
- package/vue-demo-app/src/components/TypeScriptDemo.vue +133 -0
- package/vue-demo-app/src/database/Database.ts +29 -0
- package/vue-demo-app/src/entities/Post.ts +48 -0
- package/vue-demo-app/src/entities/PostTag.ts +24 -0
- package/vue-demo-app/src/entities/Profile.ts +41 -0
- package/vue-demo-app/src/entities/Tag.ts +35 -0
- package/vue-demo-app/src/entities/User.ts +61 -0
- package/vue-demo-app/src/main.ts +29 -0
- package/vue-demo-app/src/migrations/001-add-user-email-index.ts +23 -0
- package/vue-demo-app/src/migrations/002-add-post-category.ts +46 -0
- package/vue-demo-app/src/migrations/index.ts +14 -0
- package/vue-demo-app/src/services/useAppLogic.ts +565 -0
- package/vue-demo-app/src/services/useCloudSyncDemo.ts +84 -0
- package/vue-demo-app/src/services/useLiveQueryDemo.ts +82 -0
- package/vue-demo-app/src/services/usePostsLiveQueryDemo.ts +77 -0
- package/vue-demo-app/src/services/useTypeScriptDemo.ts +56 -0
- package/vue-demo-app/src/vite-env.d.ts +1 -0
- package/vue-demo-app/tsconfig.json +25 -0
- package/vue-demo-app/tsconfig.node.json +10 -0
- package/vue-demo-app/vite.config.ts +16 -0
package/dist/Database.js
ADDED
|
@@ -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
|
+
}
|