Merge branch 'master' into fix_seeds

This commit is contained in:
einhornimmond 2025-11-14 12:08:55 +01:00
commit ecfeeadcb1
309 changed files with 6595 additions and 19549 deletions

View File

@ -12,6 +12,7 @@ jobs:
backend: ${{ steps.backend.outputs.success }}
database: ${{ steps.database.outputs.success }}
dht-node: ${{ steps.dht-node.outputs.success }}
dlt-connector: ${{ steps.dlt-connector.outputs.success }}
federation: ${{ steps.federation.outputs.success }}
steps:
- name: Checkout
@ -57,6 +58,12 @@ jobs:
cd ./dht-node
biome ci .
echo "success=$([ $? -eq 0 ] && echo true || echo false)" >> $GITHUB_OUTPUT
- name: Lint - DLT Connector
id: dlt-connector
run: |
cd ./dlt-connector
biome ci .
echo "success=$([ $? -eq 0 ] && echo true || echo false)" >> $GITHUB_OUTPUT
- name: Lint - Federation
id: federation
run: |
@ -112,6 +119,14 @@ jobs:
- name: Check result from previous step
run: if [ "${{ needs.lint.outputs.dht-node }}" != "true" ]; then exit 1; fi
lint_dlt_connector:
name: Lint - DLT Connector
needs: lint
runs-on: ubuntu-latest
steps:
- name: Check result from previous step
run: if [ "${{ needs.lint.outputs.dlt-connector }}" != "true" ]; then exit 1; fi
lint_federation:
name: Lint - Federation
needs: lint

View File

@ -28,29 +28,20 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: create .env
run: |
cd dlt-connector
cat <<EOF > .env
GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET=$(openssl rand -hex 16)
GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY=$(openssl rand -hex 16)
HOME_COMMUNITY_SEED=$(openssl rand -hex 32)
HIERO_OPERATOR_KEY=$(openssl rand -hex 32)
HIERO_OPERATOR_ID="0.0.2"
EOF
- name: Build 'test' image
run: |
docker build --target test -t "gradido/dlt-connector:test" -f dlt-connector/Dockerfile .
docker save "gradido/dlt-connector:test" > /tmp/dlt-connector.tar
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: docker-dlt-connector-test
path: /tmp/dlt-connector.tar
lint:
name: Lint - DLT Connector
if: needs.files-changed.outputs.dlt_connector == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Lint
run: cd dlt-connector && yarn && yarn run lint
run: docker build --target production -t "gradido/dlt-connector:productionTest" -f dlt-connector/Dockerfile .
unit_test:
name: Unit Tests - DLT Connector
@ -60,13 +51,21 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: DLT-Connector | docker-compose mariadb
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb
- name: install bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: '.bun-version'
- name: Sleep for 30 seconds
run: sleep 30s
shell: bash
- name: install dependencies
run: cd dlt-connector && bun install --frozen-lockfile
- name: typecheck && unit test
run: |
export GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET=$(openssl rand -hex 16)
export GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY=$(openssl rand -hex 16)
export HOME_COMMUNITY_SEED=$(openssl rand -hex 32)
export HIERO_OPERATOR_KEY=$(openssl rand -hex 32)
export HIERO_OPERATOR_ID="0.0.2"
cd dlt-connector && bun typecheck && bun test
- name: DLT-Connector | Unit tests
run: cd dlt-database && yarn && yarn build && cd ../dlt-connector && yarn && yarn test

View File

@ -1,49 +1,10 @@
import { Transaction as DbTransaction } from 'database'
import { Decimal } from 'decimal.js-light'
import { DataSource } from 'typeorm'
import { cleanDB, testEnvironment } from '@test/helpers'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { DltConnectorClient } from './DltConnectorClient'
let con: DataSource
let testEnv: {
con: DataSource
}
// Mock the GraphQLClient
jest.mock('graphql-request', () => {
const originalModule = jest.requireActual('graphql-request')
return {
__esModule: true,
...originalModule,
GraphQLClient: jest.fn().mockImplementation((url: string) => {
if (url === 'invalid') {
throw new Error('invalid url')
}
return {
// why not using mockResolvedValueOnce or mockReturnValueOnce?
// I have tried, but it didn't work and return every time the first value
request: jest.fn().mockImplementation(() => {
return Promise.resolve({
transmitTransaction: {
succeed: true,
},
})
}),
}
}),
}
})
describe('undefined DltConnectorClient', () => {
it('invalid url', () => {
CONFIG.DLT_CONNECTOR_URL = 'invalid'
CONFIG.DLT_CONNECTOR_URL = ''
CONFIG.DLT_CONNECTOR = true
const result = DltConnectorClient.getInstance()
expect(result).toBeUndefined()
@ -58,90 +19,4 @@ describe('undefined DltConnectorClient', () => {
})
})
/*
describe.skip('transmitTransaction, without db connection', () => {
const transaction = new DbTransaction()
transaction.typeId = 2 // Example transaction type ID
transaction.amount = new Decimal('10.00') // Example amount
transaction.balanceDate = new Date() // Example creation date
transaction.id = 1 // Example transaction ID
it('cannot query for transaction id', async () => {
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(false)
})
})
*/
describe('transmitTransaction', () => {
beforeAll(async () => {
testEnv = await testEnvironment()
con = testEnv.con
await cleanDB()
})
afterAll(async () => {
await cleanDB()
await con.destroy()
})
const transaction = new DbTransaction()
transaction.typeId = 2 // Example transaction type ID
transaction.amount = new Decimal('10.00') // Example amount
transaction.balanceDate = new Date() // Example creation date
transaction.id = 1 // Example transaction ID
// data needed to let save succeed
transaction.memo = "I'm a dummy memo"
transaction.userId = 1
transaction.userGradidoID = 'dummy gradido id'
/*
it.skip('cannot find transaction in db', async () => {
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(false)
})
*/
it('invalid transaction type', async () => {
const localTransaction = new DbTransaction()
localTransaction.typeId = 12
try {
await DltConnectorClient.getInstance()?.transmitTransaction(localTransaction)
} catch (e) {
expect(e).toMatchObject(
new LogError(`invalid transaction type id: ${localTransaction.typeId.toString()}`),
)
}
})
/*
it.skip('should transmit the transaction and update the dltTransactionId in the database', async () => {
await transaction.save()
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(true)
})
it.skip('invalid dltTransactionId (maximal 32 Bytes in Binary)', async () => {
await transaction.save()
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(false)
})
*/
})
/*
describe.skip('try transmitTransaction but graphql request failed', () => {
it('graphql request should throw', async () => {
const transaction = new DbTransaction()
transaction.typeId = 2 // Example transaction type ID
transaction.amount = new Decimal('10.00') // Example amount
transaction.balanceDate = new Date() // Example creation date
transaction.id = 1 // Example transaction ID
const result = await DltConnectorClient.getInstance()?.transmitTransaction(transaction)
expect(result).toBe(false)
})
})
*/

View File

@ -1,36 +1,12 @@
import { Transaction as DbTransaction } from 'database'
import { GraphQLClient, gql } from 'graphql-request'
import { CONFIG } from '@/config'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { TransactionTypeId } from 'core'
import { LogError } from '@/server/LogError'
import { getLogger } from 'log4js'
import { TransactionResult } from './model/TransactionResult'
import { UserIdentifier } from './model/UserIdentifier'
import { TransactionDraft } from './model/TransactionDraft'
import { IRestResponse, RestClient } from 'typed-rest-client'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.apis.dltConnector`)
const sendTransaction = gql`
mutation ($input: TransactionInput!) {
sendTransaction(data: $input) {
dltTransactionIdHex
}
}
`
// from ChatGPT
function getTransactionTypeString(id: TransactionTypeId): string {
const key = Object.keys(TransactionTypeId).find(
(key) => TransactionTypeId[key as keyof typeof TransactionTypeId] === id,
)
if (key === undefined) {
throw new LogError('invalid transaction type id: ' + id.toString())
}
return key
}
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
// and ../federation/client/FederationClientFactory.ts
/**
@ -40,7 +16,7 @@ function getTransactionTypeString(id: TransactionTypeId): string {
export class DltConnectorClient {
private static instance: DltConnectorClient
client: GraphQLClient
client: RestClient
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
@ -64,13 +40,12 @@ export class DltConnectorClient {
}
if (!DltConnectorClient.instance.client) {
try {
DltConnectorClient.instance.client = new GraphQLClient(CONFIG.DLT_CONNECTOR_URL, {
method: 'GET',
jsonSerializer: {
parse: JSON.parse,
stringify: JSON.stringify,
},
})
DltConnectorClient.instance.client = new RestClient(
'gradido-backend',
CONFIG.DLT_CONNECTOR_URL,
undefined,
{ keepAlive: true }
)
} catch (e) {
logger.error("couldn't connect to dlt-connector: ", e)
return
@ -80,47 +55,14 @@ export class DltConnectorClient {
}
/**
* transmit transaction via dlt-connector to iota
* and update dltTransactionId of transaction in db with iota message id
* transmit transaction via dlt-connector to hiero
* and update dltTransactionId of transaction in db with hiero transaction id
*/
public async transmitTransaction(transaction: DbTransaction): Promise<boolean> {
const typeString = getTransactionTypeString(transaction.typeId)
// no negative values in dlt connector, gradido concept don't use negative values so the code don't use it too
const amountString = transaction.amount.abs().toString()
const params = {
input: {
user: {
uuid: transaction.userGradidoID,
communityUuid: transaction.userCommunityUuid,
} as UserIdentifier,
linkedUser: {
uuid: transaction.linkedUserGradidoID,
communityUuid: transaction.linkedUserCommunityUuid,
} as UserIdentifier,
amount: amountString,
type: typeString,
createdAt: transaction.balanceDate.toISOString(),
backendTransactionId: transaction.id,
targetDate: transaction.creationDate?.toISOString(),
},
}
try {
// TODO: add account nr for user after they have also more than one account in backend
logger.debug('transmit transaction to dlt connector', params)
const {
data: {
sendTransaction: { error, succeed },
},
} = await this.client.rawRequest<{ sendTransaction: TransactionResult }>(
sendTransaction,
params,
)
if (error) {
throw new Error(error.message)
}
return succeed
} catch (e) {
throw new LogError('Error send sending transaction to dlt-connector: ', e)
}
public async sendTransaction(input: TransactionDraft): Promise<IRestResponse<{ transactionId: string }>> {
logger.debug('transmit transaction or user to dlt connector', input)
return await this.client.create<{ transactionId: string }>(
'/sendTransaction',
input
)
}
}

View File

@ -0,0 +1,55 @@
import { CONFIG } from '@/config'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLogger } from 'log4js'
import { TransactionDraft } from '@/apis/dltConnector/model/TransactionDraft'
import { IRestResponse } from 'typed-rest-client'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.apis.dltConnector`)
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
// and ../federation/client/FederationClientFactory.ts
/**
* A Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
*/
export class DltConnectorClient {
private static instance: DltConnectorClient
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
private constructor() {}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(): DltConnectorClient | undefined {
if (!CONFIG.DLT_CONNECTOR || !CONFIG.DLT_CONNECTOR_URL) {
logger.info(`dlt-connector are disabled via config...`)
return
}
if (!DltConnectorClient.instance) {
DltConnectorClient.instance = new DltConnectorClient()
}
return DltConnectorClient.instance
}
/**
* transmit transaction via dlt-connector to hiero
* and update dltTransactionId of transaction in db with hiero transaction id
*/
public async sendTransaction(input: TransactionDraft): Promise<IRestResponse<string>> {
logger.debug('transmit transaction or user to dlt connector', input)
return Promise.resolve({
statusCode: 200,
result: 'test',
headers: {},
})
}
}

View File

@ -0,0 +1,9 @@
export enum AccountType {
NONE = 'NONE', // if no address was found
COMMUNITY_HUMAN = 'COMMUNITY_HUMAN', // creation account for human
COMMUNITY_GMW = 'COMMUNITY_GMW', // community public budget account
COMMUNITY_AUF = 'COMMUNITY_AUF', // community compensation and environment founds account
COMMUNITY_PROJECT = 'COMMUNITY_PROJECT', // no creations allowed
SUBACCOUNT = 'SUBACCOUNT', // no creations allowed
CRYPTO_ACCOUNT = 'CRYPTO_ACCOUNT', // user control his keys, no creations
}

View File

@ -0,0 +1,9 @@
export enum DltTransactionType {
UNKNOWN = 0,
REGISTER_ADDRESS = 1,
CREATION = 2,
TRANSFER = 3,
DEFERRED_TRANSFER = 4,
REDEEM_DEFERRED_TRANSFER = 5,
DELETE_DEFERRED_TRANSFER = 6,
}

View File

@ -1,14 +0,0 @@
/**
* Error Types for dlt-connector graphql responses
*/
export enum TransactionErrorType {
NOT_IMPLEMENTED_YET = 'Not Implemented yet',
MISSING_PARAMETER = 'Missing parameter',
ALREADY_EXIST = 'Already exist',
DB_ERROR = 'DB Error',
PROTO_DECODE_ERROR = 'Proto Decode Error',
PROTO_ENCODE_ERROR = 'Proto Encode Error',
INVALID_SIGNATURE = 'Invalid Signature',
LOGIC_ERROR = 'Logic Error',
NOT_FOUND = 'Not found',
}

View File

@ -1,11 +1,19 @@
import { registerEnumType } from 'type-graphql'
/**
* Transaction Types on Blockchain
*/
export enum TransactionType {
GRADIDO_TRANSFER = 1,
GRADIDO_CREATION = 2,
GROUP_FRIENDS_UPDATE = 3,
REGISTER_ADDRESS = 4,
GRADIDO_DEFERRED_TRANSFER = 5,
COMMUNITY_ROOT = 6,
GRADIDO_TRANSFER = 'GRADIDO_TRANSFER',
GRADIDO_CREATION = 'GRADIDO_CREATION',
GROUP_FRIENDS_UPDATE = 'GROUP_FRIENDS_UPDATE',
REGISTER_ADDRESS = 'REGISTER_ADDRESS',
GRADIDO_DEFERRED_TRANSFER = 'GRADIDO_DEFERRED_TRANSFER',
GRADIDO_REDEEM_DEFERRED_TRANSFER = 'GRADIDO_REDEEM_DEFERRED_TRANSFER',
COMMUNITY_ROOT = 'COMMUNITY_ROOT',
}
registerEnumType(TransactionType, {
name: 'TransactionType', // this one is mandatory
description: 'Type of the transaction', // this one is optional
})

View File

@ -0,0 +1,164 @@
import { IRestResponse } from 'typed-rest-client'
import { DltTransactionType } from './enum/DltTransactionType'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { DltConnectorClient } from './DltConnectorClient'
import {
Community as DbCommunity,
Contribution as DbContribution,
DltTransaction as DbDltTransaction,
TransactionLink as DbTransactionLink,
User as DbUser,
getCommunityByUuid,
getHomeCommunity,
getUserById,
UserLoggingView,
} from 'database'
import { TransactionDraft } from './model/TransactionDraft'
import { CONFIG } from '@/config'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.dltConnector`)
// will be undefined if dlt connect is disabled
const dltConnectorClient = DltConnectorClient.getInstance()
async function checkDltConnectorResult(dltTransaction: DbDltTransaction, clientResponse: Promise<IRestResponse<{ transactionId: string }>>)
: Promise<DbDltTransaction> {
// check result from dlt connector
try {
const response = await clientResponse
if (response.statusCode === 200 && response.result) {
dltTransaction.messageId = response.result.transactionId
} else {
dltTransaction.error = `empty result with status code ${response.statusCode}`
logger.error('error from dlt-connector', response)
}
} catch (e) {
logger.debug(e)
if (e instanceof Error) {
dltTransaction.error = e.message
} else if (typeof e === 'string') {
dltTransaction.error = e
} else {
throw e
}
}
return dltTransaction
}
async function executeDltTransaction(draft: TransactionDraft | null, typeId: DltTransactionType, persist = true): Promise<DbDltTransaction | null> {
if (draft && dltConnectorClient) {
const clientResponse = dltConnectorClient.sendTransaction(draft)
let dltTransaction = new DbDltTransaction()
dltTransaction.typeId = typeId
dltTransaction = await checkDltConnectorResult(dltTransaction, clientResponse)
if (persist) {
return await dltTransaction.save()
}
return dltTransaction
}
return Promise.resolve(null)
}
/**
* send register address transaction via dlt-connector to hiero
* and update dltTransactionId of transaction in db with hiero transaction id
*/
export async function registerAddressTransaction(user: DbUser, community: DbCommunity): Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
return Promise.resolve(null)
}
if (!user.id) {
logger.error(`missing id for user: ${user.gradidoID}, please call registerAddressTransaction after user.save()`)
return null
}
// return null if some data where missing and log error
const draft = TransactionDraft.createRegisterAddress(user, community)
const dltTransaction = await executeDltTransaction(draft, DltTransactionType.REGISTER_ADDRESS, false)
if (dltTransaction) {
if (user.id) {
dltTransaction.userId = user.id
}
return await dltTransaction.save()
}
return null
}
export async function contributionTransaction(
contribution: DbContribution,
signingUser: DbUser,
createdAt: Date,
): Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
return Promise.resolve(null)
}
const homeCommunity = await getHomeCommunity()
if (!homeCommunity) {
logger.error('home community not found')
return null
}
const draft = TransactionDraft.createContribution(contribution, createdAt, signingUser, homeCommunity)
return await executeDltTransaction(draft, DltTransactionType.CREATION)
}
export async function transferTransaction(
senderUser: DbUser,
recipientUser: DbUser,
amount: string,
memo: string,
createdAt: Date
): Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
return Promise.resolve(null)
}
// load community if not already loaded, maybe they are remote communities
if (!senderUser.community) {
senderUser.community = await getCommunityByUuid(senderUser.communityUuid)
}
if (!recipientUser.community) {
recipientUser.community = await getCommunityByUuid(recipientUser.communityUuid)
}
logger.info(`sender user: ${new UserLoggingView(senderUser)}`)
logger.info(`recipient user: ${new UserLoggingView(recipientUser)}`)
const draft = TransactionDraft.createTransfer(senderUser, recipientUser, amount, memo, createdAt)
return await executeDltTransaction(draft, DltTransactionType.TRANSFER)
}
export async function deferredTransferTransaction(senderUser: DbUser, transactionLink: DbTransactionLink)
: Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
return Promise.resolve(null)
}
// load community if not already loaded
if (!senderUser.community) {
senderUser.community = await getCommunityByUuid(senderUser.communityUuid)
}
const draft = TransactionDraft.createDeferredTransfer(senderUser, transactionLink)
return await executeDltTransaction(draft, DltTransactionType.DEFERRED_TRANSFER)
}
export async function redeemDeferredTransferTransaction(transactionLink: DbTransactionLink, amount: string, createdAt: Date, recipientUser: DbUser)
: Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) {
return Promise.resolve(null)
}
// load user and communities if not already loaded
if (!transactionLink.user) {
logger.debug('load sender user')
transactionLink.user = await getUserById(transactionLink.userId, true, false)
}
if (!transactionLink.user.community) {
logger.debug('load sender community')
transactionLink.user.community = await getCommunityByUuid(transactionLink.user.communityUuid)
}
if (!recipientUser.community) {
logger.debug('load recipient community')
recipientUser.community = await getCommunityByUuid(recipientUser.communityUuid)
}
logger.debug(`sender: ${new UserLoggingView(transactionLink.user)}`)
logger.debug(`recipient: ${new UserLoggingView(recipientUser)}`)
const draft = TransactionDraft.redeemDeferredTransfer(transactionLink, amount, createdAt, recipientUser)
return await executeDltTransaction(draft, DltTransactionType.REDEEM_DEFERRED_TRANSFER)
}

View File

@ -0,0 +1,17 @@
import { CommunityAccountIdentifier } from './CommunityAccountIdentifier'
import { IdentifierSeed } from './IdentifierSeed'
export class AccountIdentifier {
communityTopicId: string
account?: CommunityAccountIdentifier
seed?: IdentifierSeed // used for deferred transfers
constructor(communityTopicId: string, input: CommunityAccountIdentifier | IdentifierSeed) {
if (input instanceof CommunityAccountIdentifier) {
this.account = input
} else if (input instanceof IdentifierSeed) {
this.seed = input
}
this.communityTopicId = communityTopicId
}
}

View File

@ -0,0 +1,10 @@
export class CommunityAccountIdentifier {
// for community user, uuid and communityUuid used
userUuid: string
accountNr?: number
constructor(userUuid: string, accountNr?: number) {
this.userUuid = userUuid
this.accountNr = accountNr
}
}

View File

@ -0,0 +1,9 @@
// https://www.npmjs.com/package/@apollo/protobufjs
export class IdentifierSeed {
seed: string
constructor(seed: string) {
this.seed = seed
}
}

View File

@ -0,0 +1,129 @@
// https://www.npmjs.com/package/@apollo/protobufjs
import { AccountType } from '@dltConnector/enum/AccountType'
import { TransactionType } from '@dltConnector/enum/TransactionType'
import { AccountIdentifier } from './AccountIdentifier'
import {
Community as DbCommunity,
Contribution as DbContribution,
TransactionLink as DbTransactionLink,
User as DbUser
} from 'database'
import { CommunityAccountIdentifier } from './CommunityAccountIdentifier'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { IdentifierSeed } from './IdentifierSeed'
import { CODE_VALID_DAYS_DURATION } from '@/graphql/resolver/const/const'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.dltConnector.model.TransactionDraft`)
export class TransactionDraft {
user: AccountIdentifier
// not used for simply register address
linkedUser?: AccountIdentifier
// not used for register address
amount?: string
memo?: string
type: TransactionType
createdAt: string
// only for creation transaction
targetDate?: string
// only for deferred transaction, duration in seconds
timeoutDuration?: number
// only for register address
accountType?: AccountType
static createRegisterAddress(user: DbUser, community: DbCommunity): TransactionDraft | null {
if (community.hieroTopicId) {
const draft = new TransactionDraft()
draft.user = new AccountIdentifier(community.hieroTopicId, new CommunityAccountIdentifier(user.gradidoID))
draft.type = TransactionType.REGISTER_ADDRESS
draft.createdAt = user.createdAt.toISOString()
draft.accountType = AccountType.COMMUNITY_HUMAN
return draft
} else {
logger.warn(`missing topicId for community ${community.id}`)
}
return null
}
static createContribution(contribution: DbContribution, createdAt: Date, signingUser: DbUser, community: DbCommunity): TransactionDraft | null {
if (community.hieroTopicId) {
const draft = new TransactionDraft()
draft.user = new AccountIdentifier(community.hieroTopicId, new CommunityAccountIdentifier(contribution.user.gradidoID))
draft.linkedUser = new AccountIdentifier(community.hieroTopicId, new CommunityAccountIdentifier(signingUser.gradidoID))
draft.type = TransactionType.GRADIDO_CREATION
draft.createdAt = createdAt.toISOString()
draft.amount = contribution.amount.toString()
draft.memo = contribution.memo
draft.targetDate = contribution.contributionDate.toISOString()
return draft
} else {
logger.warn(`missing topicId for community ${community.id}`)
}
return null
}
static createTransfer(sendingUser: DbUser, receivingUser: DbUser, amount: string, memo: string, createdAt: Date): TransactionDraft | null {
if (!sendingUser.community || !receivingUser.community) {
throw new Error(`missing community for user ${sendingUser.id} and/or ${receivingUser.id}`)
}
const senderUserTopic = sendingUser.community.hieroTopicId
const receiverUserTopic = receivingUser.community.hieroTopicId
if (!senderUserTopic || !receiverUserTopic) {
throw new Error(`missing topicId for community ${sendingUser.community.id} and/or ${receivingUser.community.id}`)
}
const draft = new TransactionDraft()
draft.user = new AccountIdentifier(senderUserTopic, new CommunityAccountIdentifier(sendingUser.gradidoID))
draft.linkedUser = new AccountIdentifier(receiverUserTopic, new CommunityAccountIdentifier(receivingUser.gradidoID))
draft.type = TransactionType.GRADIDO_TRANSFER
draft.createdAt = createdAt.toISOString()
draft.amount = amount
draft.memo = memo
return draft
}
static createDeferredTransfer(sendingUser: DbUser, transactionLink: DbTransactionLink)
: TransactionDraft | null {
if (!sendingUser.community) {
throw new Error(`missing community for user ${sendingUser.id}`)
}
const senderUserTopic = sendingUser.community.hieroTopicId
if (!senderUserTopic) {
throw new Error(`missing topicId for community ${sendingUser.community.id}`)
}
const draft = new TransactionDraft()
draft.user = new AccountIdentifier(senderUserTopic, new CommunityAccountIdentifier(sendingUser.gradidoID))
draft.linkedUser = new AccountIdentifier(senderUserTopic, new IdentifierSeed(transactionLink.code))
draft.type = TransactionType.GRADIDO_DEFERRED_TRANSFER
draft.createdAt = transactionLink.createdAt.toISOString()
draft.amount = transactionLink.amount.toString()
draft.memo = transactionLink.memo
draft.timeoutDuration = CODE_VALID_DAYS_DURATION * 24 * 60 * 60
return draft
}
static redeemDeferredTransfer(transactionLink: DbTransactionLink, amount: string, createdAt: Date, recipientUser: DbUser): TransactionDraft | null {
if (!transactionLink.user.community) {
throw new Error(`missing community for user ${transactionLink.user.id}`)
}
if (!recipientUser.community) {
throw new Error(`missing community for user ${recipientUser.id}`)
}
const senderUserTopic = transactionLink.user.community.hieroTopicId
if (!senderUserTopic) {
throw new Error(`missing topicId for community ${transactionLink.user.community.id}`)
}
const recipientUserTopic = recipientUser.community.hieroTopicId
if (!recipientUserTopic) {
throw new Error(`missing topicId for community ${recipientUser.community.id}`)
}
const draft = new TransactionDraft()
draft.user = new AccountIdentifier(senderUserTopic, new IdentifierSeed(transactionLink.code))
draft.linkedUser = new AccountIdentifier(recipientUserTopic, new CommunityAccountIdentifier(recipientUser.gradidoID))
draft.type = TransactionType.GRADIDO_REDEEM_DEFERRED_TRANSFER
draft.createdAt = createdAt.toISOString()
draft.amount = amount
return draft
}
}

View File

@ -1,7 +0,0 @@
import { TransactionErrorType } from '@dltConnector/enum/TransactionErrorType'
export interface TransactionError {
type: TransactionErrorType
message: string
name: string
}

View File

@ -1,8 +0,0 @@
import { TransactionType } from '@dltConnector/enum/TransactionType'
export interface TransactionRecipe {
id: number
createdAt: string
type: TransactionType
topic: string
}

View File

@ -1,8 +0,0 @@
import { TransactionError } from './TransactionError'
import { TransactionRecipe } from './TransactionRecipe'
export interface TransactionResult {
error?: TransactionError
recipe?: TransactionRecipe
succeed: boolean
}

View File

@ -1,5 +0,0 @@
export interface UserIdentifier {
uuid: string
communityUuid: string
accountNr?: number
}

View File

