Skip to content

What's new in 0.2.1

0.2.1 only adds: no 0.2.0 export, signature or behavior changes, and no peer range moves. Upgrade the ten packages above to ^0.2.1 to use the following.

Each add-on publishes its decision as a context.items entry and now exports a builder that turns it into flat attributes, {} when the behavior did not run and never a key. AttributesBehavior of @nestjs-pipeline/opentelemetry runs the builders you choose after the chain has finished and adds the result to the attributes TraceBehavior puts on the span:

import { buildCacheAttributes } from '@nestjs-pipeline/cache';
import { buildDeadLetterAttributes } from '@nestjs-pipeline/deadletter';
import { buildFeatureFlagAttributes } from '@nestjs-pipeline/feature-flags';
import { buildIdempotencyAttributes } from '@nestjs-pipeline/idempotency';
import {
AttributesBehavior,
MetricsBehavior,
TraceBehavior,
} from '@nestjs-pipeline/opentelemetry';
import { buildRateLimitAttributes } from '@nestjs-pipeline/rate-limit';
PipelineModule.forRoot({
globalBehaviors: {
before: [
TraceBehavior,
MetricsBehavior,
[AttributesBehavior, {
factories: [
buildFeatureFlagAttributes, // feature_flag.key, .enabled, .variant, .reason, .error_code
buildCacheAttributes, // cache.hit
buildIdempotencyAttributes, // idempotency.replayed, .ownership_lost
buildRateLimitAttributes, // rate_limit.remaining_points
buildDeadLetterAttributes, // dead_letter.captured
],
}],
],
},
});

The builders are plain data, not telemetry-only. Wrap one to rename an attribute, or use it as audit metadata, with audit() declared outside the behavior it describes:

const cacheHit = (context: IPipelineContext) => {
const { 'cache.hit': hit } = buildCacheAttributes(context);
return hit === undefined ? {} : { 'app.cache_hit': hit };
};
@UsePipeline(
audit({ action: 'order.create', metadata: buildIdempotencyAttributes }),
idempotent({ keyFactory }),
)

@nestjs-pipeline/casl exports abilityDigest(context?): the SHA-256 of the caller’s effective rules, with conditions already resolved against the principal, so any change that can change a decision changes it. Use it as the scope of a principal-scoped cache key, and requireAbilityDigest, which throws MissingAbilityError without an ability, as an idempotency replay scope:

import { abilityDigest, getCaslPrincipal, requireAbilityDigest } from '@nestjs-pipeline/casl';
const overviewKey = createPartitionedCacheKeyFactory({
principal: (ctx) => getCaslPrincipal(ctx)?.id,
scope: abilityDigest,
});
@UsePipeline(idempotent({ keyFactory, replayScopeFactory: requireAbilityDigest }))

CaslAuthorizer.dependsOnEntity(action, subject) tells a handler that a conditional rule decides on entity attributes, so it reads the entity fresh instead of from a cache:

const refresh = this.authorizer.dependsOnEntity('read', 'User');
const user = await this.users.find(new GetUserQuery({ userId }, { refresh }));

@cqrs-ddd/core adds requireTenant(purpose, source?). It returns the tenant from source, or from the resolver registered with setTenantResolver, and throws MissingTenantContextError when there is none:

import { requireTenant } from '@cqrs-ddd/core/application';
requireTenant('access token issuance'); // the current tenant
requireTenant('users.create idempotency key', ctx); // ctx.tenantId, else the current one

requireTenantId(source, purpose) keeps working and is deprecated: swap the arguments and call requireTenant.