@cmflow/atlas 3.4.0-beta.3 → 3.4.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,13 +50,60 @@ The configuration imports `defineConfig` from Atlas:
50
50
  import { defineConfig } from "@cmflow/atlas";
51
51
 
52
52
  export default defineConfig({
53
- backendTypesFile: "app/_infra/back/BackendTypes.ts",
53
+ backends: ["ICC", "CMS", "CMS_B2C"],
54
54
  openapiUrl: "https://api.example.com/openapi.json",
55
55
  openapiTimeoutMs: 60_000,
56
56
  directusUrl: "https://cms.api.clubmed"
57
57
  });
58
58
  ```
59
59
 
60
+ Atlas reads `compilerOptions.paths` from the project's `tsconfig.json` and uses them as module aliases. Add `resolver.alias` only to override or complement those aliases.
61
+
62
+ ## Custom expression rules
63
+
64
+ Use `defineExpressionRule` when a project-specific helper hides a backend field or wraps an expression that Atlas should follow. Add the rule to `analysis.rules` in `atlas.config.ts`.
65
+
66
+ `match` is a cheap predicate that selects the `ts-morph` expression. `parse` returns the information Atlas should use: a backend field, a transparent wrapper, or both.
67
+
68
+ ```ts
69
+ import { defineConfig, defineExpressionRule } from "@cmflow/atlas";
70
+ import { Node } from "ts-morph";
71
+
72
+ const localizedFieldRule = defineExpressionRule({
73
+ name: "localized-field",
74
+ match: (expression) =>
75
+ Node.isCallExpression(expression) && expression.getExpression().getText() === "localizedField",
76
+ parse: (expression) => {
77
+ if (!Node.isCallExpression(expression)) return undefined;
78
+
79
+ const [source, field] = expression.getArguments();
80
+ if (!source || !Node.isExpression(source) || !Node.isStringLiteral(field)) return undefined;
81
+
82
+ return { backendField: `${source.getText()}.${field.getLiteralValue()}` };
83
+ }
84
+ });
85
+
86
+ export default defineConfig({
87
+ // …other Atlas options
88
+ analysis: {
89
+ // …other analysis options
90
+ rules: [localizedFieldRule]
91
+ }
92
+ });
93
+ ```
94
+
95
+ For a wrapper that does not change the field path, return `transparent: true`:
96
+
97
+ ```ts
98
+ const unwrapRule = defineExpressionRule({
99
+ name: "unwrap-api-value",
100
+ match: (expression) => Node.isCallExpression(expression) && expression.getExpression().getText() === "unwrap",
101
+ parse: () => ({ transparent: true })
102
+ });
103
+ ```
104
+
105
+ `parse` may also set `mapperType` to annotate the generated mapping, or `apiMapping: true` when the helper maps an API value rather than a backend value.
106
+
60
107
  Create a `.env.local` in the target API project when pushing to Directus:
61
108
 
62
109
  ```dotenv
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, InvalidArgumentError, Option } from "commander";
3
3
  import path from "node:path";
4
+ import fs from "node:fs";
4
5
  import { pathToFileURL } from "node:url";
6
+ import { Node, Project, SyntaxKind } from "ts-morph";
5
7
  import { cancel, intro, log, note, outro, progress, spinner } from "@clack/prompts";
6
- import fs from "node:fs/promises";
8
+ import fs$1 from "node:fs/promises";
7
9
  import { createDirectus, createItem, deleteItem, readItems, rest, staticToken, updateItem } from "@directus/sdk";
8
10
  import { AsyncLocalStorage } from "node:async_hooks";
9
11
  import { globby } from "globby";
10
- import { Node, Project, SyntaxKind } from "ts-morph";
11
12
  import { minimatch } from "minimatch";
12
- import fs$1 from "node:fs";
13
13
  import { createHash } from "node:crypto";
14
14
  import { parse, stringify } from "yaml";
15
15
  import { Worker } from "node:worker_threads";
@@ -25,7 +25,20 @@ function getUserConfig() {
25
25
  if (!_config) throw new Error("Atlas config not loaded. Run Atlas from a directory containing atlas.config.ts or pass --config.");
26
26
  return _config;
27
27
  }
