@sphereon/ssi-sdk.data-store 0.34.1-feature.SSISDK.82.and.SSISDK.70.349 → 0.34.1-feature.SSISDK.82.and.SSISDK.70.352

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphereon/ssi-sdk.data-store",
3
- "version": "0.34.1-feature.SSISDK.82.and.SSISDK.70.349+8423ec8c",
3
+ "version": "0.34.1-feature.SSISDK.82.and.SSISDK.70.352+54837639",
4
4
  "source": "src/index.ts",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -28,12 +28,12 @@
28
28
  "dependencies": {
29
29
  "@sphereon/kmp-mdoc-core": "0.2.0-SNAPSHOT.26",
30
30
  "@sphereon/pex": "5.0.0-unstable.28",
31
- "@sphereon/ssi-sdk-ext.did-utils": "0.34.1-feature.SSISDK.82.and.SSISDK.70.349+8423ec8c",
32
- "@sphereon/ssi-sdk-ext.identifier-resolution": "0.34.1-feature.SSISDK.82.and.SSISDK.70.349+8423ec8c",
33
- "@sphereon/ssi-sdk.agent-config": "0.34.1-feature.SSISDK.82.and.SSISDK.70.349+8423ec8c",
34
- "@sphereon/ssi-sdk.core": "0.34.1-feature.SSISDK.82.and.SSISDK.70.349+8423ec8c",
35
- "@sphereon/ssi-sdk.data-store-types": "0.34.1-feature.SSISDK.82.and.SSISDK.70.349+8423ec8c",
36
- "@sphereon/ssi-types": "0.34.1-feature.SSISDK.82.and.SSISDK.70.349+8423ec8c",
31
+ "@sphereon/ssi-sdk-ext.did-utils": "0.34.1-feature.SSISDK.82.and.SSISDK.70.352+54837639",
32
+ "@sphereon/ssi-sdk-ext.identifier-resolution": "0.34.1-feature.SSISDK.82.and.SSISDK.70.352+54837639",
33
+ "@sphereon/ssi-sdk.agent-config": "0.34.1-feature.SSISDK.82.and.SSISDK.70.352+54837639",
34
+ "@sphereon/ssi-sdk.core": "0.34.1-feature.SSISDK.82.and.SSISDK.70.352+54837639",
35
+ "@sphereon/ssi-sdk.data-store-types": "0.34.1-feature.SSISDK.82.and.SSISDK.70.352+54837639",
36
+ "@sphereon/ssi-types": "0.34.1-feature.SSISDK.82.and.SSISDK.70.352+54837639",
37
37
  "@veramo/core": "4.2.0",
38
38
  "@veramo/utils": "4.2.0",
39
39
  "blakejs": "^1.2.1",
@@ -66,5 +66,5 @@
66
66
  "PostgreSQL",
67
67
  "Contact Store"
68
68
  ],
69
- "gitHead": "8423ec8c4953d573753106202593191705c9ce23"
69
+ "gitHead": "54837639c5105ab91196f760a56749e935583712"
70
70
  }
@@ -1,11 +1,5 @@
1
1
  import { DataSources } from '@sphereon/ssi-sdk.agent-config'
2
2
  import { defaultHasher } from '@sphereon/ssi-sdk.core'
