Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cspell.words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ bytea
pg_dumpall
PGPASSWORD
psql
esnext
esnext
SEPA
10 changes: 1 addition & 9 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,7 @@ import { configs } from 'eslint-config-service-soft';
/** @type {import('eslint').Linter.Config} */
export default [
...configs,
{
files: ['**/*.ts'],
languageOptions: {
parserOptions: {
project: ['tsconfig.eslint.json']
}
}
},
{ ignores: ['tsconfig.json', 'tsup.config.ts', 'sandbox', 'docs', 'src/di/default/temp', '**/__testing__/file-output/**'] },
{ ignores: ['sandbox', 'docs', 'src/di/default/temp', '**/__testing__/file-output/**'] },
{
files: ['**/__testing__/**/*.ts'],
rules: {
Expand Down
2 changes: 1 addition & 1 deletion jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const config = {
testEnvironment: 'node',
rootDir: 'src',
transform: {
'^.+.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.eslint.json' }]
'^.+.tsx?$': ['ts-jest']
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
bail: false,
Expand Down
1,259 changes: 628 additions & 631 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zibri",
"version": "2.1.7",
"version": "2.2.0",
"main": "./dist/cjs/index.js",
"types": "./dist/cjs/index.d.ts",
"module": "./dist/esm/index.mjs",
Expand Down
6 changes: 3 additions & 3 deletions sandbox/src/services/dep.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Inject, Injectable } from 'zibri';
import { AssetService, Inject, Injectable, ZIBRI_DI_TOKENS } from 'zibri';

@Injectable()
export class DepService {
constructor(
@Inject('42')
private readonly numberValue: string
@Inject(ZIBRI_DI_TOKENS.ASSET_SERVICE)
private readonly assetService: AssetService
) {}
}
12 changes: 7 additions & 5 deletions src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { DataSourceServiceInterface } from './data-source';
import { ZIBRI_DI_TOKENS, inject } from './di';
import { register } from './di/register.function';
import { EmailServiceInterface, MailingListServiceInterface } from './email';
import { UnmatchedRouteError } from './error-handling';
import { GlobalErrorHandler, UnmatchedRouteError } from './error-handling';
import { GlobalRegistry } from './global';
import { HandlebarUtilities } from './handlebars/handlebar.utilities';
import { LoggerInterface } from './logging';
Expand Down Expand Up @@ -76,17 +76,19 @@ export class ZibriApplication {
}

// eslint-disable-next-line jsdoc/require-jsdoc
use(handler: RequestHandler): express.Express;
use(handler: RequestHandler): void;
// eslint-disable-next-line jsdoc/require-jsdoc
use(errorHandler: GlobalErrorHandler): void;
// eslint-disable-next-line jsdoc/require-jsdoc
use(...handlers: RequestHandler[]): void;
// eslint-disable-next-line jsdoc/require-jsdoc
use(path: Route, ...handlers: RequestHandler[]): void;
// eslint-disable-next-line jsdoc/require-jsdoc
use(path: Route, handlers: RequestHandler[]): void;
// eslint-disable-next-line jsdoc/require-jsdoc, typescript/no-explicit-any
use(...args: any[]): express.Express {
use(...args: any[]): void {
// eslint-disable-next-line typescript/no-unsafe-argument
return this.express.use(...args);
this.express.use(...args);
}

/**
Expand Down Expand Up @@ -167,7 +169,7 @@ export class ZibriApplication {
this.multithreadingService = inject(ZIBRI_DI_TOKENS.MULTITHREADING_SERVICE);
await this.multithreadingService.init();

this.websocketService = inject(ZIBRI_DI_TOKENS.WEBSOCKET_SERVICE);
this.websocketService = inject<WebsocketServiceInterface<BaseWebsocketConnection>>(ZIBRI_DI_TOKENS.WEBSOCKET_SERVICE);
await this.websocketService.attachTo(this);

this.backupService = inject(ZIBRI_DI_TOKENS.BACKUP_SERVICE);
Expand Down
3 changes: 2 additions & 1 deletion src/assets/asset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { LoggerInterface } from '../logging';
import { FileResponse } from '../parsing/form-data/file-response.model';
import { HtmlResponse } from '../parsing/html/html-response.model';
import { Route } from '../routing';
import { ObjectUtilities } from '../utilities';

// eslint-disable-next-line jsdoc/require-jsdoc
type FileNode = { type: 'file', name: string, route: string };
Expand Down Expand Up @@ -131,7 +132,7 @@ export class AssetService implements AssetServiceInterface {
}

private mapToTree(nodes: NodeMap): TreeNode[] {
return Object.entries(nodes).map(([name, info]) => {
return ObjectUtilities.entries(nodes).map(([name, info]) => {
if (!info) {
throw new Error('Error building the assets tree');
}
Expand Down
5 changes: 1 addition & 4 deletions src/auth/2fa/two-factor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,7 @@ export class TwoFactorService implements TwoFactorServiceInterface {
): Promise<boolean> {
try {
await Promise.any(
allowedMethods.map(async m => {
const twoFactorMethod: TwoFactorMethod<unknown, unknown> = inject(m);
await twoFactorMethod.validate(user, request);
})
allowedMethods.map(m => inject(m).validate(user, request))
);
return true;
}
Expand Down
11 changes: 7 additions & 4 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UnauthorizedError } from '../error-handling';
import { HttpRequest } from '../http';
import { LoggerInterface } from '../logging';
import { Newable } from '../types';
import { MetadataUtilities } from '../utilities';
import { MetadataUtilities, PromiseUtilities } from '../utilities';
import { WebsocketRequest } from '../websocket';
import { TwoFactorServiceInterface } from './2fa';
import { AuthServiceInterface } from './auth-service.interface';
Expand Down Expand Up @@ -274,7 +274,7 @@ export class AuthService implements AuthServiceInterface {
// eslint-disable-next-line stylistic/max-len
const strategies: AuthStrategyInterface<string, BaseUser<string>, unknown, unknown, unknown, unknown, unknown, unknown>[] = allowedStrategies.map(s => inject(s));
try {
return await Promise.any(strategies.map(s => s.isLoggedIn(request)));
return await PromiseUtilities.anyValueTrue(strategies, s => s.isLoggedIn(request));
}
catch {
return false;
Expand All @@ -290,7 +290,7 @@ export class AuthService implements AuthServiceInterface {
// eslint-disable-next-line stylistic/max-len
const strategies: AuthStrategyInterface<string, BaseUser<string>, unknown, unknown, unknown, unknown, unknown, unknown>[] = allowedStrategies.map(s => inject(s));
try {
return await Promise.any(strategies.map(s => s.hasRole(request, allowedRoles)));
return await PromiseUtilities.anyValueTrue(strategies, s => s.hasRole(request, allowedRoles));
}
catch {
return false;
Expand All @@ -308,7 +308,10 @@ export class AuthService implements AuthServiceInterface {
// eslint-disable-next-line stylistic/max-len
const strategies: AuthStrategyInterface<string, BaseUser<string>, unknown, unknown, unknown, unknown, unknown, unknown>[] = allowedStrategies.map(s => inject(s));
try {
return await Promise.any(strategies.map(s => s.belongsTo(request, targetEntity, targetUserIdKey, targetIdParamKey)));
return await PromiseUtilities.anyValueTrue(
strategies,
s => s.belongsTo(request, targetEntity, targetUserIdKey, targetIdParamKey)
);
}
catch {
return false;
Expand Down
20 changes: 16 additions & 4 deletions src/auth/strategies/jwt/jwt.auth-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { JwtRefreshToken, JwtRefreshTokenCreateDto } from './jwt-refresh-token.m
import { JwtRequestPasswordResetData } from './jwt-request-password-reset-data.model';
import { JwtUtilities } from './jwt.utilities';
import { Repository } from '../../../data-source';
import { inject, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../../../di';
import { inject, NoProviderError, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../../../di';
import { EmailPriority, EmailServiceInterface } from '../../../email';
import { BaseEntity } from '../../../entity/base-entity.model';
import { TooManyRequestsError, UnauthorizedError } from '../../../error-handling';
Expand Down Expand Up @@ -77,14 +77,26 @@ implements AuthStrategyInterface<
}

constructor() {
this.accessTokenSecret = inject(ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_SECRET);
const accessTokenSecret: string | undefined = inject(ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_SECRET);
if (!accessTokenSecret) {
throw new NoProviderError(ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_SECRET, [JwtAuthStrategy]);
}
const refreshTokenSecret: string | undefined = inject(ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_SECRET);
if (!refreshTokenSecret) {
throw new NoProviderError(ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_SECRET, [JwtAuthStrategy]);
}
const confirmPasswordResetUrl: string | undefined = inject(ZIBRI_DI_TOKENS.JWT_CONFIRM_PASSWORD_RESET_URL);
if (!confirmPasswordResetUrl) {
throw new NoProviderError(ZIBRI_DI_TOKENS.JWT_CONFIRM_PASSWORD_RESET_URL, [JwtAuthStrategy]);
}
this.accessTokenSecret = accessTokenSecret;
this.accessTokenExpiresInMs = inject(ZIBRI_DI_TOKENS.JWT_ACCESS_TOKEN_EXPIRES_IN_MS);
this.refreshTokenSecret = inject(ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_SECRET);
this.refreshTokenSecret = refreshTokenSecret;
this.refreshTokenExpiresInMs = inject(ZIBRI_DI_TOKENS.JWT_REFRESH_TOKEN_EXPIRES_IN_MS);
this.passwordResetTokenExpiresInMs = inject(ZIBRI_DI_TOKENS.JWT_PASSWORD_RESET_TOKEN_EXPIRES_IN_MS);
this.userService = inject(ZIBRI_DI_TOKENS.USER_SERVICE);
this.emailService = inject(ZIBRI_DI_TOKENS.EMAIL_SERVICE);
this.confirmPasswordResetUrl = inject(ZIBRI_DI_TOKENS.JWT_CONFIRM_PASSWORD_RESET_URL);
this.confirmPasswordResetUrl = confirmPasswordResetUrl;
}

// eslint-disable-next-line jsdoc/require-jsdoc
Expand Down
2 changes: 1 addition & 1 deletion src/auth/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { UserServiceInterface } from './user-service.interface';
import { inject } from '../../di';
import { NotFoundError } from '../../error-handling';
import { GlobalRegistry } from '../../global';
import { BaseUser } from '../models';
import { UserServiceInterface } from './user-service.interface';

// eslint-disable-next-line jsdoc/require-jsdoc
export const NO_USER_REPOSITORIES_PROVIDED_ERROR_MESSAGE: string = 'No user repositories have been provided.';
Expand Down
4 changes: 2 additions & 2 deletions src/backup/backup-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import path from 'path';
import { beforeAll, afterAll, describe, it, expect } from '@jest/globals';
import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql';

import { BackupService } from './backup.service';
import { POSTGRES_TEST_IMAGE, testFileFolder } from '../__testing__';
import { BackupEntity } from './backup-entity.model';
import { BackupResourceEntity } from './backup-resource-entity.model';
Expand All @@ -18,6 +17,7 @@ import { Newable } from '../types';
import { Backup } from './decorators/backup-resource.decorator';
import { FsBackupTransport } from './transports';
import { PostgresDataSource, PostgresOptions } from '../data-source';
import { BackupServiceInterface } from './backup-service.interface';

const backupFsFolder: string = path.join(testFileFolder, 'backups');

Expand Down Expand Up @@ -57,7 +57,7 @@ describe('Create and restore postgres backup', () => {
let itemRepository: Repository<Item>;
let backupRepository: Repository<BackupEntity>;
let container: StartedPostgreSqlContainer;
let backupService: BackupService;
let backupService: BackupServiceInterface;

beforeAll(async () => {
await rm(backupFsFolder, { recursive: true, force: true });
Expand Down
4 changes: 2 additions & 2 deletions src/backup/backup.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { inject, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../di';
import { GlobalRegistry } from '../global';
import { LoggerInterface } from '../logging';
import { Newable } from '../types';
import { chunkedPromiseAll, MetadataUtilities, validateEntitiesRegistered } from '../utilities';
import { MetadataUtilities, PromiseUtilities, validateEntitiesRegistered } from '../utilities';
import { BackupEntity, BackupEntityCreateData } from './backup-entity.model';
import { BackupResourceEntity, BackupResourceEntityCreateData } from './backup-resource-entity.model';
import { BackupResourceInterface } from './backup-resource.interface';
Expand Down Expand Up @@ -80,7 +80,7 @@ export class BackupService implements BackupServiceInterface {

const groupedEntities: Record<string, BackupEntity[]> = this.groupEntitiesById(entitiesToSync);
const mergedEntities: BackupEntity[] = this.mergeEntities(groupedEntities);
await chunkedPromiseAll(mergedEntities.map(e => this.backupRepository.create(e, { allowId: true })));
await PromiseUtilities.allChunked(mergedEntities, e => this.backupRepository.create(e, { allowId: true }));
}

private mergeEntities(groupedEntities: Record<string, BackupEntity[]>): BackupEntity[] {
Expand Down
9 changes: 5 additions & 4 deletions src/change-sets/change-set-repository.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { inject, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../di';
import { PropertyMetadata } from '../entity';
import { BadRequestError } from '../error-handling';
import { HttpRequest } from '../http';
import { chunkedPromiseAll, MetadataUtilities } from '../utilities';
import { MetadataUtilities, ObjectUtilities, PromiseUtilities } from '../utilities';

/**
* The result for resetting a change set on an entity.
Expand Down Expand Up @@ -55,7 +55,7 @@ export class ChangeSetRepository<

this.keysToExcludeFromChangeSets = ['changeSets'];
const props: Record<string, PropertyMetadata> = MetadataUtilities.getModelProperties(entityClass);
for (const [key, m] of Object.entries(props)) {
for (const [key, m] of ObjectUtilities.entries(props)) {
if (m.excludeFromChangeSets) {
this.keysToExcludeFromChangeSets.push(key as keyof T);
}
Expand Down Expand Up @@ -311,8 +311,9 @@ export class ChangeSetRepository<
options?: BaseRepositoryOptions
): Promise<number> {
const entitiesToRollback: T[] = await this.findAll({ where: where, ...options });
await chunkedPromiseAll(
entitiesToRollback.map(e => this.rollbackToDate(e, date, createChangeSet, preserveCreateChangeSet, options))
await PromiseUtilities.allChunked(
entitiesToRollback,
e => this.rollbackToDate(e, date, createChangeSet, preserveCreateChangeSet, options)
);
return entitiesToRollback.length;
}
Expand Down
5 changes: 3 additions & 2 deletions src/data-source/data-source.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { inject, ZIBRI_DI_TOKENS } from '../di';
import { MailingList, MailingListSubscriber } from '../email';
import { BaseEntity } from '../entity/base-entity.model';
import { Log, LoggerInterface } from '../logging';
import { Invoice, NumberInvoices } from '../plugin';
import { Invoice, NumberInvoices, Payment } from '../plugin';
import { Newable } from '../types';
import { validateEntitiesRegistered } from '../utilities';
import { DataSourceInterface } from './data-sources/data-source.interface';
Expand All @@ -28,7 +28,8 @@ export class DataSourceService implements DataSourceServiceInterface {
BackupResourceEntity,
BackupEntity,
NumberInvoices,
Invoice
Invoice,
Payment
];

constructor() {
Expand Down
21 changes: 11 additions & 10 deletions src/data-source/data-sources/postgres-data-source.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { ChildProcessByStdio, spawn } from 'node:child_process';
import { PassThrough, Readable, Writable } from 'node:stream';

import { DataSource as TODataSource, Repository as TORepository, EntityMetadata as TOEntityMetadata, EntitySchema, EntitySchemaColumnOptions, QueryRunner, EntitySchemaRelationOptions, Table, TableColumnOptions, TableColumn, EntityTarget } from 'typeorm';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
import { IsolationLevel } from 'typeorm/driver/types/IsolationLevel';
import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
import { OnDeleteType } from 'typeorm/metadata/types/OnDeleteType';
import { OnUpdateType } from 'typeorm/metadata/types/OnUpdateType';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
import { IsolationLevel } from 'typeorm/driver/types/IsolationLevel.js';
import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata.js';
import { OnDeleteType } from 'typeorm/metadata/types/OnDeleteType.js';
import { OnUpdateType } from 'typeorm/metadata/types/OnUpdateType.js';

import { DataSourceInterface } from '.';
import { DataSourceInterface } from './data-source.interface';
import { ChangeSetEntity, ChangeSetRepository, isChangeSetEntityNewable, isSoftDeleteEntityNewable, SoftDeleteEntity, SoftDeleteRepository } from '../../change-sets';
import { inject, repositoryTokenFor, ZIBRI_DI_TOKENS } from '../../di';
import { register } from '../../di/register.function';
Expand All @@ -19,6 +19,7 @@ import { GlobalRegistry } from '../../global';
import { LoggerInterface } from '../../logging';
import { ExcludeStrict, Newable, OmitStrict, Version } from '../../types';
import { compareVersion, MetadataUtilities } from '../../utilities';
import { ObjectUtilities } from '../../utilities/object.utilities';
import { Migration, MigrationEntity } from '../migration';
import { ColumnType, DataSourceOptions } from '../models';
import { Repository } from '../repository';
Expand Down Expand Up @@ -197,7 +198,7 @@ export abstract class PostgresDataSource implements DataSourceInterface {
}
const props: Record<string, PropertyMetadata> = MetadataUtilities.getModelProperties(cls);

const numberOfPrimaryKeys: number = Object.values(props).filter(d => (d as StringPropertyMetadata).primary).length;
const numberOfPrimaryKeys: number = ObjectUtilities.values(props).filter(d => (d as StringPropertyMetadata).primary).length;
if (numberOfPrimaryKeys === 0) {
throw new Error(`no primary key specified for entity "${cls.name}".`);
}
Expand All @@ -207,7 +208,7 @@ export abstract class PostgresDataSource implements DataSourceInterface {

const columns: Record<string, EntitySchemaColumnOptions> = {};
const relations: Record<string, EntitySchemaRelationOptions> = {};
for (const [key, m] of Object.entries(props)) {
for (const [key, m] of ObjectUtilities.entries(props)) {
if (
m.type === Relation.MANY_TO_ONE
|| m.type === Relation.ONE_TO_MANY
Expand Down Expand Up @@ -351,7 +352,7 @@ export abstract class PostgresDataSource implements DataSourceInterface {
...metadata,
type: metadata.format === 'uuid' || metadata.primary ? 'uuid' : this.columnTypeMapping[metadata.type],
length: metadata.maxLength,
enum: metadata.enum ? Object.values(metadata.enum) : undefined,
enum: metadata.enum ? ObjectUtilities.values(metadata.enum) : undefined,
default: undefined
};
}
Expand Down Expand Up @@ -478,7 +479,7 @@ export abstract class PostgresDataSource implements DataSourceInterface {
...columnMetadata,
...newColumn,
enum: 'enum' in newColumn && newColumn.enum
? Object.values(newColumn.enum).map(v => String(v))
? ObjectUtilities.values(newColumn.enum).map(v => String(v))
: columnMetadata.enum
? columnMetadata.enum.map(v => String(v))
: undefined,
Expand Down
Loading
Loading