From 8672b82ecb9904223130bfc897855cedbf57cb29 Mon Sep 17 00:00:00 2001 From: Maikel Maas Date: Fri, 16 Feb 2024 13:37:17 -0300 Subject: [PATCH] feat: Created migrations and refactored the database layer --- .../xstatePersistence/XStateEntity.ts} | 14 +--- packages/data-store/src/index.ts | 9 +++ .../migrations/generic/6-CreateXStateStore.ts | 66 +++++++++++++++++++ .../src/migrations/generic/index.ts | 3 + packages/data-store/src/migrations/index.ts | 1 + .../1708097018115-CreateXStateStore.ts | 18 +++++ .../sqlite/1708096002272-CreateXStateStore.ts | 16 +++++ packages/data-store/src/types/index.ts | 1 + .../src/types/xstatePersistence/types.ts | 19 ++++++ .../xstatePersistence/IAbstractStateStore.ts | 7 ++ .../src/xstatePersistence/XStateStore.ts | 40 +++++++++++ 11 files changed, 183 insertions(+), 11 deletions(-) rename packages/{xstate-state-manager/src/entities/State.ts => data-store/src/entities/xstatePersistence/XStateEntity.ts} (68%) create mode 100644 packages/data-store/src/migrations/generic/6-CreateXStateStore.ts create mode 100644 packages/data-store/src/migrations/postgres/1708097018115-CreateXStateStore.ts create mode 100644 packages/data-store/src/migrations/sqlite/1708096002272-CreateXStateStore.ts create mode 100644 packages/data-store/src/types/xstatePersistence/types.ts create mode 100644 packages/data-store/src/xstatePersistence/IAbstractStateStore.ts create mode 100644 packages/data-store/src/xstatePersistence/XStateStore.ts diff --git a/packages/xstate-state-manager/src/entities/State.ts b/packages/data-store/src/entities/xstatePersistence/XStateEntity.ts similarity index 68% rename from packages/xstate-state-manager/src/entities/State.ts rename to packages/data-store/src/entities/xstatePersistence/XStateEntity.ts index 2dce11c1c..1c6c80dcf 100644 --- a/packages/xstate-state-manager/src/entities/State.ts +++ b/packages/data-store/src/entities/xstatePersistence/XStateEntity.ts @@ -1,29 +1,21 @@ import {BaseEntity, Column, Entity, PrimaryColumn} from "typeorm"; -@Entity('state') -export class State extends BaseEntity { - @PrimaryColumn() - // @ts-ignore +@Entity('XstateEntity') +export class XStateEntity extends BaseEntity { + @PrimaryColumn({ name: 'id', type: 'varchar' }) id: string @Column() - // @ts-ignore state: string @Column() - // @ts-ignore type: string @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) - // @ts-ignore createdAt: Date @Column({ type: 'timestamp', onUpdate: 'CURRENT_TIMESTAMP', nullable: true }) - // @ts-ignore updatedAt: Date @Column({ type: 'timestamp', nullable: true }) - // @ts-ignore completedAt: Date @Column() - // @ts-ignore tenantId?: string @Column({ default: 0 }) - // @ts-ignore ttl: number } \ No newline at end of file diff --git a/packages/data-store/src/index.ts b/packages/data-store/src/index.ts index 56642b401..9aab400a5 100644 --- a/packages/data-store/src/index.ts +++ b/packages/data-store/src/index.ts @@ -18,6 +18,7 @@ import { IssuerBrandingEntity, issuerBrandingEntityFrom } from './entities/issua import { TextAttributesEntity, textAttributesEntityFrom } from './entities/issuanceBranding/TextAttributesEntity' import { StatusListEntity } from './entities/statusList2021/StatusList2021Entity' import { StatusListEntryEntity } from './entities/statusList2021/StatusList2021EntryEntity' +import {XStateEntity} from "./entities/xstatePersistence/XStateEntity"; import { IStatusListEntity, IStatusListEntryEntity } from './types' import { PartyRelationshipEntity } from './entities/contact/PartyRelationshipEntity' import { PartyTypeEntity } from './entities/contact/PartyTypeEntity' @@ -32,12 +33,16 @@ export { StatusListStore } from './statusList/StatusListStore' import { AuditEventEntity, auditEventEntityFrom } from './entities/eventLogger/AuditEventEntity' export { AbstractEventLoggerStore } from './eventLogger/AbstractEventLoggerStore' export { EventLoggerStore } from './eventLogger/EventLoggerStore' +export { IAbstractStateStore } from './xstatePersistence/IAbstractStateStore' +export { XStateStore } from './xstatePersistence/XStateStore' + export { DataStoreMigrations, DataStoreEventLoggerMigrations, DataStoreContactMigrations, DataStoreIssuanceBrandingMigrations, DataStoreStatusListMigrations, + DataStoreXStatePersistenceMigration, } from './migrations' export * from './types' export * from './utils/contact/MappingUtils' @@ -75,12 +80,15 @@ export const DataStoreStatusListEntities = [StatusListEntity, StatusListEntryEnt export const DataStoreEventLoggerEntities = [AuditEventEntity] +export const DataStoreXStateStoreEntities = [XStateEntity] + // All entities combined if a party wants to enable them all at once export const DataStoreEntities = [ ...DataStoreContactEntities, ...DataStoreIssuanceBrandingEntities, ...DataStoreStatusListEntities, ...DataStoreEventLoggerEntities, + ...DataStoreXStateStoreEntities ] export { @@ -116,4 +124,5 @@ export { StatusListEntryEntity, AuditEventEntity, auditEventEntityFrom, + XStateEntity } diff --git a/packages/data-store/src/migrations/generic/6-CreateXStateStore.ts b/packages/data-store/src/migrations/generic/6-CreateXStateStore.ts new file mode 100644 index 000000000..51b8df8db --- /dev/null +++ b/packages/data-store/src/migrations/generic/6-CreateXStateStore.ts @@ -0,0 +1,66 @@ +import { DatabaseType, MigrationInterface, QueryRunner } from 'typeorm' +import Debug from 'debug' +import { CreateXStateStore1708097018115 } from '../postgres/1708097018115-CreateXStateStore' +import { CreateXStateStore1708096002272 } from '../sqlite/1708096002272-CreateXStateStore' + +const debug: Debug.Debugger = Debug('sphereon:ssi-sdk:migrations') + +export class CreateXStateStore1708098041262 implements MigrationInterface { + name = 'CreateXStateStore1708098041262' + + public async up(queryRunner: QueryRunner): Promise { + debug('migration: creating contacts tables') + const dbType: DatabaseType = queryRunner.connection.driver.options.type + + switch (dbType) { + case 'postgres': { + debug('using postgres migration file') + const mig: CreateXStateStore1708097018115 = new CreateXStateStore1708097018115() + await mig.up(queryRunner) + debug('Migration statements executed') + return + } + case 'sqlite': + case 'expo': + case 'react-native': { + debug('using sqlite/react-native migration file') + const mig: CreateXStateStore1708096002272 = new CreateXStateStore1708096002272() + await mig.up(queryRunner) + debug('Migration statements executed') + return + } + default: + return Promise.reject( + `Migrations are currently only supported for sqlite, react-native, expo and postgres. Was ${dbType}. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now` + ) + } + } + + public async down(queryRunner: QueryRunner): Promise { + debug('migration: reverting contacts tables') + const dbType: DatabaseType = queryRunner.connection.driver.options.type + + switch (dbType) { + case 'postgres': { + debug('using postgres migration file') + const mig: CreateXStateStore1708097018115 = new CreateXStateStore1708097018115() + await mig.down(queryRunner) + debug('Migration statements executed') + return + } + case 'sqlite': + case 'expo': + case 'react-native': { + debug('using sqlite/react-native migration file') + const mig: CreateXStateStore1708096002272 = new CreateXStateStore1708096002272() + await mig.down(queryRunner) + debug('Migration statements executed') + return + } + default: + return Promise.reject( + `Migrations are currently only supported for sqlite, react-native, expo and postgres. Was ${dbType}. Please run your database without migrations and with 'migrationsRun: false' and 'synchronize: true' for now` + ) + } + } +} diff --git a/packages/data-store/src/migrations/generic/index.ts b/packages/data-store/src/migrations/generic/index.ts index 27cc68d40..8765b65dd 100644 --- a/packages/data-store/src/migrations/generic/index.ts +++ b/packages/data-store/src/migrations/generic/index.ts @@ -3,6 +3,7 @@ import { CreateIssuanceBranding1659463079429 } from './2-CreateIssuanceBranding' import { CreateContacts1690925872318 } from './3-CreateContacts' import { CreateStatusList1693866470000 } from './4-CreateStatusList' import { CreateAuditEvents1701635835330 } from './5-CreateAuditEvents' +import {CreateXStateStore1708098041262} from "./6-CreateXStateStore"; /** * The migrations array that SHOULD be used when initializing a TypeORM database connection. @@ -17,6 +18,7 @@ export const DataStoreContactMigrations = [CreateContacts1659463079429, CreateCo export const DataStoreIssuanceBrandingMigrations = [CreateIssuanceBranding1659463079429] export const DataStoreStatusListMigrations = [CreateStatusList1693866470000] export const DataStoreEventLoggerMigrations = [CreateAuditEvents1701635835330] +export const DataStoreXStatePersistenceMigration = [CreateXStateStore1708098041262] // All migrations together export const DataStoreMigrations = [ @@ -24,4 +26,5 @@ export const DataStoreMigrations = [ ...DataStoreIssuanceBrandingMigrations, ...DataStoreStatusListMigrations, ...DataStoreEventLoggerMigrations, + ...DataStoreXStatePersistenceMigration ] diff --git a/packages/data-store/src/migrations/index.ts b/packages/data-store/src/migrations/index.ts index 9088bea28..8c53e8015 100644 --- a/packages/data-store/src/migrations/index.ts +++ b/packages/data-store/src/migrations/index.ts @@ -4,4 +4,5 @@ export { DataStoreContactMigrations, DataStoreIssuanceBrandingMigrations, DataStoreStatusListMigrations, + DataStoreXStatePersistenceMigration } from './generic' diff --git a/packages/data-store/src/migrations/postgres/1708097018115-CreateXStateStore.ts b/packages/data-store/src/migrations/postgres/1708097018115-CreateXStateStore.ts new file mode 100644 index 000000000..6fcbf49d2 --- /dev/null +++ b/packages/data-store/src/migrations/postgres/1708097018115-CreateXStateStore.ts @@ -0,0 +1,18 @@ +import {enablePostgresUuidExtension} from "@sphereon/ssi-sdk.core"; +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class CreateXStateStore1708097018115 implements MigrationInterface { + name = 'CreateXStateStore1708097018115' + + public async up(queryRunner: QueryRunner): Promise { + await enablePostgresUuidExtension(queryRunner) + await queryRunner.query( + `CREATE TABLE "XStateStore" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "state" varchar(255) NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, CONSTRAINT PK_XStateStore_id PRIMARY KEY ("id")` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "XStateStore" DROP CONSTRAINT "PK_XStateStore_id"`) + await queryRunner.query(`DROP TABLE "XStateStore"`) + } +} \ No newline at end of file diff --git a/packages/data-store/src/migrations/sqlite/1708096002272-CreateXStateStore.ts b/packages/data-store/src/migrations/sqlite/1708096002272-CreateXStateStore.ts new file mode 100644 index 000000000..386333f01 --- /dev/null +++ b/packages/data-store/src/migrations/sqlite/1708096002272-CreateXStateStore.ts @@ -0,0 +1,16 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class CreateXStateStore1708096002272 implements MigrationInterface { + name = 'CreateXStateStore1708096002272' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "XStateStore" ("id" varchar PRIMARY KEY NOT NULL, "state" varchar(255) NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, CONSTRAINT "PK_XStateStore_id"` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "XStateStore" DROP CONSTRAINT "PK_XStateStore_id"`) + await queryRunner.query(`DROP TABLE "XStateStore"`) + } +} \ No newline at end of file diff --git a/packages/data-store/src/types/index.ts b/packages/data-store/src/types/index.ts index 9aaea348f..0714231a4 100644 --- a/packages/data-store/src/types/index.ts +++ b/packages/data-store/src/types/index.ts @@ -7,3 +7,4 @@ export * from './statusList/statusList' export * from './statusList/IAbstractStatusListStore' export * from './eventLogger/IAbstractEventLoggerStore' export * from './eventLogger/eventLogger' +export * from './xstatePersistence/types' diff --git a/packages/data-store/src/types/xstatePersistence/types.ts b/packages/data-store/src/types/xstatePersistence/types.ts new file mode 100644 index 000000000..98aa7efd8 --- /dev/null +++ b/packages/data-store/src/types/xstatePersistence/types.ts @@ -0,0 +1,19 @@ +import {XStateEntity} from "../../entities/xstatePersistence/XStateEntity"; + +export type PersistStateArgs = { + state: string + type: string + createdAt: Date + updatedAt: Date + completedAt: Date + tenantId?: string + ttl: number +} + +export type LoadStateArgs = Pick + +export type DeleteStateArgs = Pick + +export type VoidResult = void + +export type LoadStateResult = XStateEntity | null diff --git a/packages/data-store/src/xstatePersistence/IAbstractStateStore.ts b/packages/data-store/src/xstatePersistence/IAbstractStateStore.ts new file mode 100644 index 000000000..06c82c298 --- /dev/null +++ b/packages/data-store/src/xstatePersistence/IAbstractStateStore.ts @@ -0,0 +1,7 @@ +import {LoadStateArgs, LoadStateResult, PersistStateArgs, VoidResult} from "../types"; + +export abstract class IAbstractStateStore { + abstract persistState(state: PersistStateArgs): Promise + abstract loadState(args: LoadStateArgs): Promise + abstract deleteState(args: LoadStateArgs): Promise +} \ No newline at end of file diff --git a/packages/data-store/src/xstatePersistence/XStateStore.ts b/packages/data-store/src/xstatePersistence/XStateStore.ts new file mode 100644 index 000000000..dbab0fbff --- /dev/null +++ b/packages/data-store/src/xstatePersistence/XStateStore.ts @@ -0,0 +1,40 @@ +import {OrPromise} from "@sphereon/ssi-types"; +import Debug from 'debug' +import {DataSource} from "typeorm"; + +import {XStateEntity} from "../entities/xstatePersistence/XStateEntity"; +import {DeleteStateArgs, LoadStateArgs, LoadStateResult, PersistStateArgs, VoidResult,} from "../types"; +import {IAbstractStateStore} from "./IAbstractStateStore"; + +const debug = Debug('sphereon:ssi-sdk:xstatePersistence') + +export class XStateStore extends IAbstractStateStore { + private readonly dbConnection: OrPromise + + constructor(dbConnection: OrPromise) { + super() + this.dbConnection = dbConnection + } + + async persistState(state: PersistStateArgs): Promise { + const connection: DataSource = await this.dbConnection + debug.log(`Executing persistState with state: ${JSON.stringify(state)}`) + await connection.getRepository(XStateEntity).save(state) + } + + async loadState(args: LoadStateArgs): Promise { + const connection: DataSource = await this.dbConnection + debug.log(`Executing loadState query with type: ${args.type}`) + return await connection.getRepository(XStateEntity) + .findOne({ + where: { type: args.type } + }) + } + + async deleteState(args: DeleteStateArgs): Promise { + const connection: DataSource = await this.dbConnection + debug.log(`Executing loadState query with type: ${args.type}`) + await connection.getRepository(XStateEntity) + .delete({ type: args.type }) + } +}