1+ import { Database , KeyPath , DataClass , Index , EntityRepository , KeyGenerators , CompositeKeyPath } from '../index' ;
2+
3+ // Test entities with different key configurations
4+
5+ // Auto-increment key
6+ @DataClass ( { version : 1 } )
7+ class AutoIncrementEntity {
8+ @KeyPath ( { autoIncrement : true } )
9+ id ! : number ;
10+
11+ name ! : string ;
12+ value ! : number ;
13+
14+ constructor ( name : string , value : number ) {
15+ this . name = name ;
16+ this . value = value ;
17+ }
18+ }
19+
20+ // UUID generated key
21+ @DataClass ( { version : 1 } )
22+ class UUIDEntity {
23+ @KeyPath ( { generator : 'uuid' } )
24+ uuid ! : string ;
25+
26+ @Index ( )
27+ category ! : string ;
28+
29+ title ! : string ;
30+
31+ constructor ( category : string , title : string ) {
32+ this . category = category ;
33+ this . title = title ;
34+ }
35+ }
36+
37+ // Timestamp generated key
38+ @DataClass ( { version : 1 } )
39+ class TimestampEntity {
40+ @KeyPath ( { generator : 'timestamp' } )
41+ timestamp ! : number ;
42+
43+ event ! : string ;
44+ data ! : any ;
45+
46+ constructor ( event : string , data : any ) {
47+ this . event = event ;
48+ this . data = data ;
49+ }
50+ }
51+
52+ // Random generated key
53+ @DataClass ( { version : 1 } )
54+ class RandomEntity {
55+ @KeyPath ( { generator : 'random' } )
56+ randomId ! : string ;
57+
58+ description ! : string ;
59+
60+ constructor ( description : string ) {
61+ this . description = description ;
62+ }
63+ }
64+
65+ // Custom key generator
66+ @DataClass ( { version : 1 } )
67+ class CustomKeyEntity {
68+ @KeyPath ( { generator : ( item : any ) => `custom_${ item . type } _${ Date . now ( ) } ` } )
69+ customId ! : string ;
70+
71+ type ! : string ;
72+ content ! : string ;
73+
74+ constructor ( type : string , content : string ) {
75+ this . type = type ;
76+ this . content = content ;
77+ }
78+ }
79+
80+ // Composite key entity
81+ @DataClass ( { version : 1 } )
82+ @CompositeKeyPath ( [ 'userId' , 'projectId' ] )
83+ class UserProject {
84+ userId ! : string ;
85+ projectId ! : string ;
86+
87+ @Index ( )
88+ role ! : string ;
89+
90+ @Index ( )
91+ joinedAt ! : Date ;
92+
93+ constructor ( userId : string , projectId : string , role : string ) {
94+ this . userId = userId ;
95+ this . projectId = projectId ;
96+ this . role = role ;
97+ this . joinedAt = new Date ( ) ;
98+ }
99+ }
100+
101+ // Traditional single key (for comparison)
102+ @DataClass ( { version : 1 } )
103+ class TraditionalEntity {
104+ @KeyPath ( )
105+ id ! : string ;
106+
107+ name ! : string ;
108+
109+ constructor ( id : string , name : string ) {
110+ this . id = id ;
111+ this . name = name ;
112+ }
113+ }
114+
115+ describe ( 'Multi-Field & Composite Key Support' , ( ) => {
116+ let db : any ;
117+
118+ beforeAll ( async ( ) => {
119+ // Clear any existing database
120+ const deleteRequest = indexedDB . deleteDatabase ( 'CompositeKeyTestDB' ) ;
121+ await new Promise < void > ( ( resolve ) => {
122+ deleteRequest . onsuccess = ( ) => resolve ( ) ;
123+ deleteRequest . onerror = ( ) => resolve ( ) ; // Continue even if deletion fails
124+ } ) ;
125+
126+ db = await Database . build ( 'CompositeKeyTestDB' , [
127+ AutoIncrementEntity ,
128+ UUIDEntity ,
129+ TimestampEntity ,
130+ RandomEntity ,
131+ CustomKeyEntity ,
132+ UserProject ,
133+ TraditionalEntity
134+ ] ) ;
135+ } ) ;
136+
137+ describe ( 'Auto-increment keys' , ( ) => {
138+ it ( 'should create entities with auto-increment keys' , async ( ) => {
139+ const entity1 = new AutoIncrementEntity ( 'First' , 100 ) ;
140+ const entity2 = new AutoIncrementEntity ( 'Second' , 200 ) ;
141+
142+ await db . AutoIncrementEntity . create ( entity1 ) ;
143+ await db . AutoIncrementEntity . create ( entity2 ) ;
144+
145+ const items = await db . AutoIncrementEntity . list ( ) ;
146+ expect ( items . length ) . toBe ( 2 ) ;
147+ expect ( items [ 0 ] . id ) . toBeDefined ( ) ;
148+ expect ( items [ 1 ] . id ) . toBeDefined ( ) ;
149+ expect ( items [ 0 ] . id ) . not . toBe ( items [ 1 ] . id ) ;
150+ } ) ;
151+ } ) ;
152+
153+ describe ( 'UUID generated keys' , ( ) => {
154+ it ( 'should create entities with UUID keys' , async ( ) => {
155+ const entity = new UUIDEntity ( 'test' , 'Test Entity' ) ;
156+
157+ await db . UUIDEntity . create ( entity ) ;
158+
159+ expect ( entity . uuid ) . toBeDefined ( ) ;
160+ expect ( entity . uuid ) . toMatch ( / ^ [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - 4 [ 0 - 9 a - f ] { 3 } - [ 8 9 a b ] [ 0 - 9 a - f ] { 3 } - [ 0 - 9 a - f ] { 12 } $ / i) ;
161+
162+ const retrieved = await db . UUIDEntity . read ( entity . uuid ) ;
163+ expect ( retrieved ) . toEqual ( entity ) ;
164+ } ) ;
165+
166+ it ( 'should generate different UUIDs for different entities' , async ( ) => {
167+ const entity1 = new UUIDEntity ( 'test1' , 'Test Entity 1' ) ;
168+ const entity2 = new UUIDEntity ( 'test2' , 'Test Entity 2' ) ;
169+
170+ await db . UUIDEntity . create ( entity1 ) ;
171+ await db . UUIDEntity . create ( entity2 ) ;
172+
173+ expect ( entity1 . uuid ) . not . toBe ( entity2 . uuid ) ;
174+ } ) ;
175+ } ) ;
176+
177+ describe ( 'Timestamp generated keys' , ( ) => {
178+ it ( 'should create entities with timestamp keys' , async ( ) => {
179+ const beforeTime = Date . now ( ) ;
180+ const entity = new TimestampEntity ( 'user_login' , { userId : 'user123' } ) ;
181+
182+ await db . TimestampEntity . create ( entity ) ;
183+ const afterTime = Date . now ( ) ;
184+
185+ expect ( entity . timestamp ) . toBeDefined ( ) ;
186+ expect ( entity . timestamp ) . toBeGreaterThanOrEqual ( beforeTime ) ;
187+ expect ( entity . timestamp ) . toBeLessThanOrEqual ( afterTime ) ;
188+
189+ const retrieved = await db . TimestampEntity . read ( entity . timestamp ) ;
190+ expect ( retrieved ) . toEqual ( entity ) ;
191+ } ) ;
192+ } ) ;
193+
194+ describe ( 'Random generated keys' , ( ) => {
195+ it ( 'should create entities with random keys' , async ( ) => {
196+ const entity = new RandomEntity ( 'Random test entity' ) ;
197+
198+ await db . RandomEntity . create ( entity ) ;
199+
200+ expect ( entity . randomId ) . toBeDefined ( ) ;
201+ expect ( typeof entity . randomId ) . toBe ( 'string' ) ;
202+ expect ( entity . randomId . length ) . toBeGreaterThan ( 0 ) ;
203+
204+ const retrieved = await db . RandomEntity . read ( entity . randomId ) ;
205+ expect ( retrieved ) . toEqual ( entity ) ;
206+ } ) ;
207+
208+ it ( 'should generate different random keys for different entities' , async ( ) => {
209+ const entity1 = new RandomEntity ( 'Random entity 1' ) ;
210+ const entity2 = new RandomEntity ( 'Random entity 2' ) ;
211+
212+ await db . RandomEntity . create ( entity1 ) ;
213+ await db . RandomEntity . create ( entity2 ) ;
214+
215+ expect ( entity1 . randomId ) . not . toBe ( entity2 . randomId ) ;
216+ } ) ;
217+ } ) ;
218+
219+ describe ( 'Custom key generators' , ( ) => {
220+ it ( 'should create entities with custom generated keys' , async ( ) => {
221+ const entity = new CustomKeyEntity ( 'blog_post' , 'My first blog post' ) ;
222+
223+ await db . CustomKeyEntity . create ( entity ) ;
224+
225+ expect ( entity . customId ) . toBeDefined ( ) ;
226+ expect ( entity . customId ) . toMatch ( / ^ c u s t o m _ b l o g _ p o s t _ \d + $ / ) ;
227+
228+ const retrieved = await db . CustomKeyEntity . read ( entity . customId ) ;
229+ expect ( retrieved ) . toEqual ( entity ) ;
230+ } ) ;
231+ } ) ;
232+
233+ describe ( 'Composite keys' , ( ) => {
234+ it ( 'should create entities with composite keys' , async ( ) => {
235+ const userProject = new UserProject ( 'user123' , 'project456' , 'developer' ) ;
236+
237+ await db . UserProject . create ( userProject ) ;
238+
239+ // Read using composite key
240+ const retrieved = await db . UserProject . read ( [ 'user123' , 'project456' ] ) ;
241+ expect ( retrieved ) . toBeDefined ( ) ;
242+ expect ( retrieved . userId ) . toBe ( 'user123' ) ;
243+ expect ( retrieved . projectId ) . toBe ( 'project456' ) ;
244+ expect ( retrieved . role ) . toBe ( 'developer' ) ;
245+ } ) ;
246+
247+ it ( 'should handle multiple entities with different composite keys' , async ( ) => {
248+ const userProject1 = new UserProject ( 'user123' , 'project789' , 'admin' ) ;
249+ const userProject2 = new UserProject ( 'user456' , 'project456' , 'viewer' ) ;
250+
251+ await db . UserProject . create ( userProject1 ) ;
252+ await db . UserProject . create ( userProject2 ) ;
253+
254+ const retrieved1 = await db . UserProject . read ( [ 'user123' , 'project789' ] ) ;
255+ const retrieved2 = await db . UserProject . read ( [ 'user456' , 'project456' ] ) ;
256+
257+ expect ( retrieved1 ?. role ) . toBe ( 'admin' ) ;
258+ expect ( retrieved2 ?. role ) . toBe ( 'viewer' ) ;
259+ } ) ;
260+
261+ it ( 'should support updates with composite keys' , async ( ) => {
262+ const userProject = new UserProject ( 'user789' , 'project123' , 'contributor' ) ;
263+ await db . UserProject . create ( userProject ) ;
264+
265+ // Update the entity
266+ userProject . role = 'maintainer' ;
267+ await db . UserProject . update ( userProject ) ;
268+
269+ const retrieved = await db . UserProject . read ( [ 'user789' , 'project123' ] ) ;
270+ expect ( retrieved ?. role ) . toBe ( 'maintainer' ) ;
271+ } ) ;
272+
273+ it ( 'should support deletion with composite keys' , async ( ) => {
274+ const userProject = new UserProject ( 'user999' , 'project999' , 'temp' ) ;
275+ await db . UserProject . create ( userProject ) ;
276+
277+ // Verify it exists
278+ let retrieved = await db . UserProject . read ( [ 'user999' , 'project999' ] ) ;
279+ expect ( retrieved ) . toBeDefined ( ) ;
280+
281+ // Delete it
282+ await db . UserProject . delete ( [ 'user999' , 'project999' ] ) ;
283+
284+ // Verify it's gone
285+ retrieved = await db . UserProject . read ( [ 'user999' , 'project999' ] ) ;
286+ expect ( retrieved ) . toBeUndefined ( ) ;
287+ } ) ;
288+
289+ it ( 'should support index queries on composite key entities' , async ( ) => {
290+ // Find by role index
291+ const developers = await db . UserProject . findByIndex ( 'role' , 'developer' ) ;
292+ expect ( developers . length ) . toBeGreaterThanOrEqual ( 1 ) ;
293+ expect ( developers . every ( ( up : any ) => up . role === 'developer' ) ) . toBe ( true ) ;
294+ } ) ;
295+ } ) ;
296+
297+ describe ( 'Traditional single keys (backward compatibility)' , ( ) => {
298+ it ( 'should still work with traditional single key syntax' , async ( ) => {
299+ const entity = new TraditionalEntity ( 'trad_1' , 'Traditional Entity' ) ;
300+
301+ await db . TraditionalEntity . create ( entity ) ;
302+
303+ const retrieved = await db . TraditionalEntity . read ( 'trad_1' ) ;
304+ expect ( retrieved ) . toEqual ( entity ) ;
305+ } ) ;
306+ } ) ;
307+
308+ describe ( 'Key generation utilities' , ( ) => {
309+ it ( 'should provide access to key generators' , async ( ) => {
310+ expect ( KeyGenerators . uuid ) . toBeDefined ( ) ;
311+ expect ( KeyGenerators . timestamp ) . toBeDefined ( ) ;
312+ expect ( KeyGenerators . random ) . toBeDefined ( ) ;
313+
314+ const uuid = KeyGenerators . uuid ( ) ;
315+ const timestamp = KeyGenerators . timestamp ( ) ;
316+ const random = KeyGenerators . random ( ) ;
317+
318+ expect ( typeof uuid ) . toBe ( 'string' ) ;
319+ expect ( typeof timestamp ) . toBe ( 'number' ) ;
320+ expect ( typeof random ) . toBe ( 'string' ) ;
321+ } ) ;
322+ } ) ;
323+
324+ describe ( 'Error handling' , ( ) => {
325+ it ( 'should handle missing composite key parts gracefully' , async ( ) => {
326+ try {
327+ await db . UserProject . read ( [ 'user123' ] ) ; // Missing projectId
328+ // Should either work or throw a clear error
329+ } catch ( error ) {
330+ expect ( error ) . toBeDefined ( ) ;
331+ }
332+ } ) ;
333+ } ) ;
334+ } ) ;
0 commit comments