
Research
Malicious npm Packages Impersonate Flashbots SDKs, Targeting Ethereum Wallet Credentials
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
@produce8/p8-cube-helper
Advanced tools
[](https://github.com/semantic-release/semantic-release) [ utilizes domain driven design.
Domain-Driven Design (DDD) is an approach to software development that focuses on aligning the software design with the business domain it represents. It emphasizes understanding the domain and using that understanding to drive the design and implementation of the software system.
Domain-driven Design (DDD) typically involves the following layers:
These layers work together to create a software system that aligns with the business domain and promotes maintainability, extensibility, and flexibility.
/src
/common
/ddd
/decorators
/enum
/exceptions
/guards
/interface
/port
/utils
/core
/application
/services
/ports
/domain
/entities
/aggregates
/services
/infrastructure
/adapters
/mappers
/interface
/controllers
/presenters
/dtos
/queries
/commands
<feature-name>|<entity-name>[.<request>|<response>].<type>.ts
Example:
- DTOs:
- get-app-metrics-totals-whole-team.request.dto.ts
- get-app-metrics-totals-whole-team.response.dto.ts
- Controller:
- get-app-metrics-totals-whole-team.controller.ts
- Composite Controller:
- get-app-metrics-totals-whole-team.composite-controller.ts
- Query:
- get-app-metrics-totals-whole-team.query.ts
- Command:
- expand-app-metrics-totals-whole-team.command.ts
- Entity:
- app-metrics-totals-whole-team.entity.ts
- Mapper:
- app-metrics-totals-whole-team.mapper.ts
- Presenter:
- get-app-metrics-totals-whole-team.presenter.ts
- Application service:
- get-app-metrics-totals-whole-team.application-service.ts
- Application service:
- analytics-activity.port.ts
- Domain service:
- get-app-metrics-totals-whole-team.domain-service.ts
Before writing any code, sit down with the frontend developer and work through the Figma mockups for a given feature to produce interfaces that define the minimal logically grouped queries that can be performed. If a component consists of; a chart, an average for this period value and an all time average value, the data that needs to be queried for these respective sub components can’t be queried together. These define your domains!
These domains define the controllers you will then build in the cube helper package.
Heres an example of a minimal interface:
interface CubeRequestConfig {
token: string;
}
interface GetAppMetricsTotalsWholeTeamRequestDto {
params: {
startDate: string;
endDate: string;
teamId: string;
userIds: string[];
};
config: CubeRequestConfig;
}
interface GetAppMetricsTotalsWholeTeamResponseDto {
timeInApp: Number;
timeInCalls: Number;
}
Once the minimal interfaces (domains) have been defined, you can then use those interfaces to extrapolate the rest of your feature. Those interfaces become the request and response DTOs used by your controllers.
A complete feature consists of the following components:
Example:
interface CubeRequestConfig {
token: string;
}
interface GetAppMetricsTotalsWholeTeamRequestDto {
params: {
startDate: string;
endDate: string;
teamId: string;
userIds: string[];
};
config: CubeRequestConfig;
}
interface GetAppMetricsTotalsWholeTeamResponseDto {
timeInApp: Number;
timeInCalls: Number;
}
Example:
export class GetAppMetricsTotalWholeTeamController {
constructor(
protected readonly logManager: LogManager,
protected readonly query: GetAppMetricsTotalWholeTeamQuery,
protected readonly mapper: AppMetricsTotalWholeTeamMapper
) {}
async get(
props: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<GetAppMetricsTotalsWholeTeamResponseDto> {
try {
const result = await this.query.execute(props);
return this.mapper.toResponse(result);
} catch (error) {
throw new Error(
`AppMetricsTotalWholeTeamComponentController.get encountered an error ${error}`
);
}
}
}
Example:
export class GetAppMetricsTotalWholeTeamQuery {
constructor(
protected readonly logManager: LogManager,
protected readonly service: GetAppMetricsTotalWholeTeamApplicationService
) {}
async execute(
props: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<AppMetricsTotalWholeTeamEntity> {
try {
return await this.service.execute(props);
} catch (error) {
throw new InternalServerErrorException(error);
}
}
}
export class ExpandAppMetricsTotalWholeTeamCommand {
constructor(
protected readonly logManager: LogManager,
// this can be an application service if you are sending data to some external
// system. if you never need to leave the domain layer, a domain
// service is just fine!
protected readonly service: ExpandAppMetricsTotalWholeTeamDomainService
) {}
async execute(
props: ExpansAppMetricsTotalsWholeTeamRequestDto
): Promise<AppMetricsTotalWholeTeamEntity> {
try {
return await this.service.execute(props);
} catch (error) {
throw new InternalServerErrorException(error);
}
}
}
Example:
export class GetAppMetricsTotalWholeTeamApplicationService {
constructor(
protected readonly logManager: LogManager,
protected readonly port: AnalyticsActivityPort,
protected readonly domainService: GetAppMetricsTotalWholeTeamDomainService
) {}
async execute(
props: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<AppMetricsTotalWholeTeamEntity> {
try {
const result: AppMetricsTotalWholeTeamEntity[] =
await this.port.getAppMetricsTotalsWholeTeam({
startDate: props.params.startDate,
endDate: props.params.endDate,
teamId: props.params.teamId,
})
return this.domainService({ entity: result });
} catch (error) {
throw new Error(
`GetAppMetricsTotalWholeTeamService.execute encountered an error ${error}`
);
}
}
}
Example:
import { Guard } from 'src/common/guards/guard';
export class AnalyticsActivityPort {
constructor(
protected readonly logManager: LogManager,
protected readonly adapter: CubeAdapter
) {
}
async getAppMetricsTotalsWholeTeam(props:{
startDate: string,
endDate: string,
teamId: string
}): Promise<AppMetricsTotalWholeTeamEntity> {
try {
const result: IAppMetricsTotalWholeTeamPersistenceDto[] =
await this.adapter.query(props.token, {
measures: [
IAnalyticsEventEnum.Count,
IAnalyticsEventEnum.TotalDuration,
],
timeDimensions: [
{
dimension: IAnalyticsEventEnum.LocalStartTime,
dateRange: [props.startDate, props.endDate],
},
],
filters: [
{
member: IAnalyticsEventEnum.EventType,
operator: 'equals',
values: [
AnalyticsEventTypes.INTERNAL,
AnalyticsEventTypes.EXTERNAL,
],
},
],
});
if (result.length > 0) {
const filteredResults = await Guard.filterValidResponses(
IAppMetricsTotalWholeTeamPersistenceDto,
result
);
const [entity] = filteredResults.map((record) => {
return SelfCalendarScoreMapper.toDomain(record);
});
return entity;
}
return null;
} catch (error) {
throw new InternalServerErrorException(error);
}
}
}
The Domain Service is for orchestrating relationships between domain objects: ie Entities and Aggregate Roots - as opposed to the Application Service which interfaces with external data
export interface GetAppMetricsTotalWholeTeamDomainServiceProps {
entity: AppMetricsTotalWholeTeamEntity
}
export class GetAppMetricsTotalWholeTeamDomainService {
constructor(protected readonly logManager: LogManager) {}
execute(
props: GetAppMetricsTotalWholeTeamDomainServiceProps
): AppMetricsTotalWholeTeamEntity {
try {
// perform hydration on missing values
let entity = props.entity
if (!entity) {
AppMetricsTotalWholeTeamEntity.create({
timeInApp: 0,
timeInCalls: 0
}
)
}
// if the entity needs to access any values from the FE rather than from Cube values
// add them with setter methods here
// if an aggregate root is returned from the application service,
// construct it out of the inputted entities and return here.
return entity
} catch (error) {
throw new Error(
`GetAppMetricsTotalWholeTeamDomainService.execute encountered an error ${error}`
);
}
}
}
Example:
interface AppMetricsTotalWholeTeamEntityProps {
timeInApp: number;
timeInCalls: number;
}
class AppMetricsTotalWholeTeamEntity extends Entity<AppMetricsTotalWholeTeamEntityProps> {
protected _id: string;
protected _someSecretInternalValue: number;
static create(
props: AppMetricsTotalWholeTeamEntityProps,
entityId?: string
): AppMetricsTotalWholeTeamEntity {
const id = entityId || v4();
const user = new AppMetricsTotalWholeTeamEntity({
id,
props,
});
return user;
}
public validate(): void {}
get timeInApp() {
return this.props.timeInApp;
}
get timeInCalls() {
return this.props.timeInCalls;
}
// setters can be used in the following approach where data needs to be
// drilled down from the interface layer or another domain entity.
setSomeSecretInternalValue(value: number): void {
this._someSecretInternalValue = value;
}
get timeInCallsMinusSecretValue(): number {
return this.props.timeInCalls - this._someSecretInternalValue;
}
// logic such as mathematical calculations can be achieved as such.
// these can then be invoked in the toResponse method of the mapper.
get someMathematicalCalculation() {
return Math.round(this.timeInApp / this.timeInCalls)
}
}
Example:
export class IAppMetricsTotalWholeTeamPersistenceDto {
@IsNotEmpty()
[ITeamAnalyticsActivityV2Enum.TotalDuration]: string;
@IsNotEmpty()
[ITeamAnalyticsActivityV2Enum.TotalCallDuration]: string;
}
export class AppMetricsTotalWholeTeamMapper
implements
Mapper<
IAppMetricsTotalWholeTeamPersistenceDto, // <-- persistence
AppMetricsTotalWholeTeamEntity, // <-- entity
GetAppMetricsTotalsWholeTeamResponseDto // <-- dto
>
{
toPersistence(
entity: AppMetricsTotalWholeTeamEntity
): IAppMetricsTotalWholeTeamPersistenceDto {
return AppMetricsTotalWholeTeamMapper.toPersistence(entity);
}
static toPersistence(
entity: AppMetricsTotalWholeTeamEntity
): IAppMetricsTotalWholeTeamPersistenceDto {
return {
[ITeamAnalyticsActivityV2Enum.TotalDuration]: String(entity.timeInApp),
[ITeamAnalyticsActivityV2Enum.TotalCallDuration]: String(
entity.timeInCalls
),
};
}
toDomain(
record: IAppMetricsTotalWholeTeamPersistenceResponse
): AppMetricsTotalWholeTeamEntity {
return AppMetricsTotalWholeTeamMapper.toDomain(record);
}
static toDomain(
record: IAppMetricsTotalWholeTeamPersistenceResponse
): AppMetricsTotalWholeTeamEntity {
const entity = AppMetricsTotalWholeTeamEntity.create({
timeInApp: Number(record[ITeamAnalyticsActivityV2Enum.TotalDuration]),
timeInCalls: Number(
record[ITeamAnalyticsActivityV2Enum.TotalCallDuration]
),
});
return entity;
}
toResponse(
entity: AppMetricsTotalWholeTeamEntity
): GetAppMetricsTotalsWholeTeamResponseDto {
return AppMetricsTotalWholeTeamMapper.toResponse(entity);
}
static toResponse(
entity: AppMetricsTotalWholeTeamEntity
): GetAppMetricsTotalsWholeTeamResponseDto {
return {
timeInApp: entity.timeInApp,
timeInCalls: entity.timeInCalls,
// someAvgValue: entity.someMathematicalCalculation < -- math operations delayed as long as possible
};
}
}
Example:
type ScoreAggregateRoots =
| SelfDigitalIntensityScoreAggregateRoot
| SelfCalendarScoreAggregateRoot
| SelfDigitalWorkingHoursScoreAggregateRoot
| SelfFocusScoreAggregateRoot
| SelfTimeInCallsScoreAggregateRoot
| SelfScreentimeScoreAggregateRoot;
export interface GetSelfAllScoresPresenterProps
extends Record<string, ScoreAggregateRoots> {
digitalIntensityScore: SelfDigitalIntensityScoreAggregateRoot;
digitalWorkingHoursScore: SelfDigitalWorkingHoursScoreAggregateRoot;
focusScore: SelfFocusScoreAggregateRoot;
screentimeScore: SelfScreentimeScoreAggregateRoot;
calendarScore: SelfCalendarScoreAggregateRoot;
timeInCallsScore: SelfTimeInCallsScoreAggregateRoot;
}
export class GetSelfAllScoresPresenter
implements
Presenter<
ScoreAggregateRoots,
Omit<SelfGetAllScoresResponseDto, 'startDate' | 'endDate'>
>
{
constructor(
protected readonly logManager: LogManager,
protected readonly digitalIntensityMapper: SelfDigitalIntensityScoreAggregateRootMapper,
protected readonly digitalWorkingHoursMapper: SelfDigitalWorkingHoursScoreAggregateRootMapper,
protected readonly focusMapper: SelfFocusScoreAggregateRootMapper,
protected readonly screentimeMapper: SelfScreentimeScoreAggregateRootMapper,
protected readonly calendarMapper: SelfCalendarScoreAggregateRootMapper,
protected readonly timeInCallsMapper: SelfTimeInCallsScoreAggregateRootMapper
) {}
/**
* Remove legacy 'hasData' property from the response once the frontend is updated to handle empty data
*/
toResponse(
entities: GetSelfAllScoresPresenterProps
): Omit<SelfGetAllScoresResponseDto, 'startDate' | 'endDate'> {
try {
const digitalIntensity = this.digitalIntensityMapper.toResponse(
entities.digitalIntensityScore
);
const digitalWorkingHours = this.digitalWorkingHoursMapper.toResponse(
entities.digitalWorkingHoursScore
);
const focus = this.focusMapper.toResponse(entities.focusScore);
const screentime = this.screentimeMapper.toResponse(
entities.screentimeScore
);
const calendar = this.calendarMapper.toResponse(entities.calendarScore);
const timeInCalls = this.timeInCallsMapper.toResponse(
entities.timeInCallsScore
);
const hasData =
!digitalIntensity ||
!digitalWorkingHours ||
!focus ||
!screentime ||
!calendar ||
!timeInCalls
? false
: true;
return {
hasData,
digitalIntensity,
workingHours: digitalWorkingHours,
focus,
screenTime: screentime,
timeInMeetings: calendar,
timeInCalls,
};
} catch (error) {
throw new Error(
`GetSelfAllScoresPresenter.toResponse encountered an error ${error}`
);
}
}
}
Example
export class GetAppMetricsTotalWholeTeamCompositeController {
constructor(
protected readonly logManager: LogManager,
protected readonly queryOne: GetAppMetricsTotalWholeTeamQuery,
protected readonly queryTwo: GetAppMetricsTotalWholeTeamQuery,
protected readonly presenter: AppMetricsTotalWholeTeamPresenter
) {}
async get(
props: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<GetAppMetricsTotalsWholeTeamResponseDto> {
try {
const [resultOne, resultTwo] = await Promise.all([
this.queryOne.execute(props),
this.queryTwo.execute(props)
])
return this.presenter.toResponse({resultOne, resultTwo});
} catch (error) {
throw new Error(
`GetAppMetricsTotalWholeTeamCompositeController.get encountered an error ${error}`
);
}
}
}
As we currently have partial support for the legacy codebase (under the /lib
directory in the cube helper package), we must inject any new controllers into the pre-existing client class for Frontend enablement.
To achieve this, we’ve defined an interface to store new feature methods for the client to use:
interface IReforgedCubeHelperMethods {...}
This interface is then extended in the entry point interface for the cube helper package library. Under /lib/index.ts
:
interface ICubeQueryHelper extends IReforgedCubeHelperMethods {...}
When implementing a new feature, ensure you expose it to the frontend by adding an appropriately named method to the Reforged methods interface and implementing it in the cube query helper class.
Example:
export class CubeQueryHelper implements ICubeQueryHelper {
...
async getAppMetricsTotalsWholeTeam(
params: GetAppMetricsTotalsWholeTeamRequestDto
): Promise<GetAppMetricsTotalsWholeTeamResponseDto> {
try {
const mapper = new AppMetricsTotalWholeTeamMapper();
const service = new GetAppMetricsTotalWholeTeamService(
this.logManager,
this.adapter,
mapper
);
const query = new GetAppMetricsTotalWholeTeamQuery(
this.logManager,
service
);
const controller = new GetAppMetricsTotalWholeTeamController(
this.logManager,
query,
mapper
);
return await controller.get(params);
} catch (error) {
this.logManager.error(
'TeamMetricsController.getAppMetricsTotalsWholeTeam encountered an error',
error
);
}
};
...
}
<component-location>/test/unit/
<feature-name>|<entity-name>.<type>.test.ts
Example:
describe('Given GetAppMetricsTotalWholeTeamService', () => {
const mockLogManager = mock<LogManager>();
const mockAdapter = mock<CubeAdapter>();
const mockMapper = mock<AppMetricsTotalWholeTeamMapper>();
const sut = new GetAppMetricsTotalWholeTeamService(
mockLogManager,
mockAdapter,
mockMapper
);
afterEach(() => {
jest.clearAllMocks();
});
describe('Given valid input', () => {
it('Should return without error', async () => {
mockAdapter.query.mockResolvedValueOnce([
{
[ITeamAnalyticsActivityV2Enum.TotalDuration]: '1',
[ITeamAnalyticsActivityV2Enum.TotalCallDuration]: '1',
[ITeamAnalyticsActivityV2Enum.UserCount]: '1',
},
]);
const entity = AppMetricsTotalWholeTeamEntity.create({
timeInApp: 1,
timeInCalls: 1,
});
mockMapper.toDomain.mockReturnValueOnce(entity);
const result = await sut.execute({
params: {
startDate: '2024-01-01',
endDate: '2024-01-01',
teamId: '',
userIds: [''],
},
config: { token: '' },
});
expect(result.equals(entity)).toEqual(true);
expect(mockAdapter.query).toHaveBeenCalled();
expect(mockMapper.toDomain).toHaveBeenCalled();
});
it('Should return error service throws error', async () => {
mockAdapter.query.mockRejectedValueOnce('some error');
await expect(
sut.execute({
params: {
startDate: '2024-01-01',
endDate: '2024-01-01',
teamId: '',
userIds: [''],
},
config: { token: '' },
})
).rejects.toThrow();
});
});
});
Large teams working together on a project that requires a consistent release cycle creates friction if said release cycle is managed by humans. Humans are naturally fallible.
Using a consistent, standard commit message approach, an automated solution can be introduced that allows for semantic releases to happen without the need for human input (beyond merging the odd pull request).
Integrate one or more tools to enable automated semantic version releases as part of our CI/CD pipeline.
To integrate this solution with an existing project, here's what you'll need:
npm install --save-dev semantic-release husky @commitlint/{cli,config-conventional}
or
yarn add --dev semantic-release husky @commitlint/{cli,config-conventional}
.npmrc
file with the following command:
echo "access=public" >> .npmrc
npx husky init
echo "npx --no -- commitlint --edit \\$1" > .husky/commit-msg
echo "export default { extends: ["@commitlint/config-conventional"] };" > commitlint.config.ts
.github/workflows/release.yml:
name: Release
on:
push:
branches:
- main ## or master
permissions:
contents: read ## for checkout
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write ## to be able to publish a GitHub release
issues: write ## to be able to comment on released issues
pull-requests: write ## to be able to comment on released pull requests
id-token: write ## to enable use of OIDC for npm provenance
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "lts/*"
- name: Install dependencies
run: npm clean-install
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
run: npm audit signatures
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
When a pull request is merged into main, the Github workflow Release action is triggered which invokes semantic-release. This traverses the commit history and compiles a release based on the commit convention.
<type>(<scope>): <short summary>
│ │ │
│ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
│ │
│ └─⫸ Commit Scope(Optional): animations|bazel|benchpress|common|compiler|compiler-cli|core|
│ elements|forms|http|language-service|localize|platform-browser|
│ platform-browser-dynamic|platform-server|router|service-worker|
│ upgrade|zone.js|packaging|changelog|docs-infra|migrations|
│ devtools
│
└─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
The following commit message patterns will trigger a release of their given scope:
fix(pencil): stop graphite breaking when too much pressure applied
feat(pencil): stop graphite breaking when too much pressure applied
(Note that the BREAKING CHANGE: token must be in the footer of the commit)
feat(pencil): stop graphite breaking when too much pressure applied
BREAKING CHANGE: The graphiteWidth option has been removed.
The default graphite width of 10mm is always used for performance reasons.
Feature | Deadline | Complete | |
---|---|---|---|
Dependency container for dependency injection solution | TBD | yes/ not currently implemented | |
Code linting (https://www.npmjs.com/package/eslint-plugin-hexagonal-architecture) | TBD | ||
CLI tool for dev experience | TBD | ||
class validator @Sasha Mahalia | 2024-04-05 | yes | |
semantic-release | |||
commitlint | https://commitlint.js.org/guides/local-setup.html |
Semantic Release
https://semantic-release.gitbook.io/semantic-release
https://commitlint.js.org/guides/getting-started.html
https://www.conventionalcommits.org/en/v1.0.0/
top level domain entity
is the entity that returns data to the client. This is achieved via the use of a Controller and Mapper. This can be an Entity
or and AggregateRoot
toResponse
mapper method as it's not needed. However, if the given entity is also a top level domain entity
then it does indeed need a complete mapper interface, as it will return a response interface to the client.operator does not exist: character varying < timestamp with time zone
or similar is a Postgres error that refers to having a mismatch between the operation a Cube query is trying to perform with the datatype that isn’t a timestamp. Converting the offending postgres datatype - ie “varchar” to “timestamp” will solve the issue.test/integration
folder.FAQs
[](https://github.com/semantic-release/semantic-release) [![Tests](https://github.com/produce8/p8-cube-helper/actions/workflows/test
The npm package @produce8/p8-cube-helper receives a total of 451 weekly downloads. As such, @produce8/p8-cube-helper popularity was classified as not popular.
We found that @produce8/p8-cube-helper demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.