28
- async function loadAtlasConfig(configPath) {
28
+ function getTsconfigAliases(repoRoot) {
29
+ const tsconfigPath = path.join(repoRoot, "tsconfig.json");
30
+ if (!fs.existsSync(tsconfigPath)) return {};
31
+ const paths = new Project({
32
+ tsConfigFilePath: tsconfigPath,
33
+ skipAddingFilesFromTsConfig: true
34
+ }).getCompilerOptions().paths ?? {};
35
+ return Object.fromEntries(Object.entries(paths).flatMap(([alias, targets]) => {
36
+ const target = targets?.[0];
37
+ if (!target) return [];
38
+ return [[alias.replace(/\/\*$/, ""), target.replace(/^\.\//, "").replace(/\/\*$/, "")]];
39
+ }));
40
+ }
41
+ async function loadAtlasConfig(configPath, projectRoot) {
29
42
  const filePath = path.resolve(configPath);
30
43
  try {
31
44
  let mod;
@@ -34,11 +47,15 @@ async function loadAtlasConfig(configPath) {
34
47
  mod = await tsImport(filePath, import.meta.url);
35
48
  } else mod = await import(pathToFileURL(filePath).href);
36
49
  const config = mod.default ?? mod;
37
- const repoRoot = config.repoRoot || process.cwd();
50
+ const repoRoot = projectRoot || config.repoRoot || process.cwd();
38
51
  return {
39
52
  ...config,
40
53
  repoRoot,
41
- cwd: repoRoot
54
+ cwd: repoRoot,
55
+ resolver: { alias: {
56
+ ...getTsconfigAliases(repoRoot),
57
+ ...config.resolver?.alias
58
+ } }
42
59
  };
43
60
  } catch (error) {
44
61
  throw new Error(`Unable to load Atlas configuration at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
@@ -375,18 +392,11 @@ function extractOpenApiOutputProperties(operation, swagger) {
375
392
  }
376
393
  //#endregion
377
394
  //#region src/services/backendTypeService.ts
378
- let knownBackendTypesCache = null;
395
+ function getBackendTypes() {
396
+ return Object.fromEntries(getUserConfig().backends.map((backend) => [backend, backend]));
397
+ }
379
398
  function isKnownBackendType(value) {
380
- const config = getUserConfig();
381
- const filePath = path.resolve(config.repoRoot, config.backendTypesFile);
382
- if (!knownBackendTypesCache || knownBackendTypesCache.filePath !== filePath) {
383
- const fileContent = fs$1.readFileSync(filePath, "utf8");
384
- knownBackendTypesCache = {
385
- filePath,
386
- values: new Set([...fileContent.matchAll(/([A-Z0-9_]+)\s*=\s*"([A-Z0-9_]+)"/g)].map(([, key, backend]) => key === backend ? backend : null).filter((backend) => Boolean(backend)))
387
- };
388
- }
389
- return knownBackendTypesCache.values.has(value);
399
+ return Object.hasOwn(getBackendTypes(), value);
390
400
  }
391
401
  //#endregion
392
402
  //#region src/services/backendSourceService.ts
@@ -481,7 +491,7 @@ function tryResolveWithExtensions(basePath) {
481
491
  ].filter((candidate) => Boolean(candidate));
482
492
  for (const candidate of candidates) try {
483
493
  const normalized = path.normalize(candidate);
484
- if (fs$1.existsSync(normalized)) return normalized;
494
+ if (fs.existsSync(normalized)) return normalized;
485
495
  } catch {
486
496
  continue;
487
497
  }
@@ -2349,7 +2359,7 @@ async function pushCatalogueToDirectus(catalogue, options) {
2349
2359
  }
2350
2360
  }
2351
2361
  }
2352
- if (options.outputPath) await fs.appendFile(options.outputPath, "", "utf8");
2362
+ if (options.outputPath) await fs$1.appendFile(options.outputPath, "", "utf8");
2353
2363
  const removedOrphanedLinks = await cleanupOrphanedPropertyLinks(client);
2354
2364
  taskProgressService.report("Finalizing Directus synchronization");
2355
2365
  if (warnings.size) {
@@ -2545,12 +2555,12 @@ async function writeRouteReviewDocument(catalogue, outputDir, repoRoot, routeKey
2545
2555
  if (!route) throw new Error(`Unable to write review document for unknown route key ${routeKey}`);
2546
2556
  const document = relativizeRouteReviewDocument(buildRouteReviewDocument(catalogue, route.key), repoRoot);
2547
2557
  const filePath = path.join(outputDir, buildRouteReviewRelativePath(route.method, route.path));
2548
- await fs.mkdir(path.dirname(filePath), { recursive: true });
2549
- await fs.writeFile(filePath, `${stringify(document)}\n`, "utf8");
2558
+ await fs$1.mkdir(path.dirname(filePath), { recursive: true });
2559
+ await fs$1.writeFile(filePath, `${stringify(document)}\n`, "utf8");
2550
2560
  return filePath;
2551
2561
  }
2552
2562
  async function writeRouteReviewDocuments(catalogue, outputDir, repoRoot) {
2553
- await fs.mkdir(outputDir, { recursive: true });
2563
+ await fs$1.mkdir(outputDir, { recursive: true });
2554
2564
  for (const [index, route] of catalogue.routes.entries()) {
2555
2565
  taskProgressService.log(`[${index + 1}/${catalogue.routes.length}] - ${route.method} ${route.path} ...`);
2556
2566
  await writeRouteReviewDocument(catalogue, outputDir, repoRoot, route.key);
@@ -2558,8 +2568,8 @@ async function writeRouteReviewDocuments(catalogue, outputDir, repoRoot) {
2558
2568
  return outputDir;
2559
2569
  }
2560
2570
  async function prepareCatalogueOutputDirectory(outputDir) {
2561
- await fs.mkdir(outputDir, { recursive: true });
2562
- await Promise.all(["catalogue.yaml", "catalogue.yml"].map((fileName) => fs.rm(path.join(outputDir, fileName), { force: true })));
2571
+ await fs$1.mkdir(outputDir, { recursive: true });
2572
+ await Promise.all(["catalogue.yaml", "catalogue.yml"].map((fileName) => fs$1.rm(path.join(outputDir, fileName), { force: true })));
2563
2573
  }
2564
2574
  async function writeCatalogueArtifacts(catalogue, outputDir, repoRoot = process.cwd()) {
2565
2575
  await prepareCatalogueOutputDirectory(outputDir);
@@ -2737,7 +2747,7 @@ async function readCatalogueFromDirectory(inputDir) {
2737
2747
  if (!files.length) throw new Error(`No route YAML documents found in ${inputDir}`);
2738
2748
  return {
2739
2749
  catalogue: mergeRouteReviewDocuments(await Promise.all(files.sort().map(async (file) => {
2740
- const parsed = parse(await fs.readFile(file, "utf8"));
2750
+ const parsed = parse(await fs$1.readFile(file, "utf8"));
2741
2751
  if (parsed?.schema_version !== 1 || !parsed?.route?.method || !parsed?.route?.path) throw new Error(`Invalid route YAML document: ${file}`);
2742
2752
  return parsed;
2743
2753
  }))),
@@ -2931,8 +2941,8 @@ function buildBackendTopologyRelativePath(method, routePath) {
2931
2941
  }
2932
2942
  async function writeBackendTopologyArtifact(artifact, outputDir) {
2933
2943
  const filePath = path.join(outputDir, buildBackendTopologyRelativePath(artifact.route.method, artifact.route.path));
2934
- await fs.mkdir(path.dirname(filePath), { recursive: true });
2935
- await fs.writeFile(filePath, `${stringify(artifact, { aliasDuplicateObjects: false })}\n`, "utf8");
2944
+ await fs$1.mkdir(path.dirname(filePath), { recursive: true });
2945
+ await fs$1.writeFile(filePath, `${stringify(artifact, { aliasDuplicateObjects: false })}\n`, "utf8");
2936
2946
  return filePath;
2937
2947
  }
2938
2948
  function isRecord(value) {
@@ -2951,7 +2961,7 @@ async function loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePat
2951
2961
  const filePath = path.join(outputDir, buildBackendTopologyRelativePath(method, routePath));
2952
2962
  let content;
2953
2963
  try {
2954
- content = await fs.readFile(filePath, "utf8");
2964
+ content = await fs$1.readFile(filePath, "utf8");
2955
2965
  } catch (error) {
2956
2966
  if (isRecord(error) && error.code === "ENOENT") throw new Error(`Backend graph artifact not found for ${method} ${routePath}: ${filePath}. Run generate:graph first.`);
2957
2967
  throw error;
@@ -2972,7 +2982,7 @@ async function loadBackendTopologyAnalysisScope(outputDir, cwd, method, routePat
2972
2982
  const missingFiles = [];
2973
2983
  await Promise.all(analysisFiles.map(async (analysisFile) => {
2974
2984
  try {
2975
- await fs.access(analysisFile);
2985
+ await fs$1.access(analysisFile);
2976
2986
  } catch {
2977
2987
  missingFiles.push(path.relative(cwd, analysisFile));
2978
2988
  }
@@ -3432,7 +3442,7 @@ async function readSourceExcerpts(document) {
3432
3442
  const excerpts = [];
3433
3443
  let totalCharacters = 0;
3434
3444
  for (const location of uniqueLocations) try {
3435
- const lines = (await fs.readFile(location.sourceFile, "utf8")).split("\n");
3445
+ const lines = (await fs$1.readFile(location.sourceFile, "utf8")).split("\n");
3436
3446
  const startLine = Math.max(1, location.line - EXCERPT_RADIUS);
3437
3447
  const endLine = Math.min(lines.length, location.line + EXCERPT_RADIUS);
3438
3448
  const code = lines.slice(startLine - 1, endLine).map((line, index) => `${String(startLine + index).padStart(5)} | ${line}`).join("\n");
@@ -4159,13 +4169,10 @@ const configTemplate = `import { defineConfig } from "@cmflow/atlas";
4159
4169
 
4160
4170
  export default defineConfig({
4161
4171
  repoRoot: process.cwd(),
4162
- backendTypesFile: "app/_infra/back/BackendTypes.ts",
4172
+ backends: [],
4163
4173
  openapiUrl: "https://api.example.com/openapi.json",
4164
4174
  openapiTimeoutMs: 60_000,
4165
4175
  directusUrl: "https://cms.api.clubmed",
4166
- resolver: {
4167
- alias: {}
4168
- },
4169
4176
  inference: {
4170
4177
  model: process.env.AI_MODEL ?? "gpt-4o-mini",
4171
4178
  baseUrl: process.env.AI_BASE_URL ?? "https://api.openai.com/v1",
@@ -4201,18 +4208,18 @@ var init_default = (program, datasourceCommand) => void program.command("init").
4201
4208
  const configDirectory = path.dirname(configPath);
4202
4209
  intro("Initialize datasource configuration");
4203
4210
  try {
4204
- await fs.mkdir(configDirectory, { recursive: true });
4211
+ await fs$1.mkdir(configDirectory, { recursive: true });
4205
4212
  if (!options.force) {
4206
4213
  let exists = false;
4207
4214
  try {
4208
- await fs.access(configPath);
4215
+ await fs$1.access(configPath);
4209
4216
  exists = true;
4210
4217
  } catch (error) {
4211
4218
  if (error.code !== "ENOENT") throw error;
4212
4219
  }
4213
4220
  if (exists) throw new Error(`${configPath} already exists. Use --force to overwrite it.`);
4214
4221
  }
4215
- await fs.writeFile(configPath, configTemplate, "utf8");
4222
+ await fs$1.writeFile(configPath, configTemplate, "utf8");
4216
4223
  outro(`Created ${configPath}`);
4217
4224
  } catch (error) {
4218
4225
  cancel(error instanceof Error ? error.message : String(error));
@@ -4345,7 +4352,7 @@ function collectImpactedRoutes(params) {
4345
4352
  return params.graphs.filter((graph) => graph.analysisFiles.some((analysisFile) => changedFiles.has(normalizeFilePath(analysisFile)))).sort((left, right) => `${left.method} ${left.path}`.localeCompare(`${right.method} ${right.path}`));
4346
4353
  }
4347
4354
  async function readChangedFiles(filePath) {
4348
- return (await fs.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
4355
+ return (await fs$1.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
4349
4356
  }
4350
4357
  async function readRouteGraphs(outputDirectory) {
4351
4358
  const graphFiles = await globby("**/*.graph.yaml", {
@@ -4353,7 +4360,7 @@ async function readRouteGraphs(outputDirectory) {
4353
4360
  absolute: true
4354
4361
  });
4355
4362
  const graphs = await Promise.all(graphFiles.map(async (graphFile) => {
4356
- const document = parse(await fs.readFile(graphFile, "utf8"));
4363
+ const document = parse(await fs$1.readFile(graphFile, "utf8"));
4357
4364
  if (!isGraphDocument(document)) throw new Error(`Invalid graph document: ${graphFile}`);
4358
4365
  return {
4359
4366
  method: document.route.method,
@@ -4471,8 +4478,8 @@ async function reportChangedHandler(cwd, options) {
4471
4478
  routes: coverages
4472
4479
  });
4473
4480
  const reportPath = path.resolve(cwd, options.report);
4474
- await fs.mkdir(path.dirname(reportPath), { recursive: true });
4475
- await fs.writeFile(reportPath, `${report}\n`, "utf8");
4481
+ await fs$1.mkdir(path.dirname(reportPath), { recursive: true });
4482
+ await fs$1.writeFile(reportPath, `${report}\n`, "utf8");
4476
4483
  note(report, "Datasource mapping report");
4477
4484
  }
4478
4485
  var reportChanged_default = (program) => void program.command("report:changed").requiredOption("--changed-files <file>", "Newline-delimited list of changed repository files").option("-o, --output <directory>", "Directory containing generated graph artifacts", ".tmp/datasource-catalogue").option("--report <file>", "Markdown report output path", ".tmp/datasource-catalogue/changed-routes-report.md").description("Report deterministic mapping coverage for routes impacted by changed files").action(async (options) => {
@@ -4492,13 +4499,7 @@ const program = new Command();
4492
4499
  program.version("3.4.0-alpha.1");
4493
4500
  program.name("atlas").description("Manage the API-to-backend mapping catalogue").option("--project-root <path>", "Root directory of the API project to analyze").option("-c, --config <path>", "Path to atlas.config.ts", path.resolve(process.cwd(), "atlas.config.ts")).hook("preAction", async (_thisCommand, actionCommand) => {
4494
4501
  if (actionCommand.name() === "init") return;
4495
- const config = await loadAtlasConfig(program.opts().config);
4496
- const repoRoot = program.opts().projectRoot ?? config.repoRoot;
4497
- setUserConfig({
4498
- ...config,
4499
- repoRoot,
4500
- cwd: repoRoot
4501
- });
4502
+ setUserConfig(await loadAtlasConfig(program.opts().config, program.opts().projectRoot));
4502
4503
  });
4503
4504
  init_default(program, program);
4504
4505
  generate_default(program);
@@ -0,0 +1,2 @@
1
+ function e(e){return e}export{e as t};
2
+ //# sourceMappingURL=defineExpressionRule-Dfvzj6n2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineExpressionRule-Dfvzj6n2.mjs","names":[],"sources":["../src/utils/defineExpressionRule.ts"],"sourcesContent":["import type { Expression } from \"ts-morph\";\nimport type { UserConfig } from \"../models/types\";\n\nexport type FieldExtractionRuleResult = {\n backendField?: string;\n transparent?: boolean;\n mapperType?: string;\n apiMapping?: boolean;\n};\n\nexport type FieldExtractionRule = {\n name: string;\n match: (expression: Expression, config: UserConfig) => boolean;\n parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;\n};\n\nexport function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule {\n return rule;\n}\n"],"mappings":"AAgBA,SAAgB,EAAqB,EAAgD,CACnF,OAAO,CACT"}
@@ -0,0 +1,6 @@
1
+ import { r as defineExpressionRule, t as UserConfig } from "./types-DcvdOZ2j.mjs";
2
+ //#region src/utils/defineConfig.d.ts
3
+ declare function defineConfig(config: UserConfig): UserConfig;
4
+ //#endregion
5
+ export { type UserConfig, defineConfig, defineExpressionRule };
6
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./defineExpressionRule-Dfvzj6n2.mjs";function t(e){return e}export{t as defineConfig,e as defineExpressionRule};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/defineConfig.ts"],"sourcesContent":["import type { UserConfig } from \"../models/types\";\n\nexport function defineConfig(config: UserConfig): UserConfig {\n return config;\n}\n"],"mappings":"wDAEA,SAAgB,EAAa,EAAgC,CAC3D,OAAO,CACT"}
@@ -0,0 +1,6 @@
1
+ import { n as FieldExtractionRule } from "../types-DcvdOZ2j.mjs";
2
+ //#region src/rules/cleanObjectRule.d.ts
3
+ declare const cleanObjectRule: FieldExtractionRule;
4
+ //#endregion
5
+ export { cleanObjectRule };
6
+ //# sourceMappingURL=cleanObjectRule.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`clean-object-wrapper`,match:e=>t.isCallExpression(e)&&/^(?:(?:MappingUtils\.)?cleanObject|clean)$/.test(e.getExpression().getText()),parse:()=>({transparent:!0})});export{n as cleanObjectRule};
2
+ //# sourceMappingURL=cleanObjectRule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanObjectRule.mjs","names":[],"sources":["../../src/rules/cleanObjectRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const cleanObjectRule = defineExpressionRule({\n name: \"clean-object-wrapper\",\n match: (expression) =>\n Node.isCallExpression(expression) && /^(?:(?:MappingUtils\\.)?cleanObject|clean)$/.test(expression.getExpression().getText()),\n parse: () => ({ transparent: true })\n});\n"],"mappings":"yFAGA,MAAa,EAAkB,EAAqB,CAClD,KAAM,uBACN,MAAQ,GACN,EAAK,iBAAiB,CAAU,GAAK,6CAA6C,KAAK,EAAW,cAAc,CAAC,CAAC,QAAQ,CAAC,EAC7H,WAAc,CAAE,YAAa,EAAK,EACpC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { n as FieldExtractionRule } from "../types-DcvdOZ2j.mjs";
2
+ //#region src/rules/cmsI18nFieldRule.d.ts
3
+ /**
4
+ * `CmsI18n#get`/`getAll` reads a localized field off the translations array passed to the
5
+ * constructor (e.g. `new CmsI18n(offer.translations)`). Resolving to `i18n.field` breaks
6
+ * domain-path matching downstream because the receiver name ("i18n") is not the domain root
7
+ * ("offer"); resolving through the constructor argument restores that root.
8
+ */
9
+ declare const cmsI18nFieldRule: FieldExtractionRule;
10
+ //#endregion
11
+ export { cmsI18nFieldRule };
12
+ //# sourceMappingURL=cmsI18nFieldRule.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";function n(e){if(!t.isIdentifier(e))return;let n=(e.getSymbol()?.getDeclarations().find(t.isVariableDeclaration))?.getInitializer();if(!n||!t.isNewExpression(n)||n.getExpression().getText()!==`CmsI18n`)return;let r=n.getArguments()[0];return r&&t.isExpression(r)?r.getText():void 0}function r(e){if(!t.isCallExpression(e))return;let r=e.getExpression();if(!t.isPropertyAccessExpression(r)||![`get`,`getAll`].includes(r.getName()))return;let i=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));if(!i||!t.isStringLiteral(i)&&!t.isNoSubstitutionTemplateLiteral(i))return;let a=n(r.getExpression());return a?`${a}[locale].${i.getLiteralValue()}`:void 0}const i=e({name:`cms-i18n-field`,match:e=>!!r(e),parse:e=>{let t=r(e);return t?{backendField:t}:void 0}});export{i as cmsI18nFieldRule};
2
+ //# sourceMappingURL=cmsI18nFieldRule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cmsI18nFieldRule.mjs","names":[],"sources":["../../src/rules/cmsI18nFieldRule.ts"],"sourcesContent":["import { type Expression, Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nconst CMS_I18N_CLASS_NAME = \"CmsI18n\";\n\nfunction resolveCmsI18nSource(receiver: Expression): string | undefined {\n if (!Node.isIdentifier(receiver)) {\n return undefined;\n }\n\n const declaration = receiver.getSymbol()?.getDeclarations().find(Node.isVariableDeclaration);\n const initializer = declaration?.getInitializer();\n if (!initializer || !Node.isNewExpression(initializer)) {\n return undefined;\n }\n\n if (initializer.getExpression().getText() !== CMS_I18N_CLASS_NAME) {\n return undefined;\n }\n\n const constructorArgument = initializer.getArguments()[0];\n return constructorArgument && Node.isExpression(constructorArgument) ? constructorArgument.getText() : undefined;\n}\n\nfunction parseCmsI18nField(expression: Expression): string | undefined {\n if (!Node.isCallExpression(expression)) {\n return undefined;\n }\n\n const target = expression.getExpression();\n if (!Node.isPropertyAccessExpression(target) || ![\"get\", \"getAll\"].includes(target.getName())) {\n return undefined;\n }\n\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n if (!field || (!Node.isStringLiteral(field) && !Node.isNoSubstitutionTemplateLiteral(field))) {\n return undefined;\n }\n\n const source = resolveCmsI18nSource(target.getExpression());\n return source ? `${source}[locale].${field.getLiteralValue()}` : undefined;\n}\n\n/**\n * `CmsI18n#get`/`getAll` reads a localized field off the translations array passed to the\n * constructor (e.g. `new CmsI18n(offer.translations)`). Resolving to `i18n.field` breaks\n * domain-path matching downstream because the receiver name (\"i18n\") is not the domain root\n * (\"offer\"); resolving through the constructor argument restores that root.\n */\nexport const cmsI18nFieldRule = defineExpressionRule({\n name: \"cms-i18n-field\",\n match: (expression) => Boolean(parseCmsI18nField(expression)),\n parse: (expression) => {\n const backendField = parseCmsI18nField(expression);\n return backendField ? { backendField } : undefined;\n }\n});\n"],"mappings":"yFAKA,SAAS,EAAqB,EAA0C,CACtE,GAAI,CAAC,EAAK,aAAa,CAAQ,EAC7B,OAIF,IAAM,GADc,EAAS,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC,KAAK,EAAK,qBAAqB,EAAA,EAC1D,eAAe,EAKhD,GAJI,CAAC,GAAe,CAAC,EAAK,gBAAgB,CAAW,GAIjD,EAAY,cAAc,CAAC,CAAC,QAAQ,IAAM,UAC5C,OAGF,IAAM,EAAsB,EAAY,aAAa,CAAC,CAAC,GACvD,OAAO,GAAuB,EAAK,aAAa,CAAmB,EAAI,EAAoB,QAAQ,EAAI,IAAA,EACzG,CAEA,SAAS,EAAkB,EAA4C,CACrE,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,OAGF,IAAM,EAAS,EAAW,cAAc,EACxC,GAAI,CAAC,EAAK,2BAA2B,CAAM,GAAK,CAAC,CAAC,MAAO,QAAQ,CAAC,CAAC,SAAS,EAAO,QAAQ,CAAC,EAC1F,OAGF,IAAM,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EACtG,GAAI,CAAC,GAAU,CAAC,EAAK,gBAAgB,CAAK,GAAK,CAAC,EAAK,gCAAgC,CAAK,EACxF,OAGF,IAAM,EAAS,EAAqB,EAAO,cAAc,CAAC,EAC1D,OAAO,EAAS,GAAG,EAAO,WAAW,EAAM,gBAAgB,IAAM,IAAA,EACnE,CAQA,MAAa,EAAmB,EAAqB,CACnD,KAAM,iBACN,MAAQ,GAAe,EAAQ,EAAkB,CAAU,EAC3D,MAAQ,GAAe,CACrB,IAAM,EAAe,EAAkB,CAAU,EACjD,OAAO,EAAe,CAAE,cAAa,EAAI,IAAA,EAC3C,CACF,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { n as FieldExtractionRule } from "../types-DcvdOZ2j.mjs";
2
+ //#region src/rules/dateConversionRule.d.ts
3
+ declare const dateConversionRule: FieldExtractionRule;
4
+ //#endregion
5
+ export { dateConversionRule };
6
+ //# sourceMappingURL=dateConversionRule.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`date-utils-wrapper`,match:e=>t.isCallExpression(e)&&e.getExpression().getText().startsWith(`DateUtils.`),parse:()=>({transparent:!0})});export{n as dateConversionRule};
2
+ //# sourceMappingURL=dateConversionRule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dateConversionRule.mjs","names":[],"sources":["../../src/rules/dateConversionRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const dateConversionRule = defineExpressionRule({\n name: \"date-utils-wrapper\",\n match: (expression) => Node.isCallExpression(expression) && expression.getExpression().getText().startsWith(\"DateUtils.\"),\n parse: () => ({ transparent: true })\n});\n"],"mappings":"yFAGA,MAAa,EAAqB,EAAqB,CACrD,KAAM,qBACN,MAAQ,GAAe,EAAK,iBAAiB,CAAU,GAAK,EAAW,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW,YAAY,EACxH,WAAc,CAAE,YAAa,EAAK,EACpC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { n as FieldExtractionRule } from "../types-DcvdOZ2j.mjs";
2
+ //#region src/rules/lodashGetRule.d.ts
3
+ declare const lodashGetRule: FieldExtractionRule;
4
+ //#endregion
5
+ export { lodashGetRule };
6
+ //# sourceMappingURL=lodashGetRule.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`lodash-get`,match:e=>{if(!t.isCallExpression(e)||e.getExpression().getText()!==`get`)return!1;let n=e.getArguments()[1];return t.isStringLiteral(n)||t.isNoSubstitutionTemplateLiteral(n)},parse:e=>{if(!t.isCallExpression(e))return;let[n,r]=e.getArguments();if(!(!n||!t.isExpression(n)||!t.isStringLiteral(r)&&!t.isNoSubstitutionTemplateLiteral(r)))return{backendField:`${n.getText()}.${r.getLiteralValue()}`}}});export{n as lodashGetRule};
2
+ //# sourceMappingURL=lodashGetRule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lodashGetRule.mjs","names":[],"sources":["../../src/rules/lodashGetRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const lodashGetRule = defineExpressionRule({\n name: \"lodash-get\",\n match: (expression) => {\n if (!Node.isCallExpression(expression) || expression.getExpression().getText() !== \"get\") {\n return false;\n }\n\n const path = expression.getArguments()[1];\n return Node.isStringLiteral(path) || Node.isNoSubstitutionTemplateLiteral(path);\n },\n parse: (expression) => {\n if (!Node.isCallExpression(expression)) {\n return undefined;\n }\n\n const [object, path] = expression.getArguments();\n if (!object || !Node.isExpression(object) || (!Node.isStringLiteral(path) && !Node.isNoSubstitutionTemplateLiteral(path))) {\n return undefined;\n }\n\n return { backendField: `${object.getText()}.${path.getLiteralValue()}` };\n }\n});\n"],"mappings":"yFAGA,MAAa,EAAgB,EAAqB,CAChD,KAAM,aACN,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,GAAK,EAAW,cAAc,CAAC,CAAC,QAAQ,IAAM,MACjF,MAAO,GAGT,IAAM,EAAO,EAAW,aAAa,CAAC,CAAC,GACvC,OAAO,EAAK,gBAAgB,CAAI,GAAK,EAAK,gCAAgC,CAAI,CAChF,EACA,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,OAGF,GAAM,CAAC,EAAQ,GAAQ,EAAW,aAAa,EAC3C,MAAC,GAAU,CAAC,EAAK,aAAa,CAAM,GAAM,CAAC,EAAK,gBAAgB,CAAI,GAAK,CAAC,EAAK,gCAAgC,CAAI,GAIvH,MAAO,CAAE,aAAc,GAAG,EAAO,QAAQ,EAAE,GAAG,EAAK,gBAAgB,GAAI,CACzE,CACF,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { n as FieldExtractionRule } from "../types-DcvdOZ2j.mjs";
2
+ //#region src/rules/mappingUtilityRule.d.ts
3
+ declare const mappingUtilityRule: FieldExtractionRule;
4
+ //#endregion
5
+ export { mappingUtilityRule };
6
+ //# sourceMappingURL=mappingUtilityRule.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=new Set([`string`,`number`,`boolean`,`date`,`datetime`,`enum`,`array`,`object`]),r=e({name:`mapping-utility-wrapper`,match:(e,n)=>{if(!t.isCallExpression(e))return!1;let r=e.getExpression().getText();return n.analysis.neutralExpressionMatchers.some(e=>r.startsWith(e.prefix))},parse:(e,r)=>{if(!t.isCallExpression(e))return;let i=e.getExpression().getText(),a=r.analysis.neutralExpressionMatchers.find(e=>i.startsWith(e.prefix));return{transparent:!0,mapperType:i.split(`.`).find(e=>n.has(e)),apiMapping:a?.apiMapping??!1}}});export{r as mappingUtilityRule};
2
+ //# sourceMappingURL=mappingUtilityRule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mappingUtilityRule.mjs","names":[],"sources":["../../src/rules/mappingUtilityRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nconst mapperTypes = new Set([\"string\", \"number\", \"boolean\", \"date\", \"datetime\", \"enum\", \"array\", \"object\"]);\n\nexport const mappingUtilityRule = defineExpressionRule({\n name: \"mapping-utility-wrapper\",\n match: (expression, config) => {\n if (!Node.isCallExpression(expression)) return false;\n\n const callee = expression.getExpression().getText();\n return config.analysis.neutralExpressionMatchers.some((matcher) => callee.startsWith(matcher.prefix));\n },\n parse: (expression, config) => {\n if (!Node.isCallExpression(expression)) return undefined;\n\n const callee = expression.getExpression().getText();\n const matcher = config.analysis.neutralExpressionMatchers.find((item) => callee.startsWith(item.prefix));\n const mapperType = callee.split(\".\").find((segment) => mapperTypes.has(segment));\n\n return {\n transparent: true,\n mapperType,\n apiMapping: matcher?.apiMapping ?? false\n };\n }\n});\n"],"mappings":"yFAGA,MAAM,EAAc,IAAI,IAAI,CAAC,SAAU,SAAU,UAAW,OAAQ,WAAY,OAAQ,QAAS,QAAQ,CAAC,EAE7F,EAAqB,EAAqB,CACrD,KAAM,0BACN,OAAQ,EAAY,IAAW,CAC7B,GAAI,CAAC,EAAK,iBAAiB,CAAU,EAAG,MAAO,GAE/C,IAAM,EAAS,EAAW,cAAc,CAAC,CAAC,QAAQ,EAClD,OAAO,EAAO,SAAS,0BAA0B,KAAM,GAAY,EAAO,WAAW,EAAQ,MAAM,CAAC,CACtG,EACA,OAAQ,EAAY,IAAW,CAC7B,GAAI,CAAC,EAAK,iBAAiB,CAAU,EAAG,OAExC,IAAM,EAAS,EAAW,cAAc,CAAC,CAAC,QAAQ,EAC5C,EAAU,EAAO,SAAS,0BAA0B,KAAM,GAAS,EAAO,WAAW,EAAK,MAAM,CAAC,EAGvG,MAAO,CACL,YAAa,GACb,WAJiB,EAAO,MAAM,GAAG,CAAC,CAAC,KAAM,GAAY,EAAY,IAAI,CAAO,CAInE,EACT,WAAY,GAAS,YAAc,EACrC,CACF,CACF,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { n as FieldExtractionRule } from "../types-DcvdOZ2j.mjs";
2
+ //#region src/rules/memberGetFieldRule.d.ts
3
+ declare const memberGetFieldRule: FieldExtractionRule;
4
+ //#endregion
5
+ export { memberGetFieldRule };
6
+ //# sourceMappingURL=memberGetFieldRule.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";const n=e({name:`member-get-field`,match:e=>{if(!t.isCallExpression(e))return!1;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));return t.isPropertyAccessExpression(n)&&[`get`,`getAll`].includes(n.getName())&&!!r},parse:e=>{if(!t.isCallExpression(e))return;let n=e.getExpression(),r=e.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));if(!(!t.isPropertyAccessExpression(n)||!t.isStringLiteral(r)&&!t.isNoSubstitutionTemplateLiteral(r)))return{backendField:`${n.getExpression().getText()}.${r.getLiteralValue()}`}}});export{n as memberGetFieldRule};
2
+ //# sourceMappingURL=memberGetFieldRule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memberGetFieldRule.mjs","names":[],"sources":["../../src/rules/memberGetFieldRule.ts"],"sourcesContent":["import { Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nexport const memberGetFieldRule = defineExpressionRule({\n name: \"member-get-field\",\n match: (expression) => {\n if (!Node.isCallExpression(expression)) return false;\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n return Node.isPropertyAccessExpression(target) && [\"get\", \"getAll\"].includes(target.getName()) && Boolean(field);\n },\n parse: (expression) => {\n if (!Node.isCallExpression(expression)) return undefined;\n\n const target = expression.getExpression();\n const field = expression\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n if (!Node.isPropertyAccessExpression(target) || (!Node.isStringLiteral(field) && !Node.isNoSubstitutionTemplateLiteral(field))) {\n return undefined;\n }\n\n return { backendField: `${target.getExpression().getText()}.${field.getLiteralValue()}` };\n }\n});\n"],"mappings":"yFAGA,MAAa,EAAqB,EAAqB,CACrD,KAAM,mBACN,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,EAAG,MAAO,GAE/C,IAAM,EAAS,EAAW,cAAc,EAClC,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EACtG,OAAO,EAAK,2BAA2B,CAAM,GAAK,CAAC,MAAO,QAAQ,CAAC,CAAC,SAAS,EAAO,QAAQ,CAAC,GAAK,EAAQ,CAC5G,EACA,MAAQ,GAAe,CACrB,GAAI,CAAC,EAAK,iBAAiB,CAAU,EAAG,OAExC,IAAM,EAAS,EAAW,cAAc,EAClC,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EAClG,MAAC,EAAK,2BAA2B,CAAM,GAAM,CAAC,EAAK,gBAAgB,CAAK,GAAK,CAAC,EAAK,gCAAgC,CAAK,GAI5H,MAAO,CAAE,aAAc,GAAG,EAAO,cAAc,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAM,gBAAgB,GAAI,CAC1F,CACF,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { n as FieldExtractionRule } from "../types-DcvdOZ2j.mjs";
2
+ //#region src/rules/quableI18nFieldRule.d.ts
3
+ declare const quableI18nFieldRule: FieldExtractionRule;
4
+ //#endregion
5
+ export { quableI18nFieldRule };
6
+ //# sourceMappingURL=quableI18nFieldRule.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{t as e}from"../defineExpressionRule-Dfvzj6n2.mjs";import{Node as t}from"ts-morph";function n(e){if(!t.isPropertyAccessExpression(e))return;let n=e.getExpression();if(!t.isElementAccessExpression(n))return;let r=n.getExpression();if(!t.isCallExpression(r))return;let i=r.getExpression(),a=r.getArguments().find(e=>t.isStringLiteral(e)||t.isNoSubstitutionTemplateLiteral(e));if(!(!t.isPropertyAccessExpression(i)||i.getName()!==`getAll`||!a||!t.isStringLiteral(a)&&!t.isNoSubstitutionTemplateLiteral(a)))return`${a.getLiteralValue()}[locale].${e.getName()}`}const r=e({name:`quable-i18n-field`,match:e=>!!n(e),parse:e=>{let t=n(e);return t?{backendField:t}:void 0}});export{r as quableI18nFieldRule};
2
+ //# sourceMappingURL=quableI18nFieldRule.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quableI18nFieldRule.mjs","names":[],"sources":["../../src/rules/quableI18nFieldRule.ts"],"sourcesContent":["import { type Expression, Node } from \"ts-morph\";\nimport { defineExpressionRule } from \"../utils/defineExpressionRule\";\n\nfunction parseLocalizedI18nField(expression: Expression): string | undefined {\n if (!Node.isPropertyAccessExpression(expression)) {\n return undefined;\n }\n\n const elementAccess = expression.getExpression();\n\n if (!Node.isElementAccessExpression(elementAccess)) {\n return undefined;\n }\n\n const getAllCall = elementAccess.getExpression();\n\n if (!Node.isCallExpression(getAllCall)) {\n return undefined;\n }\n\n const target = getAllCall.getExpression();\n const field = getAllCall\n .getArguments()\n .find((argument) => Node.isStringLiteral(argument) || Node.isNoSubstitutionTemplateLiteral(argument));\n\n if (\n !Node.isPropertyAccessExpression(target) ||\n target.getName() !== \"getAll\" ||\n !field ||\n (!Node.isStringLiteral(field) && !Node.isNoSubstitutionTemplateLiteral(field))\n ) {\n return undefined;\n }\n\n return `${field.getLiteralValue()}[locale].${expression.getName()}`;\n}\n\nexport const quableI18nFieldRule = defineExpressionRule({\n name: \"quable-i18n-field\",\n match: (expression) => Boolean(parseLocalizedI18nField(expression)),\n parse: (expression) => {\n const backendField = parseLocalizedI18nField(expression);\n return backendField ? { backendField } : undefined;\n }\n});\n"],"mappings":"yFAGA,SAAS,EAAwB,EAA4C,CAC3E,GAAI,CAAC,EAAK,2BAA2B,CAAU,EAC7C,OAGF,IAAM,EAAgB,EAAW,cAAc,EAE/C,GAAI,CAAC,EAAK,0BAA0B,CAAa,EAC/C,OAGF,IAAM,EAAa,EAAc,cAAc,EAE/C,GAAI,CAAC,EAAK,iBAAiB,CAAU,EACnC,OAGF,IAAM,EAAS,EAAW,cAAc,EAClC,EAAQ,EACX,aAAa,CAAC,CACd,KAAM,GAAa,EAAK,gBAAgB,CAAQ,GAAK,EAAK,gCAAgC,CAAQ,CAAC,EAGpG,MAAC,EAAK,2BAA2B,CAAM,GACvC,EAAO,QAAQ,IAAM,UACrB,CAAC,GACA,CAAC,EAAK,gBAAgB,CAAK,GAAK,CAAC,EAAK,gCAAgC,CAAK,GAK9E,MAAO,GAAG,EAAM,gBAAgB,EAAE,WAAW,EAAW,QAAQ,GAClE,CAEA,MAAa,EAAsB,EAAqB,CACtD,KAAM,oBACN,MAAQ,GAAe,EAAQ,EAAwB,CAAU,EACjE,MAAQ,GAAe,CACrB,IAAM,EAAe,EAAwB,CAAU,EACvD,OAAO,EAAe,CAAE,cAAa,EAAI,IAAA,EAC3C,CACF,CAAC"}
@@ -0,0 +1,102 @@
1
+ import { Expression } from "ts-morph";
2
+ //#region src/utils/defineExpressionRule.d.ts
3
+ type FieldExtractionRuleResult = {
4
+ backendField?: string;
5
+ transparent?: boolean;
6
+ mapperType?: string;
7
+ apiMapping?: boolean;
8
+ };
9
+ type FieldExtractionRule = {
10
+ name: string;
11
+ match: (expression: Expression, config: UserConfig) => boolean;
12
+ parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;
13
+ };
14
+ declare function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule;
15
+ //#endregion
16
+ //#region src/models/types.d.ts
17
+ interface NeutralExpressionMatcher {
18
+ prefix: string;
19
+ apiMapping?: boolean;
20
+ }
21
+ interface UserConfig {
22
+ /**
23
+ * Absolute path to the root of the repository to analyze (e.g. the digital-api repo).
24
+ * Can be set via the ATLAS_CWD environment variable.
25
+ */
26
+ repoRoot?: string;
27
+ /** Backend identifiers used by the API project. */
28
+ backends: string[];
29
+ /**
30
+ * Default OpenAPI document URL used to extract the public contract exposed by the API.
31
+ * The CLI can still override this value with `--openapi-url`.
32
+ */
33
+ openapiUrl: string;
34
+ /** Maximum duration allowed to connect to and download the OpenAPI document. */
35
+ openapiTimeoutMs: number;
36
+ /** Default Directus base URL used by `push` when no CLI option or environment variable overrides it. */
37
+ directusUrl: string;
38
+ /**
39
+ * Module aliases resolved while traversing static-analysis dependencies.
40
+ * Defaults are read from `compilerOptions.paths` in the repository tsconfig.
41
+ */
42
+ resolver?: {
43
+ /** Explicit aliases override aliases read from tsconfig. */
44
+ alias?: Record<string, string>;
45
+ };
46
+ /** Defaults used by the optional AI pass that reviews unresolved static mappings. */
47
+ inference: {
48
+ /** ai/sdk model used for structured mapping suggestions. */
49
+ model: string;
50
+ /** ai/sdk HTTP API base URL. */
51
+ baseUrl: string;
52
+ /** Minimum model confidence required to replace a `needs_review` mapping. */
53
+ minimumConfidence: number;
54
+ /** Maximum duration of one route-level AI request before aborting it. */
55
+ timeoutMs: number;
56
+ /** Maximum number of route-level AI requests run concurrently. */
57
+ workers: number;
58
+ };
59
+ /**
60
+ * Static analysis options used to keep generated artifacts focused on business-relevant files and fields.
61
+ */
62
+ analysis: {
63
+ /**
64
+ * Glob patterns excluded from `analysis_files`.
65
+ * Use this to hide technical plumbing files that add noise to route review documents.
66
+ */
67
+ excluded: string[];
68
+ /**
69
+ * Input root fields considered transverse to the API, such as access headers or generic list filters.
70
+ * These fields are kept in the extracted contract but are not flagged as `needs_review`
71
+ * when they do not map to a backend property. A detected backend mapping always takes precedence.
72
+ */
73
+ transversalInputs: string[];
74
+ /** Output field paths omitted from catalogues and coverage checks, including nested fields. */
75
+ ignoredOutputs: string[];
76
+ /** Technical property-access prefixes that must not be treated as backend fields. */
77
+ backendFieldAccess: {
78
+ excludedPrefixes: string[];
79
+ };
80
+ /** Mapper naming conventions used to infer mapping direction and domain roots. */
81
+ mapperNaming: {
82
+ inputPatterns: RegExp[];
83
+ outputPatterns: RegExp[];
84
+ domainToBackendPattern: RegExp;
85
+ domainNameFromToDomainPattern: RegExp;
86
+ constructedTypeToDomainPattern: (constructorName: string) => RegExp;
87
+ };
88
+ /** Expressions that are transparent while tracing a field through a mapping. */
89
+ neutralExpressionMatchers: NeutralExpressionMatcher[];
90
+ rules: FieldExtractionRule[];
91
+ };
92
+ /** Route-level minimum resolved-field coverage accepted by the static-analysis regression check. */
93
+ test: {
94
+ coverage: Array<{
95
+ route: string;
96
+ minimum_coverage: number | string;
97
+ }>;
98
+ };
99
+ }
100
+ //#endregion
101
+ export { FieldExtractionRule as n, defineExpressionRule as r, UserConfig as t };
102
+ //# sourceMappingURL=types-DcvdOZ2j.d.mts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmflow/atlas",
3
- "version": "3.4.0-beta.3",
3
+ "version": "3.4.0-beta.5",
4
4
  "description": "API-to-backend mapping catalogue for Club Med Flow",
5
5
  "license": "MIT",
6
6
  "author": "romakita",
@@ -9,9 +9,16 @@
9
9
  "type": "module",
10
10
  "main": "./dist/index.mjs",
11
11
  "module": "./dist/index.mjs",
12
+ "types": "./dist/index.d.mts",
12
13
  "exports": {
13
- ".": "./dist/index.mjs",
14
- "./rules/*": "./dist/rules/*.mjs"
14
+ ".": {
15
+ "types": "./dist/index.d.mts",
16
+ "import": "./dist/index.mjs"
17
+ },
18
+ "./rules/*": {
19
+ "types": "./dist/rules/*.d.mts",
20
+ "import": "./dist/rules/*.mjs"
21
+ }
15
22
  },
16
23
  "scripts": {
17
24
  "build": "tsdown",
@@ -1,2 +0,0 @@
1
- function e(e){return e}export{e as t};
2
- //# sourceMappingURL=defineRule-Dfvzj6n2.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"defineRule-Dfvzj6n2.mjs","names":[],"sources":["../src/utils/defineRule.ts"],"sourcesContent":["import type { Expression } from \"ts-morph\";\nimport type { UserConfig } from \"../models/types\";\n\nexport type FieldExtractionRuleResult = {\n backendField?: string;\n transparent?: boolean;\n mapperType?: string;\n apiMapping?: boolean;\n};\n\nexport type FieldExtractionRule = {\n name: string;\n match: (expression: Expression, config: UserConfig) => boolean;\n parse: (expression: Expression, config: UserConfig) => FieldExtractionRuleResult | undefined;\n};\n\nexport function defineExpressionRule(rule: FieldExtractionRule): FieldExtractionRule {\n return rule;\n}\n"],"mappings":"AAgBA,SAAgB,EAAqB,EAAgD,CACnF,OAAO,CACT"}