@sphereon/ssi-sdk.data-store 0.34.1-feature.SSISDK.82.linkedVP.327 → 0.34.1-feature.SSISDK.82.linkedVP.341

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.linkedVP.327+81517113",
3
+ "version": "0.34.1-feature.SSISDK.82.linkedVP.341+483672e1",
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.linkedVP.327+81517113",
32
- "@sphereon/ssi-sdk-ext.identifier-resolution": "0.34.1-feature.SSISDK.82.linkedVP.327+81517113",
33
- "@sphereon/ssi-sdk.agent-config": "0.34.1-feature.SSISDK.82.linkedVP.327+81517113",
34
- "@sphereon/ssi-sdk.core": "0.34.1-feature.SSISDK.82.linkedVP.327+81517113",
35
- "@sphereon/ssi-sdk.data-store-types": "0.34.1-feature.SSISDK.82.linkedVP.327+81517113",
36
- "@sphereon/ssi-types": "0.34.1-feature.SSISDK.82.linkedVP.327+81517113",
31
+ "@sphereon/ssi-sdk-ext.did-utils": "0.34.1-feature.SSISDK.82.linkedVP.341+483672e1",
32
+ "@sphereon/ssi-sdk-ext.identifier-resolution": "0.34.1-feature.SSISDK.82.linkedVP.341+483672e1",
33
+ "@sphereon/ssi-sdk.agent-config": "0.34.1-feature.SSISDK.82.linkedVP.341+483672e1",
34
+ "@sphereon/ssi-sdk.core": "0.34.1-feature.SSISDK.82.linkedVP.341+483672e1",
35
+ "@sphereon/ssi-sdk.data-store-types": "0.34.1-feature.SSISDK.82.linkedVP.341+483672e1",
36
+ "@sphereon/ssi-types": "0.34.1-feature.SSISDK.82.linkedVP.341+483672e1",
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": "81517113906547b895df06ba83f283604b52ab2e"
69
+ "gitHead": "483672e1d9b2891a346216a0ebea64745561d6ed"
70
70
  }
@@ -15,7 +15,13 @@ import { CredentialRole, OrPromise } from '@sphereon/ssi-types'
15
15
  import Debug from 'debug'
16
16
  import { DataSource, type FindOptionsOrder, type FindOptionsWhere, Repository } from 'typeorm'
17
17
 
18
- import { digitalCredentialFrom, digitalCredentialsFrom, nonPersistedDigitalCredentialEntityFromAddArgs } from '../../src'
18
+ import {
19
+ digitalCredentialFrom,
20
+ digitalCredentialsFrom,
21
+ nonPersistedDigitalCredentialEntityFromAddArgs,
22
+ persistedDigitalCredentialEntityFromStateArgs,
23
+ persistedDigitalCredentialEntityFromUpdateArgs,
24
+ } from '../../src'
19
25
  import { DigitalCredentialEntity } from '../entities/digitalCredential/DigitalCredentialEntity'
20
26
  import { parseAndValidateOrderOptions } from '../utils/SortingUtils'
21
27
 
@@ -38,7 +44,7 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
38
44
  return Promise.reject(validationError)
39
45
  }
40
46
  const dcRepo = await this.getRepository()
41
- const createdResult: DigitalCredentialEntity = await dcRepo.save(credentialEntity)
47
+ const createdResult: DigitalCredentialEntity = await dcRepo.save(credentialEntity as any)
42
48
  return Promise.resolve(digitalCredentialFrom(createdResult))
43
49
  }
44
50
 
@@ -93,20 +99,18 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
93
99
  return Promise.reject(Error(`No credential found for args: ${JSON.stringify(whereClause)}`))
94
100
  }
95
101
 
96
- // Separate identifier from updates
97
- const { id, hash, ...updates } = args as any
102
+ // Extract updates by removing the identifier fields
103
+ const updates = Object.fromEntries(Object.entries(args).filter(([key]) => key !== 'id' && key !== 'hash')) as Partial<DigitalCredential>
104
+
105
+ const entityToSave = persistedDigitalCredentialEntityFromUpdateArgs(credential, updates)
98
106
 
99
- const updatedCredential: DigitalCredential = {
100
- ...credential,
101
- ...updates,
102
- id: credential.id,
103
- hash: credential.hash,
104
- createdAt: credential.createdAt,
105
- lastUpdatedAt: new Date(),
107
+ const validationError = this.assertValidDigitalCredential(entityToSave)
108
+ if (validationError) {
109
+ return Promise.reject(validationError)
106
110
  }
107
111
 
108
- debug('Updating credential', updatedCredential)
109
- const updatedResult: DigitalCredentialEntity = await dcRepo.save(updatedCredential, { transaction: true })
112
+ debug('Updating credential', entityToSave)
113
+ const updatedResult = await dcRepo.save(entityToSave as any, { transaction: true })
110
114
  return digitalCredentialFrom(updatedResult)
111
115
  }
112
116
 
