Loading workspace insights... Statistics interval
7 days30 daysLatest CI Pipeline Executions
344f56fb Chore/9.0 migrate (#636)
* chore(repo): pin toolchain to node 22 + pnpm 8.15.9
- add .nvmrc (node 22) and packageManager field
- fixes toolchain drift: global pnpm 11 needs node >=22.13 but active
node was 20, and repo pinned nothing
- pnpm 8.15.9 matches existing lockfileVersion 6.1; pre-9.0 lift keeps
pnpm 8, full stack bump comes next step
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(repo): migrate nx 15 -> 16
- @nrwl/* -> @nx/* package rename (devkit, jest, js, linter, nest, rollup,
web, workspace); nx-cloud runner + package rename
- nx 16 migration scripts: project.json executors, nx.json, eslint plugin
- gate green: 8 packages build, 114 integration tests + unit suites pass
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(repo): migrate nx 16 -> 19, TypeScript 4.9 -> 5.4
- nx 16->19 migration scripts (@nx/linter -> @nx/eslint, .nx/cache dir,
target-defaults, nx.json plugin/defaultBase moves)
- fix transformer-plugin for TS 5: visitNode needs an isSourceFile type
guard to narrow Node|undefined back to SourceFile (model-visitor.ts)
- transformer-plugin specs: set experimentalDecorators explicitly (TS 5.0+
defaults to stage-3 decorators; @automapper/classes uses legacy ones)
- regen one fixture for TS 5.4 commonjs emit order (exports before __decorate)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(repo): migrate nx 19 -> 22 + replace bundler with tsdown
nx 19 -> 22 migration: jest 29 -> 30, nestjs 10.4.x, reflect-metadata 0.2.2,
target-defaults/gitignore updates.
Replace the @nx/rollup package build with tsdown (tools/scripts/build-packages.mjs):
@nx/rollup 22 regressed the published format for these type:module packages
(main -> ESM, orphaned index.cjs.js that can't load as CJS). tsdown emits a
correct dual build per package:
- index.cjs + index.d.cts (CommonJS), index.mjs + index.d.mts (ESM)
- proper exports map (import/require + types), main -> cjs preserved
- externals derived from root + per-package deps so nothing third-party bundles
Contract verified vs 8.8.1 baseline: attw clean (node10 / node16-cjs / node16-esm
/ bundler) for all 6 packages incl. all 3 @automapper/classes subpaths; publint
clean. Also fixes the latent 8.8.1 bug where the transformer-plugin types entry
pointed at a missing index.d.ts. 'package' script now runs the build script.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(repo): per-package tsdown build targets, drop @nx/rollup
- each published package has its own 'package' target running
'node tools/scripts/build-packages.mjs <name>'; root 'package' script runs
'nx run-many --target=package' so the root builds all of them
- classes target builds main + mapped-types + transformer-plugin subpaths + the
frontend shim (shim/index.js, bare tsc) in one step
- removed the now-dead @nx/rollup dep and all package-lib/package-all targets
- mapped-types/transformer-plugin no longer have standalone package targets
(built as subpaths by the classes target)
note: zod (unpublished, not in package flow) still references @nx/rollup in its
build target — left for the Step 2 executor cleanup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): rewrite set() as index-walk, drop per-level alloc
set() recursed with path.slice(1) (fresh array per level) and Object.assign(
object, {[base]: value}) (1-key temp object per level) on the default member
return path. Walk the original path by index and write the leaf directly,
mirroring setMutate. Behavior-preserving (set.spec + full suite green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): remove dead hasNullMetadata scan in map()
Per mapped member, map() did metadataMap.get(dest) + Array.find(...) === null.
Array.prototype.find returns an element or undefined, never null, so the result
was structurally always false -- an O(members^2) scan computing a constant.
Deleted (behavior-preserving), along with the now-unused getMetadataMap/
metadataMap, isPrimitiveArrayEqual and MetadataClassId imports.
note: the '=== null' may have been intended as 'metaFn() == null' (cf. the
'treat as-is' comment). That would be a behavior change, not folded in here --
flagged for separate investigation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(core): guard isDateConstructor against nullish input
Object.getPrototypeOf(null|undefined) throws. isDateConstructor is called
before the primitive check in @automapper/classes get-metadata-list, so a user
type factory returning nullish or [] (-> meta[0] undefined) crashed with a
cryptic 'Cannot convert undefined or null to object'. Return false for nullish.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): compute shouldRunImplicitMap lazily in mapMember
The 4 primitive/date predicate calls ran eagerly for every forMember member but
are only read in the Condition/NullSubstitution/UndefinedSubstitution cases.
Moved inside that case behind the existing value != null guard, so MapFrom/
FromValue/MapWith/ConvertUsing/MapWithArguments/MapDefer skip them. The ||
ordering is preserved (isPrimitiveConstructor short-circuits before isDate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(benchmark): add packages/benchmark-core workspace package
- introduce pnpm-workspace.yaml (packages/*) to enable workspace:* references
- packages/benchmark-core: node app benchmarking the core map/mapArray hot path
via the pojos strategy + mitata (flat 8-prop and nested+array fixtures)
- references @automapper/core and @automapper/pojos with workspace:*
- core/pojos source package.json expose exports -> ./src/index.ts for workspace
dev resolution (benchmark runs through tsx). build-packages overwrites exports
for the published dist -- verified dist exports unchanged, attw clean, 114 tests green
- run: pnpm --filter @automapper/benchmark-core bench
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(benchmark): add classes strategy + align reflect-metadata to 0.2.2
- benchmark-core now benchmarks both pojos and classes strategies (classes uses
explicit @AutoMap(() => Type) since tsx/esbuild has no emitDecoratorMetadata)
- align reflect-metadata to 0.2.x across the repo (root ~0.2.2; classes/mikro/
sequelize peers ^0.2.0) + pnpm overrides pinning rxjs 7.8.1 and reflect-metadata
0.2.2 so the workspace resolves a single consistent version of each
- scope pnpm-workspace.yaml to the 4 packages the benchmark needs (avoids pulling
docusaurus's ancient deps, which fanned out conflicting @nestjs/rxjs versions)
- classes source exports -> ./src/index.ts for workspace dev resolution (build
overwrites for dist; attw still clean)
- jest-setup: polyfill TextEncoder/TextDecoder (jest 30 jsdom env lacks them,
broke the nestjs controller specs at import)
- gitignore: ignore nested node_modules (was /node_modules, root-only) and
packages/*/dist; untrack node_modules symlinks committed earlier
114 tests green; build + attw clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(release): 9.0.0-alpha.0 — versions, engines >=20, peer ranges
- all published packages -> 9.0.0-alpha.0
- engines node >=16 -> >=20 (declared major-version floor bump)
- inter-package peers 'latest' -> ^9.0.0-alpha.0 (prerelease-correct caret)
114 tests green; build + attw clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(tsconfig): modernize base — es2022 target/lib, bundler resolution
- target/lib es2020 -> es2022
- moduleResolution node -> bundler (fits the tsdown build; ts-jest tolerates it)
note: verbatimModuleSyntax / isolatedModules deferred — both conflict with the
pervasive const-enum usage (and emitDecoratorMetadata), needing a const-enum ->
enum conversion first (post-alpha).
114 tests green; build + attw clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(deps): typescript 5.4 -> 5.9 (latest 5.x)
TS 6.0 is blocked by ts-jest@29 (peer typescript <6) — it breaks the ts-jest
unit suites. Staying on the latest 5.x that the test toolchain supports.
note: TS 6 is unblocked by migrating Jest -> Vitest (goal #5), but Vitest needs
an swc transform for the classes emitDecoratorMetadata specs — a focused
follow-up. Flagged for post-alpha.
114 tests green; build + attw clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(core): green lint — no-prototype-builtins + unused imports
- set.ts assignEmpty: Object.prototype.hasOwnProperty.call (no-prototype-builtins)
- for-member.ts: drop 7 unused imports (createMap, getMetadataMap, MetadataClassId,
NestedMappingPair, getFlatteningPaths, getPath, isPrimitiveArrayEqual)
lint green for 9 projects; core tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: modern workflow + package-contract gate (goals #4, #7)
- replace retired nrwl/ci@v0.8 reusable workflow with a self-contained CI:
lint · build · test on a node 20/22 matrix, corepack pnpm, frozen install
- add package-contract job: publint + attw on every built package
(tools/scripts/check-packages.mjs, 'pnpm run check:packages')
- add root 'lint' script
- remove unit-test.yml (tested node 14)
- docusaurus.yml: actions @v2 -> @v4, node 16 -> 20, corepack; docs install is
standalone now (--ignore-workspace) since docs aren't a workspace member
validated locally: lint (10 projects), build (6), check:packages all clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(core): typed errors + circular-safe member error (goal #5)
- add AutoMapperError base + MappingNotFoundError + MapMemberError (errors.ts,
exported); consumers can now catch mapper errors selectively
- get-mapping throws MappingNotFoundError (carries sourceName/destinationName)
- map() member-error path throws MapMemberError and DROPS JSON.stringify(
destination) from the message (it crashed on circular refs / flooded logs)
- errorHandler still receives the message; existing toThrow tests unaffected
115 tests green (incl. new typed-errors spec); build + lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(core): fix misleading comments (goal #10)
- dispose(): replace the 'why can metadata not be clear?' TODO with the actual
reason (strategy metadata caches would desync if metadataMap were cleared)
- postMap calls: the 'seal destination' comments described sealing that never
happens — corrected to 'returned as-is, intentionally not sealed'
comment-only; no behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(release): publish to 'alpha' dist-tag
npm publish defaults to the 'latest' tag; for the prerelease all publish targets
use --tag alpha so 9.0.0-alpha.0 doesn't land on 'latest' for existing users.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): cache assertUnmappedProperties writable-keys + Set lookup (code #4)
Per map() call (per element in mapArray), assertUnmappedProperties ran
Object.keys + getOwnPropertyDescriptor over the destinationMetadata and an
O(n^2) configuredKeys.some. The writable keys are fixed per metadata object —
cache them in a WeakMap; use a Set for configuredKeys. Behavior-preserving.
benchmark (M-series, node 22): mapArray x1000 ~3-5% faster (classes flat
1.75->1.67ms, classes nested 1.74->1.66ms); single map at noise. 10 projects green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): memoize per-member identifier predicates (Kyle Phase B)
hasSameIdentifier (map.ts) and shouldRunImplicitMap (map-member.ts) ran 4
isPrimitiveConstructor/isDateConstructor calls per member per mapped object,
though the result is constant per identifier. Extracted isMappableIdentifier()
with a Map memo. Behavior-preserving (same booleans, cached).
benchmark vs prior commit: ~4-7% faster (pojos nested mapArray 1.72->1.60ms,
classes flat map 1.78->1.71us). 10 projects green; lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): replace mapper Proxy with a plain object (code #2 / Kyle dispatch)
createMapper returned a Proxy whose get-trap ran a ~15-branch if-chain and
allocated a fresh closure on every property access (map/mapArray/symbol reads),
defeating property-load inline caches. Now a plain object: eager state, lazy
STRATEGY getter, methods closing over the mapper, symbol state via
defineProperties. Real stack traces; stable method identity. Async wrappers cast
the forwarded union arg (getOptions normalizes at runtime; the old Proxy
receiver was typed any, which silently bypassed this typecheck).
benchmark: ~4-9% faster (pojos nested map 1.75 -> 1.59us, flat map 1.74 -> 1.67us).
Cumulative vs 8.8.1 ~44%. 10 projects green; lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(nestjs): support NestJS 11 (goal #9)
- widen @automapper/nestjs peer to include ^11.0.0 (also fixed a double-space
typo in the @nestjs/core range)
- bump dev @nestjs/* to 11.1.27 so the integration tests exercise v11 (Express 5)
- add .npmrc public-hoist-pattern[]=@nestjs/platform-express so NestFactory's
dynamic adapter loader resolves it under pnpm's strict node_modules
(this was the v11-under-pnpm test failure seen earlier)
interceptor/pipe/decorator API is unchanged across v11. lint + build + 115 tests
green; frozen install in sync.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(mikro): support MikroORM 6 (goal #9)
- @automapper/mikro peer @mikro-orm/core ^5.0.0 -> ^6.0.0; dev dep -> 6.6.15
- serialize-entity.ts builds clean against v6 (Utils/Reference/wrap +
IWrappedEntityInternal from @mikro-orm/core/typings + __meta.properties are
v6-compatible) — no source changes needed
(Sequelize stays on v6: v7 is still alpha and renames the package to
@sequelize/core — deferred until it's stable.)
lint + build + 10 projects green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(release): nx release with conventional commits + npm provenance (goal #8)
- nx.json release block: independent versioning of the 6 published packages,
conventionalCommits-driven bumps, {projectName}@{version} tags, per-project
GitHub changelogs/releases (validated via nx release version --dry-run:
9.0.0-alpha.0 -> 9.0.0-alpha.1)
- per-project nx-release-publish target (@nx/js:release-publish) pointed at the
built dist (dist/packages/<name>) so nx release publish ships the bundled
artifacts, not the source
- publishConfig.provenance=true on all 6 published packages (flows through the
build into dist; publint + attw still clean)
- .github/workflows/release.yml: manual-dispatch release with id-token:write for
npm OIDC provenance — version+changelog+tag (--skip-publish), build, then
nx release publish --tag=alpha with NPM_CONFIG_PROVENANCE
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: migrate Jest/ts-jest to Vitest + unplugin-swc (goal #5)
Replaces the entire Jest stack with Vitest 4 driven by an swc transform.
swc handles legacy decorators + emitDecoratorMetadata (the @AutoMap/
reflect-metadata path), and is decoupled from the TypeScript version — which is
why this lands before the TS 6 bump (ts-jest was coupling tests to TS).
- vitest.shared.ts: one config factory for all packages — swc.vite() with
legacyDecorator + decoratorMetadata + keepClassNames (the mapper keys off
constructor.name), node env, globals, reflect-metadata setup, and @automapper/*
-> source aliases mirroring the tsconfig paths ts-jest resolved
- per-package vitest.config.ts; nx test target -> nx:run-commands 'vitest run'
- node env everywhere (was jsdom by default) — nothing uses the DOM, and node
gives native TextEncoder, so the jsdom dep + jest-setup polyfill are gone
- specs: jest.fn/spyOn -> vi.* (global); supertest namespace import -> default
import (vite ESM interop; also inlined for integration-test)
- tsconfig.spec types jest -> vitest/globals; eslint: exempt vitest config files
from enforce-module-boundaries; nx.json jest target-default -> test
- removed jest, ts-jest, @types/jest, jest-environment-jsdom, jest-util,
@swc/jest, @nx/jest; bumped @types/node -> ^22 (vitest peer; we run Node 22)
- core snapshots regenerated in vitest format
all 10 projects green (lint/build/test); publint+attw clean; lockfile in sync.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* build: drop CommonJS — 9.0 is ESM-only
tsdown now emits ESM only (index.mjs + index.d.mts); the dist package.json drops
`main`/`require` and the exports map carries only the import+types conditions.
`require('@automapper/...')` no longer resolves — an intentional break for the
major (the v8 line stays dual/CJS for consumers that need it).
- build-packages.mjs: --format esm (was cjs,esm); ESM-only exports map; delete
main, types -> .d.mts
- check-packages.mjs: attw --ignore-rules cjs-resolves-to-esm (that warning IS
the intended ESM-only contract); publint + attw otherwise clean on all 6
- nx.json: add tools/scripts/build-packages.mjs to the package target inputs so a
change to the build script correctly invalidates the cache (it served a stale
dual-format dist until this was added)
build + contract green (6 packages).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(core): mapAsync honestly awaits async before/after callbacks (code #6)
The async map/mutate methods previously did the work synchronously then waited a
single setTimeout(0) macrotask — a fragile guess that 'usually' let an async
beforeMap/afterMap settle, with no actual await. Now the sync engine collects any
promise a before/after callback returns into a sink, and mapAsync/mapArrayAsync/
mutateAsync/mutateArrayAsync await those promises (Promise.all) before resolving
— a real, deterministic await instead of a timer race. With no async callbacks
the sink is empty and resolution is immediate (no pointless macrotask hop).
- map.ts: module-level async-hook sink + collectAsyncHooks/pushAsyncHook; the
!isMapArray before/after calls feed it
- core.ts: array-level before/after calls feed it too; all four async methods
wrap their work in collectAsyncHooks and await
- types.ts: JSDoc now states callbacks are awaited (member mapping stays sync)
(Member-level true-async resolvers are the separately-queued follow-up goal.)
10 projects green; lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(core): tighten extraArguments to unknown; document intentional any (code #9)
- extraArguments (user-supplied data crossing the API boundary) is now
Record<string, unknown> instead of Record<string, any> in MapCallback,
MapOptions, the selector signature, and mapMember — callers narrow rather than
getting unchecked any
- documented why the remaining `= any` generic defaults (Selector/ValueSelector/
Resolver/Converter/ModelIdentifier/Constructor) are intentional: AutoMapper is a
runtime identifier-driven mapper used without explicit generics; an any default
keeps that ergonomic while concrete call sites still infer precise types.
Return positions are already `unknown`, not `any`.
no `as any` casts exist in core. 6 build / 10 lint / 10 test green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(zod): real Zod 4 integration replacing the stub (goal #9)
@automapper/zod was a 'return "zod"' placeholder. It is now a working,
shippable integration:
- zod() strategy = pojos (Zod output is structurally a POJO)
- createMetadataMap(identifier, schema): introspects a Zod 4 schema via _zod.def
(type/element/innerType/shape) and registers AutoMapper metadata — primitives
(string/number/boolean/date/bigint), arrays, nested objects (recursive, under
derived identifiers), and unwrapping optional/nullable/default/readonly/catch
- 4 vitest specs: flat, wrapper-unwrap, nested+arrays (deep copy), forMember
- package.json: ESM, peers @automapper/core+pojos ^9 and zod ^4, provenance
- wired into the published set: build PACKAGES, check-packages, nx.json release
projects, project.json package/publish/nx-release-publish targets
build 7 / lint 10 / test 10 green; publint + attw clean on all 7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(core): label the transformation tuples as named tuples (code #3)
The Mapping/MappingProperty/MappingTransformation tuples were already named, but
the ~11 member-transformation return tuples (indexed by MapFnClassId{type,fn,
isConverted}) and NestedMappingPair were positional. Labeled them all
(type:/fn:/isConverted:, destination:/source:) so the tuple types are
self-documenting and match their ClassId enums — IDE shows the field names on
every tuple position.
Type-level only: labels don't change the runtime structure or assignability, so
there's no behavior or perf delta (no benchmark needed). build + 10 test green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): compile a cached per-mapping plan (code #1 / Kyle Phase C)
The interpreter re-destructured every property's nested positional tuple (with
its default fallbacks) and rebuilt the configuredKeys array on every map() call.
Both are invariant per mapping, so they're now destructured once into a flat
descriptor list and cached in a WeakMap keyed by the (immutable) properties
array; map() reads descriptors + reuses the precomputed configuredKeys.
hasSameIdentifier is deliberately NOT cached — it depends on the mapper's mapping
registry, which can grow after a mapping is created — so correctness is preserved
(getMapping still runs per call). The value-application logic is unchanged.
benchmark (mitata, before -> after, both re-baselined): pojos flat map
1.57 -> 1.48us (~6%), classes flat 1.61 -> 1.50 (~7%), pojos nested 1.50 -> 1.43,
classes nested 1.49 -> 1.41, mapArray ~1.52 -> ~1.42ms (~6-7%).
build 7 / lint 10 / test 10 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* build: upgrade to TypeScript 6.0 (goal #2, final modernization item)
typescript ~5.9 -> ~6.0.3. Vitest+swc (TS-version-agnostic) had already
decoupled the test run from the compiler, so this is now a clean bump.
- transformer-plugin: import compiler types/values from 'typescript' instead of
the removed 'typescript/lib/tsserverlibrary' subpath (3 src files + the spec)
- tsconfig.base: ignoreDeprecations "6.0" for the deprecated-but-still-used
baseUrl (TS6 turns the deprecation into an error otherwise)
- build-packages: drop the now-dead typescript/lib/tsserverlibrary external
- transformer 486 fixtures: prepend "use strict"; — TS6's createProgram CJS emit
adds the prologue (the only diff; the transformer's __decorate + @AutoMap
metadata emit is byte-identical, confirmed via the assertion diff)
full gate green on 6.0.3: build (6) + lint (10) + test (10) + publint/attw.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(core): TRUE async map — await async member resolvers (goal)
mapAsync/mapArrayAsync/mutateAsync/mutateArrayAsync now genuinely await
asynchronous member work, not just before/after callbacks. The sync engine runs
inside an async context (runAsyncMap); when a member transformation (mapFrom,
convertUsing, mapWith, mapWithArguments, ...) returns a thenable, the *resolved*
value is assigned to the destination instead of leaking a Promise, and after-map
callbacks are DEFERRED until all members (at every nesting level) settle so they
observe a fully-mapped destination. Nested maps share the active context, so
async members deep in mapWith chains are awaited by the outermost call.
- map.ts: AsyncMapContext { pending, deferredAfterMaps } + runAsyncMap/
settleAsyncMap/pushAsyncPending/deferAsyncAfterMap; setMember collects thenable
member values; member errors wrap via a module-level makeMemberError (no
per-member closure — keeps the hot path allocation-free)
- core.ts: all four async methods + array-level before/after hooks wired through
the context
- replaces the previous before/after-only honesty mechanism
sync path is a single null-check per member — benchmark parity confirmed
(controlled before/after: 1.45 vs 1.46-1.50us). 7 build / 10 lint / 10 test /
publint+attw green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): build compiled mapping plan eagerly at createMap
- compile per-property descriptors once at createMap, store on the mapping
(MappingClassId.compiledPlan) instead of a lazy per-call WeakMap probe
- map() reads the slot directly; lazy fallback only for mappings built
outside the normal createMap path
- extract the builder into compile-mapping.ts; types move to types.ts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core): compile mappings to per-property step closures
- build a specialized step closure per property at createMap; the map() loop
is now straight-line steps[i](context) with no per-member TransformationType
switch or descriptor destructuring
- hoist per-mapping invariants into each closure (initialize fn, typeConverter
flag, identifier-equality candidate, primitive-destination flag); the
registry-dependent getMapping stays at runtime so nested mappings remain
order-independent
- drop the per-member setMember/thunk allocations; primitive members assign
directly with no closure
~4-5x faster map()/mapArray() and ~3x less bulk array allocation on the
pojos/classes flat+nested benchmarks (controlled A/B, source-resolved). Nested
per-map bytes rise ~40% from the per-call context object; bulk mapArray
allocation still drops ~52%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(core): address mapper migration followups
- fix logger output and similar-prefix flattening
- add async callback sequencing and array callbacks
- widen class identifiers for abstract/private constructors
- fix transformer import path normalization
- add issue regressions for migration followups
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(core): restore synchronous map() fast path after async-sequencing rewrite
- the followups async before/after sequencing wrapped every map() (incl. pure
sync) in per-call closures + a runAtAsyncMapDepth try/finally, and made
assignResolved test isThenable per member — ~2x slower sync map (flat +80%,
nested +150% vs the compiled-plan baseline), uncaught by the gate
- add a synchronous fast path: when no async context is active, run before-map ->
steps -> unmapped-assert -> after-map straight-line with zero per-call closures
- split member assignment: hot auto-map path (assignMember) uses the async-first
short-circuit and never tests isThenable on the sync path; the sync-thenable
throw guard stays on the resolver path (assignResolved via setMemberValue),
where async member resolvers actually originate
- benchmarked back to parity (flat) / ~+5-8% (nested) vs dabf047, built-source A/B
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* chore(core): clean up followups leftovers
- drop unused TModel generic param from TransformerMetadataFactory (cleared the
no-unused-vars lint warning); usages already passed no type arg meaningfully
- remove dead _replaceImportPathLegacy from transformer-plugin utils + its
now-unused dirname import
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* perf(core,classes): optimize synchronous map() hot path and createMap startup (#632)
* perf(benchmark-core): add map()/createMap benchmark + verifier harness
Benchmarks for the synchronous map() hot path and createMap startup:
- bench.ts: identity, forMember(mapFrom), and snake->camel naming groups
(rotating 64-object pool to defeat constant-folding); runs under
--expose-gc so mitata reports heap + gc.
- startup-bench.ts: many-createMap, tail-skewed class-size distribution;
reports createMap wall time + retained heap.
- profile-formember.ts + analyze-cpuprofile.ts: clean CPU self-time
attribution (no mitata overhead).
- verify.ts: cross-version correctness gate + independent hrtime timing,
so a benchmark run can't be a silently no-op'd or wrong map.
Scripts: bench, bench:startup, bench:profile (+ :analyze).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(core,classes): optimize synchronous map() hot path and createMap startup
Behavior-preserving optimizations (characterization tests added first;
full suite green across all packages). ~2x faster per map and ~3-5x less
allocation vs the previous branch baseline.
map() runtime:
- precompute the unmapped-key residual at createMap (no per-call Set
allocation or writable-key scan in assertUnmappedProperties)
- replace the per-member setMemberFn closure factory with a direct
(path, dest, value) writer; build a closure only on the async arm
- specialize the mapFrom step in compileStep: call the selector directly,
dropping the per-member thunk and the mapMember type switch
- MapInitialize scalar fast-path + Symbol.toStringTag File check
- get(): single-segment fast path
- hoist the per-element options object out of mapArray/mutateArray loops
createMap / memory:
- storeMetadata O(P^2) -> O(P) (build the list in place)
- drop the unused props/configuredKeys from the retained compiled plan
- getPathRecursive: module-level Set, drop redundant own-name dedup
- size-gated metadata index + extend Set for very wide classes
- AutoMap decorator O(P^2) -> O(1) own-list push (inheritance-safe seed)
- naming / applyMetadata / getMetadataList compile-time cleanups
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(core): dedupe path-keying, simplify extend, tidy map() hot path
Quality cleanups from review (no behavior change; full suite green):
- extract a shared pathKey(path) helper for the null-byte path-key idiom
that was open-coded across create-initial-mapping and extend
- collapse extend()'s size-gated dual loop into a single Set-based pass
(compile-time only, so the Set is cheap at any size)
- extract isFileTagged() for the duplicated File-tag check in compileStep
- reuse one nested-options object across nested map() recursion instead of
allocating { extraArgs } per nested object / array element
- drop an unnecessary export on getWritableKeys
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(benchmark-core): share fixtures; read profiles from a fixed dir
- extract the shared classes-strategy model/factory/mapping setup into
fixtures.ts (imported by bench, verify, profile-formember) instead of
copy-pasting it in each; drop a dead keep-alive fixture
- analyze-cpuprofile: read the newest profile from the fixed ./profiles dir
instead of an optional CLI argument — removes the path-injection surface
entirely (no external input reaches the filesystem path)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat!: remove @automapper/zod package
- drop the @automapper/zod strategy entirely for 9.0: niche audience and it
coupled to zod's private internals (_zod.def), a recurring maintenance tax
that already broke on the zod 3->4 jump
- never published to npm, so there is no consumer migration path to provide;
schema-first users should reshape with zod .transform() or map between plain
validated models directly
- purge references: tsconfig paths, nx release projects, root devDep, vitest
alias, commitizen scope, build/check/peer-dep scripts; regenerate lockfile
BREAKING CHANGE: @automapper/zod is removed in 9.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* fix(mikro): drop forbidden @mikro-orm/core/typings subpath import
- @mikro-orm/core v6's exports map only exposes "." and "./package.json";
importing IWrappedEntityInternal from @mikro-orm/core/typings is unresolvable
under moduleResolution: bundler (latent — no prior typecheck gate caught it)
- replace with a minimal local structural type for the one shape used
(__meta.properties) and cast via unknown; runtime behavior unchanged
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* build: adopt @nx/js/typescript inferred typecheck via project references
- register @nx/js/typescript plugin + enable inference; adds a cached
`typecheck` target (tsc --build tsconfig.json --emitDeclarationOnly) the repo
never had — typechecking was only implicit via tsdown --dts and vitest
- make every library tsconfig.lib.json composite with a distinct out-tsc outDir
and a src/** include (the **/*.ts + root rootDir swept in vitest.config.ts);
declaration:true overrides base's explicit declaration:false (composite needs it)
- each project's tsconfig.json references its lib only (specs deferred to a
follow-up pass)
- add a root tsconfig.json (satisfies @nx/js:typescript-sync gate); nx sync
auto-generates the inter-package references from the dep graph
- exclude packages/benchmark-core (private harness, not buildable) from inference
- classes shim build: pass tsc --ignoreConfig so the new root tsconfig.json is
not auto-applied when files are on the command line (TS6 TS5112)
resolution now rides on the pnpm workspace symlink + each package's exports
(source index.ts), independent of tsconfig paths; references give composite
build ordering. typecheck 9/9, sync:check clean, lint/test/package/contract green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* build: resolve @automapper/* via pnpm workspace; drop tsconfig paths + vitest aliases
- make nestjs/mikro/sequelize/integration-test pnpm workspace members and declare
their @automapper/* as workspace:* devDependencies, so pnpm symlinks them (the
peers stay the published contract; devDeps are dev-only, not shipped)
- add source `exports` ("." -> ./src/index.ts) to nestjs/mikro/sequelize (they
had none — previously resolved only via tsconfig paths)
- classes self-dep (workspace:*) so its nested subpaths (mapped-types,
transformer-plugin) resolve @automapper/classes via the self-symlink
- drop the `paths` block from tsconfig.base.json and the @automapper/* aliases
from vitest.shared.ts entirely — resolution now rides purely on pnpm workspace
symlinks + each package's exports, for tsc and vitest alike
- build-packages.mjs strips devDependencies from the dist manifest (the
workspace:* dev links must not ship and can't resolve in a packed tarball)
- docs projects stay OUT of the workspace (isolated installs) to keep docusaurus's
deps from fanning out — they don't consume @automapper at build
typecheck 9/9, test 9/9, package 6/6 (dist clean), check:packages + lint green,
nx sync:check in-sync — all verified uncached with zero paths/aliases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* fix(integration-test): correct spec type errors surfaced by spec typecheck
- wide-mapping-size-gate: imported non-existent type PojosMetadata; the real
export is PojoMetadata, and the metadata map is Record<string, PojoMetadata>
- issue 358: replace jest-style `done` callbacks (Vitest types the test arg as
TestContext, not callable) with returned Promises that resolve in the rxjs
next handler and reject on error — same assertions, no hang-to-timeout
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* build: typecheck spec/test files via composite spec configs
- make tsconfig.spec.json composite (declaration:true, unique out-tsc/<proj>/spec
outDir) for the 5 projects that have specs (core, classes, classes/mapped-types,
classes/transformer-plugin, integration-test) and re-add the spec reference to
each tsconfig.json, so `nx typecheck` now covers test files too
- wire each spec config's project references to the libs its specs import
(integration-test spec -> core/classes/transformer-plugin/nestjs/pojos libs)
- scope classes spec include to src + shim and exclude the nested subpackages;
drop integration-test's vitest.config.ts from the spec include (runtime-only)
- pojos/nestjs/mikro/sequelize untouched (zero spec files — would hit TS18003)
typecheck 9/9 (incl specs), test/lint/package/sync:check green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* ci: gate typecheck + sync:check in CI
- add `typecheck` (nx run-many --target=typecheck) and `sync:check` (nx sync:check)
pnpm scripts
- run them in the verify job: sync:check right after install (fail fast on
tsconfig project-reference drift), typecheck after lint, before package
- the repo had no standalone type-only gate before; now every PR typechecks all
9 projects incl. specs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* chore(nestjs): narrow peer range to nestjs 10 || 11
- @automapper/nestjs only consumes stable @nestjs/common DI/module/pipe primitives
(Module, Inject, Optional, mixin, PipeTransform, DynamicModule, Provider, Type,
ModuleMetadata, ArgumentMetadata) — none affected by nestjs 11 breaking changes
(express 5, fastify 5, cache-manager, logger), so 10 and 11 both work; no hard
11 cutover
- drop the untested ancient ^7/^8/^9 majors from the peer range (only 10/11 are
realistically supported/tested for 9.0); the express v4->v5 concern is the
nestjs app's, not this integration's
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* build: add default condition + package.json export to dist manifests
- condFor now emits a `default` condition mirroring `import` (catch-all for
non-standard resolvers that don't honor the `import` condition); order is
types -> import -> default, still ESM-only (no `require`)
- export `./package.json` so tooling can resolve '<pkg>/package.json'
- publint + attw stay green (attw reports ./package.json as JSON); package 6/6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* build: sync pnpm-lock.yaml after nestjs peer narrow
- fe61c61 narrowed the @nestjs peer range but left pnpm-lock.yaml recording the
old specifier; `pnpm install --frozen-lockfile` (CI default) failed with
ERR_PNPM_OUTDATED_LOCKFILE. regenerate the lockfile so frozen install passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* ci: add nestjs 10 compatibility job
- certify the lower bound of @automapper/nestjs's ^10||^11 peer range: a job that
downgrades @nestjs/{common,core,testing,platform-express} to v10 and runs the
DI integration specs (Test.createTestingModule, mapper module, map pipe/
interceptor, @InjectMapper)
- validated locally first (isolated worktree): nestjs 10.4.22 resolves cleanly,
all 143 integration tests pass — nestjs 10 now proven, not just inferred
- nestjs 11 stays covered by the main verify job (installed version)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* docs: add redesign spec
* chore: ignore superpowers artifacts
* chore: ignore agent artifacts
* docs: remove unused duplicate documentation package
- packages/documentation (singular) is a stale near-duplicate of
packages/documentations (plural); the Docusaurus CI workflow only builds/deploys
the plural, and nothing references the singular project. The plural even has the
docs/api tree the singular lacks. Removing the dead copy ahead of the Astro
migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3QKehd9vh9gn8obMVYo6G
* feat(core): improve logger configuration
- add structured logger interface with expanded levels
- make logger configuration overrideable and restorable
- document early logger setup for nestjs users
- widen reflect-metadata peer range
* ci: add nx agents workflow
- add node 20 and 22 nx agent launch templates
- start agents for verify job after sync check
- keep package contract and nestjs compat jobs local
* feat(nx-cloud): setup nx cloud workspace (#634)
This commit sets up Nx Cloud for your Nx workspace, enabling distributed caching and the Nx Cloud GitHub integration for fast CI and improved developer experience.
You can access your Nx Cloud workspace by going to
https://cloud.nx.app/orgs/63ffb2b3138045000dc36fe6/workspaces/6a3e997e7427195a38f6bdbc
> [!TIP]
> Run `npx nx generate ci-workflow` if you don't have a CI script configured yet.
**Note:** This commit attempts to maintain formatting of the nx.json file, however you may need to correct formatting by running an nx format command and committing the changes.
* ci: remove nxcloudaccess token
* ci: add agent resolution diagnostics
- add verbose nx logging in ci
- print automapper package resolution from integration-test target
- run diagnostic before distributed test step
* ci: move resolution diagnostic to compat job
- run package resolution diagnostic after nestjs downgrade
- print automapper core dependency tree for compat job
- remove diagnostic from verify job
* ci: add vitest resolution diagnostics
- print automapper resolution from inside vitest
- run diagnostic before self-reference compat repro
- isolate self-reference spec before full compat suite
* ci: prefer workspace packages in compat job
- keep nestjs downgrade from resolving published automapper peers
- preserve workspace core/classes pairing for compat tests
* ci: avoid lazy imports in diagnostics
- remove dynamic automapper imports from resolver diagnostic
- keep source path assertions without tripping nx boundaries
* ci: remove resolution diagnostics
- drop temporary compat resolver checks
- remove debug-resolution target and script
- keep workspace package preference fix
* ci: DPE helped me out
* ci: goated DPE
* ci: another change
* ci: bust the cache
* ci: CTA
* ci: bust
* chore: migrate to typescript 7
- use the native compiler with the typescript 6 api compatibility package
- update type checking and documentation generation for the dual toolchain
* test: stabilize vitest in ci
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Kyle Cannon <kyle.d.cannon@gmail.com>