@sphereon/ssi-sdk.data-store 0.18.2-unstable.98 → 0.19.1-next.100

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.
Files changed (74) hide show
  1. package/dist/contact/AbstractContactStore.d.ts.map +1 -1
  2. package/dist/contact/AbstractContactStore.js.map +1 -1
  3. package/dist/contact/ContactStore.d.ts.map +1 -1
  4. package/dist/contact/ContactStore.js +12 -6
  5. package/dist/contact/ContactStore.js.map +1 -1
  6. package/dist/entities/machineState/MachineStateInfoEntity.d.ts +33 -0
  7. package/dist/entities/machineState/MachineStateInfoEntity.d.ts.map +1 -0
  8. package/dist/entities/machineState/MachineStateInfoEntity.js +85 -0
  9. package/dist/entities/machineState/MachineStateInfoEntity.js.map +1 -0
  10. package/dist/index.d.ts +7 -3
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +11 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/issuanceBranding/IssuanceBrandingStore.d.ts.map +1 -1
  15. package/dist/issuanceBranding/IssuanceBrandingStore.js.map +1 -1
  16. package/dist/machineState/IAbstractMachineStateStore.d.ts +52 -0
  17. package/dist/machineState/IAbstractMachineStateStore.d.ts.map +1 -0
  18. package/dist/machineState/IAbstractMachineStateStore.js +13 -0
  19. package/dist/machineState/IAbstractMachineStateStore.js.map +1 -0
  20. package/dist/machineState/MachineStateStore.d.ts +21 -0
  21. package/dist/machineState/MachineStateStore.d.ts.map +1 -0
  22. package/dist/machineState/MachineStateStore.js +139 -0
  23. package/dist/machineState/MachineStateStore.js.map +1 -0
  24. package/dist/migrations/generic/7-CreateMachineStateStore.d.ts +7 -0
  25. package/dist/migrations/generic/7-CreateMachineStateStore.d.ts.map +1 -0
  26. package/dist/migrations/generic/7-CreateMachineStateStore.js +78 -0
  27. package/dist/migrations/generic/7-CreateMachineStateStore.js.map +1 -0
  28. package/dist/migrations/generic/index.d.ts +2 -0
  29. package/dist/migrations/generic/index.d.ts.map +1 -1
  30. package/dist/migrations/generic/index.js +4 -1
  31. package/dist/migrations/generic/index.js.map +1 -1
  32. package/dist/migrations/index.d.ts +1 -1
  33. package/dist/migrations/index.d.ts.map +1 -1
  34. package/dist/migrations/index.js +2 -1
  35. package/dist/migrations/index.js.map +1 -1
  36. package/dist/migrations/postgres/1708797018115-CreateMachineStateStore.d.ts +7 -0
  37. package/dist/migrations/postgres/1708797018115-CreateMachineStateStore.d.ts.map +1 -0
  38. package/dist/migrations/postgres/1708797018115-CreateMachineStateStore.js +45 -0
  39. package/dist/migrations/postgres/1708797018115-CreateMachineStateStore.js.map +1 -0
  40. package/dist/migrations/sqlite/1708796002272-CreateMachineStateStore.d.ts +7 -0
  41. package/dist/migrations/sqlite/1708796002272-CreateMachineStateStore.d.ts.map +1 -0
  42. package/dist/migrations/sqlite/1708796002272-CreateMachineStateStore.js +44 -0
  43. package/dist/migrations/sqlite/1708796002272-CreateMachineStateStore.js.map +1 -0
  44. package/dist/types/contact/contact.d.ts.map +1 -1
  45. package/dist/types/contact/contact.js.map +1 -1
  46. package/dist/types/index.d.ts +1 -0
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/types/index.js +1 -0
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/types/machineState/IAbstractMachineStateStore.d.ts +58 -0
  51. package/dist/types/machineState/IAbstractMachineStateStore.d.ts.map +1 -0
  52. package/dist/types/machineState/IAbstractMachineStateStore.js +3 -0
  53. package/dist/types/machineState/IAbstractMachineStateStore.js.map +1 -0
  54. package/package.json +4 -4
  55. package/src/__tests__/contact.entities.test.ts +32 -15
  56. package/src/__tests__/contact.store.test.ts +71 -28
  57. package/src/__tests__/machineState.entities.test.ts +51 -0
  58. package/src/__tests__/machineState.store.test.ts +174 -0
  59. package/src/contact/AbstractContactStore.ts +3 -2
  60. package/src/contact/ContactStore.ts +15 -9
  61. package/src/entities/contact/PhysicalAddressEntity.ts +1 -1
  62. package/src/entities/machineState/MachineStateInfoEntity.ts +58 -0
  63. package/src/index.ts +9 -0
  64. package/src/issuanceBranding/IssuanceBrandingStore.ts +6 -8
  65. package/src/machineState/IAbstractMachineStateStore.ts +65 -0
  66. package/src/machineState/MachineStateStore.ts +149 -0
  67. package/src/migrations/generic/7-CreateMachineStateStore.ts +66 -0
  68. package/src/migrations/generic/index.ts +3 -0
  69. package/src/migrations/index.ts +1 -0
  70. package/src/migrations/postgres/1708797018115-CreateMachineStateStore.ts +29 -0
  71. package/src/migrations/sqlite/1708796002272-CreateMachineStateStore.ts +28 -0
  72. package/src/types/contact/contact.ts +13 -2
  73. package/src/types/index.ts +1 -0
  74. package/src/types/machineState/IAbstractMachineStateStore.ts +68 -0