@ -43,6 +43,7 @@ const DLT_CONNECTOR_PORT = process.env.DLT_CONNECTOR_PORT ?? 6010
const dltConnector = {
DLT_CONNECTOR: process.env.DLT_CONNECTOR === 'true' || false,
DLT_CONNECTOR_URL: process.env.DLT_CONNECTOR_URL ?? `${COMMUNITY_URL}:${DLT_CONNECTOR_PORT}`,
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER: process.env.DLT_GRADIDO_NODE_SERVER_HOME_FOLDER ?? '~/.gradido',
}
const community = {

View File

@ -79,6 +79,10 @@ export const schema = Joi.object({
.when('DLT_CONNECTOR', { is: true, then: Joi.required() })
.description('The URL for GDT API endpoint'),
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER: Joi.string()
.default('~/.gradido')
.description('The home folder for the gradido dlt node server'),
EMAIL: Joi.boolean()
.default(false)
.description('Enable or disable email functionality')

View File

@ -4,4 +4,5 @@ export interface PublicCommunityInfo {
creationDate: Date
publicKey: string
publicJwtKey: string
hieroTopicId: string | null
}

View File

@ -138,6 +138,7 @@ async function writeForeignCommunity(
com.publicKey = dbCom.publicKey
com.publicJwtKey = pubInfo.publicJwtKey
com.url = dbCom.endPoint
com.hieroTopicId = pubInfo.hieroTopicId
await DbCommunity.save(com)
}
}

View File

@ -3,6 +3,7 @@ import { ArgsType, Field, InputType } from 'type-graphql'
import { Location } from '@/graphql/model/Location'
import { isValidLocation } from '@/graphql/validator/Location'
import { isValidHieroId } from '@/graphql/validator/HieroId'
@ArgsType()
@InputType()
@ -21,5 +22,6 @@ export class EditCommunityInput {
@Field(() => String, { nullable: true })
@IsString()
@isValidHieroId()
hieroTopicId?: string | null
}

View File

@ -2,6 +2,7 @@ import {
Contribution as DbContribution,
Transaction as DbTransaction,
User as DbUser,
DltTransaction as DbDltTransaction,
UserContact,
} from 'database'
import { Decimal } from 'decimal.js-light'
@ -59,7 +60,7 @@ import { getOpenCreations, getUserCreation, validateContribution } from './util/
import { extractGraphQLFields } from './util/extractGraphQLFields'
import { findContributions } from './util/findContributions'
import { getLastTransaction } from 'database'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
import { contributionTransaction } from '@/apis/dltConnector'
const db = AppDatabase.getInstance()
const createLogger = () => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.ContributionResolver`)
@ -435,12 +436,11 @@ export class ContributionResolver {
): Promise<boolean> {
const logger = createLogger()
logger.addContext('contribution', id)
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
try {
const clientTimezoneOffset = getClientTimezoneOffset(context)
const contribution = await DbContribution.findOne({ where: { id } })
const contribution = await DbContribution.findOne({ where: { id }, relations: {user: {emailContact: true}} })
if (!contribution) {
throw new LogError('Contribution not found', id)
}
@ -450,18 +450,17 @@ export class ContributionResolver {
if (contribution.contributionStatus === 'DENIED') {
throw new LogError('Contribution already denied', id)
}
const moderatorUser = getUser(context)
if (moderatorUser.id === contribution.userId) {
throw new LogError('Moderator can not confirm own contribution')
}
const user = await DbUser.findOneOrFail({
where: { id: contribution.userId },
withDeleted: true,
relations: ['emailContact'],
})
const user = contribution.user
if (user.deletedAt) {
throw new LogError('Can not confirm contribution since the user was deleted')
}
const receivedCallDate = new Date()
const dltTransactionPromise = contributionTransaction(contribution, moderatorUser, receivedCallDate)
const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false)
validateContribution(
creations,
@ -469,12 +468,10 @@ export class ContributionResolver {
contribution.contributionDate,
clientTimezoneOffset,
)
const receivedCallDate = new Date()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED')
const lastTransaction = await getLastTransaction(contribution.userId)
logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined')
@ -491,7 +488,7 @@ export class ContributionResolver {
}
newBalance = newBalance.add(contribution.amount.toString())
const transaction = new DbTransaction()
let transaction = new DbTransaction()
transaction.typeId = TransactionTypeId.CREATION
transaction.memo = contribution.memo
transaction.userId = contribution.userId
@ -509,7 +506,7 @@ export class ContributionResolver {
transaction.balanceDate = receivedCallDate
transaction.decay = decay ? decay.decay : new Decimal(0)
transaction.decayStart = decay ? decay.start : null
await queryRunner.manager.insert(DbTransaction, transaction)
transaction = await queryRunner.manager.save(DbTransaction, transaction)
contribution.confirmedAt = receivedCallDate
contribution.confirmedBy = moderatorUser.id
@ -518,10 +515,7 @@ export class ContributionResolver {
await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution)
await queryRunner.commitTransaction()
// trigger to send transaction via dlt-connector
await sendTransactionsToDltConnector()
logger.info('creation commited successfuly.')
await sendContributionConfirmedEmail({
firstName: user.firstName,
@ -537,6 +531,17 @@ export class ContributionResolver {
contribution.createdAt,
),
})
// update transaction id in dlt transaction tables
// wait for finishing transaction by dlt-connector/hiero
const dltStartTime = new Date()
const dltTransaction = await dltTransactionPromise
if(dltTransaction) {
dltTransaction.transactionId = transaction.id
await dltTransaction.save()
}
const dltEndTime = new Date()
logger.debug(`dlt-connector contribution finished in ${dltEndTime.getTime() - dltStartTime.getTime()} ms`)
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('Creation was not successful', e)

View File

@ -37,11 +37,14 @@ import { TRANSACTIONS_LOCK } from 'database'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLogger } from 'config-schema/test/testSetup'
import { transactionLinkCode } from './TransactionLinkResolver'
import { CONFIG } from '@/config'
const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
jest.mock('@/password/EncryptorUtils')
CONFIG.DLT_CONNECTOR = false
// mock semaphore to allow use fake timers
jest.mock('database/src/util/TRANSACTIONS_LOCK')
TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn())

View File

@ -15,9 +15,13 @@ import { QueryLinkResult } from '@union/QueryLinkResult'
import { Decay, interpretEncryptedTransferArgs, TransactionTypeId } from 'core'
import {
AppDatabase, Contribution as DbContribution,
ContributionLink as DbContributionLink, FederatedCommunity as DbFederatedCommunity, Transaction as DbTransaction,
ContributionLink as DbContributionLink,
FederatedCommunity as DbFederatedCommunity,
DltTransaction as DbDltTransaction,
Transaction as DbTransaction,
TransactionLink as DbTransactionLink,
User as DbUser,
findModeratorCreatingContributionLink,
findTransactionLinkByCode,
getHomeCommunity
} from 'database'
@ -36,8 +40,16 @@ import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { calculateBalance } from '@/util/validate'
import { fullName } from 'core'
import { TRANSACTION_LINK_LOCK, TRANSACTIONS_LOCK } from 'database'
import { calculateDecay, compoundInterest, decayFormula, decode, DisburseJwtPayloadType, encode, encryptAndSign, EncryptedJWEJwtPayloadType, RedeemJwtPayloadType, verify } from 'shared'
import {
calculateDecay,
compoundInterest,
decode,
DisburseJwtPayloadType,
encode,
encryptAndSign,
RedeemJwtPayloadType,
verify
} from 'shared'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { DisbursementClient as V1_0_DisbursementClient } from '@/federation/client/1_0/DisbursementClient'
import { DisbursementClientFactory } from '@/federation/client/DisbursementClientFactory'
@ -52,9 +64,10 @@ import {
getCommunityByUuid,
} from './util/communities'
import { getUserCreation, validateContribution } from './util/creations'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
import { transactionLinkList } from './util/transactionLinkList'
import { SignedTransferPayloadType } from 'shared'
import { contributionTransaction, deferredTransferTransaction, redeemDeferredTransferTransaction } from '@/apis/dltConnector'
import { CODE_VALID_DAYS_DURATION } from './const/const'
const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.TransactionLinkResolver.${method}`)
@ -68,7 +81,7 @@ export const transactionLinkCode = (date: Date): string => {
)
}
const CODE_VALID_DAYS_DURATION = 14
const db = AppDatabase.getInstance()
export const transactionLinkExpireDate = (date: Date): Date => {
@ -106,11 +119,20 @@ export class TransactionLinkResolver {
transactionLink.code = transactionLinkCode(createdDate)
transactionLink.createdAt = createdDate
transactionLink.validUntil = validUntil
const dltTransactionPromise = deferredTransferTransaction(user, transactionLink)
await DbTransactionLink.save(transactionLink).catch((e) => {
throw new LogError('Unable to save transaction link', e)
})
await EVENT_TRANSACTION_LINK_CREATE(user, transactionLink, amount)
// wait for dlt transaction to be created
const startTime = Date.now()
const dltTransaction = await dltTransactionPromise
const endTime = Date.now()
createLogger('createTransactionLink').debug(`dlt transaction created in ${endTime - startTime} ms`)
if (dltTransaction) {
dltTransaction.transactionLinkId = transactionLink.id
await DbDltTransaction.save(dltTransaction)
}
return new TransactionLink(transactionLink, new User(user))
}
@ -134,7 +156,6 @@ export class TransactionLinkResolver {
user.id,
)
}
if (transactionLink.redeemedBy) {
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
}
@ -143,7 +164,19 @@ export class TransactionLinkResolver {
throw new LogError('Transaction link could not be deleted', e)
})
transactionLink.user = user
const dltTransactionPromise = redeemDeferredTransferTransaction(transactionLink, transactionLink.amount.toString(), transactionLink.deletedAt!, user)
await EVENT_TRANSACTION_LINK_DELETE(user, transactionLink)
// wait for dlt transaction to be created
const startTime = Date.now()
const dltTransaction = await dltTransactionPromise
const endTime = Date.now()
createLogger('deleteTransactionLink').debug(`dlt transaction created in ${endTime - startTime} ms`)
if (dltTransaction) {
dltTransaction.transactionLinkId = transactionLink.id
await DbDltTransaction.save(dltTransaction)
}
return true
}
@ -276,7 +309,7 @@ export class TransactionLinkResolver {
throw new LogError('Contribution link has unknown cycle', contributionLink.cycle)
}
}
const moderatorPromise = findModeratorCreatingContributionLink(contributionLink)
const creations = await getUserCreation(user.id, clientTimezoneOffset)
methodLogger.info('open creations', creations)
validateContribution(creations, contributionLink.amount, now, clientTimezoneOffset)
@ -290,6 +323,12 @@ export class TransactionLinkResolver {
contribution.contributionType = ContributionType.LINK
contribution.contributionStatus = ContributionStatus.CONFIRMED
let dltTransactionPromise: Promise<DbDltTransaction | null> = Promise.resolve(null)
const moderator = await moderatorPromise
if (moderator) {
dltTransactionPromise = contributionTransaction(contribution, moderator, now)
}
await queryRunner.manager.insert(DbContribution, contribution)
const lastTransaction = await getLastTransaction(user.id)
@ -335,6 +374,17 @@ export class TransactionLinkResolver {
contributionLink,
contributionLink.amount,
)
if (dltTransactionPromise) {
const startTime = new Date()
const dltTransaction = await dltTransactionPromise
const endTime = new Date()
methodLogger.info(`dlt-connector transaction finished in ${endTime.getTime() - startTime.getTime()} ms`)
if (dltTransaction) {
dltTransaction.transactionId = transaction.id
await dltTransaction.save()
}
}
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('Creation from contribution link was not successful', e)
@ -343,13 +393,11 @@ export class TransactionLinkResolver {
}
} finally {
releaseLock()
}
// trigger to send transaction via dlt-connector
await sendTransactionsToDltConnector()
}
return true
} else {
const releaseLinkLock = await TRANSACTION_LINK_LOCK.acquire()
const now = new Date()
const releaseLinkLock = await TRANSACTION_LINK_LOCK.acquire()
try {
const transactionLink = await DbTransactionLink.findOne({ where: { code } })
if (!transactionLink) {

View File

@ -33,10 +33,13 @@ import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { getLogger } from 'config-schema/test/testSetup'
import { CONFIG } from '@/config'
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
CONFIG.DLT_CONNECTOR = false
CONFIG.EMAIL = false
let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query']
@ -434,50 +437,6 @@ describe('send coins', () => {
}),
)
})
describe('sendTransactionsToDltConnector', () => {
let transaction: Transaction[]
let dltTransactions: DltTransaction[]
beforeAll(async () => {
// Find the previous created transactions of sendCoin mutation
transaction = await Transaction.find({
where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
// and read aslong as all async created dlt-transactions are finished
do {
dltTransactions = await DltTransaction.find({
where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
// order: { createdAt: 'ASC', id: 'ASC' },
})
} while (transaction.length > dltTransactions.length)
})
it('has wait till sendTransactionsToDltConnector created all dlt-transactions', () => {
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: transaction[0].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transaction[1].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
})
})
})
describe('send coins via gradido ID', () => {
it('sends the coins', async () => {

View File

@ -2,10 +2,13 @@ import {
AppDatabase,
countOpenPendingTransactions,
Community as DbCommunity,
DltTransaction as DbDltTransaction,
Transaction as dbTransaction,
TransactionLink as dbTransactionLink,
User as dbUser,
findUserByIdentifier
findUserByIdentifier,
TransactionLoggingView,
UserLoggingView
} from 'database'
import { Decimal } from 'decimal.js-light'
import { Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql'
@ -41,8 +44,8 @@ import { BalanceResolver } from './BalanceResolver'
import { GdtResolver } from './GdtResolver'
import { getCommunityName, isHomeCommunity } from './util/communities'
import { getTransactionList } from './util/getTransactionList'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
import { transactionLinkSummary } from './util/transactionLinkSummary'
import { transferTransaction, redeemDeferredTransferTransaction } from '@/apis/dltConnector'
const db = AppDatabase.getInstance()
const createLogger = () => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.TransactionResolver`)
@ -57,6 +60,13 @@ export const executeTransaction = async (
): Promise<boolean> => {
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
const receivedCallDate = new Date()
let dltTransactionPromise: Promise<DbDltTransaction | null> = Promise.resolve(null)
if (!transactionLink) {
dltTransactionPromise = transferTransaction(sender, recipient, amount.toString(), memo, receivedCallDate)
} else {
dltTransactionPromise = redeemDeferredTransferTransaction(transactionLink, amount.toString(), receivedCallDate, recipient)
}
try {
logger.info('executeTransaction', amount, memo, sender, recipient)
@ -71,8 +81,7 @@ export const executeTransaction = async (
throw new LogError('Sender and Recipient are the same', sender.id)
}
// validate amount
const receivedCallDate = new Date()
// validate amount
const sendBalance = await calculateBalance(
sender.id,
amount.mul(-1),
@ -162,9 +171,15 @@ export const executeTransaction = async (
transactionReceive,
transactionReceive.amount,
)
// trigger to send transaction via dlt-connector
await sendTransactionsToDltConnector()
// update dltTransaction with transactionId
const startTime = new Date()
const dltTransaction = await dltTransactionPromise
const endTime = new Date()
logger.debug(`dlt-connector transaction finished in ${endTime.getTime() - startTime.getTime()} ms`)
if (dltTransaction) {
dltTransaction.transactionId = transactionSend.id
await dltTransaction.save()
}
} catch (e) {
await queryRunner.rollbackTransaction()
throw new LogError('Transaction was not successful', e)

View File

@ -117,6 +117,7 @@ beforeAll(async () => {
query = testEnv.query
con = testEnv.con
CONFIG.HUMHUB_ACTIVE = false
CONFIG.DLT_CONNECTOR = false
await cleanDB()
})

View File

@ -104,6 +104,7 @@ import { deleteUserRole, setUserRole } from './util/modifyUserRole'
import { sendUserToGms } from './util/sendUserToGms'
import { syncHumhub } from './util/syncHumhub'
import { validateAlias } from 'core'
import { registerAddressTransaction } from '@/apis/dltConnector'
import { updateAllDefinedAndChanged } from 'shared'
const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl']
@ -388,6 +389,7 @@ export class UserResolver {
if (homeCom.communityUuid) {
dbUser.communityUuid = homeCom.communityUuid
}
dbUser.gradidoID = gradidoID
dbUser.firstName = firstName
dbUser.lastName = lastName
@ -398,8 +400,11 @@ export class UserResolver {
dbUser.alias = alias
}
dbUser.publisherId = publisherId ?? 0
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
logger.debug('new dbUser', new UserLoggingView(dbUser))
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
if(logger.isDebugEnabled()) {
logger.debug('new dbUser', new UserLoggingView(dbUser))
}
if (redeemCode) {
if (redeemCode.match(/^CL-/)) {
const contributionLink = await DbContributionLink.findOne({
@ -435,7 +440,7 @@ export class UserResolver {
dbUser.emailContact = emailContact
dbUser.emailId = emailContact.id
await queryRunner.manager.save(dbUser).catch((error) => {
dbUser = await queryRunner.manager.save(dbUser).catch((error) => {
throw new LogError('Error while updating dbUser', error)
})
@ -467,6 +472,8 @@ export class UserResolver {
} finally {
await queryRunner.release()
}
// register user into blockchain
const dltTransactionPromise = registerAddressTransaction(dbUser, homeCom)
logger.info('createUser() successful...')
if (CONFIG.HUMHUB_ACTIVE) {
let spaceId: number | null = null
@ -503,6 +510,11 @@ export class UserResolver {
}
}
}
// wait for finishing dlt transaction
const startTime = new Date()
await dltTransactionPromise
const endTime = new Date()
logger.info(`dlt-connector register address finished in ${endTime.getTime() - startTime.getTime()} ms`)
return new User(dbUser)
}

View File

@ -12,3 +12,4 @@ export const MEMO_MAX_CHARS = 512
export const MEMO_MIN_CHARS = 5
export const DEFAULT_PAGINATION_PAGE_SIZE = 25
export const FRONTEND_CONTRIBUTIONS_ITEM_ANCHOR_PREFIX = 'contributionListItem-'
export const CODE_VALID_DAYS_DURATION = 14

View File

@ -21,9 +21,14 @@ import {
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { CONFIG } from '@/config'
import { TRANSACTIONS_LOCK } from 'database'
jest.mock('@/password/EncryptorUtils')
CONFIG.DLT_CONNECTOR = false
CONFIG.EMAIL = false
let mutate: ApolloServerTestClient['mutate']
let con: DataSource
let testEnv: {
@ -44,7 +49,43 @@ afterAll(async () => {
await con.destroy()
})
type RunOrder = { [key: number]: { start: number, end: number } }
async function fakeWork(runOrder: RunOrder, index: number) {
const releaseLock = await TRANSACTIONS_LOCK.acquire()
const startDate = new Date()
await new Promise((resolve) => setTimeout(resolve, Math.random() * 50))
const endDate = new Date()
runOrder[index] = { start: startDate.getTime(), end: endDate.getTime() }
releaseLock()
}
describe('semaphore', () => {
it("didn't should run in parallel", async () => {
const runOrder: RunOrder = {}
await Promise.all([
fakeWork(runOrder, 1),
fakeWork(runOrder, 2),
fakeWork(runOrder, 3),
fakeWork(runOrder, 4),
fakeWork(runOrder, 5),
])
expect(runOrder[1].start).toBeLessThan(runOrder[1].end)
expect(runOrder[1].start).toBeLessThan(runOrder[2].start)
expect(runOrder[2].start).toBeLessThan(runOrder[2].end)
expect(runOrder[2].start).toBeLessThan(runOrder[3].start)
expect(runOrder[3].start).toBeLessThan(runOrder[3].end)
expect(runOrder[3].start).toBeLessThan(runOrder[4].start)
expect(runOrder[4].start).toBeLessThan(runOrder[4].end)
expect(runOrder[4].start).toBeLessThan(runOrder[5].start)
expect(runOrder[5].start).toBeLessThan(runOrder[5].end)
expect(runOrder[1].end).toBeLessThan(runOrder[2].end)
expect(runOrder[2].end).toBeLessThan(runOrder[3].end)
expect(runOrder[3].end).toBeLessThan(runOrder[4].end)
expect(runOrder[4].end).toBeLessThan(runOrder[5].end)
})
})
describe('semaphore fullstack', () => {
let contributionLinkCode = ''
let bobsTransactionLinkCode = ''
let bibisTransactionLinkCode = ''

View File

@ -1,799 +0,0 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Community, DltTransaction, Transaction } from 'database'
import { Decimal } from 'decimal.js-light'
// import { GraphQLClient } from 'graphql-request'
// import { Response } from 'graphql-request/dist/types'
import { GraphQLClient } from 'graphql-request'
import { Response } from 'graphql-request/dist/types'
import { DataSource } from 'typeorm'
import { v4 as uuidv4 } from 'uuid'
import { cleanDB, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { CONFIG } from '@/config'
import { TransactionTypeId } from 'core'
import { creations } from '@/seeds/creation'
import { creationFactory } from '@/seeds/factory/creation'
import { userFactory } from '@/seeds/factory/user'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz'
import { getLogger } from 'config-schema/test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { sendTransactionsToDltConnector } from './sendTransactionsToDltConnector'
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(
`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.sendTransactionsToDltConnector`,
)
/*
// Mock the GraphQLClient
jest.mock('graphql-request', () => {
const originalModule = jest.requireActual('graphql-request')
let testCursor = 0
return {
__esModule: true,
...originalModule,
GraphQLClient: jest.fn().mockImplementation((url: string) => {
if (url === 'invalid') {
throw new Error('invalid url')
}
return {
// why not using mockResolvedValueOnce or mockReturnValueOnce?
// I have tried, but it didn't work and return every time the first value
request: jest.fn().mockImplementation(() => {
testCursor++
if (testCursor === 4) {
return Promise.resolve(
// invalid, is 33 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516212A',
},
},
)
} else if (testCursor === 5) {
throw Error('Connection error')
} else {
return Promise.resolve(
// valid, is 32 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
},
},
)
}
}),
}
}),
}
})
let mutate: ApolloServerTestClient['mutate'],
query: ApolloServerTestClient['query'],
con: Connection
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
}
*/
async function createHomeCommunity(): Promise<Community> {
const homeCommunity = Community.create()
homeCommunity.foreign = false
homeCommunity.communityUuid = uuidv4()
homeCommunity.url = 'localhost'
homeCommunity.publicKey = Buffer.from('0x6e6a6c6d6feffe', 'hex')
await Community.save(homeCommunity)
return homeCommunity
}
async function createTxCREATION1(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(1000)
tx.balance = new Decimal(100)
tx.balanceDate = new Date('01.01.2023 00:00:00')
tx.memo = 'txCREATION1'
tx.typeId = TransactionTypeId.CREATION
tx.userGradidoID = 'txCREATION1.userGradidoID'
tx.userId = 1
tx.userName = 'txCREATION 1'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('01.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c1'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('01.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxCREATION2(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(1000)
tx.balance = new Decimal(200)
tx.balanceDate = new Date('02.01.2023 00:00:00')
tx.memo = 'txCREATION2'
tx.typeId = TransactionTypeId.CREATION
tx.userGradidoID = 'txCREATION2.userGradidoID'
tx.userId = 2
tx.userName = 'txCREATION 2'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('02.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c2'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('02.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxCREATION3(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(1000)
tx.balance = new Decimal(300)
tx.balanceDate = new Date('03.01.2023 00:00:00')
tx.memo = 'txCREATION3'
tx.typeId = TransactionTypeId.CREATION
tx.userGradidoID = 'txCREATION3.userGradidoID'
tx.userId = 3
tx.userName = 'txCREATION 3'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('03.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c3'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('03.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxSend1ToReceive2(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(100)
tx.balance = new Decimal(1000)
tx.balanceDate = new Date('11.01.2023 00:00:00')
tx.memo = 'txSEND1 to txRECEIVE2'
tx.typeId = TransactionTypeId.SEND
tx.userGradidoID = 'txSEND1.userGradidoID'
tx.userId = 1
tx.userName = 'txSEND 1'
tx.linkedUserGradidoID = 'txRECEIVE2.linkedUserGradidoID'
tx.linkedUserId = 2
tx.linkedUserName = 'txRECEIVE 2'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('11.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a1'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('11.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxReceive2FromSend1(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(100)
tx.balance = new Decimal(1300)
tx.balanceDate = new Date('11.01.2023 00:00:00')
tx.memo = 'txSEND1 to txRECEIVE2'
tx.typeId = TransactionTypeId.RECEIVE
tx.userGradidoID = 'txRECEIVE2.linkedUserGradidoID'
tx.userId = 2
tx.userName = 'txRECEIVE 2'
tx.linkedUserGradidoID = 'txSEND1.userGradidoID'
tx.linkedUserId = 1
tx.linkedUserName = 'txSEND 1'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('11.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b2'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('11.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
/*
async function createTxSend2ToReceive3(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(200)
tx.balance = new Decimal(1100)
tx.balanceDate = new Date('23.01.2023 00:00:00')
tx.memo = 'txSEND2 to txRECEIVE3'
tx.typeId = TransactionTypeId.SEND
tx.userGradidoID = 'txSEND2.userGradidoID'
tx.userId = 2
tx.userName = 'txSEND 2'
tx.linkedUserGradidoID = 'txRECEIVE3.linkedUserGradidoID'
tx.linkedUserId = 3
tx.linkedUserName = 'txRECEIVE 3'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('23.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a2'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('23.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxReceive3FromSend2(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(200)
tx.balance = new Decimal(1500)
tx.balanceDate = new Date('23.01.2023 00:00:00')
tx.memo = 'txSEND2 to txRECEIVE3'
tx.typeId = TransactionTypeId.RECEIVE
tx.userGradidoID = 'txRECEIVE3.linkedUserGradidoID'
tx.userId = 3
tx.userName = 'txRECEIVE 3'
tx.linkedUserGradidoID = 'txSEND2.userGradidoID'
tx.linkedUserId = 2
tx.linkedUserName = 'txSEND 2'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('23.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b3'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('23.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxSend3ToReceive1(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(300)
tx.balance = new Decimal(1200)
tx.balanceDate = new Date('31.01.2023 00:00:00')
tx.memo = 'txSEND3 to txRECEIVE1'
tx.typeId = TransactionTypeId.SEND
tx.userGradidoID = 'txSEND3.userGradidoID'
tx.userId = 3
tx.userName = 'txSEND 3'
tx.linkedUserGradidoID = 'txRECEIVE1.linkedUserGradidoID'
tx.linkedUserId = 1
tx.linkedUserName = 'txRECEIVE 1'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('31.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516a3'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('31.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
async function createTxReceive1FromSend3(verified: boolean): Promise<Transaction> {
let tx = Transaction.create()
tx.amount = new Decimal(300)
tx.balance = new Decimal(1300)
tx.balanceDate = new Date('31.01.2023 00:00:00')
tx.memo = 'txSEND3 to txRECEIVE1'
tx.typeId = TransactionTypeId.RECEIVE
tx.userGradidoID = 'txRECEIVE1.linkedUserGradidoID'
tx.userId = 1
tx.userName = 'txRECEIVE 1'
tx.linkedUserGradidoID = 'txSEND3.userGradidoID'
tx.linkedUserId = 3
tx.linkedUserName = 'txSEND 3'
tx = await Transaction.save(tx)
if (verified) {
const dlttx = DltTransaction.create()
dlttx.createdAt = new Date('31.01.2023 00:00:10')
dlttx.messageId = '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516b1'
dlttx.transactionId = tx.id
dlttx.verified = true
dlttx.verifiedAt = new Date('31.01.2023 00:01:10')
await DltTransaction.save(dlttx)
}
return tx
}
*/
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: DataSource
}
beforeAll(async () => {
testEnv = await testEnvironment(logger, localization)
con = testEnv.con
await cleanDB()
})
afterAll(async () => {
await cleanDB()
await con.destroy()
})
describe('create and send Transactions to DltConnector', () => {
let txCREATION1: Transaction
let txCREATION2: Transaction
let txCREATION3: Transaction
let txSEND1to2: Transaction
let txRECEIVE2From1: Transaction
// let txSEND2To3: Transaction
// let txRECEIVE3From2: Transaction
// let txSEND3To1: Transaction
// let txRECEIVE1From3: Transaction
beforeEach(() => {
jest.clearAllMocks()
})
afterEach(async () => {
await cleanDB()
})
describe('with 3 creations but inactive dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
txCREATION1 = await createTxCREATION1(false)
txCREATION2 = await createTxCREATION2(false)
txCREATION3 = await createTxCREATION3(false)
await createHomeCommunity()
CONFIG.DLT_CONNECTOR = false
await sendTransactionsToDltConnector()
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
// Find the previous created transactions of sendCoin mutation
const transactions = await Transaction.find({
// where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
const dltTransactions = await DltTransaction.find({
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[0].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[1].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[2].id,
messageId: null,
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
expect(logger.info).nthCalledWith(2, 'sending to DltConnector currently not configured...')
})
})
describe('with 3 creations and active dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
await userFactory(testEnv, bibiBloxberg)
await userFactory(testEnv, peterLustig)
await userFactory(testEnv, raeuberHotzenplotz)
await userFactory(testEnv, bobBaumeister)
let count = 0
for (const creation of creations) {
await creationFactory(testEnv, creation)
count++
// we need only 3 for testing
if (count >= 3) {
break
}
}
await createHomeCommunity()
CONFIG.DLT_CONNECTOR = true
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
sendTransaction: { succeed: true },
},
} as Response<unknown>
})
await sendTransactionsToDltConnector()
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
// Find the previous created transactions of sendCoin mutation
const transactions = await Transaction.find({
// where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
const dltTransactions = await DltTransaction.find({
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[0].id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[1].id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[2].id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
})
})
describe('with 3 verified creations, 1 sendCoins and active dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
txCREATION1 = await createTxCREATION1(true)
txCREATION2 = await createTxCREATION2(true)
txCREATION3 = await createTxCREATION3(true)
await createHomeCommunity()
txSEND1to2 = await createTxSend1ToReceive2(false)
txRECEIVE2From1 = await createTxReceive2FromSend1(false)
/*
txSEND2To3 = await createTxSend2ToReceive3()
txRECEIVE3From2 = await createTxReceive3FromSend2()
txSEND3To1 = await createTxSend3ToReceive1()
txRECEIVE1From3 = await createTxReceive1FromSend3()
*/
CONFIG.DLT_CONNECTOR = true
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
sendTransaction: { succeed: true },
},
} as Response<unknown>
})
await sendTransactionsToDltConnector()
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
// Find the previous created transactions of sendCoin mutation
/*
const transactions = await Transaction.find({
// where: { memo: 'unrepeatable memo' },
order: { balanceDate: 'ASC', id: 'ASC' },
})
*/
const dltTransactions = await DltTransaction.find({
// where: { transactionId: In([transaction[0].id, transaction[1].id]) },
// relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
transactionId: txCREATION1.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c1',
verified: true,
createdAt: new Date('01.01.2023 00:00:10'),
verifiedAt: new Date('01.01.2023 00:01:10'),
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txCREATION2.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c2',
verified: true,
createdAt: new Date('02.01.2023 00:00:10'),
verifiedAt: new Date('02.01.2023 00:01:10'),
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txCREATION3.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516c3',
verified: true,
createdAt: new Date('03.01.2023 00:00:10'),
verifiedAt: new Date('03.01.2023 00:01:10'),
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txSEND1to2.id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
expect.objectContaining({
id: expect.any(Number),
transactionId: txRECEIVE2From1.id,
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
}),
]),
)
})
/*
describe('with one Community of api 1_0 and not matching pubKey', () => {
beforeEach(async () => {
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
getPublicKey: {
publicKey: 'somePubKey',
},
},
} as Response<unknown>
})
const variables1 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '1_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables1)
.orUpdate({
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
jest.clearAllMocks()
// await validateCommunities()
})
it('logs one community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs not matching publicKeys', () => {
expect(logger.warn).toBeCalledWith(
'Federation: received not matching publicKey:',
'somePubKey',
expect.stringMatching('11111111111111111111111111111111'),
)
})
})
describe('with one Community of api 1_0 and matching pubKey', () => {
beforeEach(async () => {
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
getPublicKey: {
publicKey: '11111111111111111111111111111111',
},
},
} as Response<unknown>
})
const variables1 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '1_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables1)
.orUpdate({
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
await DbFederatedCommunity.update({}, { verifiedAt: null })
jest.clearAllMocks()
// await validateCommunities()
})
it('logs one community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs community pubKey verified', () => {
expect(logger.info).toHaveBeenNthCalledWith(
3,
'Federation: verified community with',
'http//localhost:5001/api/',
)
})
})
describe('with two Communities of api 1_0 and 1_1', () => {
beforeEach(async () => {
jest.clearAllMocks()
jest.spyOn(GraphQLClient.prototype, 'rawRequest').mockImplementation(async () => {
return {
data: {
getPublicKey: {
publicKey: '11111111111111111111111111111111',
},
},
} as Response<unknown>
})
const variables2 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '1_1',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables2)
.orUpdate({
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
await DbFederatedCommunity.update({}, { verifiedAt: null })
jest.clearAllMocks()
// await validateCommunities()
})
it('logs two communities found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 2 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs requestGetPublicKey for community api 1_1 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_1/',
)
})
})
describe('with three Communities of api 1_0, 1_1 and 2_0', () => {
let dbCom: DbFederatedCommunity
beforeEach(async () => {
const variables3 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
apiVersion: '2_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
}
await DbFederatedCommunity.createQueryBuilder()
.insert()
.into(DbFederatedCommunity)
.values(variables3)
.orUpdate({
conflict_target: ['id', 'publicKey', 'apiVersion'],
overwrite: ['end_point', 'last_announced_at'],
})
.execute()
dbCom = await DbFederatedCommunity.findOneOrFail({
where: { publicKey: variables3.publicKey, apiVersion: variables3.apiVersion },
})
await DbFederatedCommunity.update({}, { verifiedAt: null })
jest.clearAllMocks()
// await validateCommunities()
})
it('logs three community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 3 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs requestGetPublicKey for community api 1_1 ', () => {
expect(logger.info).toBeCalledWith(
'Federation: getPublicKey from endpoint',
'http//localhost:5001/api/1_1/',
)
})
it('logs unsupported api for community with api 2_0 ', () => {
expect(logger.warn).toBeCalledWith(
'Federation: dbCom with unsupported apiVersion',
dbCom.endPoint,
'2_0',
)
})
})
*/
})
})

View File

@ -1,85 +0,0 @@
import { DltTransaction, Transaction } from 'database'
import { IsNull } from 'typeorm'
import { DltConnectorClient } from '@dltConnector/DltConnectorClient'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { Monitor, MonitorNames } from '@/util/Monitor'
import { getLogger } from 'log4js'
const logger = getLogger(
`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.sendTransactionsToDltConnector`,
)
export async function sendTransactionsToDltConnector(): Promise<void> {
logger.info('sendTransactionsToDltConnector...')
// check if this logic is still occupied, no concurrecy allowed
if (!Monitor.isLocked(MonitorNames.SEND_DLT_TRANSACTIONS)) {
// mark this block for occuption to prevent concurrency
Monitor.lockIt(MonitorNames.SEND_DLT_TRANSACTIONS)
try {
await createDltTransactions()
const dltConnector = DltConnectorClient.getInstance()
if (dltConnector) {
logger.debug('with sending to DltConnector...')
const dltTransactions = await DltTransaction.find({
where: { messageId: IsNull() },
relations: ['transaction'],
order: { createdAt: 'ASC', id: 'ASC' },
})
for (const dltTx of dltTransactions) {
if (!dltTx.transaction) {
continue
}
try {
const result = await dltConnector.transmitTransaction(dltTx.transaction)
// message id isn't known at this point of time, because transaction will not direct sended to iota,
// it will first go to db and then sended, if no transaction is in db before
if (result) {
dltTx.messageId = 'sended'
await DltTransaction.save(dltTx)
logger.info(`store messageId=${dltTx.messageId} in dltTx=${dltTx.id}`)
}
} catch (e) {
logger.error(
`error while sending to dlt-connector or writing messageId of dltTx=${dltTx.id}`,
e,
)
}
}
} else {
logger.info('sending to DltConnector currently not configured...')
}
} catch (e) {
logger.error('error on sending transactions to dlt-connector.', e)
} finally {
// releae Monitor occupation
Monitor.releaseIt(MonitorNames.SEND_DLT_TRANSACTIONS)
}
} else {
logger.info('sendTransactionsToDltConnector currently locked by monitor...')
}
}
async function createDltTransactions(): Promise<void> {
const dltqb = DltTransaction.createQueryBuilder().select('transactions_id')
const newTransactions: Transaction[] = await Transaction.createQueryBuilder()
.select('id')
.addSelect('balance_date')
.where('id NOT IN (' + dltqb.getSql() + ')')
.orderBy({ balance_date: 'ASC', id: 'ASC' })
.getRawMany()
const dltTxArray: DltTransaction[] = []
let idx = 0
while (newTransactions.length > dltTxArray.length) {
// timing problems with for(let idx = 0; idx < newTransactions.length; idx++) {
const dltTx = DltTransaction.create()
dltTx.transactionId = newTransactions[idx++].id
await DltTransaction.save(dltTx)
dltTxArray.push(dltTx)
}
}

View File

@ -0,0 +1,23 @@
import { ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator'
export function isValidHieroId(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'isValidHieroId',
target: object.constructor,
propertyName,
options: validationOptions,
validator: {
validate(value: string) {
if (value.match(/[0-9]*\.[0-9]*\.[0-9]*/)) {
return true
}
return false
},
defaultMessage(args: ValidationArguments) {
return `${propertyName} must be a valid HieroId (0.0.2121), ${args.property}`
},
},
})
}
}

352
bun.lock
View File

@ -18,7 +18,7 @@
},
"admin": {
"name": "admin",
"version": "2.6.1",
"version": "2.7.0",
"dependencies": {
"@iconify/json": "^2.2.228",
"@popperjs/core": "^2.11.8",
@ -88,7 +88,7 @@
},
"backend": {
"name": "backend",
"version": "2.6.1",
"version": "2.7.0",
"dependencies": {
"cross-env": "^7.0.3",
"email-templates": "^10.0.1",
@ -166,7 +166,7 @@
},
"config-schema": {
"name": "config-schema",
"version": "2.6.1",
"version": "2.7.0",
"dependencies": {
"esbuild": "^0.25.2",
"joi": "17.13.3",
@ -184,7 +184,7 @@
},
"core": {
"name": "core",
"version": "2.6.1",
"version": "2.7.0",
"dependencies": {
"database": "*",
"esbuild": "^0.25.2",
@ -213,7 +213,7 @@
},
"database": {
"name": "database",
"version": "2.6.1",
"version": "2.7.0",
"dependencies": {
"@types/uuid": "^8.3.4",
"cross-env": "^7.0.3",
@ -255,7 +255,7 @@
},
"dht-node": {
"name": "dht-node",
"version": "2.6.1",
"version": "2.7.0",
"dependencies": {
"cross-env": "^7.0.3",
"dht-rpc": "6.18.1",
@ -292,7 +292,7 @@
},
"federation": {
"name": "federation",
"version": "2.6.1",
"version": "2.7.0",
"dependencies": {
"cross-env": "^7.0.3",
"sodium-native": "^3.4.1",
@ -348,7 +348,7 @@
},
"frontend": {
"name": "frontend",
"version": "2.6.1",
"version": "2.7.0",
"dependencies": {
"@morev/vue-transitions": "^3.0.2",
"@types/leaflet": "^1.9.12",
@ -444,7 +444,7 @@
},
"shared": {
"name": "shared",
"version": "2.6.1",
"version": "2.7.0",
"dependencies": {
"decimal.js-light": "^2.5.1",
"esbuild": "^0.25.2",
@ -492,51 +492,51 @@
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
"@aws-sdk/client-ses": ["@aws-sdk/client-ses@3.908.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.908.0", "@aws-sdk/credential-provider-node": "3.908.0", "@aws-sdk/middleware-host-header": "3.901.0", "@aws-sdk/middleware-logger": "3.901.0", "@aws-sdk/middleware-recursion-detection": "3.901.0", "@aws-sdk/middleware-user-agent": "3.908.0", "@aws-sdk/region-config-resolver": "3.901.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-endpoints": "3.901.0", "@aws-sdk/util-user-agent-browser": "3.907.0", "@aws-sdk/util-user-agent-node": "3.908.0", "@smithy/config-resolver": "^4.3.0", "@smithy/core": "^3.15.0", "@smithy/fetch-http-handler": "^5.3.1", "@smithy/hash-node": "^4.2.0", "@smithy/invalid-dependency": "^4.2.0", "@smithy/middleware-content-length": "^4.2.0", "@smithy/middleware-endpoint": "^4.3.1", "@smithy/middleware-retry": "^4.4.1", "@smithy/middleware-serde": "^4.2.0", "@smithy/middleware-stack": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/node-http-handler": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.0", "@smithy/util-defaults-mode-node": "^4.2.1", "@smithy/util-endpoints": "^3.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-e4bglczZvoDLmMeAgr0lou5QKCIOGfdCnfXp9jkEhkL6JLKNYvkmrRZpCrQiBwm/4j4H88oRPgLYk+sGQKFPlw=="],
"@aws-sdk/client-ses": ["@aws-sdk/client-ses@3.913.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.911.0", "@aws-sdk/credential-provider-node": "3.913.0", "@aws-sdk/middleware-host-header": "3.910.0", "@aws-sdk/middleware-logger": "3.910.0", "@aws-sdk/middleware-recursion-detection": "3.910.0", "@aws-sdk/middleware-user-agent": "3.911.0", "@aws-sdk/region-config-resolver": "3.910.0", "@aws-sdk/types": "3.910.0", "@aws-sdk/util-endpoints": "3.910.0", "@aws-sdk/util-user-agent-browser": "3.910.0", "@aws-sdk/util-user-agent-node": "3.911.0", "@smithy/config-resolver": "^4.3.2", "@smithy/core": "^3.16.1", "@smithy/fetch-http-handler": "^5.3.3", "@smithy/hash-node": "^4.2.2", "@smithy/invalid-dependency": "^4.2.2", "@smithy/middleware-content-length": "^4.2.2", "@smithy/middleware-endpoint": "^4.3.3", "@smithy/middleware-retry": "^4.4.3", "@smithy/middleware-serde": "^4.2.2", "@smithy/middleware-stack": "^4.2.2", "@smithy/node-config-provider": "^4.3.2", "@smithy/node-http-handler": "^4.4.1", "@smithy/protocol-http": "^5.3.2", "@smithy/smithy-client": "^4.8.1", "@smithy/types": "^4.7.1", "@smithy/url-parser": "^4.2.2", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.2", "@smithy/util-defaults-mode-node": "^4.2.3", "@smithy/util-endpoints": "^3.2.2", "@smithy/util-middleware": "^4.2.2", "@smithy/util-retry": "^4.2.2", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-jUF1mN+webeAgkNXS/tl6KpJyUbsAWxQGsQgsWoHwaNCSnxMDBEyPmgBnzbqf2CrybIa7zmzaqCO0z6FgKeZRg=="],
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.908.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.908.0", "@aws-sdk/middleware-host-header": "3.901.0", "@aws-sdk/middleware-logger": "3.901.0", "@aws-sdk/middleware-recursion-detection": "3.901.0", "@aws-sdk/middleware-user-agent": "3.908.0", "@aws-sdk/region-config-resolver": "3.901.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-endpoints": "3.901.0", "@aws-sdk/util-user-agent-browser": "3.907.0", "@aws-sdk/util-user-agent-node": "3.908.0", "@smithy/config-resolver": "^4.3.0", "@smithy/core": "^3.15.0", "@smithy/fetch-http-handler": "^5.3.1", "@smithy/hash-node": "^4.2.0", "@smithy/invalid-dependency": "^4.2.0", "@smithy/middleware-content-length": "^4.2.0", "@smithy/middleware-endpoint": "^4.3.1", "@smithy/middleware-retry": "^4.4.1", "@smithy/middleware-serde": "^4.2.0", "@smithy/middleware-stack": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/node-http-handler": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.0", "@smithy/util-defaults-mode-node": "^4.2.1", "@smithy/util-endpoints": "^3.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-PseFMWvtac+Q+zaY9DMISE+2+glNh0ROJ1yR4gMzeafNHSwkdYu4qcgxLWIOnIodGydBv/tQ6nzHPzExXnUUgw=="],
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.911.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.911.0", "@aws-sdk/middleware-host-header": "3.910.0", "@aws-sdk/middleware-logger": "3.910.0", "@aws-sdk/middleware-recursion-detection": "3.910.0", "@aws-sdk/middleware-user-agent": "3.911.0", "@aws-sdk/region-config-resolver": "3.910.0", "@aws-sdk/types": "3.910.0", "@aws-sdk/util-endpoints": "3.910.0", "@aws-sdk/util-user-agent-browser": "3.910.0", "@aws-sdk/util-user-agent-node": "3.911.0", "@smithy/config-resolver": "^4.3.2", "@smithy/core": "^3.16.1", "@smithy/fetch-http-handler": "^5.3.3", "@smithy/hash-node": "^4.2.2", "@smithy/invalid-dependency": "^4.2.2", "@smithy/middleware-content-length": "^4.2.2", "@smithy/middleware-endpoint": "^4.3.3", "@smithy/middleware-retry": "^4.4.3", "@smithy/middleware-serde": "^4.2.2", "@smithy/middleware-stack": "^4.2.2", "@smithy/node-config-provider": "^4.3.2", "@smithy/node-http-handler": "^4.4.1", "@smithy/protocol-http": "^5.3.2", "@smithy/smithy-client": "^4.8.1", "@smithy/types": "^4.7.1", "@smithy/url-parser": "^4.2.2", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.2", "@smithy/util-defaults-mode-node": "^4.2.3", "@smithy/util-endpoints": "^3.2.2", "@smithy/util-middleware": "^4.2.2", "@smithy/util-retry": "^4.2.2", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-N9QAeMvN3D1ZyKXkQp4aUgC4wUMuA5E1HuVCkajc0bq1pnH4PIke36YlrDGGREqPlyLFrXCkws2gbL5p23vtlg=="],
"@aws-sdk/core": ["@aws-sdk/core@3.908.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@aws-sdk/xml-builder": "3.901.0", "@smithy/core": "^3.15.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/signature-v4": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-okl6FC2cQT1Oidvmnmvyp/IEvqENBagKO0ww4YV5UtBkf0VlhAymCWkZqhovtklsqgq0otag2VRPAgnrMt6nVQ=="],
"@aws-sdk/core": ["@aws-sdk/core@3.911.0", "", { "dependencies": { "@aws-sdk/types": "3.910.0", "@aws-sdk/xml-builder": "3.911.0", "@smithy/core": "^3.16.1", "@smithy/node-config-provider": "^4.3.2", "@smithy/property-provider": "^4.2.2", "@smithy/protocol-http": "^5.3.2", "@smithy/signature-v4": "^5.3.2", "@smithy/smithy-client": "^4.8.1", "@smithy/types": "^4.7.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.2", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-k4QG9A+UCq/qlDJFmjozo6R0eXXfe++/KnCDMmajehIE9kh+b/5DqlGvAmbl9w4e92LOtrY6/DN3mIX1xs4sXw=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-FK2YuxoI5CxUflPOIMbVAwDbi6Xvu+2sXopXLmrHc2PfI39M3vmjEoQwYCP8WuQSRb+TbAP3xAkxHjFSBFR35w=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.911.0", "", { "dependencies": { "@aws-sdk/core": "3.911.0", "@aws-sdk/types": "3.910.0", "@smithy/property-provider": "^4.2.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-6FWRwWn3LUZzLhqBXB+TPMW2ijCWUqGICSw8bVakEdODrvbiv1RT/MVUayzFwz/ek6e6NKZn6DbSWzx07N9Hjw=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/fetch-http-handler": "^5.3.1", "@smithy/node-http-handler": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/util-stream": "^4.5.0", "tslib": "^2.6.2" } }, "sha512-eLbz0geVW9EykujQNnYfR35Of8MreI6pau5K6XDFDUSWO9GF8wqH7CQwbXpXHBlCTHtq4QSLxzorD8U5CROhUw=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.911.0", "", { "dependencies": { "@aws-sdk/core": "3.911.0", "@aws-sdk/types": "3.910.0", "@smithy/fetch-http-handler": "^5.3.3", "@smithy/node-http-handler": "^4.4.1", "@smithy/property-provider": "^4.2.2", "@smithy/protocol-http": "^5.3.2", "@smithy/smithy-client": "^4.8.1", "@smithy/types": "^4.7.1", "@smithy/util-stream": "^4.5.2", "tslib": "^2.6.2" } }, "sha512-xUlwKmIUW2fWP/eM3nF5u4CyLtOtyohlhGJ5jdsJokr3MrQ7w0tDITO43C9IhCn+28D5UbaiWnKw5ntkw7aVfA=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/credential-provider-env": "3.908.0", "@aws-sdk/credential-provider-http": "3.908.0", "@aws-sdk/credential-provider-process": "3.908.0", "@aws-sdk/credential-provider-sso": "3.908.0", "@aws-sdk/credential-provider-web-identity": "3.908.0", "@aws-sdk/nested-clients": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/credential-provider-imds": "^4.2.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-7Cgnv5wabgFtsgr+Uc/76EfPNGyxmbG8aICn3g3D3iJlcO4uuOZI8a77i0afoDdchZrTC6TG6UusS/NAW6zEoQ=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.913.0", "", { "dependencies": { "@aws-sdk/core": "3.911.0", "@aws-sdk/credential-provider-env": "3.911.0", "@aws-sdk/credential-provider-http": "3.911.0", "@aws-sdk/credential-provider-process": "3.911.0", "@aws-sdk/credential-provider-sso": "3.911.0", "@aws-sdk/credential-provider-web-identity": "3.911.0", "@aws-sdk/nested-clients": "3.911.0", "@aws-sdk/types": "3.910.0", "@smithy/credential-provider-imds": "^4.2.2", "@smithy/property-provider": "^4.2.2", "@smithy/shared-ini-file-loader": "^4.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-iR4c4NQ1OSRKQi0SxzpwD+wP1fCy+QNKtEyCajuVlD0pvmoIHdrm5THK9e+2/7/SsQDRhOXHJfLGxHapD74WJw=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.908.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.908.0", "@aws-sdk/credential-provider-http": "3.908.0", "@aws-sdk/credential-provider-ini": "3.908.0", "@aws-sdk/credential-provider-process": "3.908.0", "@aws-sdk/credential-provider-sso": "3.908.0", "@aws-sdk/credential-provider-web-identity": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/credential-provider-imds": "^4.2.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-8OKbykpGw5bdfF/pLTf8YfUi1Kl8o1CTjBqWQTsLOkE3Ho3hsp1eQx8Cz4ttrpv0919kb+lox62DgmAOEmTr1w=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.913.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.911.0", "@aws-sdk/credential-provider-http": "3.911.0", "@aws-sdk/credential-provider-ini": "3.913.0", "@aws-sdk/credential-provider-process": "3.911.0", "@aws-sdk/credential-provider-sso": "3.911.0", "@aws-sdk/credential-provider-web-identity": "3.911.0", "@aws-sdk/types": "3.910.0", "@smithy/credential-provider-imds": "^4.2.2", "@smithy/property-provider": "^4.2.2", "@smithy/shared-ini-file-loader": "^4.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-HQPLkKDxS83Q/nZKqg9bq4igWzYQeOMqhpx5LYs4u1GwsKeCsYrrfz12Iu4IHNWPp9EnGLcmdfbfYuqZGrsaSQ=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-sWnbkGjDPBi6sODUzrAh5BCDpnPw0wpK8UC/hWI13Q8KGfyatAmCBfr+9OeO3+xBHa8N5AskMncr7C4qS846yQ=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.911.0", "", { "dependencies": { "@aws-sdk/core": "3.911.0", "@aws-sdk/types": "3.910.0", "@smithy/property-provider": "^4.2.2", "@smithy/shared-ini-file-loader": "^4.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-mKshhV5jRQffZjbK9x7bs+uC2IsYKfpzYaBamFsEov3xtARCpOiKaIlM8gYKFEbHT2M+1R3rYYlhhl9ndVWS2g=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.908.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.908.0", "@aws-sdk/core": "3.908.0", "@aws-sdk/token-providers": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-WV/aOzuS6ZZhrkPty6TJ3ZG24iS8NXP0m3GuTVuZ5tKi9Guss31/PJ1CrKPRCYGm15CsIjf+mrUxVnNYv9ap5g=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.911.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.911.0", "@aws-sdk/core": "3.911.0", "@aws-sdk/token-providers": "3.911.0", "@aws-sdk/types": "3.910.0", "@smithy/property-provider": "^4.2.2", "@smithy/shared-ini-file-loader": "^4.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-JAxd4uWe0Zc9tk6+N0cVxe9XtJVcOx6Ms0k933ZU9QbuRMH6xti/wnZxp/IvGIWIDzf5fhqiGyw5MSyDeI5b1w=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/nested-clients": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-9xWrFn6nWlF5KlV4XYW+7E6F33S3wUUEGRZ/+pgDhkIZd527ycT2nPG2dZ3fWUZMlRmzijP20QIJDqEbbGWe1Q=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.911.0", "", { "dependencies": { "@aws-sdk/core": "3.911.0", "@aws-sdk/nested-clients": "3.911.0", "@aws-sdk/types": "3.910.0", "@smithy/property-provider": "^4.2.2", "@smithy/shared-ini-file-loader": "^4.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-urIbXWWG+cm54RwwTFQuRwPH0WPsMFSDF2/H9qO2J2fKoHRURuyblFCyYG3aVKZGvFBhOizJYexf5+5w3CJKBw=="],
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-yWX7GvRmqBtbNnUW7qbre3GvZmyYwU0WHefpZzDTYDoNgatuYq6LgUIQ+z5C04/kCRoFkAFrHag8a3BXqFzq5A=="],
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.910.0", "", { "dependencies": { "@aws-sdk/types": "3.910.0", "@smithy/protocol-http": "^5.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-F9Lqeu80/aTM6S/izZ8RtwSmjfhWjIuxX61LX+/9mxJyEkgaECRxv0chsLQsLHJumkGnXRy/eIyMLBhcTPF5vg=="],
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-UoHebjE7el/tfRo8/CQTj91oNUm+5Heus5/a4ECdmWaSCHCS/hXTsU3PTTHAY67oAQR8wBLFPfp3mMvXjB+L2A=="],
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.910.0", "", { "dependencies": { "@aws-sdk/types": "3.910.0", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-3LJyyfs1USvRuRDla1pGlzGRtXJBXD1zC9F+eE9Iz/V5nkmhyv52A017CvKWmYoR0DM9dzjLyPOI0BSSppEaTw=="],
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@aws/lambda-invoke-store": "^0.0.1", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-Wd2t8qa/4OL0v/oDpCHHYkgsXJr8/ttCxrvCKAt0H1zZe2LlRhY9gpDVKqdertfHrHDj786fOvEQA28G1L75Dg=="],
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.910.0", "", { "dependencies": { "@aws-sdk/types": "3.910.0", "@aws/lambda-invoke-store": "^0.0.1", "@smithy/protocol-http": "^5.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-m/oLz0EoCy+WoIVBnXRXJ4AtGpdl0kPE7U+VH9TsuUzHgxY1Re/176Q1HWLBRVlz4gr++lNsgsMWEC+VnAwMpw=="],
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-endpoints": "3.901.0", "@smithy/core": "^3.15.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-R0ePEOku72EvyJWy/D0Z5f/Ifpfxa0U9gySO3stpNhOox87XhsILpcIsCHPy0OHz1a7cMoZsF6rMKSzDeCnogQ=="],
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.911.0", "", { "dependencies": { "@aws-sdk/core": "3.911.0", "@aws-sdk/types": "3.910.0", "@aws-sdk/util-endpoints": "3.910.0", "@smithy/core": "^3.16.1", "@smithy/protocol-http": "^5.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-rY3LvGvgY/UI0nmt5f4DRzjEh8135A2TeHcva1bgOmVfOI4vkkGfA20sNRqerOkSO6hPbkxJapO50UJHFzmmyA=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.908.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.908.0", "@aws-sdk/middleware-host-header": "3.901.0", "@aws-sdk/middleware-logger": "3.901.0", "@aws-sdk/middleware-recursion-detection": "3.901.0", "@aws-sdk/middleware-user-agent": "3.908.0", "@aws-sdk/region-config-resolver": "3.901.0", "@aws-sdk/types": "3.901.0", "@aws-sdk/util-endpoints": "3.901.0", "@aws-sdk/util-user-agent-browser": "3.907.0", "@aws-sdk/util-user-agent-node": "3.908.0", "@smithy/config-resolver": "^4.3.0", "@smithy/core": "^3.15.0", "@smithy/fetch-http-handler": "^5.3.1", "@smithy/hash-node": "^4.2.0", "@smithy/invalid-dependency": "^4.2.0", "@smithy/middleware-content-length": "^4.2.0", "@smithy/middleware-endpoint": "^4.3.1", "@smithy/middleware-retry": "^4.4.1", "@smithy/middleware-serde": "^4.2.0", "@smithy/middleware-stack": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/node-http-handler": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.0", "@smithy/util-defaults-mode-node": "^4.2.1", "@smithy/util-endpoints": "^3.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZxDYrfxOKXNFHLyvJtT96TJ0p4brZOhwRE4csRXrezEVUN+pNgxuem95YvMALPVhlVqON2CTzr8BX+CcBKvX9Q=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.911.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.911.0", "@aws-sdk/middleware-host-header": "3.910.0", "@aws-sdk/middleware-logger": "3.910.0", "@aws-sdk/middleware-recursion-detection": "3.910.0", "@aws-sdk/middleware-user-agent": "3.911.0", "@aws-sdk/region-config-resolver": "3.910.0", "@aws-sdk/types": "3.910.0", "@aws-sdk/util-endpoints": "3.910.0", "@aws-sdk/util-user-agent-browser": "3.910.0", "@aws-sdk/util-user-agent-node": "3.911.0", "@smithy/config-resolver": "^4.3.2", "@smithy/core": "^3.16.1", "@smithy/fetch-http-handler": "^5.3.3", "@smithy/hash-node": "^4.2.2", "@smithy/invalid-dependency": "^4.2.2", "@smithy/middleware-content-length": "^4.2.2", "@smithy/middleware-endpoint": "^4.3.3", "@smithy/middleware-retry": "^4.4.3", "@smithy/middleware-serde": "^4.2.2", "@smithy/middleware-stack": "^4.2.2", "@smithy/node-config-provider": "^4.3.2", "@smithy/node-http-handler": "^4.4.1", "@smithy/protocol-http": "^5.3.2", "@smithy/smithy-client": "^4.8.1", "@smithy/types": "^4.7.1", "@smithy/url-parser": "^4.2.2", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.2", "@smithy/util-defaults-mode-node": "^4.2.3", "@smithy/util-endpoints": "^3.2.2", "@smithy/util-middleware": "^4.2.2", "@smithy/util-retry": "^4.2.2", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-lp/sXbdX/S0EYaMYPVKga0omjIUbNNdFi9IJITgKZkLC6CzspihIoHd5GIdl4esMJevtTQQfkVncXTFkf/a4YA=="],
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7F0N888qVLHo4CSQOsnkZ4QAp8uHLKJ4v3u09Ly5k4AEStrSlFpckTPyUx6elwGL+fxGjNE2aakK8vEgzzCV0A=="],
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.910.0", "", { "dependencies": { "@aws-sdk/types": "3.910.0", "@smithy/node-config-provider": "^4.3.2", "@smithy/types": "^4.7.1", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-gzQAkuHI3xyG6toYnH/pju+kc190XmvnB7X84vtN57GjgdQJICt9So/BD0U6h+eSfk9VBnafkVrAzBzWMEFZVw=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.908.0", "", { "dependencies": { "@aws-sdk/core": "3.908.0", "@aws-sdk/nested-clients": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-4SosHWRQ8hj1X2yDenCYHParcCjHcd7S+Mdb/lelwF0JBFCNC+dNCI9ws3cP/dFdZO/AIhJQGUBzEQtieloixw=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.911.0", "", { "dependencies": { "@aws-sdk/core": "3.911.0", "@aws-sdk/nested-clients": "3.911.0", "@aws-sdk/types": "3.910.0", "@smithy/property-provider": "^4.2.2", "@smithy/shared-ini-file-loader": "^4.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-O1c5F1pbEImgEe3Vr8j1gpWu69UXWj3nN3vvLGh77hcrG5dZ8I27tSP5RN4Labm8Dnji/6ia+vqSYpN8w6KN5A=="],
"@aws-sdk/types": ["@aws-sdk/types@3.901.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg=="],
"@aws-sdk/types": ["@aws-sdk/types@3.910.0", "", { "dependencies": { "@smithy/types": "^4.7.1", "tslib": "^2.6.2" } }, "sha512-o67gL3vjf4nhfmuSUNNkit0d62QJEwwHLxucwVJkR/rw9mfUtAWsgBs8Tp16cdUbMgsyQtCQilL8RAJDoGtadQ=="],
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.901.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-endpoints": "^3.2.0", "tslib": "^2.6.2" } }, "sha512-5nZP3hGA8FHEtKvEQf4Aww5QZOkjLW1Z+NixSd+0XKfHvA39Ah5sZboScjLx0C9kti/K3OGW1RCx5K9Zc3bZqg=="],
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.910.0", "", { "dependencies": { "@aws-sdk/types": "3.910.0", "@smithy/types": "^4.7.1", "@smithy/url-parser": "^4.2.2", "@smithy/util-endpoints": "^3.2.2", "tslib": "^2.6.2" } }, "sha512-6XgdNe42ibP8zCQgNGDWoOF53RfEKzpU/S7Z29FTTJ7hcZv0SytC0ZNQQZSx4rfBl036YWYwJRoJMlT4AA7q9A=="],
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg=="],
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.907.0", "", { "dependencies": { "@aws-sdk/types": "3.901.0", "@smithy/types": "^4.6.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-Hus/2YCQmtCEfr4Ls88d07Q99Ex59uvtktiPTV963Q7w7LHuIT/JBjrbwNxtSm2KlJR9PHNdqxwN+fSuNsMGMQ=="],
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.910.0", "", { "dependencies": { "@aws-sdk/types": "3.910.0", "@smithy/types": "^4.7.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-iOdrRdLZHrlINk9pezNZ82P/VxO/UmtmpaOAObUN+xplCUJu31WNM2EE/HccC8PQw6XlAudpdA6HDTGiW6yVGg=="],
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.908.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.908.0", "@aws-sdk/types": "3.901.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-l6AEaKUAYarcEy8T8NZ+dNZ00VGLs3fW2Cqu1AuPENaSad0/ahEU+VU7MpXS8FhMRGPgplxKVgCTLyTY0Lbssw=="],
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.911.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.911.0", "@aws-sdk/types": "3.910.0", "@smithy/node-config-provider": "^4.3.2", "@smithy/types": "^4.7.1", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-3l+f6ooLF6Z6Lz0zGi7vSKSUYn/EePPizv88eZQpEAFunBHv+CSVNPtxhxHfkm7X9tTsV4QGZRIqo3taMLolmA=="],
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.901.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-pxFCkuAP7Q94wMTNPAwi6hEtNrp/BdFf+HOrIEeFQsk4EoOmpKY3I6S+u6A9Wg295J80Kh74LqDWM22ux3z6Aw=="],
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.911.0", "", { "dependencies": { "@smithy/types": "^4.7.1", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-/yh3oe26bZfCVGrIMRM9Z4hvvGJD+qx5tOLlydOkuBkm72aXON7D9+MucjJXTAcI8tF2Yq+JHa0478eHQOhnLg=="],
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.0.1", "", {}, "sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw=="],
@ -662,57 +662,57 @@
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
@ -754,7 +754,7 @@
"@iconify-json/mdi": ["@iconify-json/mdi@1.2.3", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg=="],
"@iconify/json": ["@iconify/json@2.2.395", "", { "dependencies": { "@iconify/types": "*", "pathe": "^2.0.0" } }, "sha512-XSYOnlGqiZhJkFFBUiVK4C5VIiv4rxyKtCmkQ9nS4zfMpS4xT0BF9+qWUKOHYgeCzCLghyWfrm6Eti3Sv5kfqQ=="],
"@iconify/json": ["@iconify/json@2.2.398", "", { "dependencies": { "@iconify/types": "*", "pathe": "^2.0.0" } }, "sha512-SEUDjkyAenAKMd5hDfzNzeObZw+KgQdIHaOuWA4yv89qSf2s0dJ7L1iXhRjtEZxFnuXQ2D67uFiHmE+90jvDGQ=="],
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
@ -766,11 +766,11 @@
"@intlify/bundle-utils": ["@intlify/bundle-utils@10.0.1", "", { "dependencies": { "@intlify/message-compiler": "^11.1.2", "@intlify/shared": "^11.1.2", "acorn": "^8.8.2", "escodegen": "^2.1.0", "estree-walker": "^2.0.2", "jsonc-eslint-parser": "^2.3.0", "mlly": "^1.2.0", "source-map-js": "^1.0.1", "yaml-eslint-parser": "^1.2.2" } }, "sha512-WkaXfSevtpgtUR4t8K2M6lbR7g03mtOxFeh+vXp5KExvPqS12ppaRj1QxzwRuRI5VUto54A22BjKoBMLyHILWQ=="],
"@intlify/core-base": ["@intlify/core-base@9.14.5", "", { "dependencies": { "@intlify/message-compiler": "9.14.5", "@intlify/shared": "9.14.5" } }, "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA=="],
"@intlify/core-base": ["@intlify/core-base@9.13.1", "", { "dependencies": { "@intlify/message-compiler": "9.13.1", "@intlify/shared": "9.13.1" } }, "sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w=="],
"@intlify/eslint-plugin-vue-i18n": ["@intlify/eslint-plugin-vue-i18n@1.4.1", "", { "dependencies": { "@eslint/eslintrc": "^1.2.0", "@intlify/core-base": "^9.1.9", "@intlify/message-compiler": "^9.1.9", "debug": "^4.3.1", "glob": "^7.1.3", "ignore": "^5.0.5", "is-language-code": "^3.1.0", "js-yaml": "^4.0.0", "json5": "^2.1.3", "jsonc-eslint-parser": "^2.0.0", "lodash": "^4.17.11", "parse5": "^6.0.0", "semver": "^7.3.4", "vue-eslint-parser": "^8.0.0", "yaml-eslint-parser": "^0.5.0" }, "peerDependencies": { "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-vnhwxcUTYCL/tCeBkXMDz959DVHNaDd3SRt3jdyX5ZwHaSSx93aD7kZV7ZmJpq4lZlq7Q1eVRGhpmpTNGdvU9w=="],
"@intlify/message-compiler": ["@intlify/message-compiler@9.14.5", "", { "dependencies": { "@intlify/shared": "9.14.5", "source-map-js": "^1.0.2" } }, "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ=="],
"@intlify/message-compiler": ["@intlify/message-compiler@9.13.1", "", { "dependencies": { "@intlify/shared": "9.13.1", "source-map-js": "^1.0.2" } }, "sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w=="],
"@intlify/shared": ["@intlify/shared@9.13.1", "", {}, "sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ=="],
@ -830,7 +830,7 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@keyv/bigmap": ["@keyv/bigmap@1.0.3", "", { "dependencies": { "hookified": "^1.12.1" } }, "sha512-jUEkNlnE9tYzX2AIBeoSe1gVUvSOfIOQ5EFPL5Un8cFHGvjD9L/fxpxlS1tEivRLHgapO2RZJ3D93HYAa049pg=="],
"@keyv/bigmap": ["@keyv/bigmap@1.1.0", "", { "dependencies": { "hookified": "^1.12.2" }, "peerDependencies": { "keyv": "^5.5.3" } }, "sha512-MX7XIUNwVRK+hjZcAbNJ0Z8DREo+Weu9vinBOjGU1thEi9F6vPhICzBbk4CCf3eEefKRz7n6TfZXwUFZTSgj8Q=="],
"@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="],
@ -900,43 +900,43 @@
"@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="],
"@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.9.0", "", { "os": "android", "cpu": "arm" }, "sha512-4AxaG6TkSBQ2FiC5oGZEJQ35DjsSfAbW6/AJauebq4EzIPVOIgDJCF4de+PvX/Xi9BkNw6VtJuMXJdWW97iEAA=="],
"@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.11.0", "", { "os": "android", "cpu": "arm" }, "sha512-aN0UJg1xr0N1dADQ135z4p3bP9AYAUN1Ey2VvLMK6IwWYIJGWpKT+cr1l3AiyBeLK8QZyFDb4IDU8LHgjO9TDQ=="],
"@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.9.0", "", { "os": "android", "cpu": "arm64" }, "sha512-oOEg7rUd2M6YlmRkvPcszJ6KO6TaLGN21oDdcs27gbTVYbQQtCWYbZz5jRW5zEBJu6dopoWVx+shJNGtG1qDFw=="],
"@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.11.0", "", { "os": "android", "cpu": "arm64" }, "sha512-FckvvMclo8CSJqQjKpHueIIbKrg9L638NKWQTiJQaD8W9F61h8hTjF8+QFLlCHh6R9RcE5roVHdkkiBKHlB2Zw=="],
"@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.9.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fM6zE/j6o3C1UIkcZPV7C1f186R7w97guY2N4lyNLlhlgwwhd46acnOezLARvRNU5oyKNev4PvOJhGCCDnFMGg=="],
"@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.11.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7ZcpgaXSBnwRHM1YR8Vazq7mCTtGdYRvM7k46CscA+oipCVqmI4LbW2wLsc6HVjqX+SM/KPOfFGoGjEgmQPFTQ=="],
"@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.9.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Bg3Orw7gAxbUqQlt64YPWvHDVo3bo2JfI26Qmzv6nKo7mIMTDhQKl7YmywtLNMYbX0IgUM4qu1V90euu+WCDOw=="],
"@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.11.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Wsd1JWORokMmOKrR4t4jxpwYEWG11+AHWu9bdzjCO5EIyi0AuNpPIAEcEFCP9FNd0h8c+VUYbMRU/GooD2zOIg=="],
"@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.9.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-eBqVZqTETH6miBfIZXvpzUe98WATz2+Sh+LEFwuRpGsTsKkIpTyb4p1kwylCLkxrd3Yx7wkxQku+L0AMEGBiAA=="],
"@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.11.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YX+W10kHrMouu/+Y+rqJdCWO3dFBKM1DIils30PHsmXWp1v+ZZvhibaST2BP6zrWkWquZ8pMmsObD6N10lLgiA=="],
"@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.9.0", "", { "os": "linux", "cpu": "arm" }, "sha512-QgCk/IJnGBvpbc8rYTVgO+A3m3edJjH1zfv8Nvx7fmsxpbXwWH2l4b4tY3/SLMzasxsp7x7k87+HWt095bI5Lg=="],
"@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.11.0", "", { "os": "linux", "cpu": "arm" }, "sha512-UAhlhVkW2ui98bClmEkDLKQz4XBSccxMahG7rMeX2RepS2QByAWxYFFThaNbHtBSB+B4Rc1hudkihq8grQkU3g=="],
"@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.9.0", "", { "os": "linux", "cpu": "arm" }, "sha512-xkJH0jldIXD2GwoHpCDEF0ucJ7fvRETCL+iFLctM679o7qeDXvtzsO/E401EgFFXcWBJNKXWvH+ZfdYMKyowfA=="],
"@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.11.0", "", { "os": "linux", "cpu": "arm" }, "sha512-5pEliabSEiimXz/YyPxzyBST82q8PbM6BoEMS8kOyaDbEBuzTr7pWU1U0F7ILGBFjJmHaj3N7IAhQgeXdpdySg=="],
"@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.9.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TWq+y2psMzbMtZB9USAq2bSA7NV1TMmh9lhAFbMGQ8Yp2YV4BRC/HilD6qF++efQl6shueGBFOv0LVe9BUXaIA=="],
"@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.11.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-CiyufPFIOJrW/HovAMGsH0AbV7BSCb0oE0KDtt7z1+e+qsDo7HRlTSnqE3JbNuhJRg3Cz/j7qEYzgGqco9SE4Q=="],
"@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.9.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-8WwGLfXk7yttc6rD6g53+RnYfX5B8xOot1ffthLn8oCXzVRO4cdChlmeHStxwLD/MWx8z8BGeyfyINNrsh9N2w=="],
"@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.11.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-w07MfGtDLZV0rISdXl2cGASxD/sRrrR93Qd4q27O2Hsky4MGbLw94trbzhmAkc7OKoJI0iDg1217i3jfxmVk1Q=="],
"@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.9.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZWiAXfan6actlSzayaFS/kYO2zD6k1k0fmLb1opbujXYMKepEnjjVOvKdzCIYR/zKzudqI39dGc+ywqVdsPIpQ=="],
"@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.11.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gzM+ZfIjfcCofwX/m1eLCoTT+3T70QLWaKDOW5Hf3+ddLlxMEVRIQtUoRsp0e/VFanr7u7VKS57TxhkRubseNg=="],
"@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.9.0", "", { "os": "linux", "cpu": "none" }, "sha512-p9mCSb+Bym+eycNo9k+81wQ5SAE31E+/rtfbDmF4/7krPotkEjPsEBSc3rqunRwO+FtsUn7H68JLY7hlai49eQ=="],
"@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.11.0", "", { "os": "linux", "cpu": "none" }, "sha512-oCR0ImJQhIwmqwNShsRT0tGIgKF5/H4nhtIEkQAQ9bLzMgjtRqIrZ3DtGHqd7w58zhXWfIZdyPNF9IrSm+J/fQ=="],
"@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.9.0", "", { "os": "linux", "cpu": "none" }, "sha512-/SePuVxgFhLPciRwsJ8kLVltr+rxh0b6riGFuoPnFXBbHFclKnjNIt3TfqzUj0/vOnslXw3cVGPpmtkm2TgCgg=="],
"@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.11.0", "", { "os": "linux", "cpu": "none" }, "sha512-MjCEqsUzXMfWPfsEUX+UXttzXz6xiNU11r7sj00C5og/UCyqYw1OjrbC/B1f/dloDpTn0rd4xy6c/LTvVQl2tg=="],
"@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.9.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-zLuEjlYIzfnr1Ei2UZYQBbCTa/9deh+BEjO9rh1ai8BfEq4uj6RupTtNpgHfgAsEYdqOBVExw9EU1S6SW3RCAw=="],
"@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.11.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-4TaTX7gT3357vWQsTe3IfDtWyJNe0FejypQ4ngwxB3v1IVaW6KAUt0huSvx/tmj+YWxd3zzXdWd8AzW0jo6dpg=="],
"@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.9.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cxdg73WG+aVlPu/k4lEQPRVOhWunYOUglW6OSzclZLJJAXZU0tSZ5ymKaqPRkfTsyNSAafj1cA1XYd+P9UxBgw=="],
"@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.11.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ch1o3+tBra9vmrgXqrufVmYnvRPFlyUb7JWs/VXndBmyNSuP2KP+guAUrC0fr2aSGoOQOasAiZza7MTFU7Vrxg=="],
"@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.9.0", "", { "os": "linux", "cpu": "x64" }, "sha512-sy5nkVdMvNgqcx9sIY7G6U9TYZUZC4cmMGw/wKhJNuuD2/HFGtbje62ttXSwBAbVbmJ2GgZ4ZUo/S1OMyU+/OA=="],
"@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.11.0", "", { "os": "linux", "cpu": "x64" }, "sha512-llTdl2gJAqXaGV7iV1w5BVlqXACcoT1YD3o840pCQx1ZmKKAAz7ydPnTjYVdkGImXNWPOIWJixHW0ryDm4Mx7w=="],
"@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.9.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-dfi/a0Xh6o6nOLbJdaYuy7txncEcwkRHp9DGGZaAP7zxDiepkBZ6ewSJODQrWwhjVmMteXo+XFzEOMjsC7WUtQ=="],
"@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.11.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.7" }, "cpu": "none" }, "sha512-cROavohP0nX91NtIVVgOTugqoxlUSNxI9j7MD+B7fmD3gEFl8CVyTamR0/p6loDxLv51bQYTHRKn/ZYTd3ENzw=="],
"@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.9.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-b1yKr+eFwyi8pZMjAQwW352rXpaHAmz7FLK03vFIxdyWzWiiL6S3UrfMu+nKQud38963zu4wNNLm7rdXQazgRA=="],
"@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.11.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-6amVs34yHmxE6Q3CtTPXnSvIYGqwQJ/lVVRYccLzg9smge3WJ1knyBV5jpKKayp0n316uPYzB4EgEbgcuRvrPw=="],
"@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.9.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-DxRT+1HjCpRH8qYCmGHzgsRCYiK+X14PUM9Fb+aD4TljplA7MdDQXqMISTb4zBZ70AuclvlXKTbW+K1GZop3xA=="],
"@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.11.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-v/IZ5s2/3auHUoi0t6Ea1CDsWxrE9BvgvbDcJ04QX+nEbmTBazWPZeLsH8vWkRAh8EUKCZHXxjQsPhEH5Yk5pQ=="],
"@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.9.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gE3QJvhh0Yj9cSAkkHjRLKPmC7BTJeiaB5YyhVKVUwbnWQgTszV92lZ9pvZtNPEghP7jPbhEs4c6983A0ojQwA=="],
"@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.11.0", "", { "os": "win32", "cpu": "x64" }, "sha512-qvm+IQ6r2q4HZitSV69O+OmvCD1y4pH7SbhR6lPwLsfZS5QRHS8V20VHxmG1jJzSPPw7S8Bb1rdNcxDSqc4bYA=="],
"@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
@ -994,49 +994,49 @@
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.4", "", { "os": "android", "cpu": "arm" }, "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.4", "", { "os": "android", "cpu": "arm64" }, "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.5", "", { "os": "android", "cpu": "arm64" }, "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.4", "", { "os": "none", "cpu": "arm64" }, "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.5", "", { "os": "none", "cpu": "arm64" }, "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
@ -1056,55 +1056,55 @@
"@sinonjs/fake-timers": ["@sinonjs/fake-timers@8.1.0", "", { "dependencies": { "@sinonjs/commons": "^1.7.0" } }, "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg=="],
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg=="],
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ=="],
"@smithy/config-resolver": ["@smithy/config-resolver@4.3.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9oH+n8AVNiLPK/iK/agOsoWfrKZ3FGP3502tkksd6SRsKMYiu7AFX0YXo6YBADdsAj7C+G/aLKdsafIJHxuCkQ=="],
"@smithy/config-resolver": ["@smithy/config-resolver@4.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.3", "@smithy/types": "^4.8.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.3", "tslib": "^2.6.2" } }, "sha512-xSql8A1Bl41O9JvGU/CtgiLBlwkvpHTSKRlvz9zOBvBCPjXghZ6ZkcVzmV2f7FLAA+80+aqKmIOmy8pEDrtCaw=="],
"@smithy/core": ["@smithy/core@3.15.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-stream": "^4.5.0", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-VJWncXgt+ExNn0U2+Y7UywuATtRYaodGQKFo9mDyh70q+fJGedfrqi2XuKU1BhiLeXgg6RZrW7VEKfeqFhHAJA=="],
"@smithy/core": ["@smithy/core@3.17.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.3", "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.3", "@smithy/util-stream": "^4.5.3", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Tir3DbfoTO97fEGUZjzGeoXgcQAUBRDTmuH9A8lxuP8ATrgezrAJ6cLuRvwdKN4ZbYNlHgKlBX69Hyu3THYhtg=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-SOhFVvFH4D5HJZytb0bLKxCrSnwcqPiNlrw+S4ZXjMnsC+o9JcUQzbZOEQcA8yv9wJFNhfsUiIUKiEnYL68Big=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.3", "@smithy/property-provider": "^4.2.3", "@smithy/types": "^4.8.0", "@smithy/url-parser": "^4.2.3", "tslib": "^2.6.2" } }, "sha512-hA1MQ/WAHly4SYltJKitEsIDVsNmXcQfYBRv2e+q04fnqtAX5qXaybxy/fhUeAMCnQIdAjaGDb04fMHQefWRhw=="],
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.1", "", { "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/querystring-builder": "^4.2.0", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3AvYYbB+Dv5EPLqnJIAgYw/9+WzeBiUYS8B+rU0pHq5NMQMvrZmevUROS4V2GAt0jEOn9viBzPLrZE+riTNd5Q=="],
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.4", "", { "dependencies": { "@smithy/protocol-http": "^5.3.3", "@smithy/querystring-builder": "^4.2.3", "@smithy/types": "^4.8.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw=="],
"@smithy/hash-node": ["@smithy/hash-node@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ugv93gOhZGysTctZh9qdgng8B+xO0cj+zN0qAZ+Sgh7qTQGPOJbMdIuyP89KNfUyfAqFSNh5tMvC+h2uCpmTtA=="],
"@smithy/hash-node": ["@smithy/hash-node@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g=="],
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-ZmK5X5fUPAbtvRcUPtk28aqIClVhbfcmfoS4M7UQBTnDdrNxhsrxYVv0ZEl5NaPSyExsPWqL4GsPlRvtlwg+2A=="],
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q=="],
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.0", "", { "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-6ZAnwrXFecrA4kIDOcz6aLBhU5ih2is2NdcZtobBDSdSHtE9a+MThB5uqyK4XXesdOCvOcbCm2IGB95birTSOQ=="],
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.3", "", { "dependencies": { "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA=="],
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.1", "", { "dependencies": { "@smithy/core": "^3.15.0", "@smithy/middleware-serde": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/url-parser": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JtM4SjEgImLEJVXdsbvWHYiJ9dtuKE8bqLlvkvGi96LbejDL6qnVpVxEFUximFodoQbg0Gnkyff9EKUhFhVJFw=="],
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.4", "", { "dependencies": { "@smithy/core": "^3.17.0", "@smithy/middleware-serde": "^4.2.3", "@smithy/node-config-provider": "^4.3.3", "@smithy/shared-ini-file-loader": "^4.3.3", "@smithy/types": "^4.8.0", "@smithy/url-parser": "^4.2.3", "@smithy/util-middleware": "^4.2.3", "tslib": "^2.6.2" } }, "sha512-/RJhpYkMOaUZoJEkddamGPPIYeKICKXOu/ojhn85dKDM0n5iDIhjvYAQLP3K5FPhgB203O3GpWzoK2OehEoIUw=="],
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/protocol-http": "^5.3.0", "@smithy/service-error-classification": "^4.2.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-retry": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-wXxS4ex8cJJteL0PPQmWYkNi9QKDWZIpsndr0wZI2EL+pSSvA/qqxXU60gBOJoIc2YgtZSWY/PE86qhKCCKP1w=="],
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.3", "@smithy/protocol-http": "^5.3.3", "@smithy/service-error-classification": "^4.2.3", "@smithy/smithy-client": "^4.9.0", "@smithy/types": "^4.8.0", "@smithy/util-middleware": "^4.2.3", "@smithy/util-retry": "^4.2.3", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-vSgABQAkuUHRO03AhR2rWxVQ1un284lkBn+NFawzdahmzksAoOeVMnXXsuPViL4GlhRHXqFaMlc8Mj04OfQk1w=="],
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.0", "", { "dependencies": { "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw=="],
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.3", "", { "dependencies": { "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ=="],
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg=="],
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA=="],
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.0", "", { "dependencies": { "@smithy/property-provider": "^4.2.0", "@smithy/shared-ini-file-loader": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA=="],
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.3", "", { "dependencies": { "@smithy/property-provider": "^4.2.3", "@smithy/shared-ini-file-loader": "^4.3.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.3.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/querystring-builder": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.2", "", { "dependencies": { "@smithy/abort-controller": "^4.2.3", "@smithy/protocol-http": "^5.3.3", "@smithy/querystring-builder": "^4.2.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-MHFvTjts24cjGo1byXqhXrbqm7uznFD/ESFx8npHMWTFQVdBZjrT1hKottmp69LBTRm/JQzP/sn1vPt0/r6AYQ=="],
"@smithy/property-provider": ["@smithy/property-provider@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw=="],
"@smithy/property-provider": ["@smithy/property-provider@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-+1EZ+Y+njiefCohjlhyOcy1UNYjT+1PwGFHCxA/gYctjg3DQWAU19WigOXAco/Ql8hZokNehpzLd0/+3uCreqQ=="],
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q=="],
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw=="],
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A=="],
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ=="],
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA=="],
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA=="],
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0" } }, "sha512-Ylv1ttUeKatpR0wEOMnHf1hXMktPUMObDClSWl2TpCVT4DwtJhCeighLzSLbgH3jr5pBNM0LDXT5yYxUvZ9WpA=="],
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0" } }, "sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g=="],
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.3.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ=="],
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.3.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ=="],
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.0", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g=="],
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.3", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.3", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA=="],
"@smithy/smithy-client": ["@smithy/smithy-client@4.7.1", "", { "dependencies": { "@smithy/core": "^3.15.0", "@smithy/middleware-endpoint": "^4.3.1", "@smithy/middleware-stack": "^4.2.0", "@smithy/protocol-http": "^5.3.0", "@smithy/types": "^4.6.0", "@smithy/util-stream": "^4.5.0", "tslib": "^2.6.2" } }, "sha512-WXVbiyNf/WOS/RHUoFMkJ6leEVpln5ojCjNBnzoZeMsnCg3A0BRhLK3WYc4V7PmYcYPZh9IYzzAg9XcNSzYxYQ=="],
"@smithy/smithy-client": ["@smithy/smithy-client@4.9.0", "", { "dependencies": { "@smithy/core": "^3.17.0", "@smithy/middleware-endpoint": "^4.3.4", "@smithy/middleware-stack": "^4.2.3", "@smithy/protocol-http": "^5.3.3", "@smithy/types": "^4.8.0", "@smithy/util-stream": "^4.5.3", "tslib": "^2.6.2" } }, "sha512-qz7RTd15GGdwJ3ZCeBKLDQuUQ88m+skh2hJwcpPm1VqLeKzgZvXf6SrNbxvx7uOqvvkjCMXqx3YB5PDJyk00ww=="],
"@smithy/types": ["@smithy/types@4.6.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA=="],
"@smithy/types": ["@smithy/types@4.8.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ=="],
"@smithy/url-parser": ["@smithy/url-parser@4.2.0", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A=="],
"@smithy/url-parser": ["@smithy/url-parser@4.2.3", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw=="],
"@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="],
@ -1116,25 +1116,25 @@
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.0", "", { "dependencies": { "@smithy/property-provider": "^4.2.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-H4MAj8j8Yp19Mr7vVtGgi7noJjvjJbsKQJkvNnLlrIFduRFT5jq5Eri1k838YW7rN2g5FTnXpz5ktKVr1KVgPQ=="],
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.3", "", { "dependencies": { "@smithy/property-provider": "^4.2.3", "@smithy/smithy-client": "^4.9.0", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-vqHoybAuZXbFXZqgzquiUXtdY+UT/aU33sxa4GBPkiYklmR20LlCn+d3Wc3yA5ZM13gQ92SZe/D8xh6hkjx+IQ=="],
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.1", "", { "dependencies": { "@smithy/config-resolver": "^4.3.0", "@smithy/credential-provider-imds": "^4.2.0", "@smithy/node-config-provider": "^4.3.0", "@smithy/property-provider": "^4.2.0", "@smithy/smithy-client": "^4.7.1", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-PuDcgx7/qKEMzV1QFHJ7E4/MMeEjaA7+zS5UNcHCLPvvn59AeZQ0DSDGMpqC2xecfa/1cNGm4l8Ec/VxCuY7Ug=="],
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.4", "", { "dependencies": { "@smithy/config-resolver": "^4.3.3", "@smithy/credential-provider-imds": "^4.2.3", "@smithy/node-config-provider": "^4.3.3", "@smithy/property-provider": "^4.2.3", "@smithy/smithy-client": "^4.9.0", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-X5/xrPHedifo7hJUUWKlpxVb2oDOiqPUXlvsZv1EZSjILoutLiJyWva3coBpn00e/gPSpH8Rn2eIbgdwHQdW7Q=="],
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.0", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-TXeCn22D56vvWr/5xPqALc9oO+LN+QpFjrSM7peG/ckqEPoI3zaKZFp+bFwfmiHhn5MGWPaLCqDOJPPIixk9Wg=="],
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ=="],
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.0", "", { "dependencies": { "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA=="],
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.3", "", { "dependencies": { "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw=="],
"@smithy/util-retry": ["@smithy/util-retry@4.2.0", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-BWSiuGbwRnEE2SFfaAZEX0TqaxtvtSYPM/J73PFVm+A29Fg1HTPiYFb8TmX1DXp4hgcdyJcNQmprfd5foeORsg=="],
"@smithy/util-retry": ["@smithy/util-retry@4.2.3", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg=="],
"@smithy/util-stream": ["@smithy/util-stream@4.5.0", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.1", "@smithy/node-http-handler": "^4.3.0", "@smithy/types": "^4.6.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-0TD5M5HCGu5diEvZ/O/WquSjhJPasqv7trjoqHyWjNh/FBeBl7a0ztl9uFMOsauYtRfd8jvpzIAQhDHbx+nvZw=="],
"@smithy/util-stream": ["@smithy/util-stream@4.5.3", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.4", "@smithy/node-http-handler": "^4.4.2", "@smithy/types": "^4.8.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-oZvn8a5bwwQBNYHT2eNo0EU8Kkby3jeIg1P2Lu9EQtqDxki1LIjGRJM6dJ5CZUig8QmLxWxqOKWvg3mVoOBs5A=="],
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
"@smithy/util-waiter": ["@smithy/util-waiter@4.2.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.0", "@smithy/types": "^4.6.0", "tslib": "^2.6.2" } }, "sha512-0Z+nxUU4/4T+SL8BCNN4ztKdQjToNvUYmkF1kXO5T7Yz3Gafzh0HeIG6mrkN8Fz3gn9hSyxuAT+6h4vM+iQSBQ=="],
"@smithy/util-waiter": ["@smithy/util-waiter@4.2.3", "", { "dependencies": { "@smithy/abort-controller": "^4.2.3", "@smithy/types": "^4.8.0", "tslib": "^2.6.2" } }, "sha512-5+nU///E5sAdD7t3hs4uwvCTWQtTR8JwKwOCSJtBRx0bY1isDo1QwH87vRK86vlFLBTISqoDA2V6xvP6nF1isQ=="],
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
@ -1296,7 +1296,7 @@
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
"@types/send": ["@types/send@0.17.5", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w=="],
"@types/send": ["@types/send@1.2.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ=="],
"@types/serve-static": ["@types/serve-static@1.15.9", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA=="],
@ -1316,19 +1316,19 @@
"@types/zen-observable": ["@types/zen-observable@0.8.7", "", {}, "sha512-LKzNTjj+2j09wAo/vvVjzgw5qckJJzhdGgWHW7j69QIGdq/KnZrMAMIHQiWGl3Ccflh5/CudBAntTPYdprPltA=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.46.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.0", "@typescript-eslint/types": "^8.46.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.46.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.1", "@typescript-eslint/types": "^8.46.1", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.0", "", { "dependencies": { "@typescript-eslint/types": "8.46.0", "@typescript-eslint/visitor-keys": "8.46.0" } }, "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.1", "", { "dependencies": { "@typescript-eslint/types": "8.46.1", "@typescript-eslint/visitor-keys": "8.46.1" } }, "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.46.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.46.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.46.0", "", {}, "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.46.1", "", {}, "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.0", "@typescript-eslint/tsconfig-utils": "8.46.0", "@typescript-eslint/types": "8.46.0", "@typescript-eslint/visitor-keys": "8.46.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.1", "@typescript-eslint/tsconfig-utils": "8.46.1", "@typescript-eslint/types": "8.46.1", "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.0", "", { "dependencies": { "@typescript-eslint/types": "8.46.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.1", "", { "dependencies": { "@typescript-eslint/types": "8.46.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
@ -1618,11 +1618,11 @@
"balanced-match": ["balanced-match@2.0.0", "", {}, "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA=="],
"bare-addon-resolve": ["bare-addon-resolve@1.9.4", "", { "dependencies": { "bare-module-resolve": "^1.10.0", "bare-semver": "^1.0.0" }, "peerDependencies": { "bare-url": "*" }, "optionalPeers": ["bare-url"] }, "sha512-unn6Vy/Yke6F99vg/7tcrvM2KUvIhTNniaSqDbam4AWkd4NhvDVSrQiRYVlNzUV2P7SPobkCK7JFVxrJk9btCg=="],
"bare-addon-resolve": ["bare-addon-resolve@1.9.5", "", { "dependencies": { "bare-module-resolve": "^1.10.0", "bare-semver": "^1.0.0" }, "peerDependencies": { "bare-url": "*" }, "optionalPeers": ["bare-url"] }, "sha512-XdqrG73zLK9LDfblOJwoAxmJ+7YdfRW4ex46+f4L+wPhk7H7LDrRMAbBw8s8jkxeEFpUenyB7QHnv0ErAWd3Yg=="],
"bare-events": ["bare-events@2.8.0", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-AOhh6Bg5QmFIXdViHbMc2tLDsBIRxdkIaIddPslJF9Z5De3APBScuqGP2uThXnIpqFrgoxMNC6km7uXNIMLHXA=="],
"bare-module-resolve": ["bare-module-resolve@1.11.1", "", { "dependencies": { "bare-semver": "^1.0.0" }, "peerDependencies": { "bare-url": "*" }, "optionalPeers": ["bare-url"] }, "sha512-DCxeT9i8sTs3vUMA3w321OX/oXtNEu5EjObQOnTmCdNp5RXHBAvAaBDHvAi9ta0q/948QPz+co6SsGi6aQMYRg=="],
"bare-module-resolve": ["bare-module-resolve@1.11.2", "", { "dependencies": { "bare-semver": "^1.0.0" }, "peerDependencies": { "bare-url": "*" }, "optionalPeers": ["bare-url"] }, "sha512-HIBu9WacMejg3Dz4X1v6lJjp7ECnwpujvuLub+8I7JJLRwJaGxWMzGYvieOoS9R1n5iRByvTmLtIdPbwjfRgiQ=="],
"bare-os": ["bare-os@3.6.2", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="],
@ -1630,11 +1630,11 @@
"bare-semver": ["bare-semver@1.0.1", "", {}, "sha512-UtggzHLiTrmFOC/ogQ+Hy7VfoKoIwrP1UFcYtTxoCUdLtsIErT8+SWtOC2DH/snT9h+xDrcBEPcwKei1mzemgg=="],
"bare-url": ["bare-url@2.3.0", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-c+RCqMSZbkz97Mw1LWR0gcOqwK82oyYKfLoHJ8k13ybi1+I80ffdDzUy0TdAburdrR/kI0/VuN8YgEnJqX+Nyw=="],
"bare-url": ["bare-url@2.3.1", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-v2yl0TnaZTdEnelkKtXZGnotiV6qATBlnNuUMrHl6v9Lmmrh9mw9RYyImPU7/4RahumSwQS1k2oKXcRfXcbjJw=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.16", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.18", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w=="],
"bbump": ["bbump@1.0.2", "", { "dependencies": { "inquirer": "^9.0.0" }, "bin": { "bbump": "index.js" } }, "sha512-JGwlqjBF9cvPCjONPlb8R7YH4Uum8E06MJchUxpbjr2Ft7v/hjq+mM7zq93anI0Ww7sg9WAQulzCVDGUnd9YdQ=="],
@ -1692,11 +1692,11 @@
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"c12": ["c12@3.3.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.2", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.5.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-K9ZkuyeJQeqLEyqldbYLG3wjqwpw4BVaAqvmxq3GYKK0b1A/yYQdIcJxkzAOWcNVWhJpRXAPfZFueekiY/L8Dw=="],
"c12": ["c12@3.3.1", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-LcWQ01LT9tkoUINHgpIOv3mMs+Abv7oVCrtpMRi1PaapVEpWoMga5WuT7/DqFTu7URP9ftbOmimNw1KNIGh9DQ=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"cacheable": ["cacheable@2.1.0", "", { "dependencies": { "@cacheable/memoize": "^2.0.3", "@cacheable/memory": "^2.0.3", "@cacheable/utils": "^2.1.0", "hookified": "^1.12.1", "keyv": "^5.5.3", "qified": "^0.5.0" } }, "sha512-zzL1BxdnqwD69JRT0dihnawAcLkBMwAH+hZSKjUzeBbPedVhk3qYPjRw9VOMYWwt5xRih5xd8S+3kEdGohZm/g=="],
"cacheable": ["cacheable@2.1.1", "", { "dependencies": { "@cacheable/memoize": "^2.0.3", "@cacheable/memory": "^2.0.3", "@cacheable/utils": "^2.1.0", "hookified": "^1.12.2", "keyv": "^5.5.3", "qified": "^0.5.0" } }, "sha512-LmF4AXiSNdiRbI2UjH8pAp9NIXxeQsTotpEaegPiDcnN0YPygDJDV3l/Urc0mL72JWdATEorKqIHEx55nDlONg=="],
"cacheable-lookup": ["cacheable-lookup@7.0.0", "", {}, "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="],
@ -1714,7 +1714,7 @@
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001750", "", {}, "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001751", "", {}, "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw=="],
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
@ -1760,7 +1760,7 @@
"co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="],
"collect-v8-coverage": ["collect-v8-coverage@1.0.2", "", {}, "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q=="],
"collect-v8-coverage": ["collect-v8-coverage@1.0.3", "", {}, "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@ -1774,7 +1774,7 @@
"commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
"compact-encoding": ["compact-encoding@2.17.0", "", { "dependencies": { "b4a": "^1.3.0" } }, "sha512-A5mkpopiDqCddAHTmJ/VnP7JQ1MCytRbpi13zQJGsDcKN041B++2n8oUwxBn37C86gbIt/zIARaqLVy9vI9M5Q=="],
"compact-encoding": ["compact-encoding@2.18.0", "", { "dependencies": { "b4a": "^1.3.0" } }, "sha512-goACAOlhMI2xo5jGOMUDfOLnGdRE1jGfyZ+zie8N5114nHrbPIqf6GLUtzbLof6DSyrERlYRm3EcBplte5LcQw=="],
"compact-encoding-net": ["compact-encoding-net@1.2.0", "", { "dependencies": { "compact-encoding": "^2.4.1" } }, "sha512-LVXpNpF7PGQeHRVVLGgYWzuVoYAaDZvKUsUxRioGfkotzvOh4AzoQF1HBH3zMNaSnx7gJXuUr3hkjnijaH/Eng=="],
@ -1800,7 +1800,7 @@
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
"cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
@ -1962,7 +1962,7 @@
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.234", "", {}, "sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg=="],
"electron-to-chromium": ["electron-to-chromium@1.5.237", "", {}, "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg=="],
"email-templates": ["email-templates@10.0.1", "", { "dependencies": { "@ladjs/i18n": "^8.0.1", "consolidate": "^0.16.0", "get-paths": "^0.0.7", "html-to-text": "^8.2.0", "juice": "^8.0.0", "lodash": "^4.17.21", "nodemailer": "^6.7.7", "preview-email": "^3.0.7" } }, "sha512-LNZKS0WW9XQkjuDZd/4p/1Q/pwqaqXOP3iDxTIVIQY9vuHlIUEcRLFo8/Xh3GtZCBnm181VgvOXIABKTVyTePA=="],
@ -2004,7 +2004,7 @@
"es6-promise": ["es6-promise@4.2.8", "", {}, "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="],
"esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
"esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@ -2266,7 +2266,7 @@
"graphql-request": ["graphql-request@5.0.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "cross-fetch": "^3.1.5", "extract-files": "^9.0.0", "form-data": "^3.0.0" }, "peerDependencies": { "graphql": "14 - 16" } }, "sha512-SpVEnIo2J5k2+Zf76cUkdvIRaq5FMZvGQYnA4lUWYbc99m+fHh4CZYRRO/Ff4tCLQ613fzCm3SiDT64ubW5Gyw=="],
"graphql-scalars": ["graphql-scalars@1.24.2", "", { "dependencies": { "tslib": "^2.5.0" }, "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, "sha512-FoZ11yxIauEnH0E5rCUkhDXHVn/A6BBfovJdimRZCQlFCl+h7aVvarKmI15zG4VtQunmCDdqdtNs6ixThy3uAg=="],
"graphql-scalars": ["graphql-scalars@1.25.0", "", { "dependencies": { "tslib": "^2.5.0" }, "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, "sha512-b0xyXZeRFkne4Eq7NAnL400gStGqG/Sx9VqX0A05nHyEbv57UJnWKsjNnrpVqv5e/8N1MUxkt0wwcRXbiyKcFg=="],
"graphql-subscriptions": ["graphql-subscriptions@1.2.1", "", { "dependencies": { "iterall": "^1.3.0" }, "peerDependencies": { "graphql": "^0.10.5 || ^0.11.3 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" } }, "sha512-95yD/tKi24q8xYa7Q9rhQN16AYj5wPbrb8tmHGM3WRc9EBmWrG/0kkMl+tQG8wcEuE9ibR4zyOM31p5Sdr2v4g=="],
@ -2302,7 +2302,7 @@
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
"hookified": ["hookified@1.12.1", "", {}, "sha512-xnKGl+iMIlhrZmGHB729MqlmPoWBznctSQTYCpFKqNsCgimJQmithcW0xSQMMFzYnV2iKUh25alswn6epgxS0Q=="],
"hookified": ["hookified@1.12.2", "", {}, "sha512-aokUX1VdTpI0DUsndvW+OiwmBpKCu/NgRsSSkuSY0zq8PY6Q6a+lmOfAFDXAAOtBqJELvcWY9L1EVtzjbQcMdg=="],
"html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="],
@ -2748,7 +2748,7 @@
"mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
"mock-apollo-client": ["mock-apollo-client@1.3.1", "", { "peerDependencies": { "@apollo/client": "^3.0.0" } }, "sha512-jBl1YGofh9RpTUFfShwIumiry5qRkR1LYW12K1iZ576kMFh03psHTRiuY2k3dT6cUQ28RAK4gRFl9lVloazGhA=="],
"mock-apollo-client": ["mock-apollo-client@1.4.0", "", { "peerDependencies": { "@apollo/client": "^3.0.0" } }, "sha512-uUz7p5Oa+Tqgtl4UaeLQ3QkfZb6fl4MBs/R99fKj4u/+Vmzh1vf74z8JQVgvarXZg0RM/oAFGFdqNuRigJFzTA=="],
"moo": ["moo@0.5.2", "", {}, "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q=="],
@ -2802,7 +2802,7 @@
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
"node-releases": ["node-releases@2.0.23", "", {}, "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg=="],
"node-releases": ["node-releases@2.0.25", "", {}, "sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA=="],
"nodemailer": ["nodemailer@6.10.1", "", {}, "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA=="],
@ -2846,7 +2846,7 @@
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"ohash": ["ohash@1.1.6", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
@ -2866,7 +2866,7 @@
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
"oxc-resolver": ["oxc-resolver@11.9.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.9.0", "@oxc-resolver/binding-android-arm64": "11.9.0", "@oxc-resolver/binding-darwin-arm64": "11.9.0", "@oxc-resolver/binding-darwin-x64": "11.9.0", "@oxc-resolver/binding-freebsd-x64": "11.9.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.9.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.9.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.9.0", "@oxc-resolver/binding-linux-arm64-musl": "11.9.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.9.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.9.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.9.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.9.0", "@oxc-resolver/binding-linux-x64-gnu": "11.9.0", "@oxc-resolver/binding-linux-x64-musl": "11.9.0", "@oxc-resolver/binding-wasm32-wasi": "11.9.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.9.0", "@oxc-resolver/binding-win32-ia32-msvc": "11.9.0", "@oxc-resolver/binding-win32-x64-msvc": "11.9.0" } }, "sha512-u714L0DBBXpD0ERErCQlun2XwinuBfIGo2T8bA7xE8WLQ4uaJudO/VOEQCWslOmcDY2nEkS+UVir5PpyvSG23w=="],
"oxc-resolver": ["oxc-resolver@11.11.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.11.0", "@oxc-resolver/binding-android-arm64": "11.11.0", "@oxc-resolver/binding-darwin-arm64": "11.11.0", "@oxc-resolver/binding-darwin-x64": "11.11.0", "@oxc-resolver/binding-freebsd-x64": "11.11.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.11.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.11.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.11.0", "@oxc-resolver/binding-linux-arm64-musl": "11.11.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.11.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.11.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.11.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.11.0", "@oxc-resolver/binding-linux-x64-gnu": "11.11.0", "@oxc-resolver/binding-linux-x64-musl": "11.11.0", "@oxc-resolver/binding-wasm32-wasi": "11.11.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.11.0", "@oxc-resolver/binding-win32-ia32-msvc": "11.11.0", "@oxc-resolver/binding-win32-x64-msvc": "11.11.0" } }, "sha512-vVeBJf77zBeqOA/LBCTO/pr0/ETHGSleCRsI5Kmsf2OsfB5opzhhZptt6VxkqjKWZH+eF1se88fYDG5DGRLjkg=="],
"p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="],
@ -3020,7 +3020,7 @@
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
"qified": ["qified@0.5.0", "", { "dependencies": { "hookified": "^1.12.1" } }, "sha512-Zj6Q/Vc/SQ+Fzc87N90jJUzBzxD7MVQ2ZvGyMmYtnl2u1a07CejAhvtk4ZwASos+SiHKCAIylyGHJKIek75QBw=="],
"qified": ["qified@0.5.1", "", { "dependencies": { "hookified": "^1.12.2" } }, "sha512-+BtFN3dCP+IaFA6IYNOu/f/uK1B8xD2QWyOeCse0rjtAebBmkzgd2d1OAXi3ikAzJMIBSdzZDNZ3wZKEUDQs5w=="],
"qrcanvas": ["qrcanvas@3.1.2", "", { "dependencies": { "@babel/runtime": "^7.11.2", "qrcode-generator": "^1.4.4" } }, "sha512-lNcAyCHN0Eno/mJ5eBc7lHV/5ejAJxII0UELthG3bNnlLR+u8hCc7CR+hXBawbYUf96kNIosXfG2cJzx92ZWKg=="],
@ -3110,7 +3110,7 @@
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"rollup": ["rollup@4.52.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.4", "@rollup/rollup-android-arm64": "4.52.4", "@rollup/rollup-darwin-arm64": "4.52.4", "@rollup/rollup-darwin-x64": "4.52.4", "@rollup/rollup-freebsd-arm64": "4.52.4", "@rollup/rollup-freebsd-x64": "4.52.4", "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", "@rollup/rollup-linux-arm-musleabihf": "4.52.4", "@rollup/rollup-linux-arm64-gnu": "4.52.4", "@rollup/rollup-linux-arm64-musl": "4.52.4", "@rollup/rollup-linux-loong64-gnu": "4.52.4", "@rollup/rollup-linux-ppc64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-musl": "4.52.4", "@rollup/rollup-linux-s390x-gnu": "4.52.4", "@rollup/rollup-linux-x64-gnu": "4.52.4", "@rollup/rollup-linux-x64-musl": "4.52.4", "@rollup/rollup-openharmony-arm64": "4.52.4", "@rollup/rollup-win32-arm64-msvc": "4.52.4", "@rollup/rollup-win32-ia32-msvc": "4.52.4", "@rollup/rollup-win32-x64-gnu": "4.52.4", "@rollup/rollup-win32-x64-msvc": "4.52.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ=="],
"rollup": ["rollup@4.52.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw=="],
"rrweb-cssom": ["rrweb-cssom@0.7.1", "", {}, "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg=="],
@ -3234,7 +3234,7 @@
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
@ -3450,7 +3450,7 @@
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
"udx-native": ["udx-native@1.18.3", "", { "dependencies": { "b4a": "^1.5.0", "bare-events": "^2.2.0", "require-addon": "^1.1.0", "streamx": "^2.22.0" } }, "sha512-GqYA64plvp8+LY20PQvWuS2wY6/UBKLwpWb/po0iPlEYYnyxAqdag1yKeNJJ+HZtV95T4VYxKbT1xV8eV4iTiw=="],
"udx-native": ["udx-native@1.19.2", "", { "dependencies": { "b4a": "^1.5.0", "bare-events": "^2.2.0", "require-addon": "^1.1.0", "streamx": "^2.22.0" } }, "sha512-RNYh+UhfryCsF5hE2ZOuIqcZ+qdipXK3UsarwxWJwsUQZFE3ybwz0mPjwb5ev1PMBcjFahWiepS/q0wwL51c2g=="],
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
@ -3474,7 +3474,7 @@
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"unimport": ["unimport@5.4.1", "", { "dependencies": { "acorn": "^8.15.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.19", "mlly": "^1.8.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "pkg-types": "^2.3.0", "scule": "^1.3.0", "strip-literal": "^3.1.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.10", "unplugin-utils": "^0.3.0" } }, "sha512-wMZ2JKUCleCK2zfRHeWcbrUHKXOC3SVBYkyn/wTGzh0THX6sT4hSjuKXxKANN4/WMbT6ZPM4JzcDcnhD2x9Bpg=="],
"unimport": ["unimport@5.5.0", "", { "dependencies": { "acorn": "^8.15.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.19", "mlly": "^1.8.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "pkg-types": "^2.3.0", "scule": "^1.3.0", "strip-literal": "^3.1.0", "tinyglobby": "^0.2.15", "unplugin": "^2.3.10", "unplugin-utils": "^0.3.0" } }, "sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
@ -3518,7 +3518,7 @@
"vee-validate": ["vee-validate@4.15.1", "", { "dependencies": { "@vue/devtools-api": "^7.5.2", "type-fest": "^4.8.3" }, "peerDependencies": { "vue": "^3.4.26" } }, "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg=="],
"vite": ["vite@5.4.20", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g=="],
"vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
"vite-node": ["vite-node@2.1.9", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA=="],
@ -3674,6 +3674,8 @@
"@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
@ -3716,10 +3718,6 @@
"@intlify/bundle-utils/yaml-eslint-parser": ["yaml-eslint-parser@1.3.0", "", { "dependencies": { "eslint-visitor-keys": "^3.0.0", "yaml": "^2.0.0" } }, "sha512-E/+VitOorXSLiAqtTd7Yqax0/pAS3xaYMP+AUUJGOK1OZG3rhcj9fcJOM5HJ2VrP1FrStVCWr1muTfQCdj4tAA=="],
"@intlify/core-base/@intlify/shared": ["@intlify/shared@9.14.5", "", {}, "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ=="],
"@intlify/message-compiler/@intlify/shared": ["@intlify/shared@9.14.5", "", {}, "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ=="],
"@intlify/unplugin-vue-i18n/@intlify/shared": ["@intlify/shared@11.1.12", "", {}, "sha512-Om86EjuQtA69hdNj3GQec9ZC0L0vPSAnXzB3gP/gyJ7+mA7t06d9aOAiqMZ+xEOsumGP4eEBlfl8zF2LOTzf2A=="],
"@intlify/vue-i18n-extensions/@intlify/shared": ["@intlify/shared@10.0.8", "", {}, "sha512-BcmHpb5bQyeVNrptC3UhzpBZB/YHHDoEREOUERrmF2BRxsyOEuRrq+Z96C/D4+2KJb8kuHiouzAei7BXlG0YYw=="],
@ -3762,15 +3760,13 @@
"@jest/source-map/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"@jest/transform/convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
"@jest/transform/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"@jest/transform/write-file-atomic": ["write-file-atomic@3.0.3", "", { "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q=="],
"@jest/types/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@morev/utils/ohash": ["ohash@1.1.6", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="],
"@keyv/bigmap/keyv": ["keyv@5.5.3", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A=="],
"@morev/utils/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
@ -3778,6 +3774,8 @@
"@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@nuxt/kit/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"@nuxt/kit/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"@nuxt/kit/pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
@ -3824,6 +3822,8 @@
"@types/serve-static/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@types/serve-static/@types/send": ["@types/send@0.17.5", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w=="],
"@types/sodium-native/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@types/source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@ -3930,6 +3930,8 @@
"c12/dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
"c12/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"c12/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"c12/pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
@ -4294,8 +4296,6 @@
"unplugin-vue-components/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"v8-to-istanbul/convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
"vee-validate/@vue/devtools-api": ["@vue/devtools-api@7.7.7", "", { "dependencies": { "@vue/devtools-kit": "^7.7.7" } }, "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg=="],
"vee-validate/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
@ -4320,8 +4320,6 @@
"vue-apollo/throttle-debounce": ["throttle-debounce@2.3.0", "", {}, "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ=="],
"vue-i18n/@intlify/core-base": ["@intlify/core-base@9.13.1", "", { "dependencies": { "@intlify/message-compiler": "9.13.1", "@intlify/shared": "9.13.1" } }, "sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w=="],
"wcwidth/defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
"web-resource-inliner/htmlparser2": ["htmlparser2@5.0.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^3.3.0", "domutils": "^2.4.2", "entities": "^2.0.0" } }, "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ=="],
@ -4348,7 +4346,7 @@
"@hyperswarm/secret-stream/sodium-universal/sodium-native": ["sodium-native@5.0.9", "", { "dependencies": { "require-addon": "^1.1.0", "which-runtime": "^1.2.1" } }, "sha512-6fpu3d6zdrRpLhuV3CDIBO5g90KkgaeR+c3xvDDz0ZnDkAlqbbPhFW7zhMJfsskfZ9SuC3SvBbqvxcECkXRyKw=="],
"@iconify/utils/@antfu/install-pkg/package-manager-detector": ["package-manager-detector@1.4.0", "", {}, "sha512-rRZ+pR1Usc+ND9M2NkmCvE/LYJS+8ORVV9X0KuNSY/gFsp7RBHJM/ADh9LYq4Vvfq6QkKrW6/weuh8SMEtN5gw=="],
"@iconify/utils/@antfu/install-pkg/package-manager-detector": ["package-manager-detector@1.5.0", "", {}, "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw=="],
"@iconify/utils/@antfu/install-pkg/tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
@ -4600,8 +4598,6 @@
"vue-apollo/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
"vue-i18n/@intlify/core-base/@intlify/message-compiler": ["@intlify/message-compiler@9.13.1", "", { "dependencies": { "@intlify/shared": "9.13.1", "source-map-js": "^1.0.2" } }, "sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w=="],
"vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="],
"vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="],

View File

@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`
ALTER TABLE \`dlt_transactions\`
CHANGE \`transactions_id\` \`transaction_id\` INT(10) UNSIGNED NULL DEFAULT NULL,
ADD \`user_id\` INT UNSIGNED NULL DEFAULT NULL AFTER \`transaction_id\`,
ADD \`transaction_link_id\` INT UNSIGNED NULL DEFAULT NULL AFTER \`user_id\`,
ADD \`type_id\` INT UNSIGNED NOT NULL AFTER \`transaction_link_id\`,
ADD \`error\` text NULL DEFAULT NULL AFTER \`verified_at\`
;
`)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`
ALTER TABLE \`dlt_transactions\`
CHANGE \`transaction_id\` \`transactions_id\` INT(10) UNSIGNED NOT NULL,
DROP COLUMN \`user_id\`,
DROP COLUMN \`transaction_link_id\`,
DROP COLUMN \`type_id\`,
DROP COLUMN \`error\`
;
`)
}

View File

@ -93,7 +93,7 @@ export class AppDatabase {
public async destroy(): Promise<void> {
await this.dataSource?.destroy()
}
// ######################################
// private methods
// ######################################

View File

@ -1,13 +1,24 @@
import { BaseEntity, Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm'
import { Transaction } from './Transaction'
import { TransactionLink } from './TransactionLink'
import { User } from './User'
@Entity('dlt_transactions', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
export class DltTransaction extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'transactions_id', type: 'int', unsigned: true, nullable: false })
transactionId: number
@Column({ name: 'transaction_id', type: 'int', unsigned: true, nullable: true })
transactionId?: number | null
@Column({ name: 'user_id', type: 'int', unsigned: true, nullable: true })
userId?: number | null
@Column({ name: 'transaction_link_id', type: 'int', unsigned: true, nullable: true })
transactionLinkId?: number | null
@Column({ name: 'type_id', type: 'int', unsigned: true, nullable: false })
typeId: number
@Column({
name: 'message_id',
@ -34,10 +45,27 @@ export class DltTransaction extends BaseEntity {
@Column({ name: 'verified_at', type: 'datetime', precision: 3, nullable: true, default: null })
verifiedAt: Date | null
@Column({ name: 'error', type: 'text', nullable: true })
error: string | null
@OneToOne(
() => Transaction,
(transaction) => transaction.dltTransaction,
)
@JoinColumn({ name: 'transactions_id' })
@JoinColumn({ name: 'transaction_id' })
transaction?: Transaction | null
@OneToOne(
() => User,
(user) => user.dltTransaction,
)
@JoinColumn({ name: 'user_id' })
user?: User | null
@OneToOne(
() => TransactionLink,
(transactionLink) => transactionLink.dltTransaction,
)
@JoinColumn({ name: 'transaction_link_id' })
transactionLink?: TransactionLink | null
}

View File

@ -1,8 +1,17 @@
/* eslint-disable no-use-before-define */
import { Decimal } from 'decimal.js-light'
import { BaseEntity, Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm'
import {
BaseEntity,
Column,
Entity,
JoinColumn,
ManyToOne,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm'
import { Contribution } from './Contribution'
import { DltTransaction } from './DltTransaction'
import { TransactionLink } from './TransactionLink'
import { DecimalTransformer } from './transformer/DecimalTransformer'
@Entity('transactions')
@ -168,4 +177,11 @@ export class Transaction extends BaseEntity {
@OneToOne(() => Transaction)
@JoinColumn({ name: 'previous' })
previousTransaction?: Transaction | null
@ManyToOne(
() => TransactionLink,
(transactionLink) => transactionLink.transactions,
)
@JoinColumn({ name: 'transaction_link_id' })
transactionLink?: TransactionLink | null
}

View File

@ -1,6 +1,18 @@
import { Decimal } from 'decimal.js-light'
import { BaseEntity, Column, DeleteDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'
import {
BaseEntity,
Column,
DeleteDateColumn,
Entity,
JoinColumn,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm'
import { DltTransaction } from './DltTransaction'
import { Transaction } from './Transaction'
import { DecimalTransformer } from './transformer/DecimalTransformer'
import { User } from './User'
@Entity('transaction_links')
export class TransactionLink extends BaseEntity {
@ -58,4 +70,25 @@ export class TransactionLink extends BaseEntity {
@Column({ type: 'int', unsigned: true, nullable: true })
redeemedBy: number | null
@OneToOne(
() => DltTransaction,
(dlt) => dlt.transactionLinkId,
)
@JoinColumn({ name: 'id', referencedColumnName: 'transactionLinkId' })
dltTransaction?: DltTransaction | null
@OneToOne(
() => User,
(user) => user.transactionLink,
)
@JoinColumn({ name: 'userId' })
user: User
@OneToMany(
() => Transaction,
(transaction) => transaction.transactionLink,
)
@JoinColumn({ referencedColumnName: 'transaction_link_id' })
transactions: Transaction[]
}

View File

@ -13,6 +13,8 @@ import {
import { Community } from './Community'
import { Contribution } from './Contribution'
import { ContributionMessage } from './ContributionMessage'
import { DltTransaction } from './DltTransaction'
import { TransactionLink } from './TransactionLink'
import { UserContact } from './UserContact'
import { UserRole } from './UserRole'
import { GeometryTransformer } from './transformer/GeometryTransformer'
@ -213,4 +215,18 @@ export class User extends BaseEntity {
)
@JoinColumn({ name: 'user_id' })
userContacts?: UserContact[]
@OneToOne(
() => DltTransaction,
(dlt) => dlt.userId,
)
@JoinColumn({ name: 'id', referencedColumnName: 'userId' })
dltTransaction?: DltTransaction | null
@OneToOne(
() => TransactionLink,
(transactionLink) => transactionLink.userId,
)
@JoinColumn({ name: 'id', referencedColumnName: 'userId' })
transactionLink?: TransactionLink | null
}

View File

@ -0,0 +1,34 @@
import { TransactionLink } from '../entity/TransactionLink'
import { AbstractLoggingView } from './AbstractLogging.view'
import { DltTransactionLoggingView } from './DltTransactionLogging.view'
import { TransactionLoggingView } from './TransactionLogging.view'
import { UserLoggingView } from './UserLogging.view'
export class TransactionLinkLoggingView extends AbstractLoggingView {
public constructor(private self: TransactionLink) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
userId: this.self.userId,
amount: this.decimalToString(this.self.amount),
holdAvailableAmount: this.decimalToString(this.self.holdAvailableAmount),
memoLength: this.self.memo.length,
createdAt: this.dateToString(this.self.createdAt),
deletedAt: this.dateToString(this.self.deletedAt),
validUntil: this.dateToString(this.self.validUntil),
redeemedAt: this.dateToString(this.self.redeemedAt),
redeemedBy: this.self.redeemedBy,
dltTransaction: this.self.dltTransaction
? new DltTransactionLoggingView(this.self.dltTransaction).toJSON()
: undefined,
user: this.self.user ? new UserLoggingView(this.self.user).toJSON() : undefined,
transactions: this.self.transactions.forEach(
(transaction) => new TransactionLoggingView(transaction),
),
}
}
}

View File

@ -2,6 +2,7 @@ import { Transaction } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { ContributionLoggingView } from './ContributionLogging.view'
import { DltTransactionLoggingView } from './DltTransactionLogging.view'
import { TransactionLinkLoggingView } from './TransactionLinkLogging.view'
// TODO: move enum into database, maybe rename database
enum TransactionTypeId {
@ -41,7 +42,7 @@ export class TransactionLoggingView extends AbstractLoggingView {
linkedUserName: this.self.linkedUserName?.substring(0, 3) + '...',
linkedTransactionId: this.self.linkedTransactionId,
contribution: this.self.contribution
? new ContributionLoggingView(this.self.contribution)
? new ContributionLoggingView(this.self.contribution).toJSON()
: undefined,
dltTransaction: this.self.dltTransaction
? new DltTransactionLoggingView(this.self.dltTransaction).toJSON()
@ -49,6 +50,9 @@ export class TransactionLoggingView extends AbstractLoggingView {
previousTransaction: this.self.previousTransaction
? new TransactionLoggingView(this.self.previousTransaction).toJSON()
: undefined,
transactionLink: this.self.transactionLink
? new TransactionLinkLoggingView(this.self.transactionLink).toJSON()
: undefined,
}
}
}

View File

@ -8,7 +8,10 @@ enum OptInType {
}
export class UserContactLoggingView extends AbstractLoggingView {
public constructor(private self: UserContact) {
public constructor(
private self: UserContact,
private showUser = true,
) {
super()
}
@ -16,9 +19,10 @@ export class UserContactLoggingView extends AbstractLoggingView {
return {
id: this.self.id,
type: this.self.type,
user: this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
user:
this.showUser && this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
email: this.self.email?.substring(0, 3) + '...',
emailVerificationCode: this.self.emailVerificationCode?.substring(0, 4) + '...',
emailOptInTypeId: OptInType[this.self.emailOptInTypeId],

View File

@ -1,5 +1,6 @@
import { User } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { CommunityLoggingView } from './CommunityLogging.view'
import { ContributionLoggingView } from './ContributionLogging.view'
import { ContributionMessageLoggingView } from './ContributionMessageLogging.view'
import { UserContactLoggingView } from './UserContactLogging.view'
@ -21,10 +22,12 @@ export class UserLoggingView extends AbstractLoggingView {
id: this.self.id,
foreign: this.self.foreign,
gradidoID: this.self.gradidoID,
communityUuid: this.self.communityUuid,
community: this.self.community
? new CommunityLoggingView(this.self.community).toJSON()
: { id: this.self.communityUuid },
alias: this.self.alias?.substring(0, 3) + '...',
emailContact: this.self.emailContact
? new UserContactLoggingView(this.self.emailContact).toJSON()
? new UserContactLoggingView(this.self.emailContact, false).toJSON()
: { id: this.self.emailId },
firstName: this.self.firstName?.substring(0, 3) + '...',
lastName: this.self.lastName?.substring(0, 3) + '...',
@ -35,7 +38,7 @@ export class UserLoggingView extends AbstractLoggingView {
hideAmountGDD: this.self.hideAmountGDD,
hideAmountGDT: this.self.hideAmountGDT,
userRoles: this.self.userRoles
? this.self.userRoles.map((userRole) => new UserRoleLoggingView(userRole).toJSON())
? this.self.userRoles.map((userRole) => new UserRoleLoggingView(userRole, false).toJSON())
: undefined,
referrerId: this.self.referrerId,
contributionLinkId: this.self.contributionLinkId,
@ -50,7 +53,7 @@ export class UserLoggingView extends AbstractLoggingView {
: undefined,
userContacts: this.self.userContacts
? this.self.userContacts.map((userContact) =>
new UserContactLoggingView(userContact).toJSON(),
new UserContactLoggingView(userContact, false).toJSON(),
)
: undefined,
}

View File

@ -3,16 +3,20 @@ import { AbstractLoggingView } from './AbstractLogging.view'
import { UserLoggingView } from './UserLogging.view'
export class UserRoleLoggingView extends AbstractLoggingView {
public constructor(private self: UserRole) {
public constructor(
private self: UserRole,
private showUser = true,
) {
super()
}
public toJSON(): any {
return {
id: this.self.id,
user: this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
user:
this.showUser && this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
role: this.self.role,
createdAt: this.dateToString(this.self.createdAt),
updatedAt: this.dateToString(this.self.updatedAt),

View File

@ -76,7 +76,7 @@ export async function getReachableCommunities(
{
authenticatedAt: Not(IsNull()),
federatedCommunities: {
verifiedAt: MoreThanOrEqual(new Date(Date.now() - authenticationTimeoutMs))
verifiedAt: MoreThanOrEqual(new Date(Date.now() - authenticationTimeoutMs)),
}
},
{ foreign: false },

View File

@ -0,0 +1,15 @@
import { ContributionLink as DbContributionLink, Event as DbEvent, User as DbUser } from '../entity'
export async function findModeratorCreatingContributionLink(
contributionLink: DbContributionLink,
): Promise<DbUser | undefined> {
const event = await DbEvent.findOne({
where: {
involvedContributionLinkId: contributionLink.id,
// todo: move event types into db
type: 'ADMIN_CONTRIBUTION_LINK_CREATE',
},
relations: { actingUser: true },
})
return event?.actingUser
}

View File

@ -2,6 +2,7 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
export * from './user'
export * from './communities'
export * from './events'
export * from './pendingTransactions'
export * from './transactions'
export * from './transactionLinks'

View File

@ -135,6 +135,6 @@ describe('user.queries', () => {
})
})
})
})
})

View File

@ -11,6 +11,17 @@ export async function aliasExists(alias: string): Promise<boolean> {
return user !== null
}
export async function getUserById(
id: number,
withCommunity: boolean = false,
withEmailContact: boolean = false,
): Promise<DbUser> {
return DbUser.findOneOrFail({
where: { id },
relations: { community: withCommunity, emailContact: withEmailContact },
})
}
/**
*
* @param identifier could be gradidoID, alias or email of user

View File

@ -88,6 +88,7 @@ GDT_ACTIVE=false
# DLT-Connector (still in develop)
DLT_CONNECTOR=false
DLT_CONNECTOR_PORT=6010
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER=/home/gradido/.gradido
# used for combining a newsletter on klicktipp with this gradido community
# if used, user will be subscribed on register and can unsubscribe in his account

View File

@ -9,18 +9,16 @@ IOTA_API_URL=https://chrysalis-nodes.iota.org
IOTA_COMMUNITY_ALIAS=GRADIDO: TestHelloWelt2
IOTA_HOME_COMMUNITY_SEED=aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899
# Database
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=gradido_dlt
DB_DATABASE_TEST=gradido_dlt_test
TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log
# DLT-Connector
DLT_CONNECTOR_PORT=6010
# Gradido Node Server URL
DLT_NODE_SERVER_PORT=8340
# Gradido Blockchain
GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET=21ffbbc616fe
GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
# Route to Backend
BACKEND_SERVER_URL=http://localhost:4000
PORT=4000
JWT_SECRET=secret123

View File

@ -1,23 +0,0 @@
CONFIG_VERSION=$DLT_CONNECTOR_CONFIG_VERSION
JWT_SECRET=$JWT_SECRET
#IOTA
IOTA_API_URL=$IOTA_API_URL
IOTA_COMMUNITY_ALIAS=$IOTA_COMMUNITY_ALIAS
IOTA_HOME_COMMUNITY_SEED=$IOTA_HOME_COMMUNITY_SEED
# Database
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=gradido_dlt
DB_DATABASE_TEST=$DB_DATABASE_TEST
TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log
# DLT-Connector
DLT_CONNECTOR_PORT=$DLT_CONNECTOR_PORT
# Route to Backend
BACKEND_SERVER_URL=http://localhost:4000

View File

@ -1,4 +0,0 @@
node_modules
**/*.min.js
build
coverage

View File

@ -1,207 +0,0 @@
// eslint-disable-next-line import/no-commonjs, import/unambiguous
module.exports = {
root: true,
env: {
node: true,
},
parser: '@typescript-eslint/parser',
plugins: ['prettier', '@typescript-eslint', 'import', 'n', 'promise'],
extends: [
'standard',
'eslint:recommended',
'plugin:prettier/recommended',
// 'plugin:import/recommended',
// 'plugin:import/typescript',
// 'plugin:security/recommended',
'plugin:@eslint-community/eslint-comments/recommended',
'plugin:dci-lint/recommended',
],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
project: ['./tsconfig.json'],
},
node: true,
},
},
rules: {
'no-console': 'error',
camelcase: 'error',
'no-debugger': 'error',
'prettier/prettier': [
'error',
{
htmlWhitespaceSensitivity: 'ignore',
},
],
// 'dci-lint/literal-role-contracts': 'off'
// import
// 'import/export': 'error',
// 'import/no-deprecated': 'error',
// 'import/no-empty-named-blocks': 'error',
// 'import/no-extraneous-dependencies': 'error',
// 'import/no-mutable-exports': 'error',
// 'import/no-unused-modules': 'error',
// 'import/no-named-as-default': 'error',
// 'import/no-named-as-default-member': 'error',
// 'import/no-amd': 'error',
// 'import/no-commonjs': 'error',
// 'import/no-import-module-exports': 'error',
// 'import/no-nodejs-modules': 'off',
// 'import/unambiguous': 'error',
// 'import/default': 'error',
// 'import/named': 'error',
// 'import/namespace': 'error',
// 'import/no-absolute-path': 'error',
// 'import/no-cycle': 'error',
// 'import/no-dynamic-require': 'error',
// 'import/no-internal-modules': 'off',
// 'import/no-relative-packages': 'error',
// 'import/no-relative-parent-imports': ['error', { ignore: ['@/*'] }],
// 'import/no-self-import': 'error',
// 'import/no-unresolved': 'error',
// 'import/no-useless-path-segments': 'error',
// 'import/no-webpack-loader-syntax': 'error',
// 'import/consistent-type-specifier-style': 'error',
// 'import/exports-last': 'off',
// 'import/extensions': 'error',
// 'import/first': 'error',
// 'import/group-exports': 'off',
// 'import/newline-after-import': 'error',
// 'import/no-anonymous-default-export': 'error',
// 'import/no-default-export': 'error',
// 'import/no-duplicates': 'error',
// 'import/no-named-default': 'error',
// 'import/no-namespace': 'error',
// 'import/no-unassigned-import': 'error',
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
pathGroups: [
{
pattern: '@?*/**',
group: 'external',
position: 'after',
},
{
pattern: '@/**',
group: 'external',
position: 'after',
},
],
alphabetize: {
order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
caseInsensitive: true /* ignore case. Options: [true, false] */,
},
distinctGroup: true,
},
],
// 'import/prefer-default-export': 'off',
// n
'n/handle-callback-err': 'error',
'n/no-callback-literal': 'error',
'n/no-exports-assign': 'error',
'n/no-extraneous-import': 'error',
'n/no-extraneous-require': 'error',
'n/no-hide-core-modules': 'error',
'n/no-missing-import': 'off', // not compatible with typescript
'n/no-missing-require': 'error',
'n/no-new-require': 'error',
'n/no-path-concat': 'error',
'n/no-process-exit': 'error',
'n/no-unpublished-bin': 'error',
'n/no-unpublished-import': 'off', // TODO need to exclude seeds
'n/no-unpublished-require': 'error',
'n/no-unsupported-features': ['error', { ignores: ['modules'] }],
'n/no-unsupported-features/es-builtins': 'error',
'n/no-unsupported-features/es-syntax': 'error',
'n/no-unsupported-features/node-builtins': 'error',
'n/process-exit-as-throw': 'error',
'n/shebang': 'error',
'n/callback-return': 'error',
'n/exports-style': 'error',
'n/file-extension-in-import': 'off',
'n/global-require': 'error',
'n/no-mixed-requires': 'error',
'n/no-process-env': 'error',
'n/no-restricted-import': 'error',
'n/no-restricted-require': 'error',
'n/no-sync': 'error',
'n/prefer-global/buffer': 'error',
'n/prefer-global/console': 'error',
'n/prefer-global/process': 'error',
'n/prefer-global/text-decoder': 'error',
'n/prefer-global/text-encoder': 'error',
'n/prefer-global/url': 'error',
'n/prefer-global/url-search-params': 'error',
'n/prefer-promises/dns': 'error',
'n/prefer-promises/fs': 'error',
// promise
// 'promise/catch-or-return': 'error',
// 'promise/no-return-wrap': 'error',
// 'promise/param-names': 'error',
// 'promise/always-return': 'error',
// 'promise/no-native': 'off',
// 'promise/no-nesting': 'warn',
// 'promise/no-promise-in-callback': 'warn',
// 'promise/no-callback-in-promise': 'warn',
// 'promise/avoid-new': 'warn',
// 'promise/no-new-statics': 'error',
// 'promise/no-return-in-finally': 'warn',
// 'promise/valid-params': 'warn',
// 'promise/prefer-await-to-callbacks': 'error',
// 'promise/no-multiple-resolved': 'error',
// eslint comments
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
'@eslint-community/eslint-comments/no-restricted-disable': 'error',
'@eslint-community/eslint-comments/no-use': 'off',
'@eslint-community/eslint-comments/require-description': 'off',
},
overrides: [
// only for ts files
{
files: ['*.ts', '*.tsx'],
extends: [
'plugin:@typescript-eslint/recommended',
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
// 'plugin:@typescript-eslint/strict',
],
rules: {
// allow explicitly defined dangling promises
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
// ignore prefer-regexp-exec rule to allow string.match(regex)
'@typescript-eslint/prefer-regexp-exec': 'off',
// this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486
'import/unambiguous': 'off',
// this is not compatible with typeorm, due to joined tables can be null, but are not defined as nullable
'@typescript-eslint/no-unnecessary-condition': 'off',
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
// this is to properly reference the referenced project database without requirement of compiling it
// eslint-disable-next-line camelcase
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
},
},
{
files: ['*.test.ts'],
plugins: ['jest'],
rules: {
'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'error',
'jest/valid-expect': 'error',
'@typescript-eslint/unbound-method': 'off',
'jest/unbound-method': 'error',
},
},
],
}

View File

@ -2,7 +2,9 @@
/.env
/.env.bak
/build/
/locales/
package-json.lock
coverage
# emacs
*~
gradido_node

View File

@ -1 +0,0 @@
v19.5.0

View File

@ -1,9 +0,0 @@
module.exports = {
semi: false,
printWidth: 100,
singleQuote: true,
trailingComma: "all",
tabWidth: 2,
bracketSpacing: true,
endOfLine: "auto",
};

View File

@ -1 +0,0 @@
declare module 'bip32-ed25519'

View File

@ -1,8 +1,10 @@
##################################################################################
# BASE ###########################################################################
##################################################################################
FROM node:19.5.0-alpine3.17 as base
#FROM ubuntu:latest as base
#FROM node:18.20.7-bookworm-slim as base
FROM oven/bun:1.3.0-slim as base
#FROM node:18.20.7-alpine3.21 as base
# change to alpine after sodium-native ship with native alpine build
# ENVs (available in production aswell, can be overwritten by commandline or env file)
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
@ -14,14 +16,16 @@ ENV BUILD_VERSION="0.0.0.0"
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
ENV BUILD_COMMIT="0000000"
## SET NODE_ENV
ENV NODE_ENV="production"
ENV NODE_ENV=production
## App relevant Envs
ENV PORT="6010"
ENV PORT="4000"
## Timezone
ENV TZ=UTC
# Labels
LABEL org.label-schema.build-date="${BUILD_DATE}"
LABEL org.label-schema.name="gradido:dlt-connector"
LABEL org.label-schema.description="Gradido dlt-connector"
LABEL org.label-schema.description="Gradido DLT Connector"
LABEL org.label-schema.usage="https://github.com/gradido/gradido/blob/master/README.md"
LABEL org.label-schema.url="https://gradido.net"
LABEL org.label-schema.vcs-url="https://github.com/gradido/gradido/tree/master/dlt-connector"
@ -32,9 +36,9 @@ LABEL org.label-schema.schema-version="1.0"
LABEL maintainer="support@gradido.net"
# Install Additional Software
## install: @iota/client requirements
# Install Build Tool for Rust for @iota/client
RUN apk add --no-cache rust cargo python3 make g++
## install: git
#RUN apk --no-cache add git
# Settings
## Expose Container Port
@ -44,42 +48,40 @@ EXPOSE ${PORT}
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
RUN mkdir -p /dlt-database
##################################################################################
# BUN ############################################################################
##################################################################################
#FROM base as bun-base
#RUN apt update && apt install -y --no-install-recommends ca-certificates curl bash unzip
#COPY .bun-version .bun-version
#RUN apk update && apk add --no-cache curl tar bash
#RUN BUN_VERSION=$(cat .bun-version) && \
# curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}"
# Add bun's global bin directory to PATH
#ENV PATH="/root/.bun/bin:${PATH}"
##################################################################################
# DEVELOPMENT (Connected to the local environment, to reload on demand) ##########
# Development ####################################################################
##################################################################################
FROM base as development
# We don't need to copy or build anything since we gonna bind to the
# local filesystem which will need a rebuild anyway
FROM base AS development
# Run command
# (for development we need to execute yarn install since the
# node_modules are on another volume and need updating)
CMD /bin/sh -c "cd /dlt-database && yarn install && yarn build && cd /app && yarn install && yarn run dev"
CMD /bin/sh -c "cd dlt-connector && bun install --no-cache --frozen-lockfile && bun dev"
##################################################################################
# BUILD (Does contain all files and is therefore bloated) ########################
# Basic Image with bun setup and project and source code #########################
##################################################################################
FROM base as build
FROM base as base-src
COPY --chown=app:app ./dlt-connector ./dlt-connector
# Copy everything from dlt-connector
COPY ./dlt-connector/ ./
# Copy everything from dlt-database
COPY ./dlt-database/ ../dlt-database/
##################################################################################
# Build ##########################################################################
##################################################################################
FROM base-src as build
# yarn install dlt-connector
RUN yarn install --production=false --frozen-lockfile --non-interactive
# yarn install dlt-database
RUN cd ../dlt-database && yarn install --production=false --frozen-lockfile --non-interactive
# yarn build
RUN yarn run build
# yarn build dlt-database
RUN cd ../dlt-database && yarn run build
RUN cd dlt-connector && bun install --no-cache --frozen-lockfile
RUN cd dlt-connector && bun typecheck && bun run build
##################################################################################
# TEST ###########################################################################
@ -87,33 +89,31 @@ RUN cd ../dlt-database && yarn run build
FROM build as test
# Run command
CMD /bin/sh -c "yarn run start"
CMD /bin/sh -c "cd dlt-connector && bun test"
##################################################################################
# install only node modules needed for running bundle ############################
##################################################################################
FROM base-src as production-node-modules
COPY ./scripts ./scripts
# add node_modules from production_node_modules
RUN cd dlt-connector && bun install --production --frozen-lockfile --no-cache \
&& rm -rf /tmp/* ~/.cache node_modules/.cache \
&& ../scripts/clean-prebuilds.sh
##################################################################################
# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) #
##################################################################################
FROM base as production
# remove iota build tools to have production docker image smaller
RUN apk del rust cargo python3 make g++
# Copy "binary"-files from build image
COPY --from=build ${DOCKER_WORKDIR}/build ./build
COPY --from=build ${DOCKER_WORKDIR}/../dlt-database/build ../dlt-database/build
# We also copy the node_modules express and serve-static for the run script
COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
COPY --from=build ${DOCKER_WORKDIR}/../dlt-database/node_modules ../dlt-database/node_modules
# Copy static files
# COPY --from=build ${DOCKER_WORKDIR}/public ./public
# Copy package.json for script definitions (lock file should not be needed)
COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
# Copy tsconfig.json to provide alias path definitions
COPY --from=build ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json
# Copy log4js-config.json to provide log configuration
COPY --from=build ${DOCKER_WORKDIR}/log4js-config.json ./log4js-config.json
COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/dlt-connector/build/index.js ./index.js
# add node_modules from production_node_modules
COPY --chown=app:app --from=production-node-modules ${DOCKER_WORKDIR}/dlt-connector/node_modules ./node_modules
# Copy run scripts run/
# COPY --from=build ${DOCKER_WORKDIR}/run ./run
COPY ./dlt-connector/.env .
COPY ./dlt-connector/log4js-config.json .
# Run command
CMD /bin/sh -c "yarn run start"
CMD ["bun", "index.js"]

62
dlt-connector/README.md Normal file
View File

@ -0,0 +1,62 @@
# DLT Connector
## Overview
This implements the **DLT Connector** using [gradido-blockchain-js](https://github.com/gradido/gradido-blockchain-js) as a Node module.
[gradido-blockchain-js](https://github.com/gradido/gradido-blockchain-js) builds the native library [gradido_blockchain](https://github.com/gradido/gradido_blockchain) via SWIG, making it accessible to Node.js.
Most of the Logic is handled by gradido-blockchain.
The connectors purpose is to send Gradido transactions, serialized in blockchain format, through the Hiero SDK into the Hedera Network as Topic Messages.
The [gradido-node](https://github.com/gradido/gradido_node) listens to these Hedera/Hiero topics, validates the transactions, and stores them efficiently.
Transactions can then be retrieved via a JSON-RPC 2.0 API from [gradido-node](https://github.com/gradido/gradido_node)
---
## Structure
The module makes extensive use of schema validation with [Valibot](https://valibot.dev/guides/introduction/).
All objects without internal logic are represented as Valibot schemas, with their corresponding TypeScript types inferred automatically.
Valibot allows clear separation of *input* and *output* types, reducing the need for repetitive `null | undefined` checks.
When a function expects an output type, TypeScript ensures that the data has been validated via `parse` or `safeParse`, guaranteeing type safety at runtime.
---
### `src/bootstrap`
Contains initialization code executed once at program startup by `src/index.ts`.
### `src/cache`
Contains code for caching expensive computations or remote data.
Currently used only by `KeyPairCacheManager`.
### `src/client`
Contains the client implementations for communication with
[`gradido-node`](https://github.com/gradido/gradido_node), the backend, and the Hiero service.
Each `<Name>Client` class is a singleton providing:
- configuration and connection management
- API-call methods mirroring the target service
Each client may include optional `input.schema.ts` and/or `output.schema.ts` files defining Valibot schemas for its complex data structures.
### `src/config`
Contains the Valibot-based configuration schema, default values, and logic for parsing `process.env`.
If a required field is missing or invalid, the module prints an error and terminates the process.
### `src/data`
Contains DCI (Data-Context-Interaction) Data Objects:
simple domain objects that are difficult to express as Valibot schemas, as well as Logic Objects containing core business logic.
Also includes domain enums.
### `src/interactions`
Contains complex business logic (Interactions in DCI terms).
Each use case resides in its own subfolder with one Context Object and corresponding Role Objects.
### `src/schemas`
Contains Valibot schemas shared across multiple parts of the program.
### `src/server`
Contains the [Elysia](https://elysiajs.com/at-glance.html)-based REST API used by the backend to submit new Gradido transactions.
It is intended to integrate with Valibot schemas; currently, it uses `@sinclair/typebox` to convert Valibot schemas to the native format expected by Elysia.
### `src/utils`
Contains small, generic helper functions that do not clearly fit into any of the other directories.
---

154
dlt-connector/biome.json Normal file
View File

@ -0,0 +1,154 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
"files": {
"ignoreUnknown": false,
"includes": [
"**/package.json",
"src/**/*.js",
"src/**/*.ts",
"!**/build",
"!**/node_modules",
"!**/coverage"
]
},
"formatter": {
"enabled": true,
"useEditorconfig": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 100,
"attributePosition": "auto",
"bracketSpacing": true
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"enabled": true,
"rules": {
"recommended": false,
"complexity": {
"noExtraBooleanCast": "error",
"noUselessCatch": "error",
"noUselessConstructor": "error",
"noUselessLoneBlockStatements": "error",
"noUselessRename": "error",
"noUselessTernary": "error",
"noUselessUndefinedInitialization": "error",
"noVoid": "error",
"useLiteralKeys": "error",
"useRegexLiterals": "error",
"noAdjacentSpacesInRegex": "error",
"noCommaOperator": "error"
},
"correctness": {
"noConstAssign": "error",
"noConstantCondition": "error",
"noEmptyCharacterClassInRegex": "error",
"noEmptyPattern": "error",
"noGlobalObjectCalls": "error",
"noInnerDeclarations": "error",
"noInvalidConstructorSuper": "error",
"noInvalidUseBeforeDeclaration": "error",
"noNodejsModules": "off",
"noNonoctalDecimalEscape": "error",
"noPrecisionLoss": "error",
"noSelfAssign": "error",
"noSetterReturn": "error",
"noSwitchDeclarations": "error",
"noUndeclaredVariables": "error",
"noUnreachable": "error",
"noUnreachableSuper": "error",
"noUnsafeFinally": "error",
"noUnsafeOptionalChaining": "error",
"noUnusedLabels": "error",
"noUnusedVariables": "error",
"useIsNan": "error",
"useValidForDirection": "error",
"useYield": "error",
"noInvalidBuiltinInstantiation": "error",
"useValidTypeof": "error"
},
"security": { "noGlobalEval": "error" },
"style": {
"noDefaultExport": "error",
"noYodaExpression": "error",
"useBlockStatements": "error",
"useConsistentBuiltinInstantiation": "error",
"useConst": "error",
"useImportType": "off",
"useSingleVarDeclarator": "error",
"useArrayLiterals": "error"
},
"suspicious": {
"noAsyncPromiseExecutor": "error",
"noCatchAssign": "error",
"noClassAssign": "error",
"noCompareNegZero": "error",
"noConsole": "error",
"noControlCharactersInRegex": "error",
"noDebugger": "error",
"noDoubleEquals": "error",
"noDuplicateCase": "error",
"noDuplicateClassMembers": "error",
"noDuplicateObjectKeys": "error",
"noDuplicateParameters": "error",
"noEmptyBlockStatements": "error",
"noFallthroughSwitchClause": "error",
"noFunctionAssign": "error",
"noGlobalAssign": "error",
"noImportAssign": "error",
"noMisleadingCharacterClass": "error",
"noPrototypeBuiltins": "error",
"noRedeclare": "error",
"noSelfCompare": "error",
"noShadowRestrictedNames": "error",
"noSparseArray": "error",
"noUnsafeNegation": "error",
"useDefaultSwitchClauseLast": "error",
"useGetterReturn": "error",
"noWith": "error",
"noVar": "warn"
}
},
"includes": ["**", "!**/node_modules", "!**/*.min.js", "!**/build", "!**/coverage"]
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "single",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "asNeeded",
"arrowParentheses": "always",
"bracketSameLine": false,
"quoteStyle": "single",
"attributePosition": "auto",
"bracketSpacing": true
},
"globals": [
"document",
"navigator",
"window",
"describe",
"test",
"it",
"expect",
"beforeAll",
"beforeEach",
"afterAll",
"afterEach",
"jest"
],
"parser": {
"unsafeParameterDecoratorsEnabled": true
}
},
"overrides": [
{
"includes": ["**/*.ts", "**/*.tsx"],
"linter": { "rules": { "complexity": { "noVoid": "error" } } }
},
{ "includes": ["**/*.test.ts"], "linter": { "rules": {} } }
]
}

1090
dlt-connector/bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
verbose: true,
preset: 'ts-jest',
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
coverageThreshold: {
global: {
lines: 72,
},
},
setupFiles: ['<rootDir>/test/testSetup.ts'],
setupFilesAfterEnv: [],
modulePathIgnorePatterns: ['<rootDir>/build/'],
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
'@arg/(.*)': '<rootDir>/src/graphql/arg/$1',
'@controller/(.*)': '<rootDir>/src/controller/$1',
'@enum/(.*)': '<rootDir>/src/graphql/enum/$1',
'@model/(.*)': '<rootDir>/src/graphql/model/$1',
'@resolver/(.*)': '<rootDir>/src/graphql/resolver/$1',
'@input/(.*)': '<rootDir>/src/graphql/input/$1',
'@proto/(.*)': '<rootDir>/src/proto/$1',
'@test/(.*)': '<rootDir>/test/$1',
'@typeorm/(.*)': '<rootDir>/src/typeorm/$1',
'@client/(.*)': '<rootDir>/src/client/$1',
'@entity/(.*)':
// eslint-disable-next-line n/no-process-env
process.env.NODE_ENV === 'development'
? '<rootDir>/../dlt-database/entity/$1'
: '<rootDir>/../dlt-database/build/entity/$1',
'@dbTools/(.*)':
// eslint-disable-next-line n/no-process-env
process.env.NODE_ENV === 'development'
? '<rootDir>/../dlt-database/src/$1'
: '<rootDir>/../dlt-database/build/src/$1',
'@validator/(.*)': '<rootDir>/src/graphql/validator/$1',
},
}
/*
@arg/*": ["src/graphql/arg/*"],
"@enum/*": ["src/graphql/enum/*"],
"@input/*": ["src/graphql/input/*"],
"@resolver/*": ["src/graphql/resolver/*"],
"@scalar/*": ["src/graphql/scalar/*"],
"@test/*": ["test/*"],
"@proto/*" : ["src/proto/*"],
*/