@@ -185,20 +189,16 @@ export class DigitalCredentialStore extends AbstractDigitalCredentialStore {
185
189
  if (!credential) {
186
190
  return Promise.reject(Error(`No credential found for args: ${JSON.stringify(whereClause)}`))
187
191
  }
188
- const updatedCredential: DigitalCredential = {
189
- ...credential,
190
- ...(args.verifiedState !== CredentialStateType.REVOKED && { verifiedAt: args.verifiedAt }),
191
- ...(args.verifiedState === CredentialStateType.REVOKED && { revokedAt: args.revokedAt }),
192
- identifierMethod: credential.identifierMethod,
193
- lastUpdatedAt: new Date(),
194
- verifiedState: args.verifiedState,
195
- }
196
- debug('Updating credential', credential)
197
- const updatedResult: DigitalCredentialEntity = await credentialRepository.save(updatedCredential, { transaction: true })
192
+
193
+ // Create entity with state updates applied
194
+ const entityToSave = persistedDigitalCredentialEntityFromStateArgs(credential, args)
195
+
196
+ debug('Updating credential state', entityToSave)
197
+ const updatedResult: DigitalCredentialEntity = await credentialRepository.save(entityToSave as any, { transaction: true })
198
198
  return digitalCredentialFrom(updatedResult)
199
199
  }
200
200
 
201
- private assertValidDigitalCredential(credentialEntity: NonPersistedDigitalCredential): Error | undefined {
201
+ private assertValidDigitalCredential(credentialEntity: NonPersistedDigitalCredential | DigitalCredentialEntity): Error | undefined {
202
202
  const { kmsKeyRef, identifierMethod, credentialRole, isIssuerSigned } = credentialEntity
203
203
 
204
204
  const isRoleInvalid = credentialRole === CredentialRole.ISSUER || (credentialRole === CredentialRole.HOLDER && !isIssuerSigned)
@@ -1,6 +1,11 @@
1
1
  import { defaultHasher } from '@sphereon/ssi-sdk.core'
2
- import type { AddCredentialArgs, DigitalCredential, NonPersistedDigitalCredential } from '@sphereon/ssi-sdk.data-store-types'
3
- import { CredentialDocumentFormat, DocumentType, RegulationType } from '@sphereon/ssi-sdk.data-store-types'
2
+ import type {
3
+ AddCredentialArgs,
4
+ DigitalCredential,
5
+ NonPersistedDigitalCredential,
6
+ UpdateCredentialStateArgs,
7
+ } from '@sphereon/ssi-sdk.data-store-types'
8
+ import { CredentialDocumentFormat, CredentialStateType, DocumentType, RegulationType } from '@sphereon/ssi-sdk.data-store-types'
4
9
  import {
5
10
  CredentialMapper,
6
11
  DocumentFormat,
@@ -66,6 +71,20 @@ function determineCredentialDocumentFormat(documentFormat: DocumentFormat): Cred
66
71
  }
67
72
  }
68
73
 
74
+ /**
75
+ * Normalizes nullable fields by converting undefined to null.
76
+ * This ensures TypeORM actually clears the database fields instead of ignoring them.
77
+ */
78
+ export function normalizeNullableFields<T extends Record<string, any>>(obj: T, nullableKeys: Array<keyof T>): T {
79
+ const normalized = { ...obj }
80
+ for (const key of nullableKeys) {
81
+ if (normalized[key] === undefined) {
82
+ normalized[key] = null as any
83
+ }
84
+ }
85
+ return normalized
86
+ }
87
+
69
88
  function getValidUntil(uniformDocument: IVerifiableCredential | IVerifiablePresentation | SdJwtDecodedVerifiableCredentialPayload): Date | undefined {
70
89
  if ('expirationDate' in uniformDocument && uniformDocument.expirationDate) {
71
90
  return new Date(uniformDocument.expirationDate)
@@ -127,6 +146,54 @@ export const nonPersistedDigitalCredentialEntityFromAddArgs = (addCredentialArgs
127
146
  }
128
147
  }
129
148
 
149
+ export const persistedDigitalCredentialEntityFromUpdateArgs = (
150
+ existingCredential: DigitalCredentialEntity,
151
+ updates: Partial<DigitalCredential>,
152
+ ): DigitalCredentialEntity => {
153
+ const entity = new DigitalCredentialEntity()
154
+
155
+ // Copy all fields from existing credential
156
+ Object.assign(entity, existingCredential)
157
+
158
+ // Normalize nullable fields before applying updates
159
+ const normalizedUpdates = normalizeNullableFields(updates, ['linkedVpId', 'linkedVpFrom'])
160
+
161
+ // Apply updates
162
+ Object.assign(entity, normalizedUpdates)
163
+
164
+ // Ensure these fields are never overwritten
165
+ entity.id = existingCredential.id
166
+ entity.hash = existingCredential.hash
167
+ entity.createdAt = existingCredential.createdAt
168
+ entity.lastUpdatedAt = new Date()
169
+
170
+ return entity
171
+ }
172
+
173
+ export const persistedDigitalCredentialEntityFromStateArgs = (
174
+ existingCredential: DigitalCredentialEntity,
175
+ args: UpdateCredentialStateArgs,
176
+ ): DigitalCredentialEntity => {
177
+ const entity = new DigitalCredentialEntity()
178
+
179
+ // Copy all fields from existing credential
180
+ Object.assign(entity, existingCredential)
181
+
182
+ // Apply state updates
183
+ entity.verifiedState = args.verifiedState
184
+ entity.lastUpdatedAt = new Date()
185
+
186
+ if (args.verifiedState === CredentialStateType.REVOKED && args.revokedAt) {
187
+ entity.revokedAt = args.revokedAt
188
+ }
189
+
190
+ if (args.verifiedState !== CredentialStateType.REVOKED && args.verifiedAt) {
191
+ entity.verifiedAt = args.verifiedAt
192
+ }
193
+
194
+ return entity
195
+ }
196
+
130
197
  export const digitalCredentialFrom = (credentialEntity: DigitalCredentialEntity): DigitalCredential => {
131
198
  const result: DigitalCredential = {
132
199
  ...credentialEntity,