@sphereon/ssi-sdk.data-store 0.15.2-next.33 → 0.15.2-next.39

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 (79) hide show
  1. package/dist/entities/statusList2021/StatusList2021Entity.d.ts +19 -0
  2. package/dist/entities/statusList2021/StatusList2021Entity.d.ts.map +1 -0
  3. package/dist/entities/statusList2021/StatusList2021Entity.js +115 -0
  4. package/dist/entities/statusList2021/StatusList2021Entity.js.map +1 -0
  5. package/dist/entities/statusList2021/StatusList2021EntryEntity.d.ts +11 -0
  6. package/dist/entities/statusList2021/StatusList2021EntryEntity.d.ts.map +1 -0
  7. package/dist/entities/statusList2021/StatusList2021EntryEntity.js +51 -0
  8. package/dist/entities/statusList2021/StatusList2021EntryEntity.js.map +1 -0
  9. package/dist/index.d.ts +7 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +10 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/migrations/generic/{1-CreateIssuanceBranding.d.ts → 2-CreateIssuanceBranding.d.ts} +1 -1
  14. package/dist/migrations/generic/{1-CreateIssuanceBranding.d.ts.map → 2-CreateIssuanceBranding.d.ts.map} +1 -1
  15. package/dist/migrations/generic/{1-CreateIssuanceBranding.js → 2-CreateIssuanceBranding.js} +1 -1
  16. package/dist/migrations/generic/{1-CreateIssuanceBranding.js.map → 2-CreateIssuanceBranding.js.map} +1 -1
  17. package/dist/migrations/generic/3-CreateStatusList.d.ts +7 -0
  18. package/dist/migrations/generic/3-CreateStatusList.d.ts.map +1 -0
  19. package/dist/migrations/generic/3-CreateStatusList.js +72 -0
  20. package/dist/migrations/generic/3-CreateStatusList.js.map +1 -0
  21. package/dist/migrations/generic/index.d.ts +5 -0
  22. package/dist/migrations/generic/index.d.ts.map +1 -1
  23. package/dist/migrations/generic/index.js +9 -3
  24. package/dist/migrations/generic/index.js.map +1 -1
  25. package/dist/migrations/postgres/1659463079428-CreateContacts.d.ts.map +1 -1
  26. package/dist/migrations/postgres/1659463079428-CreateContacts.js +4 -2
  27. package/dist/migrations/postgres/1659463079428-CreateContacts.js.map +1 -1
  28. package/dist/migrations/postgres/1685628974232-CreateIssuanceBranding.d.ts.map +1 -1
  29. package/dist/migrations/postgres/1685628974232-CreateIssuanceBranding.js +2 -0
  30. package/dist/migrations/postgres/1685628974232-CreateIssuanceBranding.js.map +1 -1
  31. package/dist/migrations/postgres/CreateStatusList1693866470001-CreateStatusList.d.ts +7 -0
  32. package/dist/migrations/postgres/CreateStatusList1693866470001-CreateStatusList.d.ts.map +1 -0
  33. package/dist/migrations/postgres/CreateStatusList1693866470001-CreateStatusList.js +32 -0
  34. package/dist/migrations/postgres/CreateStatusList1693866470001-CreateStatusList.js.map +1 -0
  35. package/dist/migrations/postgres/uuid.d.ts +3 -0
  36. package/dist/migrations/postgres/uuid.d.ts.map +1 -0
  37. package/dist/migrations/postgres/uuid.js +25 -0
  38. package/dist/migrations/postgres/uuid.js.map +1 -0
  39. package/dist/migrations/sqlite/1693866470000-CreateStatusList.d.ts +7 -0
  40. package/dist/migrations/sqlite/1693866470000-CreateStatusList.d.ts.map +1 -0
  41. package/dist/migrations/sqlite/1693866470000-CreateStatusList.js +32 -0
  42. package/dist/migrations/sqlite/1693866470000-CreateStatusList.js.map +1 -0
  43. package/dist/statusList/IStatusListStore.d.ts +20 -0
  44. package/dist/statusList/IStatusListStore.d.ts.map +1 -0
  45. package/dist/statusList/IStatusListStore.js +3 -0
  46. package/dist/statusList/IStatusListStore.js.map +1 -0
  47. package/dist/statusList/StatusListStore.d.ts +36 -0
  48. package/dist/statusList/StatusListStore.d.ts.map +1 -0
  49. package/dist/statusList/StatusListStore.js +213 -0
  50. package/dist/statusList/StatusListStore.js.map +1 -0
  51. package/dist/types/index.d.ts +2 -0
  52. package/dist/types/index.d.ts.map +1 -1
  53. package/dist/types/index.js +2 -0
  54. package/dist/types/index.js.map +1 -1
  55. package/dist/types/statusList/IAbstractStatusListStore.d.ts +38 -0
  56. package/dist/types/statusList/IAbstractStatusListStore.d.ts.map +1 -0
  57. package/dist/types/statusList/IAbstractStatusListStore.js +3 -0
  58. package/dist/types/statusList/IAbstractStatusListStore.js.map +1 -0
  59. package/dist/types/statusList/statusList.d.ts +25 -0
  60. package/dist/types/statusList/statusList.d.ts.map +1 -0
  61. package/dist/types/statusList/statusList.js +3 -0
  62. package/dist/types/statusList/statusList.js.map +1 -0
  63. package/package.json +4 -4
  64. package/src/entities/statusList2021/StatusList2021Entity.ts +95 -0
  65. package/src/entities/statusList2021/StatusList2021EntryEntity.ts +29 -0
  66. package/src/index.ts +13 -0
  67. package/src/migrations/generic/3-CreateStatusList.ts +54 -0
  68. package/src/migrations/generic/index.ts +10 -2
  69. package/src/migrations/postgres/1659463079428-CreateContacts.ts +4 -2
  70. package/src/migrations/postgres/1685628974232-CreateIssuanceBranding.ts +2 -0
  71. package/src/migrations/postgres/CreateStatusList1693866470001-CreateStatusList.ts +24 -0
  72. package/src/migrations/postgres/uuid.ts +12 -0
  73. package/src/migrations/sqlite/1693866470000-CreateStatusList.ts +24 -0
  74. package/src/statusList/IStatusListStore.ts +44 -0
  75. package/src/statusList/StatusListStore.ts +237 -0
  76. package/src/types/index.ts +2 -0
  77. package/src/types/statusList/IAbstractStatusListStore.ts +47 -0
  78. package/src/types/statusList/statusList.ts +39 -0
  79. /package/src/migrations/generic/{1-CreateIssuanceBranding.ts → 2-CreateIssuanceBranding.ts} +0 -0