3
- import { CredentialRole, IVerifiablePresentation } from '@sphereon/ssi-types'
4
- import { DataSource } from 'typeorm'
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6
- import { DigitalCredentialStore } from '../digitalCredential/DigitalCredentialStore'
7
- import { DataStoreDigitalCredentialEntities } from '../index'
8
- import { DataStoreDigitalCredentialMigrations } from '../migrations'
9
3
  import {
10
4
  AddCredentialArgs,
11
5
  CredentialCorrelationType,
@@ -16,6 +10,12 @@ import {
16
10
  GetCredentialsArgs,
17
11
  GetCredentialsResponse,
18
12
  } from '@sphereon/ssi-sdk.data-store-types'
13
+ import { CredentialRole, IVerifiablePresentation } from '@sphereon/ssi-types'
14
+ import { DataSource } from 'typeorm'
15
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
16
+ import { DigitalCredentialStore } from '../digitalCredential/DigitalCredentialStore'
17
+ import { DataStoreDigitalCredentialEntities } from '../index'
18
+ import { DataStoreDigitalCredentialMigrations } from '../migrations'
19
19
 
20
20
  describe('Database entities tests', (): void => {
21
21
  let dbConnection: DataSource
@@ -524,4 +524,231 @@ describe('Database entities tests', (): void => {
524
524
  expect(result.verifiedState).toEqual(CredentialStateType.REVOKED)
525
525
  expect(result.revokedAt).toEqual(currentDate)
526
526
  })
527
+
528
+ it('should filter credentials by linkedVpUntil date range', async (): Promise<void> => {
529
+ const baseCredential: AddCredentialArgs = {
530
+ rawDocument:
531
+ 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw',
532
+ kmsKeyRef: 'testRef',
533
+ identifierMethod: 'did',
534
+ issuerCorrelationType: CredentialCorrelationType.DID,
535
+ subjectCorrelationType: CredentialCorrelationType.DID,
536
+ issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj',
537
+ subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj',
538
+ credentialRole: CredentialRole.VERIFIER,
539
+ tenantId: 'urn:uuid:nnag4b43-1e7a-98f8-a32c-a48dbc5b10mj',
540
+ }
541
+
542
+ // Create credentials with different linkedVpUntil dates
543
+ const credential1: DigitalCredential = await digitalCredentialStore.addCredential(baseCredential)
544
+ const date1 = new Date('2024-01-15')
545
+ await digitalCredentialStore.updateCredential({
546
+ id: credential1.id,
547
+ linkedVpId: 'vp-1',
548
+ linkedVpUntil: date1,
549
+ })
550
+
551
+ const credential2Args = {
552
+ ...baseCredential,
553
+ rawDocument: baseCredential.rawDocument + '1', // Slightly different to get different hash
554
+ }
555
+ const credential2: DigitalCredential = await digitalCredentialStore.addCredential(credential2Args)
556
+ const date2 = new Date('2024-06-15')
557
+ await digitalCredentialStore.updateCredential({
558
+ id: credential2.id,
559
+ linkedVpId: 'vp-2',
560
+ linkedVpUntil: date2,
561
+ })
562
+
563
+ const credential3Args = {
564
+ ...baseCredential,
565
+ rawDocument: baseCredential.rawDocument + '2', // Different again
566
+ }
567
+ const credential3: DigitalCredential = await digitalCredentialStore.addCredential(credential3Args)
568
+ const date3 = new Date('2024-12-31')
569
+ await digitalCredentialStore.updateCredential({
570
+ id: credential3.id,
571
+ linkedVpId: 'vp-3',
572
+ linkedVpUntil: date3,
573
+ })
574
+
575
+ // Credential without linkedVpUntil
576
+ const credential4Args = {
577
+ ...baseCredential,
578
+ rawDocument: baseCredential.rawDocument + '3',
579
+ }
580
+ await digitalCredentialStore.addCredential(credential4Args)
581
+
582
+ // Test: Get credentials expiring on or before mid-year (should get credential1 and credential2)
583
+ const filterDate1 = new Date('2024-06-15')
584
+ const result1: GetCredentialsResponse = await digitalCredentialStore.getCredentials({
585
+ filter: [{ linkedVpUntil: filterDate1 }],
586
+ })
587
+ expect(result1.total).toEqual(2)
588
+ expect(result1.data.some((c) => c.linkedVpId === 'vp-1')).toBe(true)
589
+ expect(result1.data.some((c) => c.linkedVpId === 'vp-2')).toBe(true)
590
+
591
+ // Test: Get credentials expiring on or before end of January (should only get credential1)
592
+ const filterDate2 = new Date('2024-01-31')
593
+ const result2: GetCredentialsResponse = await digitalCredentialStore.getCredentials({
594
+ filter: [{ linkedVpUntil: filterDate2 }],
595
+ })
596
+ expect(result2.total).toEqual(1)
597
+ expect(result2.data[0].linkedVpId).toEqual('vp-1')
598
+
599
+ // Test: Get credentials expiring on or before end of year (should get all 3 with linkedVpUntil)
600
+ const filterDate3 = new Date('2024-12-31')
601
+ const result3: GetCredentialsResponse = await digitalCredentialStore.getCredentials({
602
+ filter: [{ linkedVpUntil: filterDate3 }],
603
+ })
604
+ expect(result3.total).toEqual(3)
605
+ expect(result3.data.some((c) => c.linkedVpId === 'vp-1')).toBe(true)
606
+ expect(result3.data.some((c) => c.linkedVpId === 'vp-2')).toBe(true)
607
+ expect(result3.data.some((c) => c.linkedVpId === 'vp-3')).toBe(true)
608
+
609
+ // Test: Get credentials expiring before the earliest date (should get none)
610
+ const filterDate4 = new Date('2024-01-01')
611
+ const result4: GetCredentialsResponse = await digitalCredentialStore.getCredentials({
612
+ filter: [{ linkedVpUntil: filterDate4 }],
613
+ })
614
+ expect(result4.total).toEqual(0)
615
+ })
616
+
617
+ it('should filter credentials by linkedVpUntil combined with other filters', async (): Promise<void> => {
618
+ const tenant1 = 'urn:uuid:tenant-1'
619
+ const tenant2 = 'urn:uuid:tenant-2'
620
+
621
+ const baseCredential: AddCredentialArgs = {
622
+ rawDocument:
623
+ 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw',
624
+ kmsKeyRef: 'testRef',
625
+ identifierMethod: 'did',
626
+ issuerCorrelationType: CredentialCorrelationType.DID,
627
+ subjectCorrelationType: CredentialCorrelationType.DID,
628
+ issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj',
629
+ subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj',
630
+ credentialRole: CredentialRole.VERIFIER,
631
+ }
632
+
633
+ // Tenant 1 credentials
634
+ const credential1: DigitalCredential = await digitalCredentialStore.addCredential({ ...baseCredential, tenantId: tenant1 })
635
+ await digitalCredentialStore.updateCredential({
636
+ id: credential1.id,
637
+ linkedVpId: 'vp-1',
638
+ linkedVpUntil: new Date('2024-03-01'),
639
+ })
640
+
641
+ const credential2Args = { ...baseCredential, tenantId: tenant1, rawDocument: baseCredential.rawDocument + '1' }
642
+ const credential2: DigitalCredential = await digitalCredentialStore.addCredential(credential2Args)
643
+ await digitalCredentialStore.updateCredential({
644
+ id: credential2.id,
645
+ linkedVpId: 'vp-2',
646
+ linkedVpUntil: new Date('2024-08-01'),
647
+ })
648
+
649
+ // Tenant 2 credentials
650
+ const credential3Args = { ...baseCredential, tenantId: tenant2, rawDocument: baseCredential.rawDocument + '2' }
651
+ const credential3: DigitalCredential = await digitalCredentialStore.addCredential(credential3Args)
652
+ await digitalCredentialStore.updateCredential({
653
+ id: credential3.id,
654
+ linkedVpId: 'vp-3',
655
+ linkedVpUntil: new Date('2024-03-01'),
656
+ })
657
+
658
+ const credential4Args = { ...baseCredential, tenantId: tenant2, rawDocument: baseCredential.rawDocument + '3' }
659
+ const credential4: DigitalCredential = await digitalCredentialStore.addCredential(credential4Args)
660
+ await digitalCredentialStore.updateCredential({
661
+ id: credential4.id,
662
+ linkedVpId: 'vp-4',
663
+ linkedVpUntil: new Date('2024-08-01'),
664
+ })
665
+
666
+ // Test: Filter by tenant and linkedVpUntil
667
+ const filterDate = new Date('2024-06-01')
668
+ const result: GetCredentialsResponse = await digitalCredentialStore.getCredentials({
669
+ filter: [
670
+ {
671
+ tenantId: tenant1,
672
+ linkedVpUntil: filterDate,
673
+ },
674
+ ],
675
+ })
676
+
677
+ // Should only get tenant1's credential that expires before June (credential1)
678
+ expect(result.total).toEqual(1)
679
+ expect(result.data[0].linkedVpId).toEqual('vp-1')
680
+ expect(result.data[0].tenantId).toEqual(tenant1)
681
+ })
682
+
683
+ it('should handle empty results when filtering by linkedVpUntil with no matches', async (): Promise<void> => {
684
+ const baseCredential: AddCredentialArgs = {
685
+ rawDocument:
686
+ 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw',
687
+ kmsKeyRef: 'testRef',
688
+ identifierMethod: 'did',
689
+ issuerCorrelationType: CredentialCorrelationType.DID,
690
+ subjectCorrelationType: CredentialCorrelationType.DID,
691
+ issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj',
692
+ subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj',
693
+ credentialRole: CredentialRole.VERIFIER,
694
+ tenantId: 'urn:uuid:test-tenant',
695
+ }
696
+
697
+ // Create credential with linkedVpUntil in the future
698
+ const credential: DigitalCredential = await digitalCredentialStore.addCredential(baseCredential)
699
+ await digitalCredentialStore.updateCredential({
700
+ id: credential.id,
701
+ linkedVpId: 'vp-future',
702
+ linkedVpUntil: new Date('2025-12-31'),
703
+ })
704
+
705
+ // Filter by date in the past - should return no results
706
+ const filterDate = new Date('2024-01-01')
707
+ const result: GetCredentialsResponse = await digitalCredentialStore.getCredentials({
708
+ filter: [{ linkedVpUntil: filterDate }],
709
+ })
710
+
711
+ expect(result.total).toEqual(0)
712
+ expect(result.data.length).toEqual(0)
713
+ })
714
+
715
+ it('should handle credentials with null linkedVpUntil when filtering', async (): Promise<void> => {
716
+ const baseCredential: AddCredentialArgs = {
717
+ rawDocument:
718
+ 'eyJraWQiOiJkaWQ6a2V5Ono2TWtyaGt5M3B1c20yNk1laUZhWFUzbjJuZWtyYW13RlVtZ0dyZUdHa0RWNnpRaiN6Nk1rcmhreTNwdXNtMjZNZWlGYVhVM24ybmVrcmFtd0ZVbWdHcmVHR2tEVjZ6UWoiLCJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vc3BoZXJlb24tb3BlbnNvdXJjZS5naXRodWIuaW8vc3NpLW1vYmlsZS13YWxsZXQvY29udGV4dC9zcGhlcmVvbi13YWxsZXQtaWRlbnRpdHktdjEuanNvbmxkIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJTcGhlcmVvbldhbGxldElkZW50aXR5Q3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJmaXJzdE5hbWUiOiJTIiwibGFzdE5hbWUiOiJLIiwiZW1haWxBZGRyZXNzIjoic0BrIn19LCJzdWIiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJqdGkiOiJ1cm46dXVpZDpkZGE3YmYyNC04ZTdhLTQxZjgtYjY2Yy1hNDhkYmM1YjEwZmEiLCJuYmYiOjE3MDg0NDA4MDgsImlzcyI6ImRpZDprZXk6ejZNa3Joa3kzcHVzbTI2TWVpRmFYVTNuMm5la3JhbXdGVW1nR3JlR0drRFY2elFqIn0.G0M84XVAxSmzGY-NQuB9NBofNrINSn6lvxW6761Vlq6ypvYgtc2xNdpiRmw8ryVNfnpzrr4Z5cB1RlrC05rJAw',
719
+ kmsKeyRef: 'testRef',
720
+ identifierMethod: 'did',
721
+ issuerCorrelationType: CredentialCorrelationType.DID,
722
+ subjectCorrelationType: CredentialCorrelationType.DID,
723
+ issuerCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj',
724
+ subjectCorrelationId: 'did:key:z6Mkrhky3pusm26MeiFaXU3n2nekramwFUmgGreGGkDV6zQj',
725
+ credentialRole: CredentialRole.VERIFIER,
726
+ tenantId: 'urn:uuid:test-tenant',
727
+ }
728
+
729
+ // Credential with linkedVpUntil
730
+ const credential1: DigitalCredential = await digitalCredentialStore.addCredential(baseCredential)
731
+ await digitalCredentialStore.updateCredential({
732
+ id: credential1.id,
733
+ linkedVpId: 'vp-with-expiry',
734
+ linkedVpUntil: new Date('2024-06-01'),
735
+ })
736
+
737
+ // Credential without linkedVpUntil (null)
738
+ const credential2Args = { ...baseCredential, rawDocument: baseCredential.rawDocument + '1' }
739
+ const credential2: DigitalCredential = await digitalCredentialStore.addCredential(credential2Args)
740
+ await digitalCredentialStore.updateCredential({
741
+ id: credential2.id,
742
+ linkedVpId: 'vp-no-expiry',
743
+ })
744
+
745
+ // Filter should only return the credential with linkedVpUntil
746
+ const filterDate = new Date('2024-12-31')
747
+ const result: GetCredentialsResponse = await digitalCredentialStore.getCredentials({
748
+ filter: [{ linkedVpUntil: filterDate }],
749
+ })
750
+
751
+ expect(result.total).toEqual(1)
752
+ expect(result.data[0].linkedVpId).toEqual('vp-with-expiry')
753
+ })
527
754
  })
@@ -13,7 +13,7 @@ import {
13
13
  } from '@sphereon/ssi-sdk.data-store-types'
14
14
  import { CredentialRole, OrPromise } from '@sphereon/ssi-types'
15
15
  import Debug from 'debug'
16
- import { DataSource, type FindOptionsOrder, type FindOptionsWhere, Repository } from 'typeorm'
16
+ import { DataSource, type FindOptionsOrder, type FindOptionsWhere, LessThanOrEqual, Repository } from 'typeorm'
17
17
 
18
18
  import {
19
19
  digitalCredentialFrom,
@@ -67,8 +67,28 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
67
67
  ? parseAndValidateOrderOptions<DigitalCredentialEntity>(order)
68
68
  : <FindOptionsOrder<DigitalCredentialEntity>>order
69
69
  const dcRepo = await this.getRepository()
70
+
71
+ // Process filter to convert linkedVpUntil dates into LessThanOrEqual operators
72
+ const processLinkedVpUntil = (filterItem: Partial<DigitalCredential>): FindOptionsWhere<DigitalCredentialEntity> => {
73
+ const processed = { ...filterItem } as FindOptionsWhere<DigitalCredentialEntity>
74
+ if (filterItem.linkedVpUntil) {
75
+ processed.linkedVpUntil = LessThanOrEqual(filterItem.linkedVpUntil)
76
+ }
77
+ return processed
78
+ }
79
+
80
+ let whereClause: FindOptionsWhere<DigitalCredentialEntity> | FindOptionsWhere<DigitalCredentialEntity>[]
81
+
82
+ if (Array.isArray(filter) && filter.length > 0) {
83
+ whereClause = filter.map(processLinkedVpUntil)
84
+ } else if (Object.keys(filter).length > 0) {
85
+ whereClause = processLinkedVpUntil(filter as Partial<DigitalCredential>)
86
+ } else {
87
+ whereClause = filter as FindOptionsWhere<DigitalCredentialEntity>
88
+ }
89
+
70
90
  const [result, total] = await dcRepo.findAndCount({
71
- where: filter,
91
+ where: whereClause,
72
92
  skip: offset,
73
93
  take: limit,
74
94
  order: sortOptions,
@@ -82,6 +82,9 @@ export class DigitalCredentialEntity extends BaseEntity implements DigitalCreden
82
82
  @CreateDateColumn({ name: 'linked_vp_from', nullable: true, type: typeOrmDateTime() })
83
83
  linkedVpFrom?: Date
84
84
 
85
+ @CreateDateColumn({ name: 'linked_vp_until', nullable: true, type: typeOrmDateTime() })
86
+ linkedVpUntil?: Date
87
+
85
88
  @CreateDateColumn({ name: 'created_at', nullable: false, type: typeOrmDateTime() })
86
89
  createdAt!: Date
87
90
 
@@ -13,9 +13,19 @@ export class AddLinkedVpFields1763387280001 implements MigrationInterface {
13
13
  ALTER TABLE "DigitalCredential"
14
14
  ADD COLUMN "linked_vp_from" TIMESTAMP
15
15
  `)
16
+
17
+ await queryRunner.query(`
18
+ ALTER TABLE "DigitalCredential"
19
+ ADD COLUMN "linked_vp_until" TIMESTAMP
20
+ `)
16
21
  }
17
22
 
18
23
  public async down(queryRunner: QueryRunner): Promise<void> {
24
+ await queryRunner.query(`
25
+ ALTER TABLE "DigitalCredential"
26
+ DROP COLUMN "linked_vp_until"
27
+ `)
28
+
19
29
  await queryRunner.query(`
20
30
  ALTER TABLE "DigitalCredential"
21
31
  DROP COLUMN "linked_vp_from"
@@ -13,17 +13,24 @@ export class AddLinkedVpFields1763387280002 implements MigrationInterface {
13
13
  ALTER TABLE "DigitalCredential"
14
14
  ADD COLUMN "linked_vp_from" datetime
15
15
  `)
16
+
17
+ await queryRunner.query(`
18
+ ALTER TABLE "DigitalCredential"
19
+ ADD COLUMN "linked_vp_until" datetime
20
+ `)
16
21
  }
17
22
 
18
23
  public async down(queryRunner: QueryRunner): Promise<void> {
19
- // SQLite doesn't support DROP COLUMN in older versions
20
- // For production, you may need to recreate the table
21
- // For now, we'll try the direct approach which works in SQLite 3.35.0+
22
24
  await queryRunner.query(`
23
25
  ALTER TABLE "DigitalCredential"
24
26
  DROP COLUMN "linked_vp_from"
25
27
  `)
26
28
 
29
+ await queryRunner.query(`
30
+ ALTER TABLE "DigitalCredential"
31
+ DROP COLUMN "linked_vp_until"
32
+ `)
33
+
27
34
  await queryRunner.query(`
28
35
  ALTER TABLE "DigitalCredential"
29
36
  DROP COLUMN "linked_vp_id"
@@ -156,7 +156,7 @@ export const persistedDigitalCredentialEntityFromUpdateArgs = (
156
156
  Object.assign(entity, existingCredential)
157
157
 
158
158
  // Normalize nullable fields before applying updates
159
- const normalizedUpdates = normalizeNullableFields(updates, ['linkedVpId', 'linkedVpFrom'])
159
+ const normalizedUpdates = normalizeNullableFields(updates, ['linkedVpId', 'linkedVpFrom', 'linkedVpUntil'])
160
160
 
161
161
  // Apply updates
162
162
  Object.assign(entity, normalizedUpdates)