View File

@ -8,7 +8,20 @@
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%f : %l] - %m"
},
"compress": true,
"keepFileExt" : true,
"fileNameSep" : "_",
"numBackups" : 30
},
"dlt.client.HieroClient": {
"type": "dateFile",
"filename": "../logs/dlt-connector/apiversion-%v.log",
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [topicId=%X{topicId}] [%f : %l] - %m"
},
"compress": true,
"keepFileExt" : true,
@ -22,7 +35,7 @@
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%f : %l] - %m"
},
"compress": true,
"keepFileExt" : true,
@ -40,7 +53,7 @@
"type": "stdout",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%f : %l] - %m"
}
}
},

View File

@ -1,79 +1,46 @@
{
"name": "gradido-dlt-connector",
"name": "dlt-connector",
"repository": "git@github.com:gradido/gradido.git",
"version": "2.6.1",
"description": "Gradido DLT-Connector",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/",
"author": "Dario Rekowski",
"author": "Gradido Academy - https://www.gradido.net",
"license": "Apache-2.0",
"private": false,
"private": true,
"scripts": {
"build": "tsc --build",
"clean": "tsc --build --clean",
"start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js",
"dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r dotenv/config -r tsconfig-paths/register src/index.ts",
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --forceExit --detectOpenHandles"
"start": "bun run src/index.ts",
"build": "bun build src/index.ts --outdir=build --target=bun --external=gradido-blockchain-js --minify",
"dev": "bun run --watch src/index.ts",
"test": "bun test",
"test:debug": "bun test --inspect-brk",
"typecheck": "tsc --noEmit",
"lint": "biome check --error-on-warnings .",
"lint:fix": "biome check --error-on-warnings . --write"
},
"dependencies": {
"@apollo/server": "^4.7.5",
"@apollo/utils.fetcher": "^3.0.0",
"@iota/client": "^2.2.4",
"bip32-ed25519": "^0.0.4",
"bip39": "^3.1.0",
"body-parser": "^1.20.2",
"class-validator": "^0.14.0",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"decimal.js-light": "^2.5.1",
"dlt-database": "file:../dlt-database",
"dotenv": "10.0.0",
"express": "4.17.1",
"express-slow-down": "^2.0.1",
"graphql": "^16.7.1",
"graphql-request": "^6.1.0",
"graphql-scalars": "^1.22.2",
"helmet": "^7.1.0",
"jose": "^5.2.2",
"log4js": "^6.7.1",
"nodemon": "^2.0.20",
"protobufjs": "^7.2.5",
"reflect-metadata": "^0.1.13",
"sodium-native": "^4.0.4",
"tsconfig-paths": "^4.1.2",
"type-graphql": "^2.0.0-beta.2",
"uuid": "^9.0.1"
"gradido-blockchain-js": "git+https://github.com/gradido/gradido-blockchain-js"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^3.2.1",
"@graphql-tools/mock": "^9.0.0",
"@types/cors": "^2.8.13",
"@types/jest": "^27.0.2",
"@types/node": "^18.11.18",
"@types/sodium-native": "^2.3.5",
"@biomejs/biome": "2.0.0",
"@hashgraph/sdk": "^2.70.0",
"@sinclair/typebox": "^0.34.33",
"@sinclair/typemap": "^0.10.1",
"@types/adm-zip": "^0.5.7",
"@types/bun": "^1.2.17",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
"eslint-config-standard": "^17.0.0",
"eslint-import-resolver-typescript": "^3.5.4",
"eslint-plugin-dci-lint": "^0.3.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-security": "^1.7.1",
"jest": "^27.2.4",
"prettier": "^2.8.7",
"ts-jest": "^27.0.5",
"ts-node": "^10.9.1",
"typeorm": "^0.3.17",
"typeorm-extension": "^3.0.1",
"typescript": "^4.9.4"
"adm-zip": "^0.5.16",
"async-mutex": "^0.5.0",
"dotenv": "^10.0.0",
"elysia": "1.3.8",
"graphql-request": "^7.2.0",
"jose": "^5.2.2",
"jsonrpc-ts-client": "^0.2.3",
"log4js": "^6.9.1",
"typescript": "^5.8.3",
"uuid": "^8.3.2",
"valibot": "1.1.0"
},
"engines": {
"node": ">=14"
}
"node": ">=18"
},
"module": "src/index.js"
}

