mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
7
libs/common/data-access/README.md
Normal file
7
libs/common/data-access/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# common-data-access
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test common-data-access` to execute the unit tests.
|
||||
34
libs/common/data-access/eslint.config.mjs
Normal file
34
libs/common/data-access/eslint.config.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'common',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'common',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
21
libs/common/data-access/jest.config.ts
Normal file
21
libs/common/data-access/jest.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
displayName: 'common-data-access',
|
||||
preset: '../../../jest.preset.js',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
coverageDirectory: '../../../coverage/libs/common/data-access',
|
||||
transform: {
|
||||
'^.+\\.(ts|mjs|js|html)$': [
|
||||
'jest-preset-angular',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
},
|
||||
],
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||
'jest-preset-angular/build/serializers/html-comment',
|
||||
],
|
||||
};
|
||||
20
libs/common/data-access/project.json
Normal file
20
libs/common/data-access/project.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "common-data-access",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/common/data-access/src",
|
||||
"prefix": "common",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "libs/common/data-access/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
libs/common/data-access/src/index.ts
Normal file
2
libs/common/data-access/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './lib/errors';
|
||||
export * from './lib/models';
|
||||
100
libs/common/data-access/src/lib/errors/data-access.error.spec.ts
Normal file
100
libs/common/data-access/src/lib/errors/data-access.error.spec.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { DataAccessError } from './data-access.error';
|
||||
|
||||
/**
|
||||
* Test suite for DataAccessError class.
|
||||
* Validates the functionality of the base error class used for structured error handling.
|
||||
*/
|
||||
describe('DataAccessError', () => {
|
||||
/**
|
||||
* Tests basic error functionality without additional data
|
||||
*/
|
||||
describe('Basic Error without Data', () => {
|
||||
class TestError extends DataAccessError<'TEST_ERROR'> {
|
||||
constructor(message: string) {
|
||||
super('TEST_ERROR', message, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
it('should create an error with a code and message', () => {
|
||||
// Arrange & Act
|
||||
const error = new TestError('Test message');
|
||||
|
||||
// Assert
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error).toBeInstanceOf(DataAccessError);
|
||||
expect(error.code).toBe('TEST_ERROR');
|
||||
expect(error.message).toBe('Test message');
|
||||
expect(error.data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests error functionality with additional data, ensuring type safety
|
||||
* and proper data handling
|
||||
*/
|
||||
describe('Error with Data', () => {
|
||||
interface ValidationData {
|
||||
field: string;
|
||||
issues: string[];
|
||||
}
|
||||
|
||||
class ValidationError extends DataAccessError<
|
||||
'VALIDATION_ERROR',
|
||||
ValidationData
|
||||
> {
|
||||
constructor(message: string, data: ValidationData) {
|
||||
super('VALIDATION_ERROR', message, data);
|
||||
}
|
||||
}
|
||||
|
||||
it('should create an error with code, message, and data', () => {
|
||||
// Arrange
|
||||
const validationData: ValidationData = {
|
||||
field: 'email',
|
||||
issues: ['Invalid format', 'Required field'],
|
||||
};
|
||||
|
||||
// Act
|
||||
const error = new ValidationError('Validation failed', validationData);
|
||||
|
||||
// Assert
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error).toBeInstanceOf(DataAccessError);
|
||||
expect(error.code).toBe('VALIDATION_ERROR');
|
||||
expect(error.message).toBe('Validation failed');
|
||||
expect(error.data).toEqual(validationData);
|
||||
});
|
||||
|
||||
it('should maintain data type safety', () => {
|
||||
// Arrange
|
||||
const validationData: ValidationData = {
|
||||
field: 'username',
|
||||
issues: ['Too short'],
|
||||
};
|
||||
|
||||
// Act
|
||||
const error = new ValidationError('Validation failed', validationData);
|
||||
|
||||
// Assert
|
||||
expect(error.data.field).toBe('username');
|
||||
expect(error.data.issues).toContain('Too short');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests type safety enforcement for error codes and data
|
||||
*/
|
||||
describe('Error Type Safety', () => {
|
||||
it('should ensure type safety for error codes', () => {
|
||||
class TypedError extends DataAccessError<'TYPE_ERROR'> {
|
||||
constructor() {
|
||||
super('TYPE_ERROR', 'Type error message', undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const error = new TypedError();
|
||||
const errorCode: 'TYPE_ERROR' = error.code; // TypeScript will error if type safety fails
|
||||
expect(errorCode).toBe('TYPE_ERROR');
|
||||
});
|
||||
});
|
||||
});
|
||||
48
libs/common/data-access/src/lib/errors/data-access.error.ts
Normal file
48
libs/common/data-access/src/lib/errors/data-access.error.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Base error class for data access related errors. This class provides structured error
|
||||
* handling with type-safe error codes and optional additional data.
|
||||
*
|
||||
* Features:
|
||||
* - Type-safe error codes using string literals
|
||||
* - Optional additional error data with type safety
|
||||
* - Standardized error message format
|
||||
*
|
||||
* @template TCode - String literal type for the error code. Enforces type safety for error codes.
|
||||
* @template TData - Type for additional error data (defaults to void). Allows attaching context-specific data.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Define an error with a specific code and no data
|
||||
* class MyError extends DataAccessError<'MY_ERROR'> {
|
||||
* constructor(message: string) {
|
||||
* super('MY_ERROR', message);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Define an error with data
|
||||
* class ValidationError extends DataAccessError<'VALIDATION_ERROR', string[]> {
|
||||
* constructor(errors: string[]) {
|
||||
* super('VALIDATION_ERROR', 'Validation failed', errors);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link PropertyIsEmptyError} for example implementation with no data
|
||||
* @see {@link CreateReturnProcessError} for example implementation with data
|
||||
*/
|
||||
export class DataAccessError<TCode extends string, TData = void> extends Error {
|
||||
/**
|
||||
* Creates a new DataAccessError instance.
|
||||
*
|
||||
* @param code - The error code that identifies the type of error. Must match the TCode type parameter.
|
||||
* @param message - A human-readable description of the error.
|
||||
* @param data - Additional data related to the error. Must match the TData type parameter.
|
||||
*/
|
||||
constructor(
|
||||
public readonly code: TCode,
|
||||
message: string,
|
||||
public readonly data: TData,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
3
libs/common/data-access/src/lib/errors/index.ts
Normal file
3
libs/common/data-access/src/lib/errors/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './data-access.error';
|
||||
export * from './property-is-empty.error';
|
||||
export * from './property-is-null-or-undefined.error';
|
||||
@@ -0,0 +1,18 @@
|
||||
import { DataAccessError } from './data-access.error';
|
||||
import { PropertyIsEmptyError } from './property-is-empty.error';
|
||||
|
||||
describe('PropertyIsEmptyError', () => {
|
||||
it('should create an error with the correct code and message', () => {
|
||||
// Arrange
|
||||
const propertyName = 'username';
|
||||
|
||||
// Act
|
||||
const error = new PropertyIsEmptyError(propertyName);
|
||||
|
||||
// Assert
|
||||
expect(error).toBeInstanceOf(PropertyIsEmptyError);
|
||||
expect(error).toBeInstanceOf(DataAccessError);
|
||||
expect(error.code).toBe('PROPERTY_IS_EMPTY');
|
||||
expect(error.message).toBe(`Property "${propertyName}" is empty.`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { DataAccessError } from './data-access.error';
|
||||
|
||||
/**
|
||||
* Represents an error that is thrown when a required property is empty.
|
||||
*
|
||||
* @remarks
|
||||
* This error is identified by the code 'PROPERTY_IS_EMPTY'.
|
||||
*
|
||||
* @param propertyName - The name of the property that is empty.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* throw new PropertyIsEmptyError("username");
|
||||
* ```
|
||||
*/
|
||||
export class PropertyIsEmptyError extends DataAccessError<'PROPERTY_IS_EMPTY'> {
|
||||
constructor(propertyName: string) {
|
||||
super('PROPERTY_IS_EMPTY', `Property "${propertyName}" is empty.`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { PropertyNullOrUndefinedError } from './property-is-null-or-undefined.error';
|
||||
|
||||
describe('PropertyNullOrUndefinedError', () => {
|
||||
it('should create an error instance with the correct code and message', () => {
|
||||
// Arrange
|
||||
const propertyName = 'userId';
|
||||
|
||||
// Act
|
||||
const error = new PropertyNullOrUndefinedError(propertyName);
|
||||
|
||||
// Assert
|
||||
expect(error).toBeInstanceOf(PropertyNullOrUndefinedError);
|
||||
expect(error.code).toBe('PROPERTY_NULL_OR_UNDEFINED');
|
||||
expect(error.message).toBe(
|
||||
`Property "${propertyName}" is null or undefined.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { DataAccessError } from './data-access.error';
|
||||
|
||||
/**
|
||||
* Error thrown when a required property is null or undefined.
|
||||
*
|
||||
* @remarks
|
||||
* This error indicates that a non-null, non-undefined property has been found to be invalid.
|
||||
*
|
||||
* @param parameterName - The name of the property that is null or undefined.
|
||||
*
|
||||
* @example
|
||||
* throw new PropertyNullOrUndefinedError('userId');
|
||||
*/
|
||||
export class PropertyNullOrUndefinedError extends DataAccessError<'PROPERTY_NULL_OR_UNDEFINED'> {
|
||||
constructor(propertyName: string) {
|
||||
super(
|
||||
'PROPERTY_NULL_OR_UNDEFINED',
|
||||
`Property "${propertyName}" is null or undefined.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
2
libs/common/data-access/src/lib/index.ts
Normal file
2
libs/common/data-access/src/lib/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './errors';
|
||||
export * from './models';
|
||||
38
libs/common/data-access/src/lib/models/async-result.ts
Normal file
38
libs/common/data-access/src/lib/models/async-result.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Constant object defining the possible states of an asynchronous operation.
|
||||
* Used as a type-safe alternative to string literals for status values.
|
||||
*/
|
||||
export const AsyncResultStatus = {
|
||||
Idle: 'Idle',
|
||||
Pending: 'Pending',
|
||||
Success: 'Success',
|
||||
Error: 'Error',
|
||||
} as const;
|
||||
|
||||
export type AsyncResultStatus =
|
||||
(typeof AsyncResultStatus)[keyof typeof AsyncResultStatus];
|
||||
|
||||
/**
|
||||
* Generic interface representing the state of an asynchronous operation.
|
||||
* Combines status tracking with optional data and error information.
|
||||
*
|
||||
* @template T - The type of data returned on successful operations
|
||||
*/
|
||||
export interface AsyncResult<T> {
|
||||
/**
|
||||
* Current status of the asynchronous operation
|
||||
*/
|
||||
status: AsyncResultStatus;
|
||||
|
||||
/**
|
||||
* Data returned by a successful operation
|
||||
* Only defined when status is Success
|
||||
*/
|
||||
data?: T;
|
||||
|
||||
/**
|
||||
* Error information from a failed operation
|
||||
* Only defined when status is Error
|
||||
*/
|
||||
error?: unknown;
|
||||
}
|
||||
17
libs/common/data-access/src/lib/models/callback-result.ts
Normal file
17
libs/common/data-access/src/lib/models/callback-result.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Represents the result of an asynchronous operation that may succeed or fail.
|
||||
* Either contains the successful data or an error, but not both.
|
||||
*
|
||||
* @template T - The type of data returned on successful operations
|
||||
*/
|
||||
export type CallbackResult<T> =
|
||||
| { data: T; error?: unknown }
|
||||
| { data?: T; error: unknown };
|
||||
|
||||
/**
|
||||
* Function type for callbacks that receive operation results.
|
||||
* Typically used for handling asynchronous operation completions.
|
||||
*
|
||||
* @template T - The type of data returned on successful operations
|
||||
*/
|
||||
export type Callback<T> = (result: CallbackResult<T>) => void;
|
||||
34
libs/common/data-access/src/lib/models/entity-cotnainer.ts
Normal file
34
libs/common/data-access/src/lib/models/entity-cotnainer.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Generic container for entity objects that provides standard properties for display and selection state.
|
||||
* Used for consistent entity representation in lists, dropdowns, and selection components.
|
||||
*
|
||||
* @template T - The type of data contained within the entity container
|
||||
*/
|
||||
export interface EntityContainer<T> {
|
||||
/**
|
||||
* Unique identifier for the entity
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* Human-readable name for display in UI elements
|
||||
*/
|
||||
displayName?: string;
|
||||
|
||||
/**
|
||||
* The actual entity data object
|
||||
*/
|
||||
data?: T;
|
||||
|
||||
/**
|
||||
* Whether the entity is enabled/available for interaction
|
||||
* When false, the entity might be shown as disabled in UI
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the entity is currently selected
|
||||
* Useful for multi-select interfaces
|
||||
*/
|
||||
selected?: boolean;
|
||||
}
|
||||
5
libs/common/data-access/src/lib/models/index.ts
Normal file
5
libs/common/data-access/src/lib/models/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './async-result';
|
||||
export * from './callback-result';
|
||||
export * from './entity-cotnainer';
|
||||
export * from './list-response-args';
|
||||
export * from './response-args';
|
||||
27
libs/common/data-access/src/lib/models/list-response-args.ts
Normal file
27
libs/common/data-access/src/lib/models/list-response-args.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ResponseArgs } from './response-args';
|
||||
|
||||
/**
|
||||
* Extended response structure specifically for paginated list data.
|
||||
* Includes pagination metadata in addition to standard response properties.
|
||||
*
|
||||
* @template T - The type of individual items in the result array
|
||||
*/
|
||||
export interface ListResponseArgs<T> extends ResponseArgs<T[]> {
|
||||
/**
|
||||
* Number of items skipped in the result set (pagination offset)
|
||||
* Used for calculating the starting position in paginated results
|
||||
*/
|
||||
skip: number;
|
||||
|
||||
/**
|
||||
* Maximum number of items to include in the result set (page size)
|
||||
* Defines how many items should be returned per page
|
||||
*/
|
||||
take: number;
|
||||
|
||||
/**
|
||||
* Total number of items available in the complete result set
|
||||
* Used for calculating total pages and showing pagination information
|
||||
*/
|
||||
hits: number;
|
||||
}
|
||||
30
libs/common/data-access/src/lib/models/response-args.ts
Normal file
30
libs/common/data-access/src/lib/models/response-args.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Standard response structure for API requests returning data of type T.
|
||||
* Provides consistent error handling and result access across the application.
|
||||
*
|
||||
* @template T - The type of data contained in the result property
|
||||
*/
|
||||
export interface ResponseArgs<T> {
|
||||
/**
|
||||
* Indicates whether the request resulted in an error
|
||||
* When true, the result may be undefined or contain partial data
|
||||
*/
|
||||
error: boolean;
|
||||
|
||||
/**
|
||||
* Map of property names to error messages for validation failures
|
||||
* Keys represent property names, values contain validation error messages
|
||||
*/
|
||||
invalidProperties: Record<string, string>;
|
||||
|
||||
/**
|
||||
* Optional message providing additional information about the response
|
||||
* Typically used for error descriptions or success confirmations
|
||||
*/
|
||||
message?: string;
|
||||
|
||||
/**
|
||||
* The actual data returned by the request
|
||||
*/
|
||||
result: T;
|
||||
}
|
||||
6
libs/common/data-access/src/test-setup.ts
Normal file
6
libs/common/data-access/src/test-setup.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
|
||||
setupZoneTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
28
libs/common/data-access/tsconfig.json
Normal file
28
libs/common/data-access/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
17
libs/common/data-access/tsconfig.lib.json
Normal file
17
libs/common/data-access/tsconfig.lib.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/test-setup.ts",
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
16
libs/common/data-access/tsconfig.spec.json
Normal file
16
libs/common/data-access/tsconfig.spec.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"target": "es2016",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"files": ["src/test-setup.ts"],
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user