@@ -0,0 +1,95 @@
1
+ import {
2
+ IIssuer,
3
+ OriginalVerifiableCredential,
4
+ StatusListCredentialIdMode,
5
+ StatusListDriverType,
6
+ StatusListIndexingDirection,
7
+ StatusListType,
8
+ StatusPurpose2021,
9
+ } from '@sphereon/ssi-types'
10
+ import { ProofFormat } from '@veramo/core'
11
+ import { BaseEntity, Column, Entity, OneToMany, PrimaryColumn, Unique } from 'typeorm'
12
+ import { StatusListEntryEntity } from './StatusList2021EntryEntity'
13
+
14
+ @Entity('StatusList')
15
+ @Unique('UQ_correlationId', ['correlationId'])
16
+ export class StatusListEntity extends BaseEntity {
17
+ @PrimaryColumn({ name: 'id', type: 'varchar' })
18
+ id!: string
19
+
20
+ @Column({ name: 'correlationId', type: 'varchar', nullable: false })
21
+ correlationId!: string
22
+
23
+ @Column({ name: 'length', nullable: false, unique: false })
24
+ length!: number
25
+
26
+ @Column({
27
+ name: 'issuer',
28
+ type: 'text',
29
+ nullable: false,
30
+ unique: false,
31
+ transformer: {
32
+ from(value: string): string | IIssuer {
33
+ if (value?.trim()?.startsWith('{')) {
34
+ return JSON.parse(value)
35
+ }
36
+ return value
37
+ },
38
+ to(value: string | IIssuer): string {
39
+ if (typeof value === 'string') {
40
+ return value
41
+ }
42
+ return JSON.stringify(value)
43
+ },
44
+ },
45
+ })
46
+ issuer!: string | IIssuer
47
+
48
+ @Column('simple-enum', { name: 'type', enum: StatusListType, nullable: false, default: StatusListType.StatusList2021 })
49
+ type!: StatusListType
50
+
51
+ @Column('simple-enum', { name: 'driverType', enum: StatusListDriverType, nullable: false, default: StatusListDriverType.AGENT_TYPEORM })
52
+ driverType!: StatusListDriverType
53
+
54
+ @Column('simple-enum', {
55
+ name: 'credentialIdMode',
56
+ enum: StatusListCredentialIdMode,
57
+ nullable: false,
58
+ default: StatusListCredentialIdMode.ISSUANCE,
59
+ })
60
+ credentialIdMode!: StatusListCredentialIdMode
61
+
62
+ @Column({ type: 'varchar', name: 'proofFormat', enum: ['lds', 'jwt'], nullable: false, default: 'lds' })
63
+ proofFormat!: ProofFormat
64
+
65
+ @Column({ type: 'varchar', name: 'indexingDirection', enum: ['rightToLeft'], nullable: false, default: 'rightToLeft' })
66
+ indexingDirection!: StatusListIndexingDirection
67
+
68
+ @Column({ type: 'varchar', name: 'statusPurpose', nullable: false, default: 'revocation' })
69
+ statusPurpose!: StatusPurpose2021
70
+
71
+ @Column({
72
+ name: 'statusListCredential',
73
+ type: 'text',
74
+ nullable: true,
75
+ unique: false,
76
+ transformer: {
77
+ from(value: string): OriginalVerifiableCredential {
78
+ if (value?.startsWith('ey')) {
79
+ return value
80
+ }
81
+ return JSON.parse(value)
82
+ },
83
+ to(value: OriginalVerifiableCredential): string {
84
+ if (typeof value === 'string') {
85
+ return value
86
+ }
87
+ return JSON.stringify(value)
88
+ },
89
+ },
90
+ })
91
+ statusListCredential?: OriginalVerifiableCredential
92
+
93
+ @OneToMany((type) => StatusListEntryEntity, (entry) => entry.statusList)
94
+ statusListEntries!: StatusListEntryEntity[]
95
+ }
@@ -0,0 +1,29 @@
1
+ import { Validate } from 'class-validator'
2
+ import { BaseEntity, Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'
3
+ import { IsNonEmptyStringConstraint } from '../validators'
4
+ import { StatusListEntity } from './StatusList2021Entity'
5
+
6
+ @Entity('StatusListEntry')
7
+ // @Unique('uq_credential_statuslist', ['statusList', 'credentialId']) // disabled because one prop can be null
8
+ // @Unique('uq_credentialHash_statuslistId', ['statusList', 'credentialHash']) // disabled because one prop can be null
9
+ export class StatusListEntryEntity extends BaseEntity {
10
+ @PrimaryColumn({ name: 'statusListId', type: 'varchar' })
11
+ @ManyToOne(() => StatusListEntity, (statusList) => statusList.statusListEntries)
12
+ statusList!: StatusListEntity
13
+
14
+ @PrimaryColumn({ name: 'statusListIndex', nullable: false, unique: false })
15
+ @Validate(IsNonEmptyStringConstraint, { message: 'Status list index is required' })
16
+ statusListIndex!: number
17
+
18
+ @Column({ name: 'credentialId', nullable: true })
19
+ credentialId?: string
20
+
21
+ @Column({ name: 'credentialHash', length: 128, nullable: true, unique: false })
22
+ credentialHash?: string
23
+
24
+ @Column({ name: 'correlationId', length: 255, nullable: true, unique: false })
25
+ correlationId?: string
26
+
27
+ @Column({ name: 'value', length: 50, nullable: true, unique: false })
28
+ value?: string
29
+ }
package/src/index.ts CHANGED
@@ -15,11 +15,15 @@ import { ImageDimensionsEntity, imageDimensionsEntityFrom } from './entities/iss
15
15
  import { IssuerLocaleBrandingEntity, issuerLocaleBrandingEntityFrom } from './entities/issuanceBranding/IssuerLocaleBrandingEntity'
16
16
  import { IssuerBrandingEntity, issuerBrandingEntityFrom } from './entities/issuanceBranding/IssuerBrandingEntity'
17
17
  import { TextAttributesEntity, textAttributesEntityFrom } from './entities/issuanceBranding/TextAttributesEntity'
18
+ import { StatusListEntity } from './entities/statusList2021/StatusList2021Entity'
19
+ import { StatusListEntryEntity } from './entities/statusList2021/StatusList2021EntryEntity'
20
+ import { IStatusListEntity, IStatusListEntryEntity } from './types'
18
21
 
19
22
  export { ContactStore } from './contact/ContactStore'
20
23
  export { AbstractContactStore } from './contact/AbstractContactStore'
21
24
  export { AbstractIssuanceBrandingStore } from './issuanceBranding/AbstractIssuanceBrandingStore'
22
25
  export { IssuanceBrandingStore } from './issuanceBranding/IssuanceBrandingStore'
26
+ export { StatusListStore } from './statusList/StatusListStore'
23
27
  export { DataStoreMigrations } from './migrations'
24
28
  export * from './types'
25
29
 
@@ -46,6 +50,11 @@ export const DataStoreIssuanceBrandingEntities = [
46
50
  IssuerLocaleBrandingEntity,
47
51
  ]
48
52
 
53
+ export const DataStoreStatusListEntities = [StatusListEntity, StatusListEntryEntity]
54
+
55
+ // All entities combined if a party wants to enable them all at once
56
+ export const DataStoreEntities = [...DataStoreContactEntities, ...DataStoreIssuanceBrandingEntities, ...DataStoreStatusListEntities]
57
+
49
58
  export {
50
59
  BaseConfigEntity,
51
60
  ConnectionEntity,
@@ -79,4 +88,8 @@ export {
79
88
  textAttributesEntityFrom,
80
89
  issuerLocaleBrandingEntityFrom,
81
90
  credentialLocaleBrandingEntityFrom,
91
+ IStatusListEntity,
92
+ IStatusListEntryEntity,
93
+ StatusListEntity,
94
+ StatusListEntryEntity,
82
95
  }
@@ -0,0 +1,54 @@
1
+ import Debug from 'debug'
2
+ import { MigrationInterface, QueryRunner } from 'typeorm'
3
+ import { CreateStatusList1693866470001 } from '../postgres/CreateStatusList1693866470001-CreateStatusList'
4
+ import { CreateStatusList1693866470002 } from '../sqlite/1693866470000-CreateStatusList'
5
+
6
+ const debug = Debug('sphereon:ssi-sdk:migrations')
7
+
8
+ export class CreateStatusList1693866470000 implements MigrationInterface {
9
+ name = 'CreateStatusList1693866470000'
10
+
11
+ public async up(queryRunner: QueryRunner): Promise<void> {
12
+ debug('migration: creating issuance branding tables')
13
+ const dbType = queryRunner.connection.driver.options.type
14
+ if (dbType === 'postgres') {
15
+ debug('using postgres migration file')
16
+ const mig = new CreateStatusList1693866470001()
17
+ const up = await mig.up(queryRunner)
18
+ debug('Migration statements executed')
19
+ return up
20
+ } else if (dbType === 'sqlite' || 'react-native') {
21
+ debug('using sqlite/react-native migration file')
22
+ const mig = new CreateStatusList1693866470002()
23
+ const up = await mig.up(queryRunner)
24
+ debug('Migration statements executed')
25
+ return up
26
+ } else {
27
+ return Promise.reject(
28
+ "Migrations are currently only supported for sqlite, react-native and postgres. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now"
29
+ )
30
+ }
31
+ }
32
+
33
+ public async down(queryRunner: QueryRunner): Promise<void> {
34
+ debug('migration: reverting issuance branding tables')
35
+ const dbType = queryRunner.connection.driver.options.type
36
+ if (dbType === 'postgres') {
37
+ debug('using postgres migration file')
38
+ const mig = new CreateStatusList1693866470002()
39
+ const down = await mig.down(queryRunner)
40
+ debug('Migration statements executed')
41
+ return down
42
+ } else if (dbType === 'sqlite' || 'react-native') {
43
+ debug('using sqlite/react-native migration file')
44
+ const mig = new CreateStatusList1693866470002()
45
+ const down = await mig.down(queryRunner)
46
+ debug('Migration statements executed')
47
+ return down
48
+ } else {
49
+ return Promise.reject(
50
+ "Migrations are currently only supported for sqlite, react-native and postgres. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now"
51
+ )
52
+ }
53
+ }
54
+ }
@@ -1,5 +1,6 @@
1
1
  import { CreateContacts1659463079429 } from './1-CreateContacts'
2
- import { CreateIssuanceBranding1659463079429 } from './1-CreateIssuanceBranding'
2
+ import { CreateIssuanceBranding1659463079429 } from './2-CreateIssuanceBranding'
3
+ import { CreateStatusList1693866470000 } from './3-CreateStatusList'
3
4
 
4
5
  /**
5
6
  * The migrations array that SHOULD be used when initializing a TypeORM database connection.
@@ -8,4 +9,11 @@ import { CreateIssuanceBranding1659463079429 } from './1-CreateIssuanceBranding'
8
9
  *
9
10
  * @public
10
11
  */
11
- export const DataStoreMigrations = [CreateContacts1659463079429, CreateIssuanceBranding1659463079429]
12
+
13
+ // Individual migrations per purpose. Allows parties to not run migrations and thus create/update tables if they are not using a particular feature (yet)
14
+ export const DataStoreContactMigrations = [CreateContacts1659463079429]
15
+ export const DataStoreIssuanceBrandingMigrations = [CreateIssuanceBranding1659463079429]
16
+ export const DataStoreStatusListMigrations = [CreateStatusList1693866470000]
17
+
18
+ // All migrations together
19
+ export const DataStoreMigrations = [CreateContacts1659463079429, CreateIssuanceBranding1659463079429, CreateStatusList1693866470000]
@@ -1,9 +1,11 @@
1
1
  import { MigrationInterface, QueryRunner } from 'typeorm'
2
+ import { enableUuidv4 } from './uuid'
2
3
 
3
4
  export class CreateContacts1659463079428 implements MigrationInterface {
4
5
  name = 'CreateContacts1659463079428'
5
6
 
6
7
  public async up(queryRunner: QueryRunner): Promise<void> {
8
+ await enableUuidv4(queryRunner)
7
9
  await queryRunner.query(
8
10
  `CREATE TABLE "BaseConfigEntity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "client_id" character varying(255), "client_secret" character varying(255), "scopes" text, "issuer" character varying(255), "redirect_url" text, "dangerously_allow_insecure_http_requests" boolean, "client_auth_method" text, "identifier" character varying(255), "session_id" character varying(255), "type" character varying NOT NULL, "connectionId" uuid, CONSTRAINT "REL_BaseConfig_connectionId" UNIQUE ("connectionId"), CONSTRAINT "PK_BaseConfigEntity_id" PRIMARY KEY ("id"))`
9
11
  )
@@ -13,13 +15,13 @@ export class CreateContacts1659463079428 implements MigrationInterface {
13
15
  `CREATE TABLE "CorrelationIdentifier" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "type" "public"."CorrelationIdentifier_type_enum" NOT NULL, "correlation_id" text NOT NULL, "identityId" uuid, CONSTRAINT "UQ_Correlation_id" UNIQUE ("correlation_id"), CONSTRAINT "REL_CorrelationIdentifier_identityId" UNIQUE ("identityId"), CONSTRAINT "PK_CorrelationIdentifier_id" PRIMARY KEY ("id"))`
14
16
  )
15
17
  await queryRunner.query(
16
- `CREATE TABLE "Contact" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "alias" character varying(255) NOT NULL, "uri" character varying(255) NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_Name" UNIQUE ("name"), CONSTRAINT "UQ_Alias" UNIQUE ("alias"), CONSTRAINT "PK_Contact_id" PRIMARY KEY ("id"))`
18
+ `CREATE TABLE "Contact" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "alias" character varying(255) NOT NULL, "uri" character varying(255) NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_Name" UNIQUE ("name"), CONSTRAINT "UQ_Contact_Alias" UNIQUE ("alias"), CONSTRAINT "PK_Contact_id" PRIMARY KEY ("id"))`
17
19
  )
18
20
  await queryRunner.query(
19
21
  `CREATE TABLE "IdentityMetadata" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "label" character varying(255) NOT NULL, "value" character varying(255) NOT NULL, "identityId" uuid, CONSTRAINT "PK_IdentityMetadata_id" PRIMARY KEY ("id"))`
20
22
  )
21
23
  await queryRunner.query(
22
- `CREATE TABLE "Identity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "alias" character varying(255) NOT NULL, "roles" text, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(), "contactId" uuid, CONSTRAINT "UQ_Alias" UNIQUE ("alias"), CONSTRAINT "PK_Identity_id" PRIMARY KEY ("id"))`
24
+ `CREATE TABLE "Identity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "alias" character varying(255) NOT NULL, "roles" text, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "last_updated_at" TIMESTAMP NOT NULL DEFAULT now(), "contactId" uuid, CONSTRAINT "UQ_Identity_Alias" UNIQUE ("alias"), CONSTRAINT "PK_Identity_id" PRIMARY KEY ("id"))`
23
25
  )
24
26
  await queryRunner.query(`CREATE TYPE "public"."Connection_type_enum" AS ENUM('OIDC', 'SIOPv2', 'SIOPv2+OpenID4VP')`)
25
27
  await queryRunner.query(
@@ -1,9 +1,11 @@
1
1
  import { MigrationInterface, QueryRunner } from 'typeorm'
2
+ import { enableUuidv4 } from './uuid'
2
3
 
3
4
  export class CreateIssuanceBranding1685628974232 implements MigrationInterface {
4
5
  name = 'CreateIssuanceBranding1685628974232'
5
6
 
6
7
  public async up(queryRunner: QueryRunner): Promise<void> {
8
+ await enableUuidv4(queryRunner)
7
9
  await queryRunner.query(
8
10
  `CREATE TABLE "ImageDimensions" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "width" integer NOT NULL, "height" integer NOT NULL, CONSTRAINT "PK_ImageDimensions_id" PRIMARY KEY ("id"))`
9
11
  )
@@ -0,0 +1,24 @@
1
+ import { MigrationInterface, QueryRunner } from 'typeorm'
2
+
3
+ export class CreateStatusList1693866470001 implements MigrationInterface {
4
+ name = 'CreateStatusList1693866470001'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "StatusListEntry" ("statusListId" character varying NOT NULL, "statusListIndex" integer NOT NULL, "credentialId" character varying, "credentialHash" character varying(128), "correlationId" character varying(255), "value" character varying(50), CONSTRAINT "PK_68704d2d13857360c6b44a3d1d0" PRIMARY KEY ("statusListId", "statusListIndex"))`
9
+ )
10
+ await queryRunner.query(`CREATE TYPE "public"."StatusList_type_enum" AS ENUM('StatusList2021')`)
11
+ await queryRunner.query(
12
+ `CREATE TYPE "public"."StatusList_drivertype_enum" AS ENUM('agent_typeorm', 'agent_kv_store', 'github', 'agent_filesystem')`
13
+ )
14
+ await queryRunner.query(`CREATE TYPE "public"."StatusList_credentialidmode_enum" AS ENUM('ISSUANCE', 'PERSISTENCE', 'NEVER')`)
15
+ await queryRunner.query(
16
+ `CREATE TABLE "StatusList" ("id" character varying NOT NULL, "correlationId" character varying NOT NULL, "length" integer NOT NULL, "issuer" text NOT NULL, "type" "public"."StatusList_type_enum" NOT NULL DEFAULT 'StatusList2021', "driverType" "public"."StatusList_drivertype_enum" NOT NULL DEFAULT 'agent_typeorm', "credentialIdMode" "public"."StatusList_credentialidmode_enum" NOT NULL DEFAULT 'ISSUANCE', "proofFormat" character varying NOT NULL DEFAULT 'lds', "indexingDirection" character varying NOT NULL DEFAULT 'rightToLeft', "statusPurpose" character varying NOT NULL DEFAULT 'revocation', "statusListCredential" text, CONSTRAINT "UQ_correlationId" UNIQUE ("correlationId"), CONSTRAINT "PK_StatusList_Id" PRIMARY KEY ("id"))`
17
+ )
18
+ await queryRunner.query(
19
+ `ALTER TABLE "StatusListEntry" ADD CONSTRAINT "FK_statusListEntry_statusListId" FOREIGN KEY ("statusListId") REFERENCES "StatusList"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
20
+ )
21
+ }
22
+
23
+ public async down(queryRunner: QueryRunner): Promise<void> {}
24
+ }
@@ -0,0 +1,12 @@
1
+ import { QueryRunner } from 'typeorm'
2
+
3
+ export async function enableUuidv4(queryRunner: QueryRunner) {
4
+ try {
5
+ await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`)
6
+ } catch (error) {
7
+ console.log(
8
+ `Please enable the uuid-ossp.control extension in your postgresql installation. It enables generating V4 UUID and can be found in the postgresql-contrib package`
9
+ )
10
+ throw error
11
+ }
12
+ }
@@ -0,0 +1,24 @@
1
+ import { MigrationInterface, QueryRunner } from 'typeorm'
2
+
3
+ export class CreateStatusList1693866470002 implements MigrationInterface {
4
+ name = 'CreateStatusList1693866470002'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "StatusListEntry" ("statusListId" varchar NOT NULL, "statusListIndex" integer NOT NULL, "credentialId" varchar, "credentialHash" varchar(128), "correlationId" varchar(255), "value" varchar(50), PRIMARY KEY ("statusListId", "statusListIndex"))`
9
+ )
10
+ await queryRunner.query(
11
+ `CREATE TABLE "StatusList" ("id" varchar PRIMARY KEY NOT NULL, "correlationId" varchar NOT NULL, "length" integer NOT NULL, "issuer" text NOT NULL, "type" varchar CHECK( "type" IN ('StatusList2021') ) NOT NULL DEFAULT ('StatusList2021'), "driverType" varchar CHECK( "driverType" IN ('agent_typeorm','agent_kv_store','github','agent_filesystem') ) NOT NULL DEFAULT ('agent_typeorm'), "credentialIdMode" varchar CHECK( "credentialIdMode" IN ('ISSUANCE','PERSISTENCE','NEVER') ) NOT NULL DEFAULT ('ISSUANCE'), "proofFormat" varchar CHECK( "proofFormat" IN ('lds','jwt') ) NOT NULL DEFAULT ('lds'), "indexingDirection" varchar CHECK( "indexingDirection" IN ('rightToLeft') ) NOT NULL DEFAULT ('rightToLeft'), "statusPurpose" varchar NOT NULL DEFAULT ('revocation'), "statusListCredential" text, CONSTRAINT "UQ_correlationId" UNIQUE ("correlationId"))`
12
+ )
13
+ await queryRunner.query(
14
+ `CREATE TABLE "temporary_StatusListEntry" ("statusListId" varchar NOT NULL, "statusListIndex" integer NOT NULL, "credentialId" varchar, "credentialHash" varchar(128), "correlationId" varchar(255), "value" varchar(50), CONSTRAINT "FK_statusListEntry_statusListId" FOREIGN KEY ("statusListId") REFERENCES "StatusList" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, PRIMARY KEY ("statusListId", "statusListIndex"))`
15
+ )
16
+ await queryRunner.query(
17
+ `INSERT INTO "temporary_StatusListEntry"("statusListId", "statusListIndex", "credentialId", "credentialHash", "correlationId", "value") SELECT "statusListId", "statusListIndex", "credentialId", "credentialHash", "correlationId", "value" FROM "StatusListEntry"`
18
+ )
19
+ await queryRunner.query(`DROP TABLE "StatusListEntry"`)
20
+ await queryRunner.query(`ALTER TABLE "temporary_StatusListEntry" RENAME TO "StatusListEntry"`)
21
+ }
22
+
23
+ public async down(queryRunner: QueryRunner): Promise<void> {}
24
+ }
@@ -0,0 +1,44 @@
1
+ import { StatusListEntryEntity } from '../entities/statusList2021/StatusList2021EntryEntity'
2
+ import {
3
+ IAddStatusListArgs,
4
+ IAddStatusListEntryArgs,
5
+ IGetStatusListArgs,
6
+ IGetStatusListEntriesArgs,
7
+ IGetStatusListEntryByCredentialIdArgs,
8
+ IGetStatusListEntryByIndexArgs,
9
+ IGetStatusListsArgs,
10
+ IRemoveStatusListArgs,
11
+ IStatusListEntryAvailableArgs,
12
+ IUpdateStatusListIndexArgs,
13
+ } from '../types/statusList/IAbstractStatusListStore'
14
+ import { IStatusListEntity, IStatusListEntryEntity } from '../types/statusList/statusList'
15
+
16
+ export interface IStatusListStore {
17
+ getStatusList(args: IGetStatusListArgs): Promise<IStatusListEntity>
18
+
19
+ getStatusLists(args: IGetStatusListsArgs): Promise<Array<IStatusListEntity>>
20
+
21
+ removeStatusList(args: IRemoveStatusListArgs): Promise<void>
22
+
23
+ addStatusList(args: IAddStatusListArgs): Promise<IStatusListEntity>
24
+
25
+ updateStatusList(args: IUpdateStatusListIndexArgs): Promise<IStatusListEntity>
26
+
27
+ availableStatusListEntries(args: IStatusListEntryAvailableArgs): Promise<number[]>
28
+
29
+ addStatusListEntry(args: IAddStatusListEntryArgs): Promise<IStatusListEntryEntity>
30
+
31
+ updateStatusListEntry(args: IAddStatusListEntryArgs): Promise<IStatusListEntryEntity>
32
+
33
+ getStatusListEntryByIndex(args: IGetStatusListEntryByIndexArgs): Promise<StatusListEntryEntity | undefined>
34
+
35
+ getStatusListEntryByCredentialId(args: IGetStatusListEntryByCredentialIdArgs): Promise<StatusListEntryEntity | undefined>
36
+
37
+ removeStatusListEntryByIndex(args: IGetStatusListEntryByIndexArgs): Promise<boolean>
38
+
39
+ removeStatusListEntryByCredentialId(args: IGetStatusListEntryByCredentialIdArgs): Promise<boolean>
40
+
41
+ getStatusListEntries(args: IGetStatusListEntriesArgs): Promise<IStatusListEntryEntity[]>
42
+
43
+ getStatusList(args: IGetStatusListArgs): Promise<IStatusListEntity>
44
+ }
@@ -0,0 +1,237 @@
1
+ import { OrPromise } from '@sphereon/ssi-types'
2
+ import Debug from 'debug'
3
+ import { DataSource, In, Repository } from 'typeorm'
4
+ import { StatusListEntity } from '../entities/statusList2021/StatusList2021Entity'
5
+ import { StatusListEntryEntity } from '../entities/statusList2021/StatusList2021EntryEntity'
6
+ import {
7
+ IAddStatusListArgs,
8
+ IAddStatusListEntryArgs,
9
+ IGetStatusListArgs,
10
+ IGetStatusListEntriesArgs,
11
+ IGetStatusListEntryByCredentialIdArgs,
12
+ IGetStatusListEntryByIndexArgs,
13
+ IGetStatusListsArgs,
14
+ IRemoveStatusListArgs,
15
+ IStatusListEntryAvailableArgs,
16
+ IUpdateStatusListIndexArgs,
17
+ IStatusListEntity,
18
+ IStatusListEntryEntity,
19
+ } from '../types'
20
+ import { IStatusListStore } from './IStatusListStore'
21
+
22
+ const debug = Debug('sphereon:ssi-sdk:data-store:status-list')
23
+
24
+ export class StatusListStore implements IStatusListStore {
25
+ private readonly _dbConnection: OrPromise<DataSource>
26
+
27
+ constructor(dbConnection: OrPromise<DataSource>) {
28
+ this._dbConnection = dbConnection
29
+ }
30
+
31
+ /**
32
+ * Get's the available status list indices from the provided indices. Meaning it will filter out any index that is already known.
33
+ *
34
+ * The idea is that the caller provides a set of random status list indices. We can relatively easy check against the DB in an optimized way.
35
+ * If the status list is large it is probably best ot also provide at least a good number of indices. So something like 10 or 20 values.
36
+ * Callers are also expected to call this function multiple times if it does not yield results
37
+ *
38
+ * @param args
39
+ */
40
+ async availableStatusListEntries(args: IStatusListEntryAvailableArgs): Promise<number[]> {
41
+ const statusListIndex = Array.isArray(args.statusListIndex) ? args.statusListIndex : [args.statusListIndex]
42
+ const statusList = await this.getStatusList({ ...args, id: args.statusListId })
43
+ const repo = await this.getStatusListEntryRepo()
44
+ const results = (
45
+ await repo.find({
46
+ where: {
47
+ statusList,
48
+ statusListIndex: In(statusListIndex),
49
+ },
50
+ })
51
+ ).map((index) => index.statusListIndex)
52
+ return statusListIndex.filter((index) => !results.includes(index))
53
+ }
54
+
55
+ async addStatusListEntry(args: IAddStatusListEntryArgs): Promise<IStatusListEntryEntity> {
56
+ return (await this.getStatusListEntryRepo()).save(args)
57
+ }
58
+
59
+ async updateStatusListEntry(args: IAddStatusListEntryArgs): Promise<IStatusListEntryEntity> {
60
+ const statusListId = typeof args.statusList === 'string' ? args.statusList : args.statusList.id
61
+ const result = await this.getStatusListEntryByIndex({ ...args, statusListId, errorOnNotFound: false })
62
+ const updatedEntry: Partial<IStatusListEntryEntity> = {
63
+ value: args.value,
64
+ correlationId: args.correlationId,
65
+ credentialHash: args.credentialHash,
66
+ credentialId: args.credentialId,
67
+ }
68
+
69
+ const updateResult = await (
70
+ await this.getStatusListEntryRepo()
71
+ ).upsert(
72
+ { ...(result ?? { statusList: args.statusList, statusListIndex: args.statusListIndex }), ...updatedEntry },
73
+ { conflictPaths: ['statusList', 'statusListIndex'] }
74
+ )
75
+ console.log(updateResult)
76
+ return (await this.getStatusListEntryByIndex({ ...args, statusListId, errorOnNotFound: true })) as IStatusListEntryEntity
77
+ }
78
+
79
+ async getStatusListEntryByIndex(args: IGetStatusListEntryByIndexArgs): Promise<StatusListEntryEntity | undefined> {
80
+ if (!args.statusListId && !args.correlationId) {
81
+ throw Error(`Cannot get statusList entry if not either a statusList id or correlationId is provided`)
82
+ }
83
+ const result = await (
84
+ await this.getStatusListEntryRepo()
85
+ ).findOne({
86
+ where: {
87
+ ...(args.statusListId && { statusList: args.statusListId }),
88
+ ...(args.correlationId && { correlationId: args.correlationId }),
89
+ statusListIndex: args.statusListIndex,
90
+ },
91
+ })
92
+
93
+ if (!result && args.errorOnNotFound) {
94
+ throw Error(`Could not find status list index ${args.statusListIndex} for status list id ${args.statusListId}`)
95
+ }
96
+ return result ?? undefined
97
+ }
98
+
99
+ async removeStatusListEntryByIndex(args: IGetStatusListEntryByIndexArgs): Promise<boolean> {
100
+ let error = false
101
+ try {
102
+ await this.getStatusListEntryByIndex(args) // only used to check it exists
103
+ } catch (error) {
104
+ error = true
105
+ }
106
+ if (error) {
107
+ console.log(`Could not delete statusList ${args.statusListId} entry by index ${args.statusListIndex}`)
108
+ } else {
109
+ const result = await (
110
+ await this.getStatusListEntryRepo()
111
+ ).delete({
112
+ ...(args.statusListId && { statusList: args.statusListId }),
113
+ ...(args.correlationId && { correlationId: args.correlationId }),
114
+ statusListIndex: args.statusListIndex,
115
+ })
116
+ error = !result.affected || result.affected !== 1
117
+ }
118
+ return !error
119
+ }
120
+
121
+ async getStatusListEntryByCredentialId(args: IGetStatusListEntryByCredentialIdArgs): Promise<StatusListEntryEntity | undefined> {
122
+ const credentialId = args.credentialId
123
+ if (!credentialId) {
124
+ throw Error('Can only get a credential by credentialId when a credentialId is supplied')
125
+ }
126
+ const statusList = await this.getStatusList({ id: args.statusListId, correlationId: args.statusListCorrelationId })
127
+ const where = {
128
+ statusList: statusList.id,
129
+ ...(args.entryCorrelationId && { correlationId: args.entryCorrelationId }),
130
+ credentialId,
131
+ }
132
+ console.log(`Entries: ${JSON.stringify(await (await this.getStatusListEntryRepo()).find(), null, 2)}`)
133
+ const result = await (await this.getStatusListEntryRepo()).findOne({ where })
134
+
135
+ if (!result && args.errorOnNotFound) {
136
+ throw Error(`Could not find status list credential id ${credentialId} for status list id ${statusList.id}`)
137
+ }
138
+ return result ?? undefined
139
+ }
140
+
141
+ async removeStatusListEntryByCredentialId(args: IGetStatusListEntryByCredentialIdArgs): Promise<boolean> {
142
+ let error = false
143
+ try {
144
+ await this.getStatusListEntryByCredentialId(args) // only used to check it exists
145
+ } catch (error) {
146
+ error = true
147
+ }
148
+ if (!error) {
149
+ const result = await (
150
+ await this.getStatusListEntryRepo()
151
+ ).delete({
152
+ ...(args.statusListId && { statusList: args.statusListId }),
153
+ ...(args.entryCorrelationId && { correlationId: args.entryCorrelationId }),
154
+ credentialId: args.credentialId,
155
+ })
156
+ error = !result.affected || result.affected !== 1
157
+ }
158
+ return !error
159
+ }
160
+
161
+ async getStatusListEntries(args: IGetStatusListEntriesArgs): Promise<StatusListEntryEntity[]> {
162
+ return (await this.getStatusListEntryRepo()).find({ where: { ...args?.filter, statusList: args.statusListId } })
163
+ }
164
+
165
+ async getStatusList(args: IGetStatusListArgs): Promise<IStatusListEntity> {
166
+ if (!args.id && !args.correlationId) {
167
+ throw Error(`At least and 'id' or 'correlationId' needs to be provided to lookup a status list`)
168
+ }
169
+ const where = []
170
+ if (args.id) {
171
+ where.push({ id: args.id })
172
+ }
173
+ if (args.correlationId) {
174
+ where.push({ correlationId: args.correlationId })
175
+ }
176
+ const result = await (await this.getStatusListRepo()).findOne({ where })
177
+ if (!result) {
178
+ throw Error(`No status list found for id ${args.id}`)
179
+ }
180
+ return result
181
+ }
182
+
183
+ async getStatusLists(args: IGetStatusListsArgs): Promise<Array<IStatusListEntity>> {
184
+ const result = await (
185
+ await this.getStatusListRepo()
186
+ ).find({
187
+ where: args.filter,
188
+ })
189
+
190
+ if (!result) {
191
+ return []
192
+ }
193
+ return result
194
+ }
195
+
196
+ async addStatusList(args: IAddStatusListArgs): Promise<IStatusListEntity> {
197
+ const { id, correlationId } = args
198
+
199
+ const result = await (
200
+ await this.getStatusListRepo()
201
+ ).findOne({
202
+ where: [{ id }, { correlationId }],
203
+ })
204
+ if (result) {
205
+ throw Error(`Status list for id ${id}, correlationId ${correlationId} already exists`)
206
+ }
207
+
208
+ debug('Adding status list ', id)
209
+ const createdResult = await (await this.getStatusListRepo()).save(args)
210
+
211
+ return createdResult
212
+ }
213
+
214
+ async updateStatusList(args: IUpdateStatusListIndexArgs): Promise<IStatusListEntity> {
215
+ const result = await this.getStatusList(args)
216
+ debug('Updating status list', result)
217
+ const updatedResult = await (await this.getStatusListRepo()).save(args, { transaction: true })
218
+ return updatedResult
219
+ }
220
+
221
+ async removeStatusList(args: IRemoveStatusListArgs): Promise<void> {
222
+ const result = await this.getStatusList(args)
223
+ await (await this.getStatusListRepo()).delete(result)
224
+ }
225
+
226
+ private async getDS(): Promise<DataSource> {
227
+ return await this._dbConnection
228
+ }
229
+
230
+ async getStatusListRepo(): Promise<Repository<StatusListEntity>> {
231
+ return (await this.getDS()).getRepository(StatusListEntity)
232
+ }
233
+
234
+ async getStatusListEntryRepo(): Promise<Repository<StatusListEntryEntity>> {
235
+ return (await this.getDS()).getRepository(StatusListEntryEntity)
236
+ }
237
+ }
@@ -2,3 +2,5 @@ export * from './issuanceBranding/issuanceBranding'
2
2
  export * from './issuanceBranding/IAbstractIssuanceBrandingStore'
3
3
  export * from './contact/contact'
4
4
  export * from './contact/IAbstractContactStore'
5
+ export * from './statusList/statusList'
6
+ export * from './statusList/IAbstractStatusListStore'