View File

@ -1,98 +0,0 @@
# -----------------------------------------------
# !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!!
# !!! DO NOT MODIFY THIS FILE BY YOURSELF !!!
# -----------------------------------------------
type Community {
confirmedAt: String!
createdAt: String!
foreign: Boolean!
id: Int!
iotaTopic: String!
rootPublicKeyHex: String!
}
input CommunityDraft {
createdAt: String!
foreign: Boolean!
uuid: String!
}
"""The `Decimal` scalar type to represent currency values"""
scalar Decimal
"""Type of the transaction"""
enum InputTransactionType {
CREATION
RECEIVE
SEND
}
type Mutation {
addCommunity(data: CommunityDraft!): TransactionResult!
sendTransaction(data: TransactionDraft!): TransactionResult!
}
type Query {
communities(confirmed: Boolean, foreign: Boolean, uuid: String): [Community!]!
community(confirmed: Boolean, foreign: Boolean, uuid: String): Community!
isCommunityExist(confirmed: Boolean, foreign: Boolean, uuid: String): Boolean!
}
input TransactionDraft {
amount: Decimal!
backendTransactionId: Int!
createdAt: String!
recipientUser: UserIdentifier!
senderUser: UserIdentifier!
targetDate: String
type: InputTransactionType!
}
type TransactionError {
message: String!
name: String!
type: TransactionErrorType!
}
"""Transaction Error Type"""
enum TransactionErrorType {
ALREADY_EXIST
DB_ERROR
INVALID_SIGNATURE
LOGIC_ERROR
MISSING_PARAMETER
NOT_FOUND
NOT_IMPLEMENTED_YET
PROTO_DECODE_ERROR
PROTO_ENCODE_ERROR
}
type TransactionRecipe {
createdAt: String!
id: Int!
topic: String!
type: TransactionType!
}
type TransactionResult {
error: TransactionError
recipe: TransactionRecipe
succeed: Boolean!
}
"""Type of the transaction"""
enum TransactionType {
COMMUNITY_ROOT
GRADIDO_CREATION
GRADIDO_DEFERRED_TRANSFER
GRADIDO_TRANSFER
GROUP_FRIENDS_UPDATE
REGISTER_ADDRESS
}
input UserIdentifier {
accountNr: Int = 1
communityUuid: String
uuid: String!
}

