Skip to content

Commit

Permalink
feat: ability to add generic records (#702)
Browse files Browse the repository at this point in the history
feat: extension module creation (#688)

Co-authored-by: Berend Sliedrecht <[email protected]>
Co-authored-by: Timo Glastra <[email protected]>
  • Loading branch information
3 people authored May 18, 2022
1 parent 020e6ef commit e617496
Show file tree
Hide file tree
Showing 7 changed files with 354 additions and 45 deletions.
3 changes: 3 additions & 0 deletions packages/core/src/agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ConnectionsModule } from '../modules/connections/ConnectionsModule'
import { CredentialsModule } from '../modules/credentials/CredentialsModule'
import { DidsModule } from '../modules/dids/DidsModule'
import { DiscoverFeaturesModule } from '../modules/discover-features'
import { GenericRecordsModule } from '../modules/generic-records/GenericRecordsModule'
import { LedgerModule } from '../modules/ledger/LedgerModule'
import { OutOfBandModule } from '../modules/oob/OutOfBandModule'
import { ProofsModule } from '../modules/proofs/ProofsModule'
Expand Down Expand Up @@ -55,6 +56,7 @@ export class Agent {
public readonly connections: ConnectionsModule
public readonly proofs: ProofsModule
public readonly basicMessages: BasicMessagesModule
public readonly genericRecords: GenericRecordsModule
public readonly ledger: LedgerModule
public readonly credentials: CredentialsModule
public readonly mediationRecipient: RecipientModule
Expand Down Expand Up @@ -121,6 +123,7 @@ export class Agent {
this.mediator = this.container.resolve(MediatorModule)
this.mediationRecipient = this.container.resolve(RecipientModule)
this.basicMessages = this.container.resolve(BasicMessagesModule)
this.genericRecords = this.container.resolve(GenericRecordsModule)
this.ledger = this.container.resolve(LedgerModule)
this.discovery = this.container.resolve(DiscoverFeaturesModule)
this.dids = this.container.resolve(DidsModule)
Expand Down
77 changes: 77 additions & 0 deletions packages/core/src/modules/generic-records/GenericRecordsModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { Logger } from '../../logger'
import type { GenericRecord, GenericRecordTags, SaveGenericRecordOption } from './repository/GenericRecord'

import { Lifecycle, scoped } from 'tsyringe'

import { AgentConfig } from '../../agent/AgentConfig'

import { GenericRecordService } from './service/GenericRecordService'

export type ContentType = {
content: string
}

@scoped(Lifecycle.ContainerScoped)
export class GenericRecordsModule {
private genericRecordsService: GenericRecordService
private logger: Logger
public constructor(agentConfig: AgentConfig, genericRecordsService: GenericRecordService) {
this.genericRecordsService = genericRecordsService
this.logger = agentConfig.logger
}

public async save({ content, tags }: SaveGenericRecordOption) {
try {
const record = await this.genericRecordsService.save({
content: content,
tags: tags,
})
return record
} catch (error) {
this.logger.error('Error while saving generic-record', {
error,
content,
errorMessage: error instanceof Error ? error.message : error,
})
throw error
}
}

public async delete(record: GenericRecord): Promise<void> {
try {
await this.genericRecordsService.delete(record)
} catch (error) {
this.logger.error('Error while saving generic-record', {
error,
content: record.content,
errorMessage: error instanceof Error ? error.message : error,
})
throw error
}
}

public async update(record: GenericRecord): Promise<void> {
try {
await this.genericRecordsService.update(record)
} catch (error) {
this.logger.error('Error while update generic-record', {
error,
content: record.content,
errorMessage: error instanceof Error ? error.message : error,
})
throw error
}
}

public async findById(id: string) {
return this.genericRecordsService.findById(id)
}

public async findAllByQuery(query: Partial<GenericRecordTags>): Promise<GenericRecord[]> {
return this.genericRecordsService.findAllByQuery(query)
}

public async getAll(): Promise<GenericRecord[]> {
return this.genericRecordsService.getAll()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { RecordTags, TagsBase } from '../../../storage/BaseRecord'

import { BaseRecord } from '../../../storage/BaseRecord'
import { uuid } from '../../../utils/uuid'

export type GenericRecordTags = TagsBase

export type BasicMessageTags = RecordTags<GenericRecord>

export interface GenericRecordStorageProps {
id?: string
createdAt?: Date
tags?: GenericRecordTags
content: Record<string, unknown>
}

export interface SaveGenericRecordOption {
content: Record<string, unknown>
id?: string
tags?: GenericRecordTags
}

export class GenericRecord extends BaseRecord<GenericRecordTags> {
public content!: Record<string, unknown>

public static readonly type = 'GenericRecord'
public readonly type = GenericRecord.type

public constructor(props: GenericRecordStorageProps) {
super()

if (props) {
this.id = props.id ?? uuid()
this.createdAt = props.createdAt ?? new Date()
this.content = props.content
this._tags = props.tags ?? {}
}
}

public getTags() {
return {
...this._tags,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { inject, scoped, Lifecycle } from 'tsyringe'

import { InjectionSymbols } from '../../../constants'
import { Repository } from '../../../storage/Repository'
import { StorageService } from '../../../storage/StorageService'

import { GenericRecord } from './GenericRecord'

@scoped(Lifecycle.ContainerScoped)
export class GenericRecordsRepository extends Repository<GenericRecord> {
public constructor(@inject(InjectionSymbols.StorageService) storageService: StorageService<GenericRecord>) {
super(GenericRecord, storageService)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { GenericRecordTags, SaveGenericRecordOption } from '../repository/GenericRecord'

import { Lifecycle, scoped } from 'tsyringe'

import { AriesFrameworkError } from '../../../error'
import { GenericRecord } from '../repository/GenericRecord'
import { GenericRecordsRepository } from '../repository/GenericRecordsRepository'

@scoped(Lifecycle.ContainerScoped)
export class GenericRecordService {
private genericRecordsRepository: GenericRecordsRepository

public constructor(genericRecordsRepository: GenericRecordsRepository) {
this.genericRecordsRepository = genericRecordsRepository
}

public async save({ content, tags }: SaveGenericRecordOption) {
const genericRecord = new GenericRecord({
content: content,
tags: tags,
})

try {
await this.genericRecordsRepository.save(genericRecord)
return genericRecord
} catch (error) {
throw new AriesFrameworkError(
`Unable to store the genericRecord record with id ${genericRecord.id}. Message: ${error}`
)
}
}

public async delete(record: GenericRecord): Promise<void> {
try {
await this.genericRecordsRepository.delete(record)
} catch (error) {
throw new AriesFrameworkError(`Unable to delete the genericRecord record with id ${record.id}. Message: ${error}`)
}
}

public async update(record: GenericRecord): Promise<void> {
try {
await this.genericRecordsRepository.update(record)
} catch (error) {
throw new AriesFrameworkError(`Unable to update the genericRecord record with id ${record.id}. Message: ${error}`)
}
}

public async findAllByQuery(query: Partial<GenericRecordTags>) {
return this.genericRecordsRepository.findByQuery(query)
}

public async findById(id: string): Promise<GenericRecord | null> {
return this.genericRecordsRepository.findById(id)
}

public async getAll() {
return this.genericRecordsRepository.getAll()
}
}
110 changes: 110 additions & 0 deletions packages/core/tests/generic-records.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import type { GenericRecord } from '../src/modules/generic-records/repository/GenericRecord'

import { Agent } from '../src/agent/Agent'

import { getBaseConfig } from './helpers'

const aliceConfig = getBaseConfig('Agents Alice', {
endpoints: ['rxjs:alice'],
})

describe('genericRecords', () => {
let aliceAgent: Agent

const fooString = { foo: 'Some data saved' }
const fooNumber = { foo: 42 }

const barString: Record<string, unknown> = fooString
const barNumber: Record<string, unknown> = fooNumber

afterAll(async () => {
await aliceAgent.shutdown()
await aliceAgent.wallet.delete()
})

test('store generic-record record', async () => {
aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies)
await aliceAgent.initialize()

//Save genericRecord message (Minimal)

const savedRecord1: GenericRecord = await aliceAgent.genericRecords.save({ content: barString })

//Save genericRecord message with tag
const tags1 = { myTag: 'foobar1' }
const tags2 = { myTag: 'foobar2' }

const savedRecord2: GenericRecord = await aliceAgent.genericRecords.save({ content: barNumber, tags: tags1 })

expect(savedRecord1).toBeDefined()
expect(savedRecord2).toBeDefined()

const savedRecord3: GenericRecord = await aliceAgent.genericRecords.save({ content: barString, tags: tags2 })
expect(savedRecord3).toBeDefined()
})

test('get generic-record records', async () => {
//Create genericRecord message
const savedRecords = await aliceAgent.genericRecords.getAll()
expect(savedRecords.length).toBe(3)
})

test('get generic-record specific record', async () => {
//Create genericRecord message
const savedRecords1 = await aliceAgent.genericRecords.findAllByQuery({ myTag: 'foobar1' })
expect(savedRecords1?.length == 1).toBe(true)
expect(savedRecords1[0].content).toEqual({ foo: 42 })

const savedRecords2 = await aliceAgent.genericRecords.findAllByQuery({ myTag: 'foobar2' })
expect(savedRecords2?.length == 1).toBe(true)
expect(savedRecords2[0].content).toEqual({ foo: 'Some data saved' })
})

test('find generic record using id', async () => {
const myId = '100'
const savedRecord1: GenericRecord = await aliceAgent.genericRecords.save({ content: barString, id: myId })
expect(savedRecord1).toBeDefined()

const retrievedRecord: GenericRecord | null = await aliceAgent.genericRecords.findById(savedRecord1.id)

if (retrievedRecord) {
expect(retrievedRecord.content).toEqual({ foo: 'Some data saved' })
} else {
throw Error('retrieved record not found')
}
})

test('delete generic record', async () => {
const myId = '100'
const savedRecord1: GenericRecord = await aliceAgent.genericRecords.save({ content: barString, id: myId })
expect(savedRecord1).toBeDefined()

await aliceAgent.genericRecords.delete(savedRecord1)

const retrievedRecord: GenericRecord | null = await aliceAgent.genericRecords.findById(savedRecord1.id)
expect(retrievedRecord).toBeNull()
})

test('update generic record', async () => {
const myId = '100'
const savedRecord1: GenericRecord = await aliceAgent.genericRecords.save({ content: barString, id: myId })
expect(savedRecord1).toBeDefined()

let retrievedRecord: GenericRecord | null = await aliceAgent.genericRecords.findById(savedRecord1.id)
expect(retrievedRecord).toBeDefined()

const amendedFooString = { foo: 'Some even more cool data saved' }
const barString2: Record<string, unknown> = amendedFooString

savedRecord1.content = barString2

await aliceAgent.genericRecords.update(savedRecord1)

retrievedRecord = await aliceAgent.genericRecords.findById(savedRecord1.id)
if (retrievedRecord) {
expect(retrievedRecord.content).toEqual({ foo: 'Some even more cool data saved' })
} else {
throw Error('retrieved record not found in update test')
}
})
})
Loading

0 comments on commit e617496

Please sign in to comment.