Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core)!: Improved typing on metadata api #585

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CredentialState } from '../CredentialState'
import { CredentialPreviewAttribute } from '../messages'
import { CredentialRecord } from '../repository/CredentialRecord'
import { CredentialMetadataKeys } from '../repository/credentialMetadataTypes'

describe('CredentialRecord', () => {
describe('getCredentialInfo()', () => {
Expand All @@ -17,7 +18,7 @@ describe('CredentialRecord', () => {
],
})

credentialRecord.metadata.set('_internal/indyCredential', {
credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, {
credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG',
schemaId: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0',
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from '../messages'
import { CredentialRecord } from '../repository/CredentialRecord'
import { CredentialRepository } from '../repository/CredentialRepository'
import { CredentialMetadataKeys } from '../repository/credentialMetadataTypes'
import { CredentialService } from '../services'

import { CredentialProblemReportMessage } from './../messages/CredentialProblemReportMessage'
Expand Down Expand Up @@ -126,17 +127,17 @@ const mockCredentialRecord = ({
})

if (metadata?.indyRequest) {
credentialRecord.metadata.set('_internal/indyRequest', { ...metadata.indyRequest })
credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest })
}

if (metadata?.schemaId) {
credentialRecord.metadata.add('_internal/indyCredential', {
credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, {
schemaId: metadata.schemaId,
})
}

if (metadata?.credentialDefinitionId) {
credentialRecord.metadata.add('_internal/indyCredential', {
credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, {
credentialDefinitionId: metadata.credentialDefinitionId,
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { TagsBase } from '../../../storage/BaseRecord'
import type { AutoAcceptCredential } from '../CredentialAutoAcceptType'
import type { CredentialState } from '../CredentialState'
import type { CredentialMetadata } from './credentialMetadataTypes'

import { Type } from 'class-transformer'

Expand Down Expand Up @@ -44,7 +45,7 @@ export type DefaultCredentialTags = {
credentialId?: string
}

export class CredentialRecord extends BaseRecord<DefaultCredentialTags, CustomCredentialTags> {
export class CredentialRecord extends BaseRecord<DefaultCredentialTags, CustomCredentialTags, CredentialMetadata> {
public connectionId?: string
public threadId!: string
public credentialId?: string
Expand Down Expand Up @@ -118,7 +119,7 @@ export class CredentialRecord extends BaseRecord<DefaultCredentialTags, CustomCr
return new CredentialInfo({
claims,
attachments: this.linkedAttachments,
metadata: this.metadata.getAll(),
metadata: this.metadata.data,
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { CredReqMetadata } from 'indy-sdk'

export enum CredentialMetadataKeys {
IndyCredential = '_internal/indyCredential',
IndyRequest = '_internal/indyRequest',
}

export type CredentialMetadata = {
[CredentialMetadataKeys.IndyCredential]: {
schemaId?: string
credentialDefinitionId?: string
}
[CredentialMetadataKeys.IndyRequest]: CredReqMetadata
}
1 change: 1 addition & 0 deletions packages/core/src/modules/credentials/repository/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './CredentialRecord'
export * from './CredentialRepository'
export * from './credentialMetadataTypes'
32 changes: 16 additions & 16 deletions packages/core/src/modules/credentials/services/CredentialService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import type { ConnectionRecord } from '../../connections'
import type { AutoAcceptCredential } from '../CredentialAutoAcceptType'
import type { CredentialStateChangedEvent } from '../CredentialEvents'
import type { CredentialProblemReportMessage, ProposeCredentialMessageOptions } from '../messages'
import type { CredReqMetadata } from 'indy-sdk'

import { scoped, Lifecycle } from 'tsyringe'
import { Lifecycle, scoped } from 'tsyringe'

import { AgentConfig } from '../../../agent/AgentConfig'
import { EventEmitter } from '../../../agent/EventEmitter'
Expand All @@ -19,25 +18,26 @@ import { isLinkedAttachment } from '../../../utils/attachment'
import { uuid } from '../../../utils/uuid'
import { AckStatus } from '../../common'
import { ConnectionService } from '../../connections/services/ConnectionService'
import { IndyIssuerService, IndyHolderService } from '../../indy'
import { IndyHolderService, IndyIssuerService } from '../../indy'
import { IndyLedgerService } from '../../ledger/services/IndyLedgerService'
import { CredentialEventTypes } from '../CredentialEvents'
import { CredentialState } from '../CredentialState'
import { CredentialUtils } from '../CredentialUtils'
import { CredentialProblemReportError, CredentialProblemReportReason } from '../errors'
import {
CredentialAckMessage,
CredentialPreview,
INDY_CREDENTIAL_ATTACHMENT_ID,
INDY_CREDENTIAL_OFFER_ATTACHMENT_ID,
INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID,
IssueCredentialMessage,
OfferCredentialMessage,
ProposeCredentialMessage,
CredentialPreview,
RequestCredentialMessage,
CredentialAckMessage,
INDY_CREDENTIAL_ATTACHMENT_ID,
} from '../messages'
import { CredentialRepository } from '../repository'
import { CredentialRecord } from '../repository/CredentialRecord'
import { CredentialMetadataKeys } from '../repository/credentialMetadataTypes'

@scoped(Lifecycle.ContainerScoped)
export class CredentialService {
Expand Down Expand Up @@ -109,9 +109,9 @@ export class CredentialService {
})

// Set the metadata
credentialRecord.metadata.set('_internal/indyCredential', {
credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, {
schemaId: options.schemaId,
credentialDefinintionId: options.credentialDefinitionId,
credentialDefinitionId: options.credentialDefinitionId,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank god for typing :)

When was this typo introduced? Can it cause problems?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause a problem if the metadata is set at the createProposal function and was never overwritten. I think the chances of that, and the metadata still being used, is slim to none.

})

await this.credentialRepository.save(credentialRecord)
Expand Down Expand Up @@ -196,7 +196,7 @@ export class CredentialService {
state: CredentialState.ProposalReceived,
})

credentialRecord.metadata.set('_internal/indyCredential', {
credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, {
schemaId: proposalMessage.schemaId,
credentialDefinitionId: proposalMessage.credentialDefinitionId,
})
Expand Down Expand Up @@ -257,7 +257,7 @@ export class CredentialService {

credentialRecord.offerMessage = credentialOfferMessage
credentialRecord.credentialAttributes = preview.attributes
credentialRecord.metadata.set('_internal/indyCredential', {
credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, {
schemaId: credOffer.schema_id,
credentialDefinitionId: credOffer.cred_def_id,
})
Expand Down Expand Up @@ -321,7 +321,7 @@ export class CredentialService {
autoAcceptCredential: credentialTemplate.autoAcceptCredential,
})

credentialRecord.metadata.set('_internal/indyCredential', {
credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, {
credentialDefinitionId: credOffer.cred_def_id,
schemaId: credOffer.schema_id,
})
Expand Down Expand Up @@ -376,7 +376,7 @@ export class CredentialService {
credentialRecord.offerMessage = credentialOfferMessage
credentialRecord.linkedAttachments = credentialOfferMessage.attachments?.filter(isLinkedAttachment)

credentialRecord.metadata.set('_internal/indyCredential', {
credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, {
schemaId: indyCredentialOffer.schema_id,
credentialDefinitionId: indyCredentialOffer.cred_def_id,
})
Expand All @@ -392,9 +392,9 @@ export class CredentialService {
state: CredentialState.OfferReceived,
})

credentialRecord.metadata.set('_internal/indyCredential', {
credentialDefinitionId: indyCredentialOffer.cred_def_id,
credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, {
schemaId: indyCredentialOffer.schema_id,
credentialDefinitionId: indyCredentialOffer.cred_def_id,
})

// Assert
Expand Down Expand Up @@ -461,7 +461,7 @@ export class CredentialService {
})
credentialRequest.setThread({ threadId: credentialRecord.threadId })

credentialRecord.metadata.set('_internal/indyRequest', credReqMetadata)
credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, credReqMetadata)
credentialRecord.requestMessage = credentialRequest
credentialRecord.autoAcceptCredential = options?.autoAcceptCredential ?? credentialRecord.autoAcceptCredential

Expand Down Expand Up @@ -629,7 +629,7 @@ export class CredentialService {
previousSentMessage: credentialRecord.requestMessage,
})

const credentialRequestMetadata = credentialRecord.metadata.get<CredReqMetadata>('_internal/indyRequest')
const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyRequest)

if (!credentialRequestMetadata) {
throw new CredentialProblemReportError(
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/storage/BaseRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export type Tags<DefaultTags extends TagsBase, CustomTags extends TagsBase> = Cu

export type RecordTags<Record extends BaseRecord> = ReturnType<Record['getTags']>

export abstract class BaseRecord<DefaultTags extends TagsBase = TagsBase, CustomTags extends TagsBase = TagsBase> {
export abstract class BaseRecord<
DefaultTags extends TagsBase = TagsBase,
CustomTags extends TagsBase = TagsBase,
MetadataValues = undefined
> {
protected _tags: CustomTags = {} as CustomTags

public id!: string
Expand All @@ -32,7 +36,7 @@ export abstract class BaseRecord<DefaultTags extends TagsBase = TagsBase, Custom

/** @inheritdoc {Metadata#Metadata} */
@MetadataTransformer()
public metadata: Metadata = new Metadata({})
public metadata: Metadata<MetadataValues> = new Metadata({})

/**
* Get all tags. This is includes custom and default tags
Expand Down
39 changes: 23 additions & 16 deletions packages/core/src/storage/Metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@ export type MetadataBase = {
}

/**
* Metadata access class to get, set (create and update) and delete
* metadata on any record.
* Metadata access class to get, set (create and update), add (append to a record) and delete metadata on any record.
*
* set will override the previous value if it already exists
*
* note: To add persistence to these records, you have to
* update the record in the correct repository
* note: To add persistence to these records, you have to update the record in the correct repository
*
* @example
*
* ```ts
* connectionRecord.metadata.set('foo', { bar: 'baz' })
* connectionRepository.update(connectionRecord)
* connectionRecord.metadata.set('foo', { bar: 'baz' }) connectionRepository.update(connectionRecord)
* ```
*/
export class Metadata {
export class Metadata<MetadataTypes> {
public readonly data: MetadataBase

public constructor(data: MetadataBase) {
Expand All @@ -28,22 +25,29 @@ export class Metadata {
/**
* Gets the value by key in the metadata
*
* Any extension of the `BaseRecord` can implement their own typed metadata
*
* @param key the key to retrieve the metadata by
* @returns the value saved in the key value pair
* @returns null when the key could not be found
*/
public get<T extends Record<string, unknown>>(key: string): T | null {
return (this.data[key] as T) ?? null
public get<Value extends Record<string, unknown>, Key extends string = string>(
key: Key
): (Key extends keyof MetadataTypes ? MetadataTypes[Key] : Value) | null {
return (this.data[key] as Key extends keyof MetadataTypes ? MetadataTypes[Key] : Value) ?? null
}

/**
* Will set, or override, a key value pair on the metadata
* Will set, or override, a key-value pair on the metadata
*
* @param key the key to set the metadata by
* @param value the value to set in the metadata
*/
public set(key: string, value: Record<string, unknown>): void {
this.data[key] = value
public set<Value extends Record<string, unknown>, Key extends string = string>(
key: Key,
value: Key extends keyof MetadataTypes ? MetadataTypes[Key] : Value
): void {
this.data[key] = value as Record<string, unknown>
}

/**
Expand All @@ -52,7 +56,10 @@ export class Metadata {
* @param key the key to add the metadata at
* @param value the value to add in the metadata
*/
public add(key: string, value: Record<string, unknown>): void {
public add<Value extends Record<string, unknown>, Key extends string = string>(
key: Key,
value: Partial<Key extends keyof MetadataTypes ? MetadataTypes[Key] : Value>
): void {
this.data[key] = {
...this.data[key],
...value,
Expand All @@ -64,16 +71,16 @@ export class Metadata {
*
* @returns all the metadata that exists on the record
*/
public getAll(): MetadataBase {
return this.data
public get keys(): string[] {
return Object.keys(this.data)
}

/**
* Will delete the key value pair in the metadata
*
* @param key the key to delete the data by
*/
public delete(key: string): void {
public delete<Key extends string = string>(key: Key): void {
delete this.data[key]
}
}
2 changes: 1 addition & 1 deletion packages/core/src/storage/Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { BaseRecordConstructor, Query, StorageService } from './StorageServ
import { RecordDuplicateError, RecordNotFoundError } from '../error'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class Repository<T extends BaseRecord<any, any>> {
export class Repository<T extends BaseRecord<any, any, any>> {
private storageService: StorageService<T>
private recordClass: BaseRecordConstructor<T>

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/storage/StorageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface BaseRecordConstructor<T> extends Constructor<T> {
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface StorageService<T extends BaseRecord<any, any>> {
export interface StorageService<T extends BaseRecord<any, any, any>> {
/**
* Save record in storage
*
Expand Down
Loading