View File

@ -0,0 +1,26 @@
import { KeyPairCacheManager } from '../cache/KeyPairCacheManager'
import { BackendClient } from '../client/backend/BackendClient'
import { GradidoNodeClient } from '../client/GradidoNode/GradidoNodeClient'
import { HieroClient } from '../client/hiero/HieroClient'
export type AppContextClients = {
backend: BackendClient
hiero: HieroClient
gradidoNode: GradidoNodeClient
}
export type AppContext = {
cache: KeyPairCacheManager
clients: AppContextClients
}
export function createAppContext(): AppContext {
return {
cache: KeyPairCacheManager.getInstance(),
clients: {
backend: BackendClient.getInstance(),
hiero: HieroClient.getInstance(),
gradidoNode: GradidoNodeClient.getInstance(),
},
}
}

View File

@ -0,0 +1,95 @@
import { readFileSync } from 'node:fs'
import { loadCryptoKeys, MemoryBlock } from 'gradido-blockchain-js'
import { configure, getLogger, Logger } from 'log4js'
import * as v from 'valibot'
import { CONFIG } from '../config'
import { MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE } from '../config/const'
import { SendToHieroContext } from '../interactions/sendToHiero/SendToHiero.context'
import { Community, communitySchema } from '../schemas/transaction.schema'
import { isPortOpenRetry } from '../utils/network'
import { type AppContext, type AppContextClients } from './appContext'
import { initGradidoNode } from './initGradidoNode'
export function loadConfig(): Logger {
// configure log4js
// TODO: replace late by loader from config-schema
const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8'))
configure(options)
const logger = getLogger('dlt')
// load crypto keys for gradido blockchain lib
loadCryptoKeys(
MemoryBlock.fromHex(CONFIG.GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET),
MemoryBlock.fromHex(CONFIG.GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY),
)
return logger
}
export async function checkHieroAccount(logger: Logger, clients: AppContextClients): Promise<void> {
const balance = await clients.hiero.getBalance()
logger.info(`Hiero Account Balance: ${balance.hbars.toString()}`)
}
export async function checkHomeCommunity(
appContext: AppContext,
logger: Logger,
): Promise<Community> {
const { backend, hiero } = appContext.clients
// wait for backend server
await isPortOpenRetry(backend.url)
// ask backend for home community
let homeCommunity = await backend.getHomeCommunityDraft()
// on missing topicId, create one
if (!homeCommunity.hieroTopicId) {
const topicId = await hiero.createTopic(homeCommunity.name)
// update topic on backend server
homeCommunity = await backend.setHomeCommunityTopicId(homeCommunity.uuid, topicId)
} else {
// if topic exist, check if we need to update it
let topicInfo = await hiero.getTopicInfo(homeCommunity.hieroTopicId)
// console.log(`topicInfo: ${JSON.stringify(topicInfo, null, 2)}`)
if (
topicInfo.expirationTime.getTime() - new Date().getTime() <
MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE
) {
await hiero.updateTopic(homeCommunity.hieroTopicId)
topicInfo = await hiero.getTopicInfo(homeCommunity.hieroTopicId)
logger.info(
`updated topic info, new expiration time: ${topicInfo.expirationTime.toLocaleDateString()}`,
)
}
}
if (!homeCommunity.hieroTopicId) {
throw new Error('still no topic id, after creating topic and update community in backend.')
}
appContext.cache.setHomeCommunityTopicId(homeCommunity.hieroTopicId)
logger.info(`home community topic: ${homeCommunity.hieroTopicId}`)
logger.info(`gradido node server: ${appContext.clients.gradidoNode.url}`)
logger.info(`gradido backend server: ${appContext.clients.backend.url}`)
return v.parse(communitySchema, homeCommunity)
}
export async function checkGradidoNode(
clients: AppContextClients,
logger: Logger,
homeCommunity: Community,
): Promise<void> {
// check if gradido node is running, if not setup and start it
await initGradidoNode(clients)
// ask gradido node if community blockchain was created
try {
if (
!(await clients.gradidoNode.getTransaction({
transactionId: 1,
topic: homeCommunity.hieroTopicId,
}))
) {
// if not exist, create community root transaction
await SendToHieroContext(homeCommunity)
}
} catch (e) {
logger.error(`error requesting gradido node: ${e}`)
}
}

