Skip to content

Commit

Permalink
feat: expose date(time) types per database. Also enhance the datasour…
Browse files Browse the repository at this point in the history
…ces capabilities
  • Loading branch information
nklomp committed Jul 31, 2024
1 parent b001dcb commit dd37e77
Show file tree
Hide file tree
Showing 33 changed files with 251 additions and 105 deletions.
217 changes: 160 additions & 57 deletions packages/agent-config/src/dataSources.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,182 @@
import Debug from 'debug'
import { DataSource } from 'typeorm'
import { BaseDataSourceOptions } from 'typeorm/data-source/BaseDataSourceOptions'
import { DataSourceOptions } from 'typeorm/data-source/DataSourceOptions'
import {DataSource} from 'typeorm'
import {BaseDataSourceOptions} from 'typeorm/data-source/BaseDataSourceOptions'

const debug = Debug(`demo:databaseService`)
import {DataSourceOptions} from 'typeorm/data-source/DataSourceOptions'
import {DatabaseType} from "typeorm/driver/types/DatabaseType";


const debug = Debug(`sphereon:ssi-sdk:database`)

export class DataSources {
private dataSources = new Map<string, DataSource>()
private configs
get defaultDbType(): SupportedDatabaseType {
return this._defaultDbType;
}

set defaultDbType(value: SupportedDatabaseType) {
this._defaultDbType = value;
}
private dataSources = new Map<string, DataSource>()
private configs: Map<string, DataSourceOptions>
private _defaultDbType: SupportedDatabaseType = 'postgres'

private static singleton: DataSources
private static singleton: DataSources

public static singleInstance() {
if (!DataSources.singleton) {
DataSources.singleton = new DataSources()
public static singleInstance() {
if (!DataSources.singleton) {
DataSources.singleton = new DataSources()
}
return DataSources.singleton
}
return DataSources.singleton
}

public static newInstance(configs?: Map<string, DataSourceOptions>) {
return new DataSources(configs)
}
public static newInstance(configs?: Map<string, DataSourceOptions>) {
return new DataSources(configs)
}

private constructor(configs?: Map<string, DataSourceOptions>) {
this.configs = configs ?? new Map<string, BaseDataSourceOptions>()
}
private constructor(configs?: Map<string, DataSourceOptions>) {
(configs ?? new Map<string, DataSourceOptions>()).forEach((config, name) => this.addConfig(name, config))

addConfig(dbName: string, config: DataSourceOptions): this {
this.configs.set(dbName, config)
return this
}
}

deleteConfig(dbName: string): this {
this.configs.delete(dbName)
return this
}
addConfig(dbName: string, config: DataSourceOptions): this {
this.configs.set(dbName, config)
// yes we are aware last one wins
this._defaultDbType = config.type
return this
}

getConfig(dbName: string): BaseDataSourceOptions {
const config = this.configs.get(dbName)
if (!config) {
throw Error(`No DB config found for ${dbName}`)
deleteConfig(dbName: string): this {
this.configs.delete(dbName)
return this
}
has(dbName: string) {
return this.configs.has(dbName) && this.dataSources.has(dbName)
}
return config
}

public getDbNames(): string[] {
return [...this.configs.keys()]
}
delete(dbName: string): this {
this.deleteConfig(dbName)
this.dataSources.delete(dbName)
return this
}

async getDbConnection(dbName: string): Promise<DataSource> {
const config = this.getConfig(dbName)
/*if (config.synchronize) {
return Promise.reject(
`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`
getConfig(dbName: string): BaseDataSourceOptions {
const config = this.configs.get(dbName)
if (!config) {
throw Error(`No DB config found for ${dbName}`)
}
return config
}

public getDbNames(): string[] {
return [...this.configs.keys()]
}

async getDbConnection(dbName: string): Promise<DataSource> {
const config = this.getConfig(dbName)
if (!this._defaultDbType) {
this._defaultDbType = config.type
}
/*if (config.synchronize) {
return Promise.reject(
`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`
)
}*/

let dataSource = this.dataSources.get(dbName)
if (dataSource) {
return dataSource
}


dataSource = await new DataSource({...(config as DataSourceOptions), name: dbName}).initialize()
this.dataSources.set(dbName, dataSource)
if (config.synchronize) {
debug(`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`)
} else if (config.migrationsRun) {
debug(
`Migrations are currently managed from config. Please set migrationsRun and synchronize to false to get consistent behaviour. We run migrations from code explicitly`,
)
}*/
} else {
debug(`Running ${dataSource.migrations.length} migration(s) from code if needed...`)
await dataSource.runMigrations()
debug(`${dataSource.migrations.length} migration(s) from code were inspected and applied`)
}
return dataSource
}
}

let dataSource = this.dataSources.get(dbName)
if (dataSource) {
return dataSource
export type SupportedDatabaseType = Pick<DatabaseType, 'postgres' & 'sqlite'>
export type DateTimeType = 'timestamp' | 'datetime'

export type DateType = 'date'


export const getDbType = (opts?: { defaultType: SupportedDatabaseType }): SupportedDatabaseType => {
const type = (typeof process === 'object' ? process?.env?.DB_TYPE : undefined) ?? DataSources.singleInstance().defaultDbType ?? opts?.defaultType
if (!type) {
throw Error(`Could not determine DB type. Please set the DB_TYPE global variable or env var to one of 'postgres' or 'sqlite'`)
}
return type as SupportedDatabaseType
}


const typeOrmDateTime = (opts?: { defaultType: SupportedDatabaseType }): DateTimeType => {
switch (getDbType(opts)) {
case "postgres":
return 'timestamp'
case "sqlite":
return 'datetime'
default:
throw Error(`DB type ${getDbType(opts)} not supported`)
}
}

const typeormDate = (opts?: { defaultType: SupportedDatabaseType }): DateType => {
// The same for both DBs
return 'date'
}
export const TYPEORM_DATE_TIME_TYPE = typeOrmDateTime()


export const TYPEORM_DATE_TYPE = typeormDate()


/**
* Gets the database connection.
*
* Also makes sure that migrations are run (versioning for DB schema's), so we can properly update over time
*
* @param connectionName The database name
* @param opts
*/
export const getDbConnection = async (connectionName: string, opts?: {
config: BaseDataSourceOptions | any
}): Promise<DataSource> => {
return DataSources.singleInstance().addConfig(connectionName, opts?.config).getDbConnection(connectionName)
}

export const dropDatabase = async (dbName: string): Promise<void> => {
if (!DataSources.singleInstance().has(dbName)) {
return Promise.reject(Error(`No database present with name: ${dbName}`));
}

dataSource = await new DataSource({ ...(config as DataSourceOptions), name: dbName }).initialize()
this.dataSources.set(dbName, dataSource)
if (config.synchronize) {
debug(`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`)
} else if (config.migrationsRun) {
debug(
`Migrations are currently managed from config. Please set migrationsRun and synchronize to false to get consistent behaviour. We run migrations from code explicitly`,
)
const connection: DataSource = await getDbConnection(dbName);
await connection.dropDatabase();
DataSources.singleInstance().delete(dbName);
};

/**
* Runs a migration down (drops DB schema)
* @param dataSource
*/
export const revertMigration = async (dataSource: DataSource): Promise<void> => {
if (dataSource.isInitialized) {
await dataSource.undoLastMigration()
} else {
debug(`Running ${dataSource.migrations.length} migration(s) from code if needed...`)
await dataSource.runMigrations()
debug(`${dataSource.migrations.length} migration(s) from code were inspected and applied`)
console.error('DataSource is not initialized')
}
return dataSource
}
}
export const resetDatabase = async (dbName: string): Promise<void> => {
await dropDatabase(dbName);
await getDbConnection(dbName);
};
1 change: 1 addition & 0 deletions packages/data-store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@sphereon/pex": "^4.0.1",
"@sphereon/ssi-sdk-ext.did-utils": "0.23.1-next.42",
"@sphereon/ssi-sdk.agent-config": "workspace:*",
"@sphereon/ssi-sdk.core": "workspace:*",
"@sphereon/ssi-types": "workspace:*",
"@veramo/core": "4.2.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/data-store/src/__tests__/contact.entities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@ import {
physicalAddressEntityFrom,
} from '../utils/contact/MappingUtils'
import { ContactMetadataItemEntity } from '../entities/contact/ContactMetadataItemEntity'
import { DataSources } from '@sphereon/ssi-sdk.agent-config'


// TODO write test adding two contacts reusing the same contactType

describe('Database entities tests', (): void => {
let dbConnection: DataSource

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
2 changes: 2 additions & 0 deletions packages/data-store/src/__tests__/contact.store.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { DataSource } from 'typeorm'
import { DataStoreContactEntities, DataStoreMigrations, IdentityOrigin, MetadataItem, MetadataTypes, PartyOrigin } from '../index'
import { ContactStore } from '../contact/ContactStore'
Expand Down Expand Up @@ -31,6 +32,7 @@ describe('Contact store tests', (): void => {
let contactStore: ContactStore

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { DataSource } from 'typeorm'
import { CredentialRole, DataStoreDigitalCredentialEntities } from '../index'
import { DataStoreDigitalCredentialMigrations } from '../migrations'
Expand All @@ -17,6 +18,7 @@ describe('Database entities tests', (): void => {
let dbConnection: DataSource

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { DataSource } from 'typeorm'
import { DataStoreDigitalCredentialMigrations } from '../migrations'
import { CredentialRole, DataStoreDigitalCredentialEntities } from '../index'
Expand All @@ -18,6 +19,7 @@ describe('Database entities tests', (): void => {
let digitalCredentialStore: DigitalCredentialStore

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { PartyCorrelationType } from '@sphereon/ssi-sdk.core'
import { ActionType, InitiatorType, LogLevel, SubSystem, System, SystemCorrelationIdType } from '@sphereon/ssi-types'
import { DataSource } from 'typeorm'
Expand All @@ -10,6 +11,7 @@ describe('Database entities tests', (): void => {
let dbConnection: DataSource

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
4 changes: 3 additions & 1 deletion packages/data-store/src/__tests__/eventLogger.store.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { ActionType, InitiatorType, LogLevel, SubSystem, System, SystemCorrelationIdType } from '@sphereon/ssi-types'
import { DataSource } from 'typeorm'
import { DataStoreEventLoggerMigrations } from '../migrations/generic'
import { DataStoreEventLoggerMigrations } from '../migrations'
import { DataStoreEventLoggerEntities } from '../index'
import { AuditLoggingEvent, PartyCorrelationType } from '@sphereon/ssi-sdk.core'
import { EventLoggerStore } from '../eventLogger/EventLoggerStore'
Expand All @@ -11,6 +12,7 @@ describe('Database entities tests', (): void => {
let eventLoggerStore: EventLoggerStore

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { DataSource, Repository } from 'typeorm'
import { DataStoreMigrations } from '../migrations'
import {
Expand All @@ -20,6 +21,7 @@ describe('Database entities tests', (): void => {
let dbConnection: DataSource

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { DataSource } from 'typeorm'
import { IssuanceBrandingStore } from '../issuanceBranding/IssuanceBrandingStore'
import { DataStoreMigrations } from '../migrations'
Expand Down Expand Up @@ -31,6 +32,7 @@ describe('Issuance branding store tests', (): void => {
let issuanceBrandingStore: IssuanceBrandingStore

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { DataSource } from 'typeorm'
import { MachineStateInfoEntity } from '../entities/machineState/MachineStateInfoEntity'

Expand All @@ -7,6 +8,7 @@ describe('Machine State Info Database entities tests', (): void => {
let dbConnection: DataSource

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
2 changes: 2 additions & 0 deletions packages/data-store/src/__tests__/machineState.store.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { DataSource } from 'typeorm'
import { DataStoreMachineStateEntities, MachineStateStore, StoreMachineStatesFindActiveArgs, StoreMachineStatePersistArgs } from '../index'
import { DataStoreMachineStateMigrations } from '../migrations'
Expand All @@ -7,6 +8,7 @@ describe('Machine State store tests', (): void => {
let store: MachineStateStore

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
2 changes: 2 additions & 0 deletions packages/data-store/src/__tests__/pd-manager.entities.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
import { DataSource } from 'typeorm'
import { PresentationDefinitionItemEntity } from '../entities/presentationDefinition/PresentationDefinitionItemEntity'
import { DataStorePresentationDefinitionMigrations } from '../migrations'
Expand All @@ -7,6 +8,7 @@ describe('PresentationDefinitionItemEntity tests', (): void => {
let dbConnection: DataSource

beforeEach(async (): Promise<void> => {
DataSources.singleInstance().defaultDbType = 'sqlite'
dbConnection = await new DataSource({
type: 'sqlite',
database: ':memory:',
Expand Down
Loading

0 comments on commit dd37e77

Please sign in to comment.