Skip to content

Commit dd37e77

Browse files
committed
feat: expose date(time) types per database. Also enhance the datasources capabilities
1 parent b001dcb commit dd37e77

33 files changed

+251
-105
lines changed
Lines changed: 160 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,182 @@
11
import Debug from 'debug'
2-
import { DataSource } from 'typeorm'
3-
import { BaseDataSourceOptions } from 'typeorm/data-source/BaseDataSourceOptions'
4-
import { DataSourceOptions } from 'typeorm/data-source/DataSourceOptions'
2+
import {DataSource} from 'typeorm'
3+
import {BaseDataSourceOptions} from 'typeorm/data-source/BaseDataSourceOptions'
54

6-
const debug = Debug(`demo:databaseService`)
5+
import {DataSourceOptions} from 'typeorm/data-source/DataSourceOptions'
6+
import {DatabaseType} from "typeorm/driver/types/DatabaseType";
7+
8+
9+
const debug = Debug(`sphereon:ssi-sdk:database`)
710

811
export class DataSources {
9-
private dataSources = new Map<string, DataSource>()
10-
private configs
12+
get defaultDbType(): SupportedDatabaseType {
13+
return this._defaultDbType;
14+
}
15+
16+
set defaultDbType(value: SupportedDatabaseType) {
17+
this._defaultDbType = value;
18+
}
19+
private dataSources = new Map<string, DataSource>()
20+
private configs: Map<string, DataSourceOptions>
21+
private _defaultDbType: SupportedDatabaseType = 'postgres'
1122

12-
private static singleton: DataSources
23+
private static singleton: DataSources
1324

14-
public static singleInstance() {
15-
if (!DataSources.singleton) {
16-
DataSources.singleton = new DataSources()
25+
public static singleInstance() {
26+
if (!DataSources.singleton) {
27+
DataSources.singleton = new DataSources()
28+
}
29+
return DataSources.singleton
1730
}
18-
return DataSources.singleton
19-
}
2031

21-
public static newInstance(configs?: Map<string, DataSourceOptions>) {
22-
return new DataSources(configs)
23-
}
32+
public static newInstance(configs?: Map<string, DataSourceOptions>) {
33+
return new DataSources(configs)
34+
}
2435

25-
private constructor(configs?: Map<string, DataSourceOptions>) {
26-
this.configs = configs ?? new Map<string, BaseDataSourceOptions>()
27-
}
36+
private constructor(configs?: Map<string, DataSourceOptions>) {
37+
(configs ?? new Map<string, DataSourceOptions>()).forEach((config, name) => this.addConfig(name, config))
2838

29-
addConfig(dbName: string, config: DataSourceOptions): this {
30-
this.configs.set(dbName, config)
31-
return this
32-
}
39+
}
3340

34-
deleteConfig(dbName: string): this {
35-
this.configs.delete(dbName)
36-
return this
37-
}
41+
addConfig(dbName: string, config: DataSourceOptions): this {
42+
this.configs.set(dbName, config)
43+
// yes we are aware last one wins
44+
this._defaultDbType = config.type
45+
return this
46+
}
3847

39-
getConfig(dbName: string): BaseDataSourceOptions {
40-
const config = this.configs.get(dbName)
41-
if (!config) {
42-
throw Error(`No DB config found for ${dbName}`)
48+
deleteConfig(dbName: string): this {
49+
this.configs.delete(dbName)
50+
return this
51+
}
52+
has(dbName: string) {
53+
return this.configs.has(dbName) && this.dataSources.has(dbName)
4354
}
44-
return config
45-
}
4655

47-
public getDbNames(): string[] {
48-
return [...this.configs.keys()]
49-
}
56+
delete(dbName: string): this {
57+
this.deleteConfig(dbName)
58+
this.dataSources.delete(dbName)
59+
return this
60+
}
5061

51-
async getDbConnection(dbName: string): Promise<DataSource> {
52-
const config = this.getConfig(dbName)
53-
/*if (config.synchronize) {
54-
return Promise.reject(
55-
`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`
62+
getConfig(dbName: string): BaseDataSourceOptions {
63+
const config = this.configs.get(dbName)
64+
if (!config) {
65+
throw Error(`No DB config found for ${dbName}`)
66+
}
67+
return config
68+
}
69+
70+
public getDbNames(): string[] {
71+
return [...this.configs.keys()]
72+
}
73+
74+
async getDbConnection(dbName: string): Promise<DataSource> {
75+
const config = this.getConfig(dbName)
76+
if (!this._defaultDbType) {
77+
this._defaultDbType = config.type
78+
}
79+
/*if (config.synchronize) {
80+
return Promise.reject(
81+
`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`
82+
)
83+
}*/
84+
85+
let dataSource = this.dataSources.get(dbName)
86+
if (dataSource) {
87+
return dataSource
88+
}
89+
90+
91+
dataSource = await new DataSource({...(config as DataSourceOptions), name: dbName}).initialize()
92+
this.dataSources.set(dbName, dataSource)
93+
if (config.synchronize) {
94+
debug(`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`)
95+
} else if (config.migrationsRun) {
96+
debug(
97+
`Migrations are currently managed from config. Please set migrationsRun and synchronize to false to get consistent behaviour. We run migrations from code explicitly`,
5698
)
57-
}*/
99+
} else {
100+
debug(`Running ${dataSource.migrations.length} migration(s) from code if needed...`)
101+
await dataSource.runMigrations()
102+
debug(`${dataSource.migrations.length} migration(s) from code were inspected and applied`)
103+
}
104+
return dataSource
105+
}
106+
}
58107

59-
let dataSource = this.dataSources.get(dbName)
60-
if (dataSource) {
61-
return dataSource
108+
export type SupportedDatabaseType = Pick<DatabaseType, 'postgres' & 'sqlite'>
109+
export type DateTimeType = 'timestamp' | 'datetime'
110+
111+
export type DateType = 'date'
112+
113+
114+
export const getDbType = (opts?: { defaultType: SupportedDatabaseType }): SupportedDatabaseType => {
115+
const type = (typeof process === 'object' ? process?.env?.DB_TYPE : undefined) ?? DataSources.singleInstance().defaultDbType ?? opts?.defaultType
116+
if (!type) {
117+
throw Error(`Could not determine DB type. Please set the DB_TYPE global variable or env var to one of 'postgres' or 'sqlite'`)
118+
}
119+
return type as SupportedDatabaseType
120+
}
121+
122+
123+
const typeOrmDateTime = (opts?: { defaultType: SupportedDatabaseType }): DateTimeType => {
124+
switch (getDbType(opts)) {
125+
case "postgres":
126+
return 'timestamp'
127+
case "sqlite":
128+
return 'datetime'
129+
default:
130+
throw Error(`DB type ${getDbType(opts)} not supported`)
131+
}
132+
}
133+
134+
const typeormDate = (opts?: { defaultType: SupportedDatabaseType }): DateType => {
135+
// The same for both DBs
136+
return 'date'
137+
}
138+
export const TYPEORM_DATE_TIME_TYPE = typeOrmDateTime()
139+
140+
141+
export const TYPEORM_DATE_TYPE = typeormDate()
142+
143+
144+
/**
145+
* Gets the database connection.
146+
*
147+
* Also makes sure that migrations are run (versioning for DB schema's), so we can properly update over time
148+
*
149+
* @param connectionName The database name
150+
* @param opts
151+
*/
152+
export const getDbConnection = async (connectionName: string, opts?: {
153+
config: BaseDataSourceOptions | any
154+
}): Promise<DataSource> => {
155+
return DataSources.singleInstance().addConfig(connectionName, opts?.config).getDbConnection(connectionName)
156+
}
157+
158+
export const dropDatabase = async (dbName: string): Promise<void> => {
159+
if (!DataSources.singleInstance().has(dbName)) {
160+
return Promise.reject(Error(`No database present with name: ${dbName}`));
62161
}
63162

64-
dataSource = await new DataSource({ ...(config as DataSourceOptions), name: dbName }).initialize()
65-
this.dataSources.set(dbName, dataSource)
66-
if (config.synchronize) {
67-
debug(`WARNING: Automatic migrations need to be disabled in this app! Adjust the database configuration and set synchronize to false`)
68-
} else if (config.migrationsRun) {
69-
debug(
70-
`Migrations are currently managed from config. Please set migrationsRun and synchronize to false to get consistent behaviour. We run migrations from code explicitly`,
71-
)
163+
const connection: DataSource = await getDbConnection(dbName);
164+
await connection.dropDatabase();
165+
DataSources.singleInstance().delete(dbName);
166+
};
167+
168+
/**
169+
* Runs a migration down (drops DB schema)
170+
* @param dataSource
171+
*/
172+
export const revertMigration = async (dataSource: DataSource): Promise<void> => {
173+
if (dataSource.isInitialized) {
174+
await dataSource.undoLastMigration()
72175
} else {
73-
debug(`Running ${dataSource.migrations.length} migration(s) from code if needed...`)
74-
await dataSource.runMigrations()
75-
debug(`${dataSource.migrations.length} migration(s) from code were inspected and applied`)
176+
console.error('DataSource is not initialized')
76177
}
77-
return dataSource
78-
}
79178
}
179+
export const resetDatabase = async (dbName: string): Promise<void> => {
180+
await dropDatabase(dbName);
181+
await getDbConnection(dbName);
182+
};

packages/data-store/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"dependencies": {
1717
"@sphereon/pex": "^4.0.1",
1818
"@sphereon/ssi-sdk-ext.did-utils": "0.23.1-next.42",
19+
"@sphereon/ssi-sdk.agent-config": "workspace:*",
1920
"@sphereon/ssi-sdk.core": "workspace:*",
2021
"@sphereon/ssi-types": "workspace:*",
2122
"@veramo/core": "4.2.0",

packages/data-store/src/__tests__/contact.entities.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,16 @@ import {
5656
physicalAddressEntityFrom,
5757
} from '../utils/contact/MappingUtils'
5858
import { ContactMetadataItemEntity } from '../entities/contact/ContactMetadataItemEntity'
59+
import { DataSources } from '@sphereon/ssi-sdk.agent-config'
60+
5961

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

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

6567
beforeEach(async (): Promise<void> => {
68+
DataSources.singleInstance().defaultDbType = 'sqlite'
6669
dbConnection = await new DataSource({
6770
type: 'sqlite',
6871
database: ':memory:',

packages/data-store/src/__tests__/contact.store.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
12
import { DataSource } from 'typeorm'
23
import { DataStoreContactEntities, DataStoreMigrations, IdentityOrigin, MetadataItem, MetadataTypes, PartyOrigin } from '../index'
34
import { ContactStore } from '../contact/ContactStore'
@@ -31,6 +32,7 @@ describe('Contact store tests', (): void => {
3132
let contactStore: ContactStore
3233

3334
beforeEach(async (): Promise<void> => {
35+
DataSources.singleInstance().defaultDbType = 'sqlite'
3436
dbConnection = await new DataSource({
3537
type: 'sqlite',
3638
database: ':memory:',

packages/data-store/src/__tests__/digitalCredential.entities.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
12
import { DataSource } from 'typeorm'
23
import { CredentialRole, DataStoreDigitalCredentialEntities } from '../index'
34
import { DataStoreDigitalCredentialMigrations } from '../migrations'
@@ -17,6 +18,7 @@ describe('Database entities tests', (): void => {
1718
let dbConnection: DataSource
1819

1920
beforeEach(async (): Promise<void> => {
21+
DataSources.singleInstance().defaultDbType = 'sqlite'
2022
dbConnection = await new DataSource({
2123
type: 'sqlite',
2224
database: ':memory:',

packages/data-store/src/__tests__/digitalCredential.store.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
12
import { DataSource } from 'typeorm'
23
import { DataStoreDigitalCredentialMigrations } from '../migrations'
34
import { CredentialRole, DataStoreDigitalCredentialEntities } from '../index'
@@ -18,6 +19,7 @@ describe('Database entities tests', (): void => {
1819
let digitalCredentialStore: DigitalCredentialStore
1920

2021
beforeEach(async (): Promise<void> => {
22+
DataSources.singleInstance().defaultDbType = 'sqlite'
2123
dbConnection = await new DataSource({
2224
type: 'sqlite',
2325
database: ':memory:',

packages/data-store/src/__tests__/eventLogger.entities.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
12
import { PartyCorrelationType } from '@sphereon/ssi-sdk.core'
23
import { ActionType, InitiatorType, LogLevel, SubSystem, System, SystemCorrelationIdType } from '@sphereon/ssi-types'
34
import { DataSource } from 'typeorm'
@@ -10,6 +11,7 @@ describe('Database entities tests', (): void => {
1011
let dbConnection: DataSource
1112

1213
beforeEach(async (): Promise<void> => {
14+
DataSources.singleInstance().defaultDbType = 'sqlite'
1315
dbConnection = await new DataSource({
1416
type: 'sqlite',
1517
database: ':memory:',

packages/data-store/src/__tests__/eventLogger.store.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
12
import { ActionType, InitiatorType, LogLevel, SubSystem, System, SystemCorrelationIdType } from '@sphereon/ssi-types'
23
import { DataSource } from 'typeorm'
3-
import { DataStoreEventLoggerMigrations } from '../migrations/generic'
4+
import { DataStoreEventLoggerMigrations } from '../migrations'
45
import { DataStoreEventLoggerEntities } from '../index'
56
import { AuditLoggingEvent, PartyCorrelationType } from '@sphereon/ssi-sdk.core'
67
import { EventLoggerStore } from '../eventLogger/EventLoggerStore'
@@ -11,6 +12,7 @@ describe('Database entities tests', (): void => {
1112
let eventLoggerStore: EventLoggerStore
1213

1314
beforeEach(async (): Promise<void> => {
15+
DataSources.singleInstance().defaultDbType = 'sqlite'
1416
dbConnection = await new DataSource({
1517
type: 'sqlite',
1618
database: ':memory:',

packages/data-store/src/__tests__/issuanceBranding.entities.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
12
import { DataSource, Repository } from 'typeorm'
23
import { DataStoreMigrations } from '../migrations'
34
import {
@@ -20,6 +21,7 @@ describe('Database entities tests', (): void => {
2021
let dbConnection: DataSource
2122

2223
beforeEach(async (): Promise<void> => {
24+
DataSources.singleInstance().defaultDbType = 'sqlite'
2325
dbConnection = await new DataSource({
2426
type: 'sqlite',
2527
database: ':memory:',

packages/data-store/src/__tests__/issuanceBranding.store.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {DataSources} from "@sphereon/ssi-sdk.agent-config";
12
import { DataSource } from 'typeorm'
23
import { IssuanceBrandingStore } from '../issuanceBranding/IssuanceBrandingStore'
34
import { DataStoreMigrations } from '../migrations'
@@ -31,6 +32,7 @@ describe('Issuance branding store tests', (): void => {
3132
let issuanceBrandingStore: IssuanceBrandingStore
3233

3334
beforeEach(async (): Promise<void> => {
35+
DataSources.singleInstance().defaultDbType = 'sqlite'
3436
dbConnection = await new DataSource({
3537
type: 'sqlite',
3638
database: ':memory:',

0 commit comments

Comments
 (0)