View File

@ -0,0 +1,89 @@
import { execSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import AdmZip from 'adm-zip'
import { getLogger } from 'log4js'
import { exportCommunities } from '../client/GradidoNode/communities'
import { GradidoNodeProcess } from '../client/GradidoNode/GradidoNodeProcess'
import { HieroClient } from '../client/hiero/HieroClient'
import { CONFIG } from '../config'
import {
GRADIDO_NODE_HOME_FOLDER_NAME,
GRADIDO_NODE_RUNTIME_PATH,
LOG4JS_BASE_CATEGORY,
} from '../config/const'
import { checkFileExist, checkPathExist } from '../utils/filesystem'
import { isPortOpen } from '../utils/network'
import { AppContextClients } from './appContext'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.bootstrap.initGradidoNode`)
export async function initGradidoNode(clients: AppContextClients): Promise<void> {
const url = `http://localhost:${CONFIG.DLT_NODE_SERVER_PORT}`
const isOpen = await isPortOpen(url)
if (isOpen) {
logger.info(`GradidoNode is already running on ${url}`)
return
}
const gradidoNodeHomeFolder = path.join(
CONFIG.DLT_GRADIDO_NODE_SERVER_HOME_FOLDER,
GRADIDO_NODE_HOME_FOLDER_NAME,
)
// check folder, create when missing
checkPathExist(gradidoNodeHomeFolder, true)
await Promise.all([
// write Hedera Address Book
exportHederaAddressbooks(gradidoNodeHomeFolder, clients.hiero),
// check GradidoNode Runtime, download when missing
ensureGradidoNodeRuntimeAvailable(GRADIDO_NODE_RUNTIME_PATH),
// export communities to GradidoNode Folder
exportCommunities(gradidoNodeHomeFolder, clients.backend),
])
GradidoNodeProcess.getInstance().start()
}
async function exportHederaAddressbooks(
homeFolder: string,
hieroClient: HieroClient,
): Promise<void> {
const networkName = CONFIG.HIERO_HEDERA_NETWORK
const addressBook = await hieroClient.downloadAddressBook()
const addressBookPath = path.join(homeFolder, 'addressbook', `${networkName}.pb`)
checkPathExist(path.dirname(addressBookPath), true)
fs.writeFileSync(addressBookPath, addressBook.toBytes())
}
async function ensureGradidoNodeRuntimeAvailable(runtimeFileName: string): Promise<void> {
const runtimeFolder = path.dirname(runtimeFileName)
checkPathExist(runtimeFolder, true)
if (!checkFileExist(runtimeFileName)) {
const runtimeArchiveFilename = createGradidoNodeRuntimeArchiveFilename()
const downloadUrl = new URL(
`https://github.com/gradido/gradido_node/releases/download/v${CONFIG.DLT_GRADIDO_NODE_SERVER_VERSION}/${runtimeArchiveFilename}`,
)
logger.debug(`download GradidoNode Runtime from ${downloadUrl}`)
const archive = await fetch(downloadUrl)
if (!archive.ok) {
throw new Error(`Failed to download GradidoNode Runtime: ${archive.statusText}`)
}
const compressedBuffer = await archive.arrayBuffer()
if (process.platform === 'win32') {
const zip = new AdmZip(Buffer.from(compressedBuffer))
zip.extractAllTo(runtimeFolder, true)
} else {
const archivePath = path.join(runtimeFolder, runtimeArchiveFilename)
logger.debug(`GradidoNode Runtime Archive: ${archivePath}`)
fs.writeFileSync(archivePath, Buffer.from(compressedBuffer))
execSync(`tar -xzf ${archivePath}`, { cwd: runtimeFolder })
}
}
}
function createGradidoNodeRuntimeArchiveFilename(): string {
const version = CONFIG.DLT_GRADIDO_NODE_SERVER_VERSION
const platform: string = process.platform
const fileEnding = platform === 'win32' ? 'zip' : 'tar.gz'
return `gradido_node-v${version}-${platform}-${process.arch}.${fileEnding}`
}