@@ -0,0 +1,174 @@
1
+ import { DataSource } from 'typeorm'
2
+ import { DataStoreMachineStateEntities, MachineStateStore, StoreMachineStatesFindActiveArgs, StoreMachineStatePersistArgs } from '../index'
3
+ import { DataStoreMachineStateMigrations } from '../migrations'
4
+
5
+ describe('Machine State store tests', (): void => {
6
+ let dbConnection: DataSource
7
+ let store: MachineStateStore
8
+
9
+ beforeEach(async (): Promise<void> => {
10
+ dbConnection = await new DataSource({
11
+ type: 'sqlite',
12
+ database: ':memory:',
13
+ logging: 'all',
14
+ migrationsRun: false,
15
+ migrations: DataStoreMachineStateMigrations,
16
+ synchronize: false,
17
+ entities: DataStoreMachineStateEntities,
18
+ }).initialize()
19
+ await dbConnection.runMigrations()
20
+ expect(await dbConnection.showMigrations()).toBeFalsy()
21
+ store = new MachineStateStore(dbConnection)
22
+ })
23
+
24
+ afterEach(async (): Promise<void> => {
25
+ await dbConnection.destroy()
26
+ })
27
+
28
+ it('should store machine state info', async (): Promise<void> => {
29
+ const persistArgs: StoreMachineStatePersistArgs = {
30
+ instanceId: 'Onboarding1',
31
+ machineName: 'Onboarding',
32
+ updatedCount: 0,
33
+ latestStateName: 'enterPersonalDetails',
34
+ latestEventType: 'SET_PERSONAL_DATA',
35
+ state: 'test_state',
36
+ expiresAt: new Date(new Date().getDate() + 100000),
37
+ tenantId: 'test_tenant_id',
38
+ }
39
+
40
+ const persistMachineStateResult = await store.persistMachineState(persistArgs)
41
+ expect(persistMachineStateResult).toBeDefined()
42
+ })
43
+
44
+ it('should get all machines with their current state', async (): Promise<void> => {
45
+ const persistArgs: StoreMachineStatePersistArgs = {
46
+ instanceId: 'Onboarding1',
47
+ machineName: 'Onboarding',
48
+ latestStateName: 'enterPersonalDetails',
49
+ latestEventType: 'SET_PERSONAL_DATA',
50
+ updatedCount: 0,
51
+ state: 'test_state',
52
+ expiresAt: new Date(new Date().getDate() + 100000),
53
+ tenantId: 'test_tenant_id',
54
+ }
55
+
56
+ const stateEvent1 = await store.persistMachineState({ ...persistArgs })
57
+ expect(stateEvent1).toBeDefined()
58
+
59
+ const stateEvent2 = await store.persistMachineState({ ...persistArgs, instanceId: 'Onboarding2' })
60
+ expect(stateEvent2).toBeDefined()
61
+
62
+ const result = await store.findMachineStates()
63
+ expect(result).toHaveLength(2)
64
+ })
65
+
66
+ it('should retrieve a machine state', async (): Promise<void> => {
67
+ const expiresAt = new Date()
68
+ expiresAt.setTime(expiresAt.getTime() + 100000)
69
+ const persistArgs: StoreMachineStatePersistArgs = {
70
+ instanceId: 'Onboarding1',
71
+ machineName: 'Onboarding',
72
+ latestStateName: 'enterPersonalDetails',
73
+ latestEventType: 'SET_PERSONAL_DATA',
74
+ state: 'test_state',
75
+ updatedCount: 0,
76
+ expiresAt,
77
+ tenantId: 'test_tenant_id',
78
+ }
79
+
80
+ const machineStatePersisted = await store.persistMachineState(persistArgs)
81
+ expect(machineStatePersisted).toBeDefined()
82
+ const result = await store.findActiveMachineStates({ machineName: persistArgs.machineName, tenantId: persistArgs.tenantId })
83
+ expect(result).toBeDefined()
84
+ })
85
+
86
+ it('should delete a machine state', async (): Promise<void> => {
87
+ const persistArgs: StoreMachineStatePersistArgs = {
88
+ instanceId: 'Onboarding1',
89
+ machineName: 'Onboarding',
90
+ updatedCount: 0,
91
+ latestStateName: 'enterPersonalDetails',
92
+ latestEventType: 'SET_PERSONAL_DATA',
93
+ state: 'test_state',
94
+ expiresAt: new Date(new Date().getDate() + 100000),
95
+ tenantId: 'test_tenant_id',
96
+ }
97
+
98
+ const persistedState = await store.persistMachineState(persistArgs)
99
+ expect(persistedState).toBeDefined()
100
+
101
+ const result: boolean = await store.deleteMachineState({ instanceId: persistedState.instanceId })
102
+ expect(result).toBeTruthy()
103
+ })
104
+
105
+ it('should return an error if type filter does not match', async (): Promise<void> => {
106
+ const args: StoreMachineStatesFindActiveArgs = {
107
+ machineName: 'unknown_machine',
108
+ }
109
+
110
+ await expect(store.findActiveMachineStates(args)).resolves.toEqual([])
111
+ })
112
+
113
+ it('should delete the expired records', async () => {
114
+ const futureExpiresAt = new Date()
115
+ futureExpiresAt.setTime(futureExpiresAt.getTime() + 100000) // Future expiration
116
+
117
+ const pastExpiresAt = new Date()
118
+ pastExpiresAt.setTime(pastExpiresAt.getTime() - 100000) // Past expiration, already expired
119
+
120
+ const oldestXstateEvent: StoreMachineStatePersistArgs = {
121
+ instanceId: 'Onboarding1',
122
+ machineName: 'Onboarding',
123
+ latestStateName: 'enterPersonalDetails',
124
+ latestEventType: 'SET_TOC',
125
+ updatedCount: 0,
126
+ state: 'test_state',
127
+ expiresAt: futureExpiresAt,
128
+ tenantId: 'test_tenant_id',
129
+ }
130
+ const middleXstateEvent: StoreMachineStatePersistArgs = {
131
+ instanceId: 'Onboarding1',
132
+ machineName: 'Onboarding',
133
+ latestStateName: 'TOC',
134
+ latestEventType: 'SET_POLICY2',
135
+ updatedCount: 1,
136
+ state: 'test_state',
137
+ expiresAt: futureExpiresAt,
138
+ tenantId: 'test_tenant_id',
139
+ }
140
+ const newestXstateEvent: StoreMachineStatePersistArgs = {
141
+ instanceId: 'OnboardingExpired',
142
+ machineName: 'Onboarding',
143
+ latestStateName: 'POLICY',
144
+ latestEventType: 'SET_PERSONAL_DATA',
145
+ updatedCount: 0,
146
+ state: 'test_state',
147
+ expiresAt: pastExpiresAt, // This event should be already expired
148
+ tenantId: 'test_tenant_id',
149
+ }
150
+
151
+ await store.persistMachineState(oldestXstateEvent)
152
+ await store.persistMachineState(middleXstateEvent)
153
+ await store.persistMachineState(newestXstateEvent)
154
+
155
+ await expect(store.findActiveMachineStates({ machineName: 'Onboarding' })).resolves.toHaveLength(1)
156
+
157
+ await store.deleteExpiredMachineStates({})
158
+ await expect(store.findActiveMachineStates({ machineName: 'Onboarding' })).resolves.toHaveLength(1)
159
+ await expect(store.findActiveMachineStates({ machineName: 'Onboarding' })).resolves.toMatchObject([
160
+ {
161
+ completedAt: null,
162
+ createdAt: expect.anything(),
163
+ expiresAt: expect.anything(),
164
+ instanceId: 'Onboarding1',
165
+ latestEventType: 'SET_POLICY2',
166
+ latestStateName: 'TOC',
167
+ machineName: 'Onboarding',
168
+ state: 'test_state',
169
+ tenantId: 'test_tenant_id',
170
+ updatedAt: expect.anything(),
171
+ },
172
+ ])
173
+ })
174
+ })
@@ -21,7 +21,8 @@ import {
21
21
  Identity,
22
22
  Party,
23
23
  PartyRelationship,
24
- PartyType, PhysicalAddress,
24
+ PartyType,
25
+ PhysicalAddress,
25
26
  RemoveElectronicAddressArgs,
26
27
  RemoveIdentityArgs,
27
28
  RemovePartyArgs,
@@ -33,7 +34,7 @@ import {
33
34
  UpdatePartyArgs,
34
35
  UpdatePartyTypeArgs,
35
36
  UpdatePhysicalAddressArgs,
36
- UpdateRelationshipArgs
37
+ UpdateRelationshipArgs,
37
38
  } from '../types'
38
39
 
39
40
  export abstract class AbstractContactStore {
@@ -29,7 +29,7 @@ import {
29
29
  partyTypeEntityFrom,
30
30
  partyTypeFrom,
31
31
  physicalAddressEntityFrom,
32
- physicalAddressFrom
32
+ physicalAddressFrom,
33
33
  } from '../utils/contact/MappingUtils'
34
34
  import {
35
35
  AddElectronicAddressArgs,
@@ -653,23 +653,27 @@ export class ContactStore extends AbstractContactStore {
653
653
  const identityRepository: Repository<IdentityEntity> = connection.getRepository(IdentityEntity)
654
654
 
655
655
  identities.map(async (identity: IdentityEntity): Promise<void> => {
656
- await correlationIdentifierRepository.delete(identity.identifier.id)
656
+ await correlationIdentifierRepository
657
+ .delete(identity.identifier.id)
657
658
  .catch((error) => Promise.reject(Error(`Unable to remove identity.identifier with id ${identity.identifier.id}. ${error}`)))
658
659
 
659
660
  if (identity.connection) {
660
661
  await baseConfigRepository.delete(identity.connection.config.id)
661
- await connectionRepository.delete(identity.connection.id)
662
+ await connectionRepository
663
+ .delete(identity.connection.id)
662
664
  .catch((error) => Promise.reject(Error(`Unable to remove identity.connection with id ${identity.connection?.id}. ${error}`)))
663
665
  }
664
666
 
665
667
  if (identity.metadata) {
666
668
  identity.metadata.map(async (metadataItem: IdentityMetadataItemEntity): Promise<void> => {
667
- await identityMetadataItemRepository.delete(metadataItem.id)
669
+ await identityMetadataItemRepository
670
+ .delete(metadataItem.id)
668
671
  .catch((error) => Promise.reject(Error(`Unable to remove identity.metadataItem with id ${metadataItem.id}. ${error}`)))
669
672
  })
670
673
  }
671
674
 
672
- await identityRepository.delete(identity.id)
675
+ await identityRepository
676
+ .delete(identity.id)
673
677
  .catch((error) => Promise.reject(Error(`Unable to remove identity with id ${identity.id}. ${error}`)))
674
678
  })
675
679
  }
@@ -679,8 +683,9 @@ export class ContactStore extends AbstractContactStore {
679
683
 
680
684
  const electronicAddressRepository: Repository<ElectronicAddressEntity> = (await this.dbConnection).getRepository(ElectronicAddressEntity)
681
685
  electronicAddresses.map(async (electronicAddress: ElectronicAddressEntity): Promise<void> => {
682
- await electronicAddressRepository.delete(electronicAddress.id)
683
- .catch((error) => Promise.reject(Error(`Unable to remove electronic address with id ${electronicAddress.id}. ${error}`)))
686
+ await electronicAddressRepository
687
+ .delete(electronicAddress.id)
688
+ .catch((error) => Promise.reject(Error(`Unable to remove electronic address with id ${electronicAddress.id}. ${error}`)))
684
689
  })
685
690
  }
686
691
 
@@ -689,8 +694,9 @@ export class ContactStore extends AbstractContactStore {
689
694
 
690
695
  const physicalAddressRepository: Repository<PhysicalAddressEntity> = (await this.dbConnection).getRepository(PhysicalAddressEntity)
691
696
  physicalAddresses.map(async (physicalAddress: PhysicalAddressEntity): Promise<void> => {
692
- await physicalAddressRepository.delete(physicalAddress.id)
693
- .catch((error) => Promise.reject(Error(`Unable to remove physical address with id ${physicalAddress.id}. ${error}`)))
697
+ await physicalAddressRepository
698
+ .delete(physicalAddress.id)
699
+ .catch((error) => Promise.reject(Error(`Unable to remove physical address with id ${physicalAddress.id}. ${error}`)))
694
700
  })
695
701
  }
696
702
 
@@ -8,7 +8,7 @@ import {
8
8
  Entity,
9
9
  ManyToOne,
10
10
  PrimaryGeneratedColumn,
11
- UpdateDateColumn
11
+ UpdateDateColumn,
12
12
  } from 'typeorm'
13
13
  import { getConstraint } from '../../utils/ValidatorUtils'
14
14
  import { PhysicalAddressType, ValidationConstraint } from '../../types'
@@ -0,0 +1,58 @@
1
+ import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm'
2
+
3
+ /**
4
+ * @class MachineStateInfoEntity
5
+ * Represents a machine state. It allows to continue a machine at a later point in time at the point it was left of
6
+ *
7
+ * @param {string} instanceId - The instance ID of the machine state.
8
+ * @param {string} [sessionId] - The session ID of the machine state. (optional)
9
+ * @param {string} machineName - The name of the machine.
10
+ * @param {string} [latestStateName] - The name of the latest state. (optional)
11
+ * @param {string} latestEventType - The type of the latest event.
12
+ * @param {string} state - The current state of the machine.
13
+ * @param {Date} createdAt - The date and time when the machine state was created.
14
+ * @param {Date} updatedAt - The date and time when the machine state was last updated.
15
+ * @param {number} updatedCount - The number of times the machine state has been updated.
16
+ * @param {Date} [expiresAt] - The date and time when the machine state expires. (optional)
17
+ * @param {Date} [completedAt] - The date and time when the machine state was completed. (optional)
18
+ * @param {string} [tenantId] - The ID of the tenant associated with the machine state. (optional)
19
+ */
20
+ @Entity('MachineStateInfoEntity')
21
+ export class MachineStateInfoEntity extends BaseEntity {
22
+ @PrimaryColumn({ name: 'instance_id', type: 'varchar', nullable: false })
23
+ instanceId!: string
24
+
25
+ @Column({ name: 'session_id', type: 'varchar', nullable: true })
26
+ sessionId?: string
27
+
28
+ // Xstate moved to name instead of instanceId for V5. Also makes more sense
29
+ @Column({ name: 'machine_name', type: 'varchar', nullable: false })
30
+ machineName!: string
31
+
32
+ @Column({ name: 'latest_state_name', type: 'varchar', nullable: true })
33
+ latestStateName?: string
34
+
35
+ @Column({ name: 'latest_event_type', type: 'varchar', nullable: false })
36
+ latestEventType!: string
37
+
38
+ @Column({ name: 'state', type: 'text', nullable: false })
39
+ state!: string
40
+
41
+ @CreateDateColumn({ name: 'created_at', type: 'datetime', nullable: false })
42
+ createdAt!: Date
43
+
44
+ @UpdateDateColumn({ name: 'updated_at', type: 'datetime', nullable: false })
45
+ updatedAt!: Date
46
+
47
+ @Column({ name: 'updated_count', type: 'integer', nullable: false })
48
+ updatedCount!: number
49
+
50
+ @Column({ name: 'expires_at', type: 'datetime', nullable: true })
51
+ expiresAt?: Date
52
+
53
+ @Column({ name: 'completed_at', type: 'datetime', nullable: true })
54
+ completedAt?: Date
55
+
56
+ @Column({ name: 'tenant_id', type: 'varchar', nullable: true })
57
+ tenantId?: string
58
+ }
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ import { IssuerBrandingEntity, issuerBrandingEntityFrom } from './entities/issua
18
18
  import { TextAttributesEntity, textAttributesEntityFrom } from './entities/issuanceBranding/TextAttributesEntity'
19
19
  import { StatusListEntity } from './entities/statusList2021/StatusList2021Entity'
20
20
  import { StatusListEntryEntity } from './entities/statusList2021/StatusList2021EntryEntity'
21
+ import { MachineStateInfoEntity } from './entities/machineState/MachineStateInfoEntity'
21
22
  import { IStatusListEntity, IStatusListEntryEntity } from './types'
22
23
  import { PartyRelationshipEntity } from './entities/contact/PartyRelationshipEntity'
23
24
  import { PartyTypeEntity } from './entities/contact/PartyTypeEntity'
@@ -35,12 +36,16 @@ import { DigitalCredentialEntity } from './entities/digitalCredential/DigitalCre
35
36
  import { digitalCredentialFrom, digitalCredentialsFrom, nonPersistedDigitalCredentialEntityFromAddArgs } from './utils/digitalCredential/MappingUtils'
36
37
  export { AbstractEventLoggerStore } from './eventLogger/AbstractEventLoggerStore'
37
38
  export { EventLoggerStore } from './eventLogger/EventLoggerStore'
39
+ export { IAbstractMachineStateStore } from './machineState/IAbstractMachineStateStore'
40
+ export { MachineStateStore } from './machineState/MachineStateStore'
41
+
38
42
  export {
39
43
  DataStoreMigrations,
40
44
  DataStoreEventLoggerMigrations,
41
45
  DataStoreContactMigrations,
42
46
  DataStoreIssuanceBrandingMigrations,
43
47
  DataStoreStatusListMigrations,
48
+ DataStoreMachineStateMigrations,
44
49
  } from './migrations'
45
50
  export * from './types'
46
51
  export * from './utils/contact/MappingUtils'
@@ -81,6 +86,8 @@ export const DataStoreEventLoggerEntities = [AuditEventEntity]
81
86
 
82
87
  export const DataStoreDigitalCredentialEntities = [DigitalCredentialEntity]
83
88
 
89
+ export const DataStoreMachineStateEntities = [MachineStateInfoEntity]
90
+
84
91
  // All entities combined if a party wants to enable them all at once
85
92
  export const DataStoreEntities = [
86
93
  ...DataStoreContactEntities,
@@ -88,6 +95,7 @@ export const DataStoreEntities = [
88
95
  ...DataStoreStatusListEntities,
89
96
  ...DataStoreEventLoggerEntities,
90
97
  ...DataStoreDigitalCredentialEntities,
98
+ ...DataStoreMachineStateEntities,
91
99
  ]
92
100
 
93
101
  export {
@@ -128,4 +136,5 @@ export {
128
136
  digitalCredentialFrom,
129
137
  digitalCredentialsFrom,
130
138
  nonPersistedDigitalCredentialEntityFromAddArgs,
139
+ MachineStateInfoEntity,
131
140
  }
@@ -376,14 +376,12 @@ export class IssuanceBrandingStore extends AbstractIssuanceBrandingStore {
376
376
  }
377
377
 
378
378
  const issuerLocaleBrandingRepository: Repository<IssuerLocaleBrandingEntity> = (await this.dbConnection).getRepository(IssuerLocaleBrandingEntity)
379
- const addIssuerLocaleBranding: Array<Promise<void>> = localeBranding.map(
380
- async (localeBranding: IBasicIssuerLocaleBranding): Promise<void> => {
381
- const issuerLocaleBrandingEntity: IssuerLocaleBrandingEntity = issuerLocaleBrandingEntityFrom(localeBranding)
382
- debug('Adding issuer locale branding', issuerLocaleBrandingEntity)
383
- issuerLocaleBrandingEntity.issuerBranding = issuerBranding
384
- await issuerLocaleBrandingRepository.save(issuerLocaleBrandingEntity, { transaction: true })
385
- }
386
- )
379
+ const addIssuerLocaleBranding: Array<Promise<void>> = localeBranding.map(async (localeBranding: IBasicIssuerLocaleBranding): Promise<void> => {
380
+ const issuerLocaleBrandingEntity: IssuerLocaleBrandingEntity = issuerLocaleBrandingEntityFrom(localeBranding)
381
+ debug('Adding issuer locale branding', issuerLocaleBrandingEntity)
382
+ issuerLocaleBrandingEntity.issuerBranding = issuerBranding
383
+ await issuerLocaleBrandingRepository.save(issuerLocaleBrandingEntity, { transaction: true })
384
+ })
387
385
 
388
386
  await Promise.all(addIssuerLocaleBranding)
389
387
 
@@ -0,0 +1,65 @@
1
+ import {
2
+ StoreMachineStateDeleteExpiredArgs,
3
+ StoreMachineStateDeleteArgs,
4
+ StoreMachineStatesFindActiveArgs,
5
+ StoreFindMachineStatesArgs,
6
+ StoreMachineStatePersistArgs,
7
+ StoreMachineStateInfo,
8
+ StoreMachineStateGetArgs,
9
+ } from '../types'
10
+
11
+ /**
12
+ * Represents an abstract class for storing machine states.
13
+ * This class provides methods for persisting, retrieving, and deleting machine states.
14
+ *
15
+ * @interface
16
+ */
17
+ export abstract class IAbstractMachineStateStore {
18
+ /**
19
+ * Persists the machine state.
20
+ *
21
+ * @param {StoreMachineStatePersistArgs} state - The object containing the machine state to persist.
22
+ * @return {Promise<StoreMachineStateInfo>} - A Promise that resolves to the information about the persisted machine state.
23
+ */
24
+ abstract persistMachineState(state: StoreMachineStatePersistArgs): Promise<StoreMachineStateInfo>
25
+
26
+ /**
27
+ * Finds active machine states based on the given arguments.
28
+ *
29
+ * @param {StoreMachineStatesFindActiveArgs} args - The arguments for finding active machine states.
30
+ * @return {Promise<Array<StoreMachineStateInfo>>} - A promise that resolves with an array of active machine states.
31
+ */
32
+ abstract findActiveMachineStates(args: StoreMachineStatesFindActiveArgs): Promise<Array<StoreMachineStateInfo>>
33
+
34
+ /**
35
+ * Retrieves the state of a particular machine.
36
+ *
37
+ * @param {StoreMachineStateGetArgs} args - The arguments for retrieving the machine state.
38
+ * @returns {Promise<StoreMachineStateInfo>} - A promise that resolves to the machine state information.
39
+ */
40
+ abstract getMachineState(args: StoreMachineStateGetArgs): Promise<StoreMachineStateInfo>
41
+
42
+ /**
43
+ * Finds the machine states based on the given arguments.
44
+ *
45
+ * @param {StoreFindMachineStatesArgs} [args] - The arguments to filter the machine states.
46
+ * @returns {Promise<Array<StoreMachineStateInfo>>} - A promise that resolves to an array of machine state information.
47
+ */
48
+ abstract findMachineStates(args?: StoreFindMachineStatesArgs): Promise<Array<StoreMachineStateInfo>>
49
+
50
+ /**
51
+ * Deletes a machine state.
52
+ *
53
+ * @param {StoreMachineStateDeleteArgs} args - The arguments for deleting the machine state.
54
+ * @return {Promise<boolean>} - A promise that resolves to a boolean indicating if the machine state was successfully deleted or not.
55
+ */
56
+ abstract deleteMachineState(args: StoreMachineStateDeleteArgs): Promise<boolean>
57
+
58
+ /**
59
+ * Deletes expired machine states from the database.
60
+ *
61
+ * @param {StoreMachineStateDeleteExpiredArgs} args - The arguments for deleting expired machine states.
62
+ * @return {Promise<number>} - A promise that resolves to the number of deleted machine states.
63
+ */
64
+ abstract deleteExpiredMachineStates(args: StoreMachineStateDeleteExpiredArgs): Promise<number>
65
+ }
@@ -0,0 +1,149 @@
1
+ import { OrPromise } from '@sphereon/ssi-types'
2
+ import Debug from 'debug'
3
+ import { Brackets, DataSource, FindOptionsWhere, IsNull, LessThan, Not } from 'typeorm'
4
+
5
+ import { MachineStateInfoEntity } from '../entities/machineState/MachineStateInfoEntity'
6
+ import {
7
+ StoreFindMachineStatesArgs,
8
+ StoreMachineStateDeleteArgs,
9
+ StoreMachineStateDeleteExpiredArgs,
10
+ StoreMachineStateGetArgs,
11
+ StoreMachineStateInfo,
12
+ StoreMachineStatePersistArgs,
13
+ StoreMachineStatesFindActiveArgs,
14
+ } from '../types'
15
+ import { IAbstractMachineStateStore } from './IAbstractMachineStateStore'
16
+
17
+ const debug = Debug('sphereon:ssi-sdk:machine-state:store')
18
+
19
+ /**
20
+ * Represents a data store for managing machine states.
21
+ */
22
+ export class MachineStateStore extends IAbstractMachineStateStore {
23
+ private readonly _dbConnection: OrPromise<DataSource>
24
+
25
+ constructor(dbConnection: OrPromise<DataSource>) {
26
+ super()
27
+ this._dbConnection = dbConnection
28
+ }
29
+
30
+ async persistMachineState(state: StoreMachineStatePersistArgs): Promise<StoreMachineStateInfo> {
31
+ const connection: DataSource = await this._dbConnection
32
+ const { machineName, instanceId, tenantId } = state
33
+ debug(`Executing persistMachineState for machine ${machineName}, instance ${instanceId}, tenantId: ${tenantId}...`)
34
+ const entity = MachineStateStore.machineStateInfoEntityFrom(state)
35
+ const existing = await connection.getRepository(MachineStateInfoEntity).findOne({
36
+ where: {
37
+ instanceId: state.instanceId,
38
+ },
39
+ })
40
+ if (existing && existing.updatedCount > state.updatedCount) {
41
+ const error = `Updating machine state with an older version is not allowed. Machine ${existing.machineName}, last count: ${
42
+ existing.updatedCount
43
+ }, new count: ${existing.updatedCount}, last updated: ${existing.updatedAt}, current: ${new Date()}, instance: ${existing.instanceId}`
44
+ console.log(error)
45
+ return Promise.reject(new Error(error))
46
+ }
47
+ // No need for a transaction. This is a single entity. We don't want to be surprised by an isolation level hiding the state from others
48
+ const result = await connection.getRepository(MachineStateInfoEntity).save(entity, { transaction: false })
49
+ debug(`Done persistMachineState machine ${machineName}, instance ${instanceId}, tenantId: ${tenantId}`)
50
+ return MachineStateStore.machineInfoFrom(result)
51
+ }
52
+
53
+ async findActiveMachineStates(args: StoreMachineStatesFindActiveArgs): Promise<Array<StoreMachineStateInfo>> {
54
+ const { tenantId, machineName, instanceId } = args
55
+ const connection: DataSource = await this._dbConnection
56
+ debug(`Executing findActiveMachineStates query with machineName: ${machineName}, tenantId: ${tenantId}`)
57
+ const queryBuilder = connection
58
+ .getRepository(MachineStateInfoEntity)
59
+ .createQueryBuilder('state')
60
+ .where('state.completedAt IS NULL')
61
+ .andWhere(
62
+ new Brackets((qb) => {
63
+ qb.where('state.expiresAt IS NULL').orWhere('state.expiresAt > :now', { now: new Date() })
64
+ })
65
+ )
66
+
67
+ if (instanceId) {
68
+ queryBuilder.andWhere('state.instanceId = :instanceId', { instanceId })
69
+ }
70
+ if (tenantId) {
71
+ queryBuilder.andWhere('state.tenantId = :tenantId', { tenantId })
72
+ }
73
+ if (machineName) {
74
+ queryBuilder.andWhere('state.machineName = :machineName', { machineName })
75
+ }
76
+
77
+ return (
78
+ (await queryBuilder
79
+ .orderBy('state.updatedAt', 'DESC')
80
+ .getMany()
81
+ .then((entities) => entities.map(MachineStateStore.machineInfoFrom))) ?? []
82
+ )
83
+ }
84
+
85
+ async findMachineStates(args?: StoreFindMachineStatesArgs): Promise<Array<StoreMachineStateInfo>> {
86
+ const connection: DataSource = await this._dbConnection
87
+ debug('findMachineStates', args)
88
+ const result: Array<MachineStateInfoEntity> = await connection.getRepository(MachineStateInfoEntity).find({
89
+ ...(args?.filter && { where: args?.filter }),
90
+ transaction: false,
91
+ })
92
+
93
+ return result.map((event: MachineStateInfoEntity) => MachineStateStore.machineInfoFrom(event))
94
+ }
95
+
96
+ async getMachineState(args: StoreMachineStateGetArgs): Promise<StoreMachineStateInfo> {
97
+ const connection: DataSource = await this._dbConnection
98
+ debug('getMachineState', args)
99
+ return connection.getRepository(MachineStateInfoEntity).findOneOrFail({ where: { instanceId: args.instanceId } })
100
+ }
101
+
102
+ async deleteMachineState(args: StoreMachineStateDeleteArgs): Promise<boolean> {
103
+ debug(`Executing deleteMachineState query with id: ${args.instanceId}`)
104
+ if (!args.instanceId) {
105
+ throw new Error('No instanceId parameter is provided.')
106
+ }
107
+ try {
108
+ const connection: DataSource = await this._dbConnection
109
+
110
+ const result = await connection.getRepository(MachineStateInfoEntity).delete(args.instanceId)
111
+ return result.affected != null && result.affected > 0
112
+ } catch (error) {
113
+ debug(`Error deleting state: ${error}`)
114
+ return false
115
+ }
116
+ }
117
+
118
+ async deleteExpiredMachineStates(args: StoreMachineStateDeleteExpiredArgs): Promise<number> {
119
+ const { machineName, tenantId, deleteDoneStates } = args
120
+ debug(`Executing deleteExpiredMachineStates query with params: ${JSON.stringify(args)}`)
121
+ try {
122
+ const connection: DataSource = await this._dbConnection
123
+
124
+ const deleteCriteria: FindOptionsWhere<MachineStateInfoEntity> = {
125
+ ...(machineName && { machineName }),
126
+ ...(tenantId && { tenantId }),
127
+ // When deleteOnDone state is set we only look at completedAt, in other cases we compare current time with expiresAt
128
+ ...(!deleteDoneStates && { expiresAt: LessThan(new Date()) }),
129
+ ...(deleteDoneStates && { completedAt: Not(IsNull()) }),
130
+ }
131
+ const result = await connection.getRepository(MachineStateInfoEntity).delete(deleteCriteria)
132
+ return result.affected ?? 0
133
+ } catch (error) {
134
+ debug(`Error deleting machine info: ${error}`)
135
+ return Promise.reject(new Error(`Error deleting expired machine states for machine type ${machineName}`))
136
+ }
137
+ }
138
+
139
+ protected static machineInfoFrom = (machineStateInfoEntity: MachineStateInfoEntity): StoreMachineStateInfo => {
140
+ // We are making sure no entity function get copied
141
+ return JSON.parse(JSON.stringify(machineStateInfoEntity))
142
+ }
143
+
144
+ static machineStateInfoEntityFrom = (machineStateInfo: StoreMachineStateInfo | StoreMachineStatePersistArgs): MachineStateInfoEntity => {
145
+ const entity = new MachineStateInfoEntity()
146
+ Object.assign(entity, machineStateInfo)
147
+ return entity
148
+ }
149
+ }
@@ -0,0 +1,66 @@
1
+ import { DatabaseType, MigrationInterface, QueryRunner } from 'typeorm'
2
+ import Debug from 'debug'
3
+ import { CreateMachineStateStore1708797018115 } from '../postgres/1708797018115-CreateMachineStateStore'
4
+ import { CreateMachineStateStore1708796002272 } from '../sqlite/1708796002272-CreateMachineStateStore'
5
+
6
+ const debug: Debug.Debugger = Debug('sphereon:ssi-sdk:migrations')
7
+
8
+ export class CreateMachineStateStore1708098041262 implements MigrationInterface {
9
+ name = 'CreateMachineStateStore1708098041262'
10
+
11
+ public async up(queryRunner: QueryRunner): Promise<void> {
12
+ debug('migration: creating machine state tables')
13
+ const dbType: DatabaseType = queryRunner.connection.driver.options.type
14
+
15
+ switch (dbType) {
16
+ case 'postgres': {
17
+ debug('using postgres migration file')
18
+ const mig: CreateMachineStateStore1708797018115 = new CreateMachineStateStore1708797018115()
19
+ await mig.up(queryRunner)
20
+ debug('Migration statements executed')
21
+ return
22
+ }
23
+ case 'sqlite':
24
+ case 'expo':
25
+ case 'react-native': {
26
+ debug('using sqlite/react-native migration file')
27
+ const mig: CreateMachineStateStore1708796002272 = new CreateMachineStateStore1708796002272()
28
+ await mig.up(queryRunner)
29
+ debug('Migration statements executed')
30
+ return
31
+ }
32
+ default:
33
+ return Promise.reject(
34
+ `Migrations are currently only supported for sqlite, react-native, expo and postgres. Was ${dbType}. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now`
35
+ )
36
+ }
37
+ }
38
+
39
+ public async down(queryRunner: QueryRunner): Promise<void> {
40
+ debug('migration: reverting machine state tables')
41
+ const dbType: DatabaseType = queryRunner.connection.driver.options.type
42
+
43
+ switch (dbType) {
44
+ case 'postgres': {
45
+ debug('using postgres migration file')
46
+ const mig: CreateMachineStateStore1708797018115 = new CreateMachineStateStore1708797018115()
47
+ await mig.down(queryRunner)
48
+ debug('Migration statements executed')
49
+ return
50
+ }
51
+ case 'sqlite':
52
+ case 'expo':
53
+ case 'react-native': {
54
+ debug('using sqlite/react-native migration file')
55
+ const mig: CreateMachineStateStore1708796002272 = new CreateMachineStateStore1708796002272()
56
+ await mig.down(queryRunner)
57
+ debug('Migration statements executed')
58
+ return
59
+ }
60
+ default:
61
+ return Promise.reject(
62
+ `Migrations are currently only supported for sqlite, react-native, expo and postgres. Was ${dbType}. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now`
63
+ )
64
+ }
65
+ }
66
+ }