Skip to main content

content-mapper

Webda's compile-time code generation, as a TypeScript 7.1 content mapper.

TypeScript 7 removed custom emit transformers, ts-patch and language-service plugins. This package replaces them with the supported mechanism: it rewrites model and service sources (*.model.ts, *.service.ts) into ordinary TypeScript before the compiler sees them, with span maps so diagnostics, hover and rename still land on the authored text.

You rarely use it directly: webdac build (from @webda/compiler) runs it for you.

What it generates​

GeneratorOutput
accessorsTyped accessors that coerce on assignment (user.createdAt = "2020-01-01" stores a Date) and relations
behaviorsMembers of the Behaviors attached to a model
loadParametersloadParameters() for services extending Service<T>, from their parameters class
qlValidatorCompile-time validation of WebdaQLString<T> queries, reported as TypeScript diagnostics; a sealed field in a query, in a getPermissionQuery or under an @Index / @Unique tag is a warning (WQL9004), logged by the build

It also discovers the Webda objects of a project and produces the JSON schemas of models, services and operations used in webda.module.json.

Editor support​

Starting in typescript@7.1.0-dev.20261102.1, tsgo rejects content mapper extensions that end in .ts (TS18066). You can choose between two strategies depending on whether you prefer standard .ts file suffixes or an unmodified tsc binary:

Strategy A: Keep .model.ts / .service.ts with patchTsc (Default)​

Patch the installed native tsc binary so compound .model.ts and .service.ts extensions remain allowed in tsc and tsc --lsp:

node node_modules/@webda/content-mapper/lib/patch-tsc.js

Then register .model.ts and .service.ts in tsconfig.json:

{
"contentMappers": [{ "package": "@webda/content-mapper", "extensions": [".model.ts", ".service.ts"] }]
}

Strategy B: Use .model.wts / .service.wts with outputExtensions (Unmodified tsc)​

If you prefer not to patch the tsc binary, use .model.wts and .service.wts source suffixes and configure outputExtensions in @webda/content-mapper's manifest (or your mapper wrapper) so emitted files and ESM imports remain ./user.model.js and ./query.service.js:

// tsconfig.json
{
"contentMappers": [{ "package": "@webda/content-mapper", "extensions": [".model.wts", ".service.wts"] }]
}
// package.json
{
"typescript": {
"contentMapper": {
"exec": ["node", "lib/server.js"],
"compilerOptions": ["module", "target"],
"outputExtensions": {
".model.wts": ".model.js",
".service.wts": ".service.js",
".flow.wts": ".flow.js"
}
}
}
}

Note: With .wts files, configure your editor and tools (e.g. VS Code "files.associations": { "*.wts": "typescript" }, ESLint, Prettier) to treat *.wts as TypeScript.

Content mappers run external code, so TypeScript asks you to trust them: tsc --runExternalCode on the command line, initializationOptions.runExternalCode for the language server.

A model or service declared in a file without a mapped suffix (.model.ts / .service.ts or .model.wts / .service.wts) is never transformed: webdac build fails and names the file to rename (WEBDA_STRICT_FILE_NAMING=0 downgrades it to a warning while migrating).

Tests with Vitest​

The Vite plugin applies the same generators, so specs exercise the code webdac build emits:

vitest.config.ts
import { webdaContentMapper } from "@webda/content-mapper/vite";
import { defineConfig } from "vitest/config";

export default defineConfig({
plugins: [webdaContentMapper()]
});

Entry points​

ExportUse
@webda/content-mapperGenerators, WarmSession, runTwoPass, schema generation
@webda/content-mapper/serverThe content mapper process started by TypeScript
@webda/content-mapper/viteThe webdaContentMapper() Vite/Vitest plugin
@webda/content-mapper/coercionsThe default coercion rules
@webda/content-mapper/schema-workerSchema generation in a worker thread

Requires TypeScript >=7.1.

See also​