View File

@ -0,0 +1,30 @@
import { Logger } from 'log4js'
import { GradidoNodeProcess } from '../client/GradidoNode/GradidoNodeProcess'
import { type AppContextClients } from './appContext'
export function setupGracefulShutdown(logger: Logger, clients: AppContextClients) {
const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM']
signals.forEach((sig) => {
process.on(sig, async () => {
logger.info(`[shutdown] Got ${sig}, cleaning up…`)
await gracefulShutdown(logger, clients)
process.exit(0)
})
})
if (process.platform === 'win32') {
const rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
})
rl.on('SIGINT', () => {
process.emit('SIGINT' as any)
})
}
}
async function gracefulShutdown(logger: Logger, clients: AppContextClients) {
logger.info('graceful shutdown')
await clients.hiero.waitForPendingPromises()
await GradidoNodeProcess.getInstance().exit()
}

View File

@ -0,0 +1,79 @@
import { KeyPairEd25519 } from 'gradido-blockchain-js'
import { getLogger, Logger } from 'log4js'
import { LOG4JS_BASE_CATEGORY } from '../config/const'
import { HieroId } from '../schemas/typeGuard.schema'
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
// and ../federation/client/FederationClientFactory.ts
// TODO: TTL (time to live) based, maybe even optional use of redis
/**
* A Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
*/
export class KeyPairCacheManager {
private static instance: KeyPairCacheManager
private cache: Map<string, KeyPairEd25519> = new Map<string, KeyPairEd25519>()
private homeCommunityTopicId: HieroId | undefined
private logger: Logger
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
private constructor() {
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.KeyPairCacheManager`)
}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(): KeyPairCacheManager {
if (!KeyPairCacheManager.instance) {
KeyPairCacheManager.instance = new KeyPairCacheManager()
}
return KeyPairCacheManager.instance
}
public setHomeCommunityTopicId(topicId: HieroId): void {
this.homeCommunityTopicId = topicId
}
public getHomeCommunityTopicId(): HieroId {
if (!this.homeCommunityTopicId) {
throw new Error('home community topic id is not set')
}
return this.homeCommunityTopicId
}
public findKeyPair(input: string): KeyPairEd25519 | undefined {
return this.cache.get(input)
}
public addKeyPair(input: string, keyPair: KeyPairEd25519): void {
if (this.cache.has(input)) {
this.logger.warn('key already exist, cannot add', {
key: input,
publicKey: keyPair.getPublicKey()?.convertToHex(),
})
return
}
this.cache.set(input, keyPair)
}
public async getKeyPair(
input: string,
createKeyPair: () => Promise<KeyPairEd25519>,
): Promise<KeyPairEd25519> {
const keyPair = this.cache.get(input)
if (!keyPair) {
const keyPair = await createKeyPair()
this.cache.set(input, keyPair)
return keyPair
}
return keyPair
}
}

View File

@ -1,105 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { gql, GraphQLClient } from 'graphql-request'
import { SignJWT } from 'jose'
import { CONFIG } from '@/config'
import { CommunityDraft } from '@/graphql/input/CommunityDraft'
import { logger } from '@/logging/logger'
import { LogError } from '@/server/LogError'
const homeCommunity = gql`
query {
homeCommunity {
uuid
foreign
creationDate
}
}
`
interface Community {
homeCommunity: {
uuid: string
foreign: boolean
creationDate: string
}
}
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
// and ../federation/client/FederationClientFactory.ts
/**
* A Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
*/
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class BackendClient {
// eslint-disable-next-line no-use-before-define
private static instance: BackendClient
client: GraphQLClient
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
// eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function
private constructor() {}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(): BackendClient | undefined {
if (!BackendClient.instance) {
BackendClient.instance = new BackendClient()
}
if (!BackendClient.instance.client) {
try {
BackendClient.instance.client = new GraphQLClient(CONFIG.BACKEND_SERVER_URL, {
headers: {
'content-type': 'application/json',
},
method: 'GET',
jsonSerializer: {
parse: JSON.parse,
stringify: JSON.stringify,
},
})
} catch (e) {
logger.error("couldn't connect to backend: ", e)
return
}
}
return BackendClient.instance
}
public async getHomeCommunityDraft(): Promise<CommunityDraft> {
logger.info('check home community on backend')
const { data, errors } = await this.client.rawRequest<Community>(
homeCommunity,
{},
{
authorization: 'Bearer ' + (await this.createJWTToken()),
},
)
if (errors) {
throw new LogError('error getting home community from backend', errors)
}
const communityDraft = new CommunityDraft()
communityDraft.uuid = data.homeCommunity.uuid
communityDraft.foreign = data.homeCommunity.foreign
communityDraft.createdAt = data.homeCommunity.creationDate
return communityDraft
}
private async createJWTToken(): Promise<string> {
const secret = new TextEncoder().encode(CONFIG.JWT_SECRET)
const token = await new SignJWT({ gradidoID: 'dlt-connector', 'urn:gradido:claim': true })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setIssuer('urn:gradido:issuer')
.setAudience('urn:gradido:audience')
.setExpirationTime('1m')
.sign(secret)
return token
}
}

View File

@ -0,0 +1,259 @@
import { ConfirmedTransaction } from 'gradido-blockchain-js'
import JsonRpcClient from 'jsonrpc-ts-client'
import { JsonRpcEitherResponse } from 'jsonrpc-ts-client/dist/types/utils/jsonrpc'
import { getLogger, Logger } from 'log4js'
import * as v from 'valibot'
import { CONFIG } from '../../config'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { AddressType } from '../../data/AddressType.enum'
import { Uuidv4Hash } from '../../data/Uuidv4Hash'
import { addressTypeSchema, confirmedTransactionSchema } from '../../schemas/typeConverter.schema'
import { Hex32, Hex32Input, HieroId, hex32Schema } from '../../schemas/typeGuard.schema'
import { isPortOpenRetry } from '../../utils/network'
import { GradidoNodeErrorCodes } from './GradidoNodeErrorCodes'
import {
TransactionIdentifierInput,
TransactionsRangeInput,
transactionIdentifierSchema,
transactionsRangeSchema,
} from './input.schema'
export class GradidoNodeRequestError<T> extends Error {
private response?: JsonRpcEitherResponse<T>
constructor(message: string, response?: JsonRpcEitherResponse<T>) {
super(message)
this.name = 'GradidoNodeRequestError'
this.response = response
}
getResponse(): JsonRpcEitherResponse<T> | undefined {
return this.response
}
}
type WithTimeUsed<T> = T & { timeUsed?: string }
export class GradidoNodeClient {
private static instance: GradidoNodeClient
client: JsonRpcClient
logger: Logger
urlValue: string
private constructor() {
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNodeClient`)
this.urlValue = `http://localhost:${CONFIG.DLT_NODE_SERVER_PORT}/api`
this.logger.addContext('url', this.urlValue)
this.client = new JsonRpcClient({
url: this.urlValue,
})
}
public get url(): string {
return this.urlValue
}
public static getInstance(): GradidoNodeClient {
if (!GradidoNodeClient.instance) {
GradidoNodeClient.instance = new GradidoNodeClient()
}
return GradidoNodeClient.instance
}
/**
* getTransaction
* get a specific confirmed transaction from a specific community
* @param transactionIdentifier
* @returns the confirmed transaction or undefined if transaction is not found
* @throws GradidoNodeRequestError
*/
public async getTransaction(
transactionIdentifier: TransactionIdentifierInput,
): Promise<ConfirmedTransaction | undefined> {
const parameter = {
...v.parse(transactionIdentifierSchema, transactionIdentifier),
format: 'base64',
}
const response = await this.rpcCall<{ transaction: string }>('getTransaction', parameter)
if (response.isSuccess()) {
// this.logger.debug('result: ', response.result.transaction)
return v.parse(confirmedTransactionSchema, response.result.transaction)
}
if (response.isError()) {
if (response.error.code === GradidoNodeErrorCodes.TRANSACTION_NOT_FOUND) {
return undefined
}
}
throw new GradidoNodeRequestError(response.error.message, response)
}
/**
* getLastTransaction
* get the last confirmed transaction from a specific community
* @param hieroTopic the community hiero topic id
* @returns the last confirmed transaction or undefined if blockchain for community is empty or not found
* @throws GradidoNodeRequestError
*/
public async getLastTransaction(hieroTopic: HieroId): Promise<ConfirmedTransaction | undefined> {
const parameter = {
format: 'base64',
topic: hieroTopic,
}
const response = await this.rpcCall<{ transaction: string }>('getLastTransaction', parameter)
if (response.isSuccess()) {
return v.parse(confirmedTransactionSchema, response.result.transaction)
}
if (response.isError()) {
if (response.error.code === GradidoNodeErrorCodes.GRADIDO_NODE_ERROR) {
return undefined
}
}
throw new GradidoNodeRequestError(response.error.message, response)
}
/**
* getTransactions
* get list of confirmed transactions from a specific community
* @param input fromTransactionId is the id of the first transaction to return
* @param input maxResultCount is the max number of transactions to return
* @param input topic is the community hiero topic id
* @returns list of confirmed transactions
* @throws GradidoNodeRequestError
* @example
* ```
* const transactions = await getTransactions({
* fromTransactionId: 1,
* maxResultCount: 100,
* topic: communityUuid,
* })
* ```
*/
public async getTransactions(input: TransactionsRangeInput): Promise<ConfirmedTransaction[]> {
const parameter = {
...v.parse(transactionsRangeSchema, input),
format: 'base64',
}
const result = await this.rpcCallResolved<{ transactions: string[] }>(
'getTransactions',
parameter,
)
return result.transactions.map((transactionBase64) =>
v.parse(confirmedTransactionSchema, transactionBase64),
)
}
/**
* getTransactionsForAccount
* get list of confirmed transactions for a specific account
* @param transactionRange the range of transactions to return
* @param pubkey the public key of the account
* @returns list of confirmed transactions
* @throws GradidoNodeRequestError
*/
public async getTransactionsForAccount(
transactionRange: TransactionsRangeInput,
pubkey: Hex32Input,
): Promise<ConfirmedTransaction[]> {
const parameter = {
...v.parse(transactionsRangeSchema, transactionRange),
pubkey: v.parse(hex32Schema, pubkey),
format: 'base64',
}
const response = await this.rpcCallResolved<{ transactions: string[] }>(
'getTransactionsForAddress',
parameter,
)
return response.transactions.map((transactionBase64) =>
v.parse(confirmedTransactionSchema, transactionBase64),
)
}
/**
* getAddressType
* get the address type of a specific user
* can be used to check if user/account exists on blockchain
* look also for gmw, auf and deferred transfer accounts
* @param pubkey the public key of the user or account
* @param hieroTopic the community hiero topic id
* @returns the address type of the user/account, AddressType.NONE if not found
* @throws GradidoNodeRequestError
*/
public async getAddressType(pubkey: Hex32Input, hieroTopic: HieroId): Promise<AddressType> {
const parameter = {
pubkey: v.parse(hex32Schema, pubkey),
topic: hieroTopic,
}
const response = await this.rpcCallResolved<{ addressType: string }>(
'getAddressType',
parameter,
)
return v.parse(addressTypeSchema, response.addressType)
}
/**
* findUserByNameHash
* find a user by name hash
* @param nameHash the name hash of the user
* @param hieroTopic the community hiero topic id
* @returns the public key of the user as hex32 string or undefined if user is not found
* @throws GradidoNodeRequestError
*/
public async findUserByNameHash(
nameHash: Uuidv4Hash,
hieroTopic: HieroId,
): Promise<Hex32 | undefined> {
const parameter = {
nameHash: nameHash.getAsHexString(),
topic: hieroTopic,
}
const response = await this.rpcCall<{ pubkey: string; timeUsed: string }>(
'findUserByNameHash',
parameter,
)
if (response.isSuccess()) {
this.logger.info(`call findUserByNameHash, used ${response.result.timeUsed}`)
return v.parse(hex32Schema, response.result.pubkey)
}
if (
response.isError() &&
response.error.code === GradidoNodeErrorCodes.JSON_RPC_ERROR_ADDRESS_NOT_FOUND
) {
this.logger.debug(`call findUserByNameHash, return with error: ${response.error.message}`)
}
return undefined
}
// ---------------- intern helper functions -----------------------------------
// return result on success or throw error
protected resolveResponse<T, R>(
response: JsonRpcEitherResponse<T>,
onSuccess: (result: T) => R,
): R {
if (response.isSuccess()) {
return onSuccess(response.result)
} else if (response.isError()) {
throw new GradidoNodeRequestError(response.error.message, response)
}
throw new GradidoNodeRequestError('no success and no error')
}
// template rpcCall, check first if port is open before executing json rpc 2.0 request
protected async rpcCall<T>(method: string, parameter: any): Promise<JsonRpcEitherResponse<T>> {
this.logger.debug('call %s with %s', method, parameter)
await isPortOpenRetry(this.url)
return this.client.exec<T>(method, parameter)
}
// template rpcCall, check first if port is open before executing json rpc 2.0 request,
// throw error on failure, return result on success
protected async rpcCallResolved<T>(method: string, parameter: any): Promise<T> {
const response = await this.rpcCall<WithTimeUsed<T>>(method, parameter)
return this.resolveResponse(response, (result: WithTimeUsed<T>) => {
if (result.timeUsed) {
this.logger.info(`call %s, used ${result.timeUsed}`, method)
}
return result as T
})
}
}

View File

@ -0,0 +1,20 @@
export enum GradidoNodeErrorCodes {
NONE = 0,
GRADIDO_NODE_ERROR = -10000,
UNKNOWN_GROUP = -10001,
NOT_IMPLEMENTED = -10002,
TRANSACTION_NOT_FOUND = -10003,
JSON_RPC_ERROR_ADDRESS_NOT_FOUND = -10004,
// default errors from json rpc standard: https://www.jsonrpc.org/specification
// -32700 Parse error Invalid JSON was received by the server.
PARSE_ERROR = -32700,
// -32600 Invalid Request The JSON sent is not a valid Request object.
INVALID_REQUEST = -32600,
// -32601 Method not found The method does not exist / is not available.
METHODE_NOT_FOUND = -32601,
// -32602 Invalid params Invalid method parameter(s).
INVALID_PARAMS = -32602,
// -32603 Internal error Internal JSON - RPC error.
INTERNAL_ERROR = -32603,
// -32000 to -32099 Server error Reserved for implementation-defined server-errors.
}

View File

@ -0,0 +1,121 @@
import { Subprocess, spawn } from 'bun'
import { getLogger, Logger } from 'log4js'
import { CONFIG } from '../../config'
import {
GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS,
GRADIDO_NODE_MIN_RUNTIME_BEFORE_RESTART_MILLISECONDS,
GRADIDO_NODE_RUNTIME_PATH,
LOG4JS_BASE_CATEGORY,
} from '../../config/const'
/**
* A Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
*
* Singleton Managing GradidoNode if started as subprocess
* will restart GradidoNode if it exits more than `GRADIDO_NODE_MIN_RUNTIME_BEFORE_RESTART_MILLISECONDS` milliseconds after start
* if exit was called, it will first try to exit graceful with SIGTERM and then kill with SIGKILL after `GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS` milliseconds
*/
export class GradidoNodeProcess {
private static instance: GradidoNodeProcess | null = null
private proc: Subprocess | null = null
private logger: Logger
private lastStarted: Date | null = null
private exitCalled: boolean = false
private constructor() {
// constructor is private to prevent instantiation from outside
// of the class except from the static getInstance method.
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNodeProcess`)
}
/**
* Static method that returns the singleton instance of the class.
* @returns the singleton instance of the class.
*/
public static getInstance(): GradidoNodeProcess {
if (!GradidoNodeProcess.instance) {
GradidoNodeProcess.instance = new GradidoNodeProcess()
}
return GradidoNodeProcess.instance
}
public start() {
if (this.proc) {
this.logger.warn('GradidoNodeProcess already running.')
return
}
this.logger.info(`starting GradidoNodeProcess with path: ${GRADIDO_NODE_RUNTIME_PATH}`)
this.lastStarted = new Date()
const logger = this.logger
this.proc = spawn([GRADIDO_NODE_RUNTIME_PATH], {
env: {
CLIENTS_HIERO_NETWORKTYPE: CONFIG.HIERO_HEDERA_NETWORK,
SERVER_JSON_RPC_PORT: CONFIG.DLT_NODE_SERVER_PORT.toString(),
USERPROFILE: CONFIG.DLT_GRADIDO_NODE_SERVER_HOME_FOLDER,
HOME: CONFIG.DLT_GRADIDO_NODE_SERVER_HOME_FOLDER,
},
onExit(proc, exitCode, signalCode, error) {
logger.warn(`GradidoNodeProcess exited with code ${exitCode} and signalCode ${signalCode}`)
if (error) {
logger.error(`GradidoNodeProcess exit error: ${error}`)
/*if (logger.isDebugEnabled() && proc.stderr) {
// print error messages from GradidoNode in our own log if debug is enabled
proc.stderr
.getReader()
.read()
.then((chunk) => {
logger.debug(chunk.value?.toString())
})
}*/
}
logger.debug(`ressource usage: ${JSON.stringify(proc?.resourceUsage(), null, 2)}`)
const gradidoNodeProcess = GradidoNodeProcess.getInstance()
gradidoNodeProcess.proc = null
if (
!gradidoNodeProcess.exitCalled &&
gradidoNodeProcess.lastStarted &&
Date.now() - gradidoNodeProcess.lastStarted.getTime() >
GRADIDO_NODE_MIN_RUNTIME_BEFORE_RESTART_MILLISECONDS
) {
// restart only if enough time was passed since last start to prevent restart loop
gradidoNodeProcess.start()
}
},
/*stdout: 'ignore',
stderr: logger.isDebugEnabled() ? 'pipe' : 'ignore',*/
stdout: 'inherit',
stderr: 'inherit',
})
}
public async restart() {
if (this.proc) {
await this.exit()
this.exitCalled = false
this.start()
}
}
public async exit(): Promise<void> {
this.exitCalled = true
if (this.proc) {
this.proc.kill('SIGTERM')
const timeout = setTimeout(() => {
this.logger.warn(
`GradidoNode couldn't exit graceful after ${GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS} milliseconds with SIGTERM, killing with SIGKILL`,
)
this.proc?.kill('SIGKILL')
}, GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS)
try {
await this.proc.exited
} catch (error) {
this.logger.error(`GradidoNodeProcess exit error: ${error}`)
} finally {
clearTimeout(timeout)
}
} else {
return Promise.resolve()
}
}
}

View File

@ -0,0 +1,84 @@
import fs from 'node:fs'
import path from 'node:path'
import { Mutex } from 'async-mutex'
import { getLogger } from 'log4js'
import { CONFIG } from '../../config'
import { GRADIDO_NODE_HOME_FOLDER_NAME, LOG4JS_BASE_CATEGORY } from '../../config/const'
import { HieroId } from '../../schemas/typeGuard.schema'
import { checkFileExist, checkPathExist } from '../../utils/filesystem'
import { BackendClient } from '../backend/BackendClient'
import { GradidoNodeProcess } from './GradidoNodeProcess'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNode.communities`)
const ensureCommunitiesAvailableMutex: Mutex = new Mutex()
// prototype, later add api call to gradido dlt node server for adding/updating communities
type CommunityForDltNodeServer = {
communityId: string
hieroTopicId: string
alias: string
folder: string
}
export async function ensureCommunitiesAvailable(communityTopicIds: HieroId[]): Promise<void> {
const release = await ensureCommunitiesAvailableMutex.acquire()
try {
const homeFolder = path.join(
CONFIG.DLT_GRADIDO_NODE_SERVER_HOME_FOLDER,
GRADIDO_NODE_HOME_FOLDER_NAME,
)
if (!checkCommunityAvailable(communityTopicIds, homeFolder)) {
await exportCommunities(homeFolder, BackendClient.getInstance())
return GradidoNodeProcess.getInstance().restart()
}
} finally {
release()
}
}
export async function exportCommunities(homeFolder: string, client: BackendClient): Promise<void> {
const communities = await client.getReachableCommunities()
const communitiesPath = path.join(homeFolder, 'communities.json')
checkPathExist(path.dirname(communitiesPath), true)
// make sure communityName is unique
const communityName = new Set<string>()
const communitiesForDltNodeServer: CommunityForDltNodeServer[] = []
for (const com of communities) {
if (!com.uuid || !com.hieroTopicId) {
continue
}
// use name as alias if not empty and unique, otherwise use uuid
let alias = com.name
if (!alias || communityName.has(alias)) {
alias = com.uuid
}
communityName.add(alias)
communitiesForDltNodeServer.push({
communityId: com.uuid,
hieroTopicId: com.hieroTopicId,
alias,
// use only alpha-numeric chars for folder name
folder: alias.replace(/[^a-zA-Z0-9]/g, '_'),
})
}
fs.writeFileSync(communitiesPath, JSON.stringify(communitiesForDltNodeServer, null, 2))
logger.info(`exported ${communitiesForDltNodeServer.length} communities to ${communitiesPath}`)
}
export function checkCommunityAvailable(communityTopicIds: HieroId[], homeFolder: string): boolean {
const communitiesPath = path.join(homeFolder, 'communities.json')
if (!checkFileExist(communitiesPath)) {
return false
}
const communities = JSON.parse(fs.readFileSync(communitiesPath, 'utf-8'))
let foundCount = 0
for (const community of communities) {
if (communityTopicIds.includes(community.hieroTopicId)) {
foundCount++
if (foundCount >= communityTopicIds.length) {
return true
}
}
}
return false
}

View File

@ -0,0 +1,60 @@
import { beforeAll, describe, expect, it } from 'bun:test'
import * as v from 'valibot'
import {
HieroId,
HieroTransactionIdString,
hieroIdSchema,
hieroTransactionIdStringSchema,
} from '../../schemas/typeGuard.schema'
import { transactionIdentifierSchema } from './input.schema'
let topic: HieroId
const topicString = '0.0.261'
let hieroTransactionId: HieroTransactionIdString
beforeAll(() => {
topic = v.parse(hieroIdSchema, topicString)
hieroTransactionId = v.parse(hieroTransactionIdStringSchema, '0.0.261-1755348116-1281621')
})
describe('transactionIdentifierSchema ', () => {
it('valid, transaction identified by transactionNr and topic', () => {
expect(
v.parse(transactionIdentifierSchema, {
transactionId: 1,
topic: topicString,
}),
).toEqual({
transactionId: 1,
hieroTransactionId: undefined,
topic,
})
})
it('valid, transaction identified by hieroTransactionId and topic', () => {
expect(
v.parse(transactionIdentifierSchema, {
hieroTransactionId: '0.0.261-1755348116-1281621',
topic: topicString,
}),
).toEqual({
hieroTransactionId,
topic,
})
})
it('invalid, missing topic', () => {
expect(() =>
v.parse(transactionIdentifierSchema, {
transactionId: 1,
hieroTransactionId: '0.0.261-1755348116-1281621',
}),
).toThrowError(new Error('Invalid key: Expected "topic" but received undefined'))
})
it('invalid, transactionNr and iotaMessageId set', () => {
expect(() =>
v.parse(transactionIdentifierSchema, {
transactionId: 1,
hieroTransactionId: '0.0.261-1755348116-1281621',
topic,
}),
).toThrowError(new Error('expect transactionNr or hieroTransactionId not both'))
})
})

View File

@ -0,0 +1,35 @@
import * as v from 'valibot'
import { hieroIdSchema, hieroTransactionIdStringSchema } from '../../schemas/typeGuard.schema'
export const transactionsRangeSchema = v.object({
// default value is 1, from first transactions
fromTransactionId: v.nullish(v.pipe(v.number(), v.minValue(1, 'expect number >= 1')), 1),
// default value is 100, max 100 transactions
maxResultCount: v.nullish(v.pipe(v.number(), v.minValue(1, 'expect number >= 1')), 100),
topic: hieroIdSchema,
})
export type TransactionsRangeInput = v.InferInput<typeof transactionsRangeSchema>
// allow TransactionIdentifier to only contain either transactionNr or iotaMessageId
export const transactionIdentifierSchema = v.pipe(
v.object({
transactionId: v.nullish(
v.pipe(v.number('expect number type'), v.minValue(1, 'expect number >= 1')),
undefined,
),
hieroTransactionId: v.nullish(hieroTransactionIdStringSchema, undefined),
topic: hieroIdSchema,
}),
v.custom((value: any) => {
const setFieldsCount =
Number(value.transactionId !== undefined) + Number(value.hieroTransactionId !== undefined)
if (setFieldsCount !== 1) {
return false
}
return true
}, 'expect transactionNr or hieroTransactionId not both'),
)
export type TransactionIdentifierInput = v.InferInput<typeof transactionIdentifierSchema>
export type TransactionIdentifier = v.InferOutput<typeof transactionIdentifierSchema>

View File

@ -1,71 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { sendMessage, receiveMessage } from '@/client/IotaClient'
jest.mock('@iota/client', () => {
const mockMessageSender = jest.fn().mockImplementation(() => {
return {
index: jest.fn().mockReturnThis(),
data: jest.fn().mockReturnThis(),
submit: jest
.fn()
.mockReturnValue('5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710'),
}
})
const mockMessageFinder = jest.fn().mockImplementation(() => {
return {
data: jest.fn().mockReturnValue({
message: {
networkId: '1454675179895816119',
parentMessageIds: [
'5f30efecca59fdfef7c103e85ef691b2b1dc474e9eae9056888a6d58605083e7',
'77cef2fb405daedcd7469e009bb87a6d9a4840e618cdb599cd21a30a9fec88dc',
'7d2cfb39f40585ba568a29ad7e85c1478b2584496eb736d4001ac344f6a6cacf',
'c66da602874220dfa26925f6be540d37c0084d37cd04726fcc5be9d80b36f850',
],
payload: {
type: 2,
index: '4752414449444f3a205465737448656c6c6f57656c7431',
data: '48656c6c6f20576f726c64202d20546875204a756e20303820323032332031343a35393a343520474d542b3030303020284b6f6f7264696e69657274652057656c747a65697429',
},
nonce: '13835058055282465157',
},
messageId: '5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710',
}),
}
})
const mockClient = {
message: mockMessageSender,
getMessage: mockMessageFinder,
}
const mockClientBuilder = {
node: jest.fn().mockReturnThis(),
build: jest.fn(() => mockClient),
}
return {
ClientBuilder: jest.fn(() => mockClientBuilder),
}
})
describe('Iota Tests', () => {
it('test mocked sendDataMessage', async () => {
const result = await sendMessage('Test Message', 'topic')
expect(result).toBe('5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710')
})
it('should mock getMessage', async () => {
const result = await receiveMessage(
'5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710',
)
expect(result).toMatchObject({
message: {
payload: {
data: '48656c6c6f20576f726c64202d20546875204a756e20303820323032332031343a35393a343520474d542b3030303020284b6f6f7264696e69657274652057656c747a65697429',
index: '4752414449444f3a205465737448656c6c6f57656c7431',
},
},
messageId: '5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710',
})
})
})

View File

@ -1,53 +0,0 @@
import { ClientBuilder } from '@iota/client'
import { MessageWrapper } from '@iota/client/lib/types'
import { CONFIG } from '@/config'
const client = new ClientBuilder().node(CONFIG.IOTA_API_URL).build()
/**
* send data message onto iota tangle
* @param {string | Uint8Array} message - the message as utf based string, will be converted to hex automatically from @iota/client
* @param {string | Uint8Array} topic - the iota topic to which the message will be sended
* @return {Promise<MessageWrapper>} the iota message typed
*/
function sendMessage(
message: string | Uint8Array,
topic: string | Uint8Array,
): Promise<MessageWrapper> {
return client.message().index(topic).data(message).submit()
}
/**
* receive message for known message id from iota tangle
* @param {string} messageId - as hex string
* @return {Promise<MessageWrapper>} the iota message typed
*/
function receiveMessage(messageId: string): Promise<MessageWrapper> {
return client.getMessage().data(messageId)
}
export { sendMessage, receiveMessage }
/**
* example for message:
```json
{
message: {
networkId: '1454675179895816119',
parentMessageIds: [
'5f30efecca59fdfef7c103e85ef691b2b1dc474e9eae9056888a6d58605083e7',
'77cef2fb405daedcd7469e009bb87a6d9a4840e618cdb599cd21a30a9fec88dc',
'7d2cfb39f40585ba568a29ad7e85c1478b2584496eb736d4001ac344f6a6cacf',
'c66da602874220dfa26925f6be540d37c0084d37cd04726fcc5be9d80b36f850'
],
payload: {
type: 2,
index: '4752414449444f3a205465737448656c6c6f57656c7431',
data: '48656c6c6f20576f726c64202d20546875204a756e20303820323032332031343a35393a343520474d542b3030303020284b6f6f7264696e69657274652057656c747a65697429'
},
nonce: '13835058055282465157'
},
messageId: '5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710'
}
```
*/

View File

@ -0,0 +1,123 @@
import { GraphQLClient } from 'graphql-request'
import { SignJWT } from 'jose'
import { getLogger, Logger } from 'log4js'
import * as v from 'valibot'
import { CONFIG } from '../../config'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { HieroId, Uuidv4 } from '../../schemas/typeGuard.schema'
import {
getReachableCommunities,
homeCommunityGraphqlQuery,
setHomeCommunityTopicId,
} from './graphql'
import { type Community, communitySchema } from './output.schema'
// Source: https://refactoring.guru/design-patterns/singleton/typescript/example
// and ../federation/client/FederationClientFactory.ts
/**
* A Singleton class defines the `getInstance` method that lets clients access
* the unique singleton instance.
*/
export class BackendClient {
private static instance: BackendClient
client: GraphQLClient
logger: Logger
urlValue: string
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
private constructor() {
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.BackendClient`)
this.urlValue = `http://localhost:${CONFIG.PORT}`
this.logger.addContext('url', this.urlValue)
this.client = new GraphQLClient(this.urlValue, {
headers: {
'content-type': 'application/json',
},
method: 'GET',
jsonSerializer: {
parse: JSON.parse,
stringify: JSON.stringify,
},
})
}
public get url(): string {
return this.urlValue
}
/**
* The static method that controls the access to the singleton instance.
*
* This implementation let you subclass the Singleton class while keeping
* just one instance of each subclass around.
*/
public static getInstance(): BackendClient {
if (!BackendClient.instance) {
BackendClient.instance = new BackendClient()
}
return BackendClient.instance
}
public async getHomeCommunityDraft(): Promise<Community> {
this.logger.info('check home community on backend')
const { data, errors } = await this.client.rawRequest<{ homeCommunity: Community }>(
homeCommunityGraphqlQuery,
{},
await this.getRequestHeader(),
)
if (errors) {
throw errors[0]
}
return v.parse(communitySchema, data.homeCommunity)
}
public async setHomeCommunityTopicId(uuid: Uuidv4, hieroTopicId: HieroId): Promise<Community> {
this.logger.info('update home community on backend')
const { data, errors } = await this.client.rawRequest<{ updateHomeCommunity: Community }>(
setHomeCommunityTopicId,
{ uuid, hieroTopicId },
await this.getRequestHeader(),
)
if (errors) {
throw errors[0]
}
return v.parse(communitySchema, data.updateHomeCommunity)
}
public async getReachableCommunities(): Promise<Community[]> {
this.logger.info('get reachable communities on backend')
const { data, errors } = await this.client.rawRequest<{ reachableCommunities: Community[] }>(
getReachableCommunities,
{},
await this.getRequestHeader(),
)
if (errors) {
throw errors[0]
}
return v.parse(v.array(communitySchema), data.reachableCommunities)
}
private async getRequestHeader(): Promise<{
authorization: string
}> {
return {
authorization: 'Bearer ' + (await this.createJWTToken()),
}
}
private async createJWTToken(): Promise<string> {
const secret = new TextEncoder().encode(CONFIG.JWT_SECRET)
const token = await new SignJWT({ gradidoID: 'dlt-connector', 'urn:gradido:claim': true })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setIssuer('urn:gradido:issuer')
.setAudience('urn:gradido:audience')
.setExpirationTime('1m')
.sign(secret)
return token
}
}

