@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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getColumnMetadata, getCompoundIndexMetadata,
|
|
3
|
+
} from '../metadata/Column';
|
|
4
|
+
import { getEntityMetadata } from '../metadata/Entity';
|
|
5
|
+
import type { EntityConstructor } from '../types';
|
|
6
|
+
import { logger } from '../utils/logger';
|
|
7
|
+
import { getDefinedCompoundIndexes } from './EntityRegistry';
|
|
8
|
+
|
|
9
|
+
export interface TableSchema {
|
|
10
|
+
tableName: string;
|
|
11
|
+
schema: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SchemaChange {
|
|
15
|
+
tableName: string;
|
|
16
|
+
changeType: 'added' | 'removed' | 'modified';
|
|
17
|
+
oldSchema?: string;
|
|
18
|
+
newSchema?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class SchemaBuilder {
|
|
22
|
+
/**
|
|
23
|
+
* Build indexeddb schema from entities
|
|
24
|
+
* @param entities - Array of entity constructors
|
|
25
|
+
* @returns IndexedDB schema object with table definitions
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const schema = SchemaBuilder.buildSchema([User, Post]);
|
|
29
|
+
*/
|
|
30
|
+
static buildSchema(entities: EntityConstructor[]): Record<string, string> {
|
|
31
|
+
const schema: Record<string, string> = {};
|
|
32
|
+
|
|
33
|
+
schema.migrationMetadata = 'key, value, updatedAt';
|
|
34
|
+
|
|
35
|
+
entities.forEach(entity => {
|
|
36
|
+
const metadata = getEntityMetadata(entity);
|
|
37
|
+
const tableName = metadata?.tableName || entity.name.toLowerCase() + 's';
|
|
38
|
+
|
|
39
|
+
const columns = getColumnMetadata(entity);
|
|
40
|
+
const compoundIndexes = getDefinedCompoundIndexes(entity)
|
|
41
|
+
|| getCompoundIndexMetadata(entity);
|
|
42
|
+
|
|
43
|
+
let primaryKeySpec = '';
|
|
44
|
+
const indexSpecs: string[] = [];
|
|
45
|
+
|
|
46
|
+
Object.entries(columns).forEach(([propertyKey, columnMeta]) => {
|
|
47
|
+
if (columnMeta.primaryKey) {
|
|
48
|
+
primaryKeySpec = columnMeta.autoIncrement
|
|
49
|
+
? `++${propertyKey}`
|
|
50
|
+
: propertyKey;
|
|
51
|
+
} else if (columnMeta.unique) {
|
|
52
|
+
indexSpecs.push(`&${propertyKey}`);
|
|
53
|
+
} else if (columnMeta.indexed) {
|
|
54
|
+
indexSpecs.push(`${propertyKey}`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const compoundIndexStrings: string[] = [];
|
|
59
|
+
|
|
60
|
+
compoundIndexes.forEach(compoundIndex => {
|
|
61
|
+
const indexColumns = compoundIndex.columns.join('+');
|
|
62
|
+
const bracketed = `[${indexColumns}]`;
|
|
63
|
+
const spec = compoundIndex.unique ? `&${bracketed}` : `${bracketed}`;
|
|
64
|
+
|
|
65
|
+
compoundIndexStrings.push(spec);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const parts = [
|
|
69
|
+
primaryKeySpec, ...indexSpecs, ...compoundIndexStrings,
|
|
70
|
+
].filter(Boolean);
|
|
71
|
+
|
|
72
|
+
schema[tableName] = parts.join(',');
|
|
73
|
+
|
|
74
|
+
if (!schema[tableName]) {
|
|
75
|
+
schema[tableName] = '++id';
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return schema;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Build detailed table schemas for comparison
|
|
84
|
+
* @param entities - Array of entity constructors
|
|
85
|
+
* @returns Array of detailed table schema objects
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* const tables = SchemaBuilder.buildTableSchemas([User, Post]);
|
|
89
|
+
*/
|
|
90
|
+
static buildTableSchemas(entities: EntityConstructor[]): TableSchema[] {
|
|
91
|
+
return entities.map(entity => {
|
|
92
|
+
const metadata = getEntityMetadata(entity);
|
|
93
|
+
const tableName = metadata?.tableName || entity.name.toLowerCase() + 's';
|
|
94
|
+
const columns = getColumnMetadata(entity);
|
|
95
|
+
const compoundIndexes = getCompoundIndexMetadata(entity);
|
|
96
|
+
|
|
97
|
+
let primaryKeySpec = '';
|
|
98
|
+
const indexSpecs: string[] = [];
|
|
99
|
+
|
|
100
|
+
Object.entries(columns).forEach(([propertyKey, columnMeta]) => {
|
|
101
|
+
if (columnMeta.primaryKey) {
|
|
102
|
+
primaryKeySpec = columnMeta.autoIncrement
|
|
103
|
+
? `++${propertyKey}`
|
|
104
|
+
: propertyKey;
|
|
105
|
+
} else if (columnMeta.unique) {
|
|
106
|
+
indexSpecs.push(`&${propertyKey}`);
|
|
107
|
+
} else if (columnMeta.indexed) {
|
|
108
|
+
indexSpecs.push(`${propertyKey}`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const compoundIndexStrings: string[] = [];
|
|
113
|
+
|
|
114
|
+
compoundIndexes.forEach(compoundIndex => {
|
|
115
|
+
const indexColumns = compoundIndex.columns.join('+');
|
|
116
|
+
const bracketed = `[${indexColumns}]`;
|
|
117
|
+
const spec = compoundIndex.unique ? `&${bracketed}` : `${bracketed}`;
|
|
118
|
+
compoundIndexStrings.push(spec);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const parts = [
|
|
122
|
+
primaryKeySpec, ...indexSpecs, ...compoundIndexStrings,
|
|
123
|
+
].filter(Boolean);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
tableName,
|
|
127
|
+
schema: parts.join(','),
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate hash for schema to detect changes
|
|
134
|
+
* @param schema - Schema object to hash
|
|
135
|
+
* @returns Hash string for schema comparison
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const hash = SchemaBuilder.generateSchemaHash(schema);
|
|
139
|
+
*/
|
|
140
|
+
static generateSchemaHash(schema: Record<string, string>): string {
|
|
141
|
+
const schemaString = JSON.stringify(schema, Object.keys(schema).sort());
|
|
142
|
+
|
|
143
|
+
return btoa(schemaString).slice(0, 16);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Compare two schema arrays and return changes
|
|
148
|
+
* @param oldSchemas - Previous schema array
|
|
149
|
+
* @param newSchemas - Current schema array
|
|
150
|
+
* @returns Array of schema changes detected
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* const changes = SchemaBuilder.compareSchemas(oldSchemas, newSchemas);
|
|
154
|
+
*/
|
|
155
|
+
static compareSchemas(
|
|
156
|
+
oldSchemas: TableSchema[],
|
|
157
|
+
newSchemas: TableSchema[],
|
|
158
|
+
): SchemaChange[] {
|
|
159
|
+
const changes: SchemaChange[] = [];
|
|
160
|
+
const oldSchemaMap = new Map(oldSchemas.map(s => [s.tableName, s]));
|
|
161
|
+
const newSchemaMap = new Map(newSchemas.map(s => [s.tableName, s]));
|
|
162
|
+
|
|
163
|
+
newSchemas.forEach(newSchema => {
|
|
164
|
+
if (!oldSchemaMap.has(newSchema.tableName)) {
|
|
165
|
+
changes.push({
|
|
166
|
+
tableName: newSchema.tableName,
|
|
167
|
+
changeType: 'added',
|
|
168
|
+
newSchema: newSchema.schema,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
oldSchemas.forEach(oldSchema => {
|
|
174
|
+
if (!newSchemaMap.has(oldSchema.tableName)) {
|
|
175
|
+
changes.push({
|
|
176
|
+
tableName: oldSchema.tableName,
|
|
177
|
+
changeType: 'removed',
|
|
178
|
+
oldSchema: oldSchema.schema,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
newSchemas.forEach(newSchema => {
|
|
184
|
+
const oldSchema = oldSchemaMap.get(newSchema.tableName);
|
|
185
|
+
|
|
186
|
+
if (oldSchema && oldSchema.schema !== newSchema.schema) {
|
|
187
|
+
changes.push({
|
|
188
|
+
tableName: newSchema.tableName,
|
|
189
|
+
changeType: 'modified',
|
|
190
|
+
oldSchema: oldSchema.schema,
|
|
191
|
+
newSchema: newSchema.schema,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return changes;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Store table schemas in localStorage
|
|
201
|
+
* @param dbName - Database name for key generation
|
|
202
|
+
* @param tableSchemas - Array of table schemas to store
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* SchemaBuilder.storeTableSchemas('app-db', tables);
|
|
206
|
+
*/
|
|
207
|
+
static storeTableSchemas(dbName: string, tableSchemas: TableSchema[]): void {
|
|
208
|
+
const key = `${dbName}_table_schemas`;
|
|
209
|
+
const serialized = JSON.stringify(tableSchemas);
|
|
210
|
+
|
|
211
|
+
localStorage.setItem(key, serialized);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get stored table schemas from localStorage
|
|
216
|
+
* @param dbName - Database name for key lookup
|
|
217
|
+
* @returns Array of stored table schemas or empty array if none found
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* const tables = SchemaBuilder.getStoredTableSchemas('app-db');
|
|
221
|
+
*/
|
|
222
|
+
static getStoredTableSchemas(dbName: string): TableSchema[] {
|
|
223
|
+
const key = `${dbName}_table_schemas`;
|
|
224
|
+
const stored = localStorage.getItem(key);
|
|
225
|
+
if (!stored) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
return JSON.parse(stored);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
logger.error('Failed to parse stored table schemas:', error);
|
|
233
|
+
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { AggregationService } from './AggregationService';
|
|
2
|
+
export { BaseEntity } from './BaseEntity';
|
|
3
|
+
export { CloudSyncService } from './CloudSyncService';
|
|
4
|
+
export { EntitySchema } from './EntitySchema';
|
|
5
|
+
export { MigrationManager } from './MigrationManager';
|
|
6
|
+
export { RelationLoader } from './RelationLoader';
|
|
7
|
+
export { SchemaBuilder } from './SchemaBuilder';
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module 'dexie-cloud-addon';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type Dexie from 'dexie';
|
|
2
|
+
import type { Table } from 'dexie';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
import { BaseEntity } from './services/BaseEntity';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Constructor type for ORM entities.
|
|
9
|
+
*/
|
|
10
|
+
export interface EntityConstructor<T extends BaseEntity = BaseEntity> {
|
|
11
|
+
new (): T;
|
|
12
|
+
schema?: z.ZodSchema;
|
|
13
|
+
tableName?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generic plain object shape for entity instances used in serialization.
|
|
18
|
+
*/
|
|
19
|
+
export interface EntityInstance {
|
|
20
|
+
id?: number | string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Options for an entity column.
|
|
26
|
+
*/
|
|
27
|
+
export interface ColumnOptions {
|
|
28
|
+
kind?: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
29
|
+
unique?: boolean;
|
|
30
|
+
indexed?: boolean;
|
|
31
|
+
required?: boolean;
|
|
32
|
+
default?: unknown;
|
|
33
|
+
primaryKey?: boolean;
|
|
34
|
+
autoIncrement?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Definition of a compound index across multiple columns.
|
|
39
|
+
*/
|
|
40
|
+
export interface CompoundIndexOptions {
|
|
41
|
+
name?: string;
|
|
42
|
+
unique?: boolean;
|
|
43
|
+
columns: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type RelationType = 'one-to-one' | 'one-to-many' | 'many-to-many';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Configuration for a relation between two entities.
|
|
50
|
+
*/
|
|
51
|
+
export interface RelationOptions {
|
|
52
|
+
type: RelationType;
|
|
53
|
+
target: EntityConstructor | string;
|
|
54
|
+
/** Foreign key for one-to-many and many-to-many relations. */
|
|
55
|
+
foreignKey?: string;
|
|
56
|
+
/** Name of the join table for many-to-many relations. */
|
|
57
|
+
joinTable?: string;
|
|
58
|
+
/** If true, related records are deleted automatically. */
|
|
59
|
+
cascade?: boolean;
|
|
60
|
+
/** If true, the relation is auto-loaded with the entity. */
|
|
61
|
+
eager?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Internal metadata stored for a relation decorator.
|
|
66
|
+
*/
|
|
67
|
+
export interface RelationMetadata extends RelationOptions {
|
|
68
|
+
propertyKey: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Options for configuring an entity class.
|
|
73
|
+
*/
|
|
74
|
+
export interface EntityOptions {
|
|
75
|
+
tableName?: string;
|
|
76
|
+
schema?: z.ZodSchema;
|
|
77
|
+
timestamps?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Result of entity validation.
|
|
82
|
+
*/
|
|
83
|
+
export interface ValidationResult {
|
|
84
|
+
isValid: boolean;
|
|
85
|
+
errors: string[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A single database migration step.
|
|
90
|
+
*/
|
|
91
|
+
export interface Migration {
|
|
92
|
+
version: number;
|
|
93
|
+
name: string;
|
|
94
|
+
up: (_db: Dexie) => Promise<void>;
|
|
95
|
+
down?: (_db: Dexie) => Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Configuration for optional cloud synchronization.
|
|
100
|
+
*/
|
|
101
|
+
export interface CloudSyncConfig {
|
|
102
|
+
/** Base URL of the synchronization server. */
|
|
103
|
+
databaseUrl: string;
|
|
104
|
+
/** If enabled, queue changes and retry when offline. */
|
|
105
|
+
enableOfflineSupport?: boolean;
|
|
106
|
+
/** Periodic sync interval in milliseconds. */
|
|
107
|
+
syncInterval?: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Configuration for creating a Database instance.
|
|
112
|
+
*/
|
|
113
|
+
export interface DatabaseConfig {
|
|
114
|
+
/** Name of the IndexedDB database. */
|
|
115
|
+
name: string;
|
|
116
|
+
/** Schema version number. */
|
|
117
|
+
version: number;
|
|
118
|
+
/** List of registered entity classes to include in the schema. */
|
|
119
|
+
entities: EntityConstructor[];
|
|
120
|
+
/** Strategy for handling schema changes. */
|
|
121
|
+
onSchemaChangeStrategy?: 'selective' | 'all';
|
|
122
|
+
/** Optional list of migrations to execute. */
|
|
123
|
+
migrations?: Migration[];
|
|
124
|
+
/** Optional cloud synchronization configuration. */
|
|
125
|
+
cloudSync?: CloudSyncConfig;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type TableGetter<T extends BaseEntity> = () => Table<T>;
|
|
129
|
+
/**
|
|
130
|
+
* Options for the aggregation query on a specific entity.
|
|
131
|
+
*/
|
|
132
|
+
export interface AggregationOptions<T extends BaseEntity> {
|
|
133
|
+
/** Partial where filter applied to records before aggregation. */
|
|
134
|
+
where?: Partial<T>;
|
|
135
|
+
count?: boolean;
|
|
136
|
+
sum?: (keyof T)[];
|
|
137
|
+
avg?: (keyof T)[];
|
|
138
|
+
min?: (keyof T)[];
|
|
139
|
+
max?: (keyof T)[];
|
|
140
|
+
groupBy?: keyof T;
|
|
141
|
+
include?: string[];
|
|
142
|
+
limit?: number;
|
|
143
|
+
sort?: {
|
|
144
|
+
field: keyof T;
|
|
145
|
+
direction: 'asc' | 'desc';
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Result returned by the aggregation service.
|
|
151
|
+
*/
|
|
152
|
+
export interface AggregationResult {
|
|
153
|
+
count?: number;
|
|
154
|
+
sum?: Record<string, number>;
|
|
155
|
+
avg?: Record<string, number>;
|
|
156
|
+
min?: Record<string, number>;
|
|
157
|
+
max?: Record<string, number>;
|
|
158
|
+
groups?: Array<{
|
|
159
|
+
key: unknown;
|
|
160
|
+
count: number;
|
|
161
|
+
sum?: Record<string, number>;
|
|
162
|
+
avg?: Record<string, number>;
|
|
163
|
+
min?: Record<string, number>;
|
|
164
|
+
max?: Record<string, number>;
|
|
165
|
+
}>;
|
|
166
|
+
data?: unknown[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import log from 'loglevel';
|
|
2
|
+
|
|
3
|
+
const isDevHost = (
|
|
4
|
+
typeof window !== 'undefined' &&
|
|
5
|
+
typeof window.location !== 'undefined' &&
|
|
6
|
+
(
|
|
7
|
+
window.location.hostname === 'localhost' ||
|
|
8
|
+
window.location.hostname === '127.0.0.1' ||
|
|
9
|
+
window.location.hostname === '0.0.0.0'
|
|
10
|
+
)
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
log.setLevel(isDevHost ? 'debug' : 'error');
|
|
14
|
+
|
|
15
|
+
export const createServiceLogger = (serviceName: string) => {
|
|
16
|
+
return {
|
|
17
|
+
debug: (message: string, ...args: unknown[]) =>
|
|
18
|
+
log.debug(
|
|
19
|
+
`[${serviceName}] ${message}`,
|
|
20
|
+
...args,
|
|
21
|
+
),
|
|
22
|
+
info: (message: string, ...args: unknown[]) =>
|
|
23
|
+
log.info(
|
|
24
|
+
`[${serviceName}] ${message}`,
|
|
25
|
+
...args,
|
|
26
|
+
),
|
|
27
|
+
warn: (message: string, ...args: unknown[]) =>
|
|
28
|
+
log.warn(
|
|
29
|
+
`[${serviceName}] ${message}`,
|
|
30
|
+
...args,
|
|
31
|
+
),
|
|
32
|
+
error: (message: string, ...args: unknown[]) =>
|
|
33
|
+
log.error(
|
|
34
|
+
`[${serviceName}] ${message}`,
|
|
35
|
+
...args,
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const logger = createServiceLogger('indexeddb-orm');
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Dexie ORM Demo App
|
|
2
|
+
|
|
3
|
+
A demonstration app showcasing the capabilities of the **Dexie ORM for IndexedDB** library.
|
|
4
|
+
|
|
5
|
+
## Library Features
|
|
6
|
+
|
|
7
|
+
- **Entity Configuration** - define entities using `defineEntity()`
|
|
8
|
+
- **Automatic Schema** - automatic schema generation with primary keys and indexes
|
|
9
|
+
- **Relations** - entity relationships with helpers
|
|
10
|
+
- **Zod Validation** - runtime validation
|
|
11
|
+
- **Aggregations** - aggregate functions (count, avg, sum)
|
|
12
|
+
- **Live Queries** - reactive queries with `useLiveQuery()`
|
|
13
|
+
- **TypeScript** - full type safety
|
|
14
|
+
- **Cloud Sync** - synchronization with Dexie Cloud
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Requirements
|
|
19
|
+
- Node.js 18+
|
|
20
|
+
- npm or yarn
|
|
21
|
+
|
|
22
|
+
### Installation and Running
|
|
23
|
+
|
|
24
|
+
1. **Install dependencies:**
|
|
25
|
+
```bash
|
|
26
|
+
npm install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. **Run in development mode:**
|
|
30
|
+
```bash
|
|
31
|
+
npm run dev
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
3. **Open in browser:**
|
|
35
|
+
```
|
|
36
|
+
http://localhost:5173
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Production Build
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run build
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 📱 App Features
|
|
46
|
+
|
|
47
|
+
### Admin Tab
|
|
48
|
+
- **Overview** - library capabilities overview
|
|
49
|
+
- **Admin Actions** - function testing buttons
|
|
50
|
+
- **Live Query Demo** - reactive queries demonstration
|
|
51
|
+
- **TypeScript Demo** - type safety testing
|
|
52
|
+
- **Posts Demo** - advanced queries and aggregations
|
|
53
|
+
|
|
54
|
+
### Cloud Sync Tab
|
|
55
|
+
- **Sync Status** - synchronization status
|
|
56
|
+
- **Sync Controls** - synchronization management buttons
|
|
57
|
+
- **Instructions** - configuration instructions
|
|
58
|
+
|
|
59
|
+
## More Information
|
|
60
|
+
|
|
61
|
+
For more information visit our official [page](https://idb-orm.com).
|