View File

@ -0,0 +1,46 @@
import { gql } from 'graphql-request'
/**
* Schema Definitions for graphql requests
*/
const communityFragment = gql`
fragment Community_common on Community {
uuid
name
hieroTopicId
foreign
creationDate
}
`
// graphql query for getting home community in tune with community schema
export const homeCommunityGraphqlQuery = gql`
query {
homeCommunity {
...Community_common
}
}
${communityFragment}
`
export const setHomeCommunityTopicId = gql`
mutation ($uuid: String!, $hieroTopicId: String){
updateHomeCommunity(uuid: $uuid, hieroTopicId: $hieroTopicId) {
uuid
name
hieroTopicId
foreign
creationDate
}
}
`
export const getReachableCommunities = gql`
query {
reachableCommunities {
...Community_common
}
}
${communityFragment}
`

View File

@ -0,0 +1,25 @@
// only for IDE, bun don't need this to work
import { describe, expect, it } from 'bun:test'
import * as v from 'valibot'
import { hieroIdSchema, uuidv4Schema } from '../../schemas/typeGuard.schema'
import { communitySchema } from './output.schema'
describe('community.schema', () => {
it('community', () => {
expect(
v.parse(communitySchema, {
uuid: '4f28e081-5c39-4dde-b6a4-3bde71de8d65',
hieroTopicId: '0.0.4',
foreign: false,
name: 'Test',
creationDate: '2021-01-01',
}),
).toEqual({
hieroTopicId: v.parse(hieroIdSchema, '0.0.4'),
uuid: v.parse(uuidv4Schema, '4f28e081-5c39-4dde-b6a4-3bde71de8d65'),
foreign: false,
name: 'Test',
creationDate: new Date('2021-01-01'),
})
})
})

View File

@ -0,0 +1,14 @@
import * as v from 'valibot'
import { dateSchema } from '../../schemas/typeConverter.schema'
import { hieroIdSchema, uuidv4Schema } from '../../schemas/typeGuard.schema'
export const communitySchema = v.object({
uuid: uuidv4Schema,
name: v.string('expect string type'),
hieroTopicId: v.nullish(hieroIdSchema),
foreign: v.boolean('expect boolean type'),
creationDate: dateSchema,
})
export type CommunityInput = v.InferInput<typeof communitySchema>
export type Community = v.InferOutput<typeof communitySchema>

View File

@ -0,0 +1,195 @@
import {
AccountBalance,
AccountBalanceQuery,
AddressBookQuery,
Client,
FileId,
LocalProvider,
NodeAddressBook,
PrivateKey,
TopicCreateTransaction,
TopicId,
TopicInfoQuery,
TopicMessageSubmitTransaction,
TopicUpdateTransaction,
TransactionId,
Wallet,
} from '@hashgraph/sdk'
import { GradidoTransaction } from 'gradido-blockchain-js'
import { getLogger, Logger } from 'log4js'
import * as v from 'valibot'
import { CONFIG } from '../../config'
import { LOG4JS_BASE_CATEGORY } from '../../config/const'
import { HieroId, hieroIdSchema } from '../../schemas/typeGuard.schema'
import { type TopicInfoOutput, topicInfoSchema } from './output.schema'
// https://docs.hedera.com/hedera/sdks-and-apis/hedera-api/consensus/consensusupdatetopic
export const MIN_AUTORENEW_PERIOD = 6999999 //seconds
export const MAX_AUTORENEW_PERIOD = 8000001 // seconds
export class HieroClient {
private static instance: HieroClient
wallet: Wallet
client: Client
logger: Logger
// transaction counter for logging
transactionInternNr: number = 0
pendingPromises: Promise<void>[] = []
private constructor() {
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.HieroClient`)
this.client = Client.forName(CONFIG.HIERO_HEDERA_NETWORK)
const provider = LocalProvider.fromClient(this.client)
let operatorKey: PrivateKey
if (CONFIG.HIERO_OPERATOR_KEY.length === 64) {
operatorKey = PrivateKey.fromStringED25519(CONFIG.HIERO_OPERATOR_KEY)
} else {
operatorKey = PrivateKey.fromStringECDSA(CONFIG.HIERO_OPERATOR_KEY)
}
this.wallet = new Wallet(CONFIG.HIERO_OPERATOR_ID, operatorKey, provider)
this.client.setOperator(CONFIG.HIERO_OPERATOR_ID, operatorKey)
}
public static getInstance(): HieroClient {
if (!HieroClient.instance) {
HieroClient.instance = new HieroClient()
}
return HieroClient.instance
}
public async waitForPendingPromises() {
const startTime = new Date()
this.logger.info(`waiting for ${this.pendingPromises.length} pending promises`)
await Promise.all(this.pendingPromises)
const endTime = new Date()
this.logger.info(
`all pending promises resolved, used time: ${endTime.getTime() - startTime.getTime()}ms`,
)
}
public async sendMessage(
topicId: HieroId,
transaction: GradidoTransaction,
): Promise<TransactionId | null> {
const startTime = new Date()
this.transactionInternNr++
const logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.HieroClient`)
logger.addContext('trNr', this.transactionInternNr)
logger.addContext('topicId', topicId.toString())
const serializedTransaction = transaction.getSerializedTransaction()
if (!serializedTransaction) {
throw new Error('cannot serialize transaction')
}
// send one message
const hieroTransaction = await new TopicMessageSubmitTransaction({
topicId,
message: serializedTransaction.data(),
}).freezeWithSigner(this.wallet)
// sign and execute transaction needs some time, so let it run in background
const pendingPromiseIndex = this.pendingPromises.push(
hieroTransaction
.signWithSigner(this.wallet)
.then(async (signedHieroTransaction) => {
const sendResponse = await signedHieroTransaction.executeWithSigner(this.wallet)
logger.info(
`message sent to topic ${topicId}, transaction id: ${sendResponse.transactionId.toString()}`,
)
if (logger.isInfoEnabled()) {
// only for logging
sendResponse.getReceiptWithSigner(this.wallet).then((receipt) => {
logger.info(`message send status: ${receipt.status.toString()}`)
})
// only for logging
sendResponse.getRecordWithSigner(this.wallet).then((record) => {
logger.info(`message sent, cost: ${record.transactionFee.toString()}`)
const localEndTime = new Date()
logger.info(
`HieroClient.sendMessage used time (full process): ${localEndTime.getTime() - startTime.getTime()}ms`,
)
})
}
})
.catch((e) => {
logger.error(e)
})
.finally(() => {
this.pendingPromises.splice(pendingPromiseIndex, 1)
}),
)
const endTime = new Date()
logger.info(`HieroClient.sendMessage used time: ${endTime.getTime() - startTime.getTime()}ms`)
return hieroTransaction.transactionId
}
public async getBalance(): Promise<AccountBalance> {
const balance = await new AccountBalanceQuery()
.setAccountId(this.wallet.getAccountId())
.executeWithSigner(this.wallet)
return balance
}
public async getTopicInfo(topicId: HieroId): Promise<TopicInfoOutput> {
this.logger.addContext('topicId', topicId.toString())
const info = await new TopicInfoQuery()
.setTopicId(TopicId.fromString(topicId))
.execute(this.client)
this.logger.info(`topic is valid until ${info.expirationTime?.toDate()?.toLocaleString()}`)
if (info.topicMemo) {
this.logger.info(`topic memo: ${info.topicMemo}`)
}
this.logger.debug(`topic sequence number: ${info.sequenceNumber.toNumber()}`)
// this.logger.debug(JSON.stringify(info, null, 2))
return v.parse(topicInfoSchema, {
topicId: topicId.toString(),
sequenceNumber: info.sequenceNumber.toNumber(),
expirationTime: info.expirationTime?.toDate(),
autoRenewPeriod: info.autoRenewPeriod?.seconds.toNumber(),
autoRenewAccountId: info.autoRenewAccountId?.toString(),
})
}
public async createTopic(topicMemo?: string): Promise<HieroId> {
let transaction = new TopicCreateTransaction({
topicMemo,
adminKey: undefined,
submitKey: undefined,
autoRenewPeriod: undefined,
autoRenewAccountId: undefined,
})
transaction = await transaction.freezeWithSigner(this.wallet)
transaction = await transaction.signWithSigner(this.wallet)
const createResponse = await transaction.executeWithSigner(this.wallet)
const createReceipt = await createResponse.getReceiptWithSigner(this.wallet)
this.logger.debug(createReceipt.toString())
this.logger.addContext('topicId', createReceipt.topicId?.toString())
const record = await createResponse.getRecordWithSigner(this.wallet)
this.logger.info(`topic created, cost: ${record.transactionFee.toString()}`)
return v.parse(hieroIdSchema, createReceipt.topicId?.toString())
}
public async downloadAddressBook(): Promise<NodeAddressBook> {
const query = new AddressBookQuery().setFileId(FileId.ADDRESS_BOOK)
try {
return await query.execute(this.client)
} catch (e) {
this.logger.error(e)
throw e
}
}
public async updateTopic(topicId: HieroId): Promise<void> {
this.logger.addContext('topicId', topicId.toString())
let transaction = new TopicUpdateTransaction()
transaction.setExpirationTime(new Date(new Date().getTime() + MIN_AUTORENEW_PERIOD * 1000))
transaction.setTopicId(TopicId.fromString(topicId))
transaction = await transaction.freezeWithSigner(this.wallet)
transaction = await transaction.signWithSigner(this.wallet)
const updateResponse = await transaction.executeWithSigner(this.wallet)
const updateReceipt = await updateResponse.getReceiptWithSigner(this.wallet)
this.logger.debug(updateReceipt.toString())
const record = await updateResponse.getRecordWithSigner(this.wallet)
this.logger.info(`topic updated, cost: ${record.transactionFee.toString()}`)
}
}

View File

@ -0,0 +1,21 @@
import * as v from 'valibot'
import { dateSchema } from '../../schemas/typeConverter.schema'
import { hieroIdSchema } from '../../schemas/typeGuard.schema'
// schema definitions for exporting data from hiero request as json back to caller
/*export const dateStringSchema = v.pipe(
v.enum([v.string(), v.date()],
v.transform(in: string | Date)
)*/
export const positiveNumberSchema = v.pipe(v.number(), v.minValue(0))
export const topicInfoSchema = v.object({
topicId: hieroIdSchema,
sequenceNumber: positiveNumberSchema,
expirationTime: dateSchema,
autoRenewPeriod: v.optional(positiveNumberSchema, 0),
autoRenewAccountId: v.optional(hieroIdSchema, '0.0.0'),
})
export type TopicInfoOutput = v.InferOutput<typeof topicInfoSchema>

View File

@ -0,0 +1,21 @@
import path from 'node:path'
export const LOG4JS_BASE_CATEGORY = 'dlt'
// 7 days
export const MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE = 1000 * 60 * 60 * 24 * 7
// 10 minutes
export const MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_SEND_MESSAGE = 1000 * 60 * 10
export const GRADIDO_NODE_RUNTIME_PATH = path.join(
__dirname,
'..',
'..',
'gradido_node',
'bin',
'GradidoNode',
)
// if last start was less than this time, do not restart
export const GRADIDO_NODE_MIN_RUNTIME_BEFORE_RESTART_MILLISECONDS = 1000 * 30
export const GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS = 10000
// currently hard coded in gradido node, update in future
export const GRADIDO_NODE_HOME_FOLDER_NAME = '.gradido'

View File

@ -1,65 +1,25 @@
/* eslint-disable n/no-process-env */
import dotenv from 'dotenv'
import * as v from 'valibot'
import { configSchema } from './schema'
dotenv.config()
const constants = {
LOG4JS_CONFIG: 'log4js-config.json',
DB_VERSION: '0003-refactor_transaction_recipe',
// default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v6.2024-02-20',
CURRENT: '',
},
type ConfigOutput = v.InferOutput<typeof configSchema>
let config: ConfigOutput
try {
config = v.parse(configSchema, process.env)
} catch (error) {
if (error instanceof v.ValiError) {
// biome-ignore lint/suspicious/noConsole: need to parse config before initializing logger
console.error(
`${error.issues[0].path[0].key}: ${error.message} received: ${error.issues[0].received}`,
)
} else {
// biome-ignore lint/suspicious/noConsole: need to parse config before initializing logger
console.error(error)
}
process.exit(1)
}
const server = {
PRODUCTION: process.env.NODE_ENV === 'production' ?? false,
JWT_SECRET: process.env.JWT_SECRET ?? 'secret123',
}
const database = {
DB_HOST: process.env.DB_HOST ?? 'localhost',
DB_PORT: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 3306,
DB_USER: process.env.DB_USER ?? 'root',
DB_PASSWORD: process.env.DB_PASSWORD ?? '',
DB_DATABASE: process.env.DB_DATABASE ?? 'gradido_dlt',
DB_DATABASE_TEST: process.env.DB_DATABASE_TEST ?? 'gradido_dlt_test',
TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH ?? 'typeorm.backend.log',
}
const iota = {
IOTA_API_URL: process.env.IOTA_API_URL ?? 'https://chrysalis-nodes.iota.org',
IOTA_COMMUNITY_ALIAS: process.env.IOTA_COMMUNITY_ALIAS ?? 'GRADIDO: TestHelloWelt2',
IOTA_HOME_COMMUNITY_SEED: process.env.IOTA_HOME_COMMUNITY_SEED?.substring(0, 32) ?? null,
}
const dltConnector = {
DLT_CONNECTOR_PORT: process.env.DLT_CONNECTOR_PORT ?? 6010,
}
const backendServer = {
BACKEND_SERVER_URL: process.env.BACKEND_SERVER_URL ?? 'http://backend:4000',
}
// Check config version
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION ?? constants.CONFIG_VERSION.DEFAULT
if (
![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes(
constants.CONFIG_VERSION.CURRENT,
)
) {
throw new Error(
`Fatal: Config Version incorrect - expected "${constants.CONFIG_VERSION.EXPECTED}" or "${constants.CONFIG_VERSION.DEFAULT}", but found "${constants.CONFIG_VERSION.CURRENT}"`,
)
}
export const CONFIG = {
...constants,
...server,
...database,
...iota,
...dltConnector,
...backendServer,
}
export const CONFIG = config

View File

@ -0,0 +1,99 @@
import path from 'node:path'
import { MemoryBlock } from 'gradido-blockchain-js'
import * as v from 'valibot'
const hexSchema = v.pipe(v.string('expect string type'), v.hexadecimal('expect hexadecimal string'))
const hex16Schema = v.pipe(hexSchema, v.length(32, 'expect string length = 32'))
export const configSchema = v.object({
LOG4JS_CONFIG: v.optional(
v.string('The path to the log4js configuration file'),
'./log4js-config.json',
),
LOG_LEVEL: v.optional(v.string('The log level'), 'info'),
DLT_CONNECTOR_PORT: v.optional(
v.pipe(
v.string('A valid port on which the DLT connector is running'),
v.transform<string, number>((input: string) => Number(input)),
v.minValue(1),
v.maxValue(65535),
),
'6010',
),
JWT_SECRET: v.optional(
v.pipe(
v.string('The JWT secret for connecting to the backend'),
v.custom((input: unknown): boolean => {
if (process.env.NODE_ENV === 'production' && input === 'secret123') {
return false
}
return true
}, "Shouldn't use default value in production"),
),
'secret123',
),
GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET: hexSchema,
GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY: hex16Schema,
HOME_COMMUNITY_SEED: v.pipe(
hexSchema,
v.length(64, 'expect seed length minimum 64 characters (32 Bytes)'),
v.transform<string, MemoryBlock>((input: string) => MemoryBlock.fromHex(input)),
),
HIERO_HEDERA_NETWORK: v.optional(
v.union([v.literal('mainnet'), v.literal('testnet'), v.literal('previewnet')]),
'testnet',
),
HIERO_OPERATOR_ID: v.pipe(
v.string('The operator ID (Account id) for Hiero integration'),
v.regex(/^[0-9]+\.[0-9]+\.[0-9]+$/),
),
HIERO_OPERATOR_KEY: v.pipe(
v.string('The operator key (Private key) for Hiero integration'),
v.hexadecimal(),
v.minLength(64),
v.maxLength(96),
),
CONNECT_TIMEOUT_MS: v.optional(
v.pipe(v.number('The connection timeout in milliseconds'), v.minValue(200), v.maxValue(120000)),
1000,
),
CONNECT_RETRY_COUNT: v.optional(
v.pipe(v.number('The connection retry count'), v.minValue(1), v.maxValue(50)),
15,
),
CONNECT_RETRY_DELAY_MS: v.optional(
v.pipe(
v.number('The connection retry delay in milliseconds'),
v.minValue(100),
v.maxValue(10000),
),
500,
),
DLT_NODE_SERVER_PORT: v.optional(
v.pipe(
v.string('A valid port on which the DLT node server is running'),
v.transform<string, number>((input: string) => Number(input)),
v.minValue(1),
v.maxValue(65535),
),
'8340',
),
DLT_GRADIDO_NODE_SERVER_VERSION: v.optional(
v.string('The version of the DLT node server'),
'0.9.0',
),
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER: v.optional(
v.string('The home folder for the gradido dlt node server'),
path.join(__dirname, '..', '..', 'gradido_node'),
),
PORT: v.optional(
v.pipe(
v.string('A valid port on which the backend server is running'),
v.transform<string, number>((input: string) => Number(input)),
v.minValue(1),
v.maxValue(65535),
),
'4000',
),
})

View File

@ -1,60 +0,0 @@
import { Account } from '@entity/Account'
import Decimal from 'decimal.js-light'
import { KeyPair } from '@/data/KeyPair'
import { AddressType } from '@/data/proto/3_3/enum/AddressType'
import { UserAccountDraft } from '@/graphql/input/UserAccountDraft'
import { hardenDerivationIndex } from '@/utils/derivationHelper'
import { accountTypeToAddressType } from '@/utils/typeConverter'
const GMW_ACCOUNT_DERIVATION_INDEX = 1
const AUF_ACCOUNT_DERIVATION_INDEX = 2
export class AccountFactory {
public static createAccount(
createdAt: Date,
derivationIndex: number,
type: AddressType,
parentKeyPair: KeyPair,
): Account {
const account = Account.create()
account.derivationIndex = derivationIndex
account.derive2Pubkey = parentKeyPair.derive([derivationIndex]).publicKey
account.type = type.valueOf()
account.createdAt = createdAt
account.balanceOnConfirmation = new Decimal(0)
account.balanceOnCreation = new Decimal(0)
account.balanceCreatedAt = createdAt
return account
}
public static createAccountFromUserAccountDraft(
{ createdAt, accountType, user }: UserAccountDraft,
parentKeyPair: KeyPair,
): Account {
return AccountFactory.createAccount(
new Date(createdAt),
user.accountNr ?? 1,
accountTypeToAddressType(accountType),
parentKeyPair,
)
}
public static createGmwAccount(keyPair: KeyPair, createdAt: Date): Account {
return AccountFactory.createAccount(
createdAt,
hardenDerivationIndex(GMW_ACCOUNT_DERIVATION_INDEX),
AddressType.COMMUNITY_GMW,
keyPair,
)
}
public static createAufAccount(keyPair: KeyPair, createdAt: Date): Account {
return AccountFactory.createAccount(
createdAt,
hardenDerivationIndex(AUF_ACCOUNT_DERIVATION_INDEX),
AddressType.COMMUNITY_AUF,
keyPair,
)
}
}

View File

@ -1,35 +0,0 @@
import { Account } from '@entity/Account'
import { LogError } from '@/server/LogError'
import { KeyPair } from './KeyPair'
import { UserLogic } from './User.logic'
export class AccountLogic {
// eslint-disable-next-line no-useless-constructor
public constructor(private self: Account) {}
/**
* calculate account key pair starting from community key pair => derive user key pair => derive account key pair
* @param communityKeyPair
*/
public calculateKeyPair(communityKeyPair: KeyPair): KeyPair {
if (!this.self.user) {
throw new LogError('missing user')
}
const userLogic = new UserLogic(this.self.user)
const accountKeyPair = userLogic
.calculateKeyPair(communityKeyPair)
.derive([this.self.derivationIndex])
if (
this.self.derive2Pubkey &&
this.self.derive2Pubkey.compare(accountKeyPair.publicKey) !== 0
) {
throw new LogError(
'The freshly derived public key does not correspond to the stored public key',
)
}
return accountKeyPair
}
}

View File

@ -1,34 +0,0 @@
import { Account } from '@entity/Account'
import { User } from '@entity/User'
import { In } from 'typeorm'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { getDataSource } from '@/typeorm/DataSource'
export const AccountRepository = getDataSource()
.getRepository(Account)
.extend({
findAccountsByPublicKeys(publicKeys: Buffer[]): Promise<Account[]> {
return this.findBy({ derive2Pubkey: In(publicKeys) })
},
async findAccountByPublicKey(publicKey: Buffer | undefined): Promise<Account | undefined> {
if (!publicKey) return undefined
return (await this.findOneBy({ derive2Pubkey: Buffer.from(publicKey) })) ?? undefined
},
async findAccountByUserIdentifier({
uuid,
accountNr,
}: UserIdentifier): Promise<Account | undefined> {
const user = await User.findOne({
where: { gradidoID: uuid, accounts: { derivationIndex: accountNr ?? 1 } },
relations: { accounts: true },
})
if (user && user.accounts?.length === 1) {
const account = user.accounts[0]
account.user = user
return account
}
},
})

View File

@ -1,197 +0,0 @@
import 'reflect-metadata'
import { Decimal } from 'decimal.js-light'
import { TestDB } from '@test/TestDB'
import { AccountType } from '@/graphql/enum/AccountType'
import { UserAccountDraft } from '@/graphql/input/UserAccountDraft'
import { UserIdentifier } from '@/graphql/input/UserIdentifier'
import { AccountFactory } from './Account.factory'
import { AccountRepository } from './Account.repository'
import { KeyPair } from './KeyPair'
import { Mnemonic } from './Mnemonic'
import { AddressType } from './proto/3_3/enum/AddressType'
import { UserFactory } from './User.factory'
import { UserLogic } from './User.logic'
const con = TestDB.instance
jest.mock('@typeorm/DataSource', () => ({
getDataSource: jest.fn(() => TestDB.instance.dbConnect),
}))
describe('data/Account test factory and repository', () => {
const now = new Date()
const keyPair1 = new KeyPair(new Mnemonic('62ef251edc2416f162cd24ab1711982b'))
const keyPair2 = new KeyPair(new Mnemonic('000a0000000002000000000003000070'))
const keyPair3 = new KeyPair(new Mnemonic('00ba541a1000020000000000300bda70'))
const userGradidoID = '6be949ab-8198-4acf-ba63-740089081d61'
describe('test factory methods', () => {
beforeAll(async () => {
await con.setupTestDB()
})
afterAll(async () => {
await con.teardownTestDB()
})
it('test createAccount', () => {
const account = AccountFactory.createAccount(now, 1, AddressType.COMMUNITY_HUMAN, keyPair1)
expect(account).toMatchObject({
derivationIndex: 1,
derive2Pubkey: Buffer.from(
'cb88043ef4833afc01d6ed9b34e1aa48e79dce5ff97c07090c6600ec05f6d994',
'hex',
),
type: AddressType.COMMUNITY_HUMAN,
createdAt: now,
balanceCreatedAt: now,
balanceOnConfirmation: new Decimal(0),
balanceOnCreation: new Decimal(0),
})
})
it('test createAccountFromUserAccountDraft', () => {
const userAccountDraft = new UserAccountDraft()
userAccountDraft.createdAt = now.toISOString()
userAccountDraft.accountType = AccountType.COMMUNITY_HUMAN
userAccountDraft.user = new UserIdentifier()
userAccountDraft.user.accountNr = 1
const account = AccountFactory.createAccountFromUserAccountDraft(userAccountDraft, keyPair1)
expect(account).toMatchObject({
derivationIndex: 1,
derive2Pubkey: Buffer.from(
'cb88043ef4833afc01d6ed9b34e1aa48e79dce5ff97c07090c6600ec05f6d994',
'hex',
),
type: AddressType.COMMUNITY_HUMAN,
createdAt: now,
balanceCreatedAt: now,
balanceOnConfirmation: new Decimal(0),
balanceOnCreation: new Decimal(0),
})
})
it('test createGmwAccount', () => {
const account = AccountFactory.createGmwAccount(keyPair1, now)
expect(account).toMatchObject({
derivationIndex: 2147483649,
derive2Pubkey: Buffer.from(
'05f0060357bb73bd290283870fc47a10b3764f02ca26938479ed853f46145366',
'hex',
),
type: AddressType.COMMUNITY_GMW,
createdAt: now,
balanceCreatedAt: now,
balanceOnConfirmation: new Decimal(0),
balanceOnCreation: new Decimal(0),
})
})
it('test createAufAccount', () => {
const account = AccountFactory.createAufAccount(keyPair1, now)
expect(account).toMatchObject({
derivationIndex: 2147483650,
derive2Pubkey: Buffer.from(
'6c749f8693a4a58c948e5ae54df11e2db33d2f98673b56e0cf19c0132614ab59',
'hex',
),
type: AddressType.COMMUNITY_AUF,
createdAt: now,
balanceCreatedAt: now,
balanceOnConfirmation: new Decimal(0),
balanceOnCreation: new Decimal(0),
})
})
})
describe('test repository functions', () => {
beforeAll(async () => {
await con.setupTestDB()
await Promise.all([
AccountFactory.createAufAccount(keyPair1, now).save(),
AccountFactory.createGmwAccount(keyPair1, now).save(),
AccountFactory.createAufAccount(keyPair2, now).save(),
AccountFactory.createGmwAccount(keyPair2, now).save(),
AccountFactory.createAufAccount(keyPair3, now).save(),
AccountFactory.createGmwAccount(keyPair3, now).save(),
])
const userAccountDraft = new UserAccountDraft()
userAccountDraft.accountType = AccountType.COMMUNITY_HUMAN
userAccountDraft.createdAt = now.toString()
userAccountDraft.user = new UserIdentifier()
userAccountDraft.user.accountNr = 1
userAccountDraft.user.uuid = userGradidoID
const user = UserFactory.create(userAccountDraft, keyPair1)
const userLogic = new UserLogic(user)
const account = AccountFactory.createAccountFromUserAccountDraft(
userAccountDraft,
userLogic.calculateKeyPair(keyPair1),
)
account.user = user
// user is set to cascade: ['insert'] will be saved together with account
await account.save()
})
afterAll(async () => {
await con.teardownTestDB()
})
it('test findAccountsByPublicKeys', async () => {
const accounts = await AccountRepository.findAccountsByPublicKeys([
Buffer.from('6c749f8693a4a58c948e5ae54df11e2db33d2f98673b56e0cf19c0132614ab59', 'hex'),
Buffer.from('0fa996b73b624592fe326b8500cb1e3f10026112b374d84c87d097f4d489c019', 'hex'),
Buffer.from('0ffa996b73b624592f26b850b0cb1e3f1026112b374d84c87d017f4d489c0197', 'hex'), // invalid
])
expect(accounts).toHaveLength(2)
expect(accounts).toMatchObject(
expect.arrayContaining([
expect.objectContaining({
derivationIndex: 2147483649,
derive2Pubkey: Buffer.from(
'0fa996b73b624592fe326b8500cb1e3f10026112b374d84c87d097f4d489c019',
'hex',
),
type: AddressType.COMMUNITY_GMW,
}),
expect.objectContaining({
derivationIndex: 2147483650,
derive2Pubkey: Buffer.from(
'6c749f8693a4a58c948e5ae54df11e2db33d2f98673b56e0cf19c0132614ab59',
'hex',
),
type: AddressType.COMMUNITY_AUF,
}),
]),
)
})
it('test findAccountByPublicKey', async () => {
expect(
await AccountRepository.findAccountByPublicKey(
Buffer.from('6c749f8693a4a58c948e5ae54df11e2db33d2f98673b56e0cf19c0132614ab59', 'hex'),
),
).toMatchObject({
derivationIndex: 2147483650,
derive2Pubkey: Buffer.from(
'6c749f8693a4a58c948e5ae54df11e2db33d2f98673b56e0cf19c0132614ab59',
'hex',
),
type: AddressType.COMMUNITY_AUF,
})
})
it('test findAccountByUserIdentifier', async () => {
const userIdentifier = new UserIdentifier()
userIdentifier.accountNr = 1
userIdentifier.uuid = userGradidoID
expect(await AccountRepository.findAccountByUserIdentifier(userIdentifier)).toMatchObject({
derivationIndex: 1,
derive2Pubkey: Buffer.from(
'2099c004a26e5387c9fbbc9bb0f552a9642d3fd7c710ae5802b775d24ff36f93',
'hex',
),
type: AddressType.COMMUNITY_HUMAN,
})
})
})
})

View File

@ -1,5 +1,3 @@
import { registerEnumType } from 'type-graphql'
/**
* enum for graphql
* describe input account type in UserAccountDraft
@ -12,10 +10,6 @@ export enum AccountType {
COMMUNITY_AUF = 'COMMUNITY_AUF', // community compensation and environment founds account
COMMUNITY_PROJECT = 'COMMUNITY_PROJECT', // no creations allowed
SUBACCOUNT = 'SUBACCOUNT', // no creations allowed
CRYPTO_ACCOUNT = 'CRYPTO_ACCOUNT', // user control his keys, no creations
CRYPTO_ACCOUNT = 'CRYPTO_ACCOUNT', // user control his keys, no creations,
DEFERRED_TRANSFER = 'DEFERRED_TRANSFER', // no creations allowed
}
registerEnumType(AccountType, {
name: 'AccountType', // this one is mandatory
description: 'Type of account', // this one is optional
})

View File

@ -0,0 +1,21 @@
import {
AddressType_COMMUNITY_AUF,
AddressType_COMMUNITY_GMW,
AddressType_COMMUNITY_HUMAN,
AddressType_COMMUNITY_PROJECT,
AddressType_CRYPTO_ACCOUNT,
AddressType_DEFERRED_TRANSFER,
AddressType_NONE,
AddressType_SUBACCOUNT,
} from 'gradido-blockchain-js'
export enum AddressType {
COMMUNITY_AUF = AddressType_COMMUNITY_AUF,
COMMUNITY_GMW = AddressType_COMMUNITY_GMW,
COMMUNITY_HUMAN = AddressType_COMMUNITY_HUMAN,
COMMUNITY_PROJECT = AddressType_COMMUNITY_PROJECT,
CRYPTO_ACCOUNT = AddressType_CRYPTO_ACCOUNT,
NONE = AddressType_NONE,
SUBACCOUNT = AddressType_SUBACCOUNT,
DEFERRED_TRANSFER = AddressType_DEFERRED_TRANSFER,
}

View File

@ -1,13 +0,0 @@
import { BackendTransaction } from '@entity/BackendTransaction'
import { TransactionDraft } from '@/graphql/input/TransactionDraft'
export class BackendTransactionFactory {
public static createFromTransactionDraft(transactionDraft: TransactionDraft): BackendTransaction {
const backendTransaction = BackendTransaction.create()
backendTransaction.backendTransactionId = transactionDraft.backendTransactionId
backendTransaction.typeId = transactionDraft.type
backendTransaction.createdAt = new Date(transactionDraft.createdAt)
return backendTransaction
}
}

View File

@ -1,7 +0,0 @@
import { BackendTransaction } from '@entity/BackendTransaction'
import { getDataSource } from '@/typeorm/DataSource'
export const BackendTransactionRepository = getDataSource()
.getRepository(BackendTransaction)
.extend({})

Some files were not shown because too many files have changed in this diff Show More