@cmflow/atlas 3.4.0-beta.21 → 3.4.0-beta.22

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.
@@ -3,8 +3,8 @@ import { fileURLToPath as __atlasFileURLToPath } from "node:url";
3
3
  const __filename = __atlasFileURLToPath(import.meta.url);
4
4
  import { n as __require, r as __toESM, t as __commonJSMin } from "../rolldown-runtime-CGR6nZuH.mjs";
5
5
  import { a as note, c as select, d as setUserConfig, f as userConfig$1, i as log, l as isCancel, n as cancel, o as outro, r as intro, s as progress, t as taskProgressService, u as getUserConfig } from "../taskProgressService-CAC_RIoa.mjs";
6
- import { a as isKnownBackendType, c as globby, i as inferBackendNameFromFile, n as generateBackendTopologyArtifactsInWorkers, o as filterAnalysisFiles, r as resolveModulePath, s as shouldKeepAnalysisFile } from "../routeBackendTopologyService-CkOLUfcj.mjs";
7
- import { i as loadOpenApiDocument, n as extractOpenApiInputProperties, r as extractOpenApiOutputProperties } from "../propertyExtractionService-8BjqpPFa.mjs";
6
+ import { a as inferBackendNameFromFile, c as shouldKeepAnalysisFile, i as resolveModulePath, l as normalizeFilePath, n as generateBackendTopologyArtifactsInWorkers, o as isKnownBackendType, r as matchesRouteSelector, s as filterAnalysisFiles, u as globby } from "../routeBackendTopologyService-CQyRyoSN.mjs";
7
+ import { i as loadOpenApiDocument, n as extractOpenApiInputProperties, r as extractOpenApiOutputProperties } from "../propertyExtractionService-BLat-Hsf.mjs";
8
8
  import { n as require_auth_errors, r as require_token_error, t as require_token_util } from "../token-util-Dnzm6rU4.mjs";
9
9
  import path from "node:path";
10
10
  import { Node, Project, SyntaxKind } from "ts-morph";
@@ -2888,6 +2888,233 @@ const r = (r = {}) => (i) => {
2888
2888
  } };
2889
2889
  };
2890
2890
 
2891
+ //#endregion
2892
+ //#region src/utils/isRecord.ts
2893
+ function isRecord$1(value) {
2894
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2895
+ }
2896
+
2897
+ //#endregion
2898
+ //#region src/services/directus/utils/isServiceUnavailableError.ts
2899
+ function isServiceUnavailableError(error) {
2900
+ if (!isRecord$1(error)) return false;
2901
+ if ((isRecord$1(error.response) ? error.response.status : void 0) === 503) return true;
2902
+ return Array.isArray(error.errors) && error.errors.some((item) => isRecord$1(item) && isRecord$1(item.extensions) && item.extensions.code === "SERVICE_UNAVAILABLE");
2903
+ }
2904
+
2905
+ //#endregion
2906
+ //#region src/services/directus/utils/isTransientNetworkError.ts
2907
+ function isTransientNetworkError(error) {
2908
+ if (error instanceof TypeError && error.message === "fetch failed") return true;
2909
+ if (!isRecord$1(error)) return false;
2910
+ if (error.message === "fetch failed") return true;
2911
+ const cause = isRecord$1(error.cause) ? error.cause : void 0;
2912
+ return cause?.code === "ECONNRESET" || cause?.code === "ECONNREFUSED" || cause?.code === "ETIMEDOUT" || cause?.code === "UND_ERR_CONNECT_TIMEOUT";
2913
+ }
2914
+
2915
+ //#endregion
2916
+ //#region src/services/directus/utils/retryDirectusRequest.ts
2917
+ const DIRECTUS_RETRY_MAX_ATTEMPTS = 6;
2918
+ const DIRECTUS_RETRY_INITIAL_DELAY_MS = 1e3;
2919
+ const DIRECTUS_RETRY_MAX_DELAY_MS = 8e3;
2920
+ async function retryDirectusRequest(request, options = {}) {
2921
+ const maxAttempts = options.maxAttempts ?? DIRECTUS_RETRY_MAX_ATTEMPTS;
2922
+ const initialDelayMs = options.initialDelayMs ?? DIRECTUS_RETRY_INITIAL_DELAY_MS;
2923
+ const maxDelayMs = options.maxDelayMs ?? DIRECTUS_RETRY_MAX_DELAY_MS;
2924
+ const sleep = options.sleep || ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
2925
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) try {
2926
+ return await request();
2927
+ } catch (error) {
2928
+ if (!isServiceUnavailableError(error) && !isTransientNetworkError(error) || attempt === maxAttempts) throw error;
2929
+ const delayMs = Math.min(initialDelayMs * 2 ** (attempt - 1), maxDelayMs);
2930
+ taskProgressService.report(`Directus request temporarily unavailable; retrying ${attempt + 1}/${maxAttempts} in ${(delayMs / 1e3).toFixed(1)}s`);
2931
+ await sleep(delayMs);
2932
+ }
2933
+ throw new Error("Directus retry attempts exhausted");
2934
+ }
2935
+
2936
+ //#endregion
2937
+ //#region src/services/directus/createClient.ts
2938
+ async function createClient() {
2939
+ const baseUrl = getUserConfig().directusUrl;
2940
+ const token = getUserConfig().directusToken;
2941
+ if (!token) throw new Error("Directus push requested but Directus token is missing");
2942
+ const client = t$1(baseUrl).with(r()).with(e$3(token));
2943
+ return { request: (command) => retryDirectusRequest(() => client.request(command)) };
2944
+ }
2945
+
2946
+ //#endregion
2947
+ //#region src/services/directus/queries/deleteOrphanedPropertyLinks.ts
2948
+ const DIRECTUS_DELETE_WORKERS = 2;
2949
+ async function deleteOrphanedPropertyLinks(client, orphanedLinks) {
2950
+ const links = [...orphanedLinks.input.map((link) => ({
2951
+ collection: "route_input_properties_backend_properties",
2952
+ id: link.id
2953
+ })), ...orphanedLinks.output.map((link) => ({
2954
+ collection: "route_output_properties_backend_properties",
2955
+ id: link.id
2956
+ }))];
2957
+ let nextIndex = 0;
2958
+ await Promise.all(Array.from({ length: Math.min(DIRECTUS_DELETE_WORKERS, links.length) }, async () => {
2959
+ while (nextIndex < links.length) {
2960
+ const link = links[nextIndex++];
2961
+ await client.request(r$1(link.collection, link.id));
2962
+ }
2963
+ }));
2964
+ return links.length;
2965
+ }
2966
+
2967
+ //#endregion
2968
+ //#region src/services/directus/queries/findOrphanedPropertyLinks.ts
2969
+ async function findOrphanedPropertyLinks(client) {
2970
+ const [inputProperties, outputProperties, backendProperties, inputLinks, outputLinks] = await Promise.all([
2971
+ client.request(n$1("route_input_properties", {
2972
+ fields: ["id"],
2973
+ limit: -1
2974
+ })),
2975
+ client.request(n$1("route_output_properties", {
2976
+ fields: ["id"],
2977
+ limit: -1
2978
+ })),
2979
+ client.request(n$1("backend_properties", {
2980
+ fields: ["id"],
2981
+ limit: -1
2982
+ })),
2983
+ client.request(n$1("route_input_properties_backend_properties", {
2984
+ fields: [
2985
+ "id",
2986
+ "route_input_properties_id",
2987
+ "backend_properties_id"
2988
+ ],
2989
+ limit: -1
2990
+ })),
2991
+ client.request(n$1("route_output_properties_backend_properties", {
2992
+ fields: [
2993
+ "id",
2994
+ "route_output_properties_id",
2995
+ "backend_properties_id"
2996
+ ],
2997
+ limit: -1
2998
+ }))
2999
+ ]);
3000
+ const ids = (items) => new Set(items.map((item) => String(item.id)));
3001
+ const inputIds = ids(inputProperties);
3002
+ const outputIds = ids(outputProperties);
3003
+ const backendIds = ids(backendProperties);
3004
+ return {
3005
+ input: inputLinks.filter((link) => !inputIds.has(String(link.route_input_properties_id)) || !backendIds.has(String(link.backend_properties_id))),
3006
+ output: outputLinks.filter((link) => !outputIds.has(String(link.route_output_properties_id)) || !backendIds.has(String(link.backend_properties_id)))
3007
+ };
3008
+ }
3009
+
3010
+ //#endregion
3011
+ //#region src/services/directus/cleanDirectusOrphanedPropertyLinks.ts
3012
+ async function cleanDirectusOrphanedPropertyLinks(options) {
3013
+ taskProgressService.report("Connecting to Directus");
3014
+ const client = await createClient();
3015
+ taskProgressService.report("Finding orphaned property links");
3016
+ const orphanedLinks = await findOrphanedPropertyLinks(client);
3017
+ const orphanedCount = orphanedLinks.input.length + orphanedLinks.output.length;
3018
+ if (options.dryRun) {
3019
+ taskProgressService.report(`${orphanedCount} orphaned property links found (dry-run)`);
3020
+ return {
3021
+ orphanedLinks: orphanedCount,
3022
+ removedOrphanedLinks: 0
3023
+ };
3024
+ }
3025
+ taskProgressService.report("Removing orphaned property links");
3026
+ const removedOrphanedLinks = await deleteOrphanedPropertyLinks(client, orphanedLinks);
3027
+ taskProgressService.report(`Removed ${removedOrphanedLinks} orphaned property links`);
3028
+ return {
3029
+ orphanedLinks: orphanedCount,
3030
+ removedOrphanedLinks
3031
+ };
3032
+ }
3033
+
3034
+ //#endregion
3035
+ //#region src/utils/normalizeLookupKey.ts
3036
+ function normalizeLookupKey(value) {
3037
+ return value.toUpperCase().replace(/[^A-Z0-9]/g, "");
3038
+ }
3039
+
3040
+ //#endregion
3041
+ //#region src/services/directus/cleanupOrphanedPropertyLinks.ts
3042
+ async function cleanupOrphanedPropertyLinks(client) {
3043
+ taskProgressService.report("Cleaning orphaned property links");
3044
+ const removed = await deleteOrphanedPropertyLinks(client, await findOrphanedPropertyLinks(client));
3045
+ taskProgressService.report(`Removed ${removed} orphaned property links`);
3046
+ return removed;
3047
+ }
3048
+
3049
+ //#endregion
3050
+ //#region src/services/directus/queries/ensureInputLink.ts
3051
+ async function ensureInputLink(client, inputPropertyId, backendPropertyId) {
3052
+ const [existing] = await client.request(n$1("route_input_properties_backend_properties", {
3053
+ filter: {
3054
+ route_input_properties_id: { _eq: inputPropertyId },
3055
+ backend_properties_id: { _eq: backendPropertyId }
3056
+ },
3057
+ fields: ["id"],
3058
+ limit: 1
3059
+ }));
3060
+ if (existing) return false;
3061
+ await client.request(n$2("route_input_properties_backend_properties", {
3062
+ route_input_properties_id: inputPropertyId,
3063
+ backend_properties_id: backendPropertyId
3064
+ }));
3065
+ return true;
3066
+ }
3067
+
3068
+ //#endregion
3069
+ //#region src/services/directus/queries/ensureOutputLink.ts
3070
+ async function ensureOutputLink(client, outputPropertyId, backendPropertyId) {
3071
+ const [existing] = await client.request(n$1("route_output_properties_backend_properties", {
3072
+ filter: {
3073
+ route_output_properties_id: { _eq: outputPropertyId },
3074
+ backend_properties_id: { _eq: backendPropertyId }
3075
+ },
3076
+ fields: ["id"],
3077
+ limit: 1
3078
+ }));
3079
+ if (existing) return false;
3080
+ await client.request(n$2("route_output_properties_backend_properties", {
3081
+ route_output_properties_id: outputPropertyId,
3082
+ backend_properties_id: backendPropertyId
3083
+ }));
3084
+ return true;
3085
+ }
3086
+
3087
+ //#endregion
3088
+ //#region src/services/directus/queries/loadDirectusReferenceData.ts
3089
+ async function loadDirectusReferenceData(client) {
3090
+ const values = await client.request(n$1("predefined_values", {
3091
+ filter: { type: { _in: [
3092
+ "backend",
3093
+ "in_type",
3094
+ "out_type"
3095
+ ] } },
3096
+ fields: [
3097
+ "id",
3098
+ "label",
3099
+ "type"
3100
+ ],
3101
+ limit: -1
3102
+ }));
3103
+ const backendIds = /* @__PURE__ */ new Map();
3104
+ const inTypeIds = /* @__PURE__ */ new Map();
3105
+ const outTypeIds = /* @__PURE__ */ new Map();
3106
+ for (const value of values) {
3107
+ const normalizedKeys = [normalizeLookupKey(value.id), normalizeLookupKey(value.label)];
3108
+ const target = value.type === "backend" ? backendIds : value.type === "in_type" ? inTypeIds : outTypeIds;
3109
+ for (const key of normalizedKeys) target.set(key, value.id);
3110
+ }
3111
+ return {
3112
+ backendIds,
3113
+ inTypeIds,
3114
+ outTypeIds
3115
+ };
3116
+ }
3117
+
2891
3118
  //#endregion
2892
3119
  //#region src/utils/catalogueStats.ts
2893
3120
  function calculateNeedsReviewPercentage(inputProperties, outputProperties, needsReview) {
@@ -2896,10 +3123,36 @@ function calculateNeedsReviewPercentage(inputProperties, outputProperties, needs
2896
3123
  }
2897
3124
 
2898
3125
  //#endregion
2899
- //#region src/services/useCaseAnalysisService.ts
3126
+ //#region src/utils/hash/stableKey.ts
3127
+ function stableKey(...parts) {
3128
+ return createHash("sha1").update(parts.join("::")).digest("hex");
3129
+ }
3130
+
3131
+ //#endregion
3132
+ //#region src/utils/normalizeField.ts
3133
+ function normalizeField(value) {
3134
+ return value.replace(/\[locale\]/g, "").replace(/\[\]/g, "").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
3135
+ }
3136
+
3137
+ //#endregion
3138
+ //#region src/utils/matchBackendProperty.ts
3139
+ function matchBackendProperty(field, properties) {
3140
+ if (!properties) return;
3141
+ const normalized = normalizeField(field);
3142
+ return properties.find((property) => {
3143
+ const reference = normalizeField(property.field);
3144
+ return normalized === reference || normalized.endsWith(reference) || reference.endsWith(normalized);
3145
+ });
3146
+ }
3147
+
3148
+ //#endregion
3149
+ //#region src/utils/normalizeCodeText.ts
2900
3150
  function normalizeCodeText(value) {
2901
- return value.replace(/\s+/g, " ").replace(/;$/, "").trim();
3151
+ return value.replace(/\s+/g, " ").trim().replace(/;$/, "");
2902
3152
  }
3153
+
3154
+ //#endregion
3155
+ //#region src/services/useCaseAnalysisService.ts
2903
3156
  function describeRuleEffect(node) {
2904
3157
  if (Node.isBlock(node)) return node.getStatements().map(describeRuleEffect).filter(Boolean).join("; ");
2905
3158
  if (Node.isReturnStatement(node)) return node.getExpression() ? `return ${normalizeCodeText(node.getExpression().getText())}` : "return";
@@ -3015,7 +3268,7 @@ var AnalysisCacheService = class {
3015
3268
  const analysisCacheService = new AnalysisCacheService();
3016
3269
 
3017
3270
  //#endregion
3018
- //#region src/services/backendPropertyService.ts
3271
+ //#region src/services/resolveBackendProperties.ts
3019
3272
  const propertiesBySource = /* @__PURE__ */ new WeakMap();
3020
3273
  async function resolveBackendProperties(sources) {
3021
3274
  const resolved = await Promise.all(sources.map(async (source) => {
@@ -3032,17 +3285,6 @@ async function resolveBackendProperties(sources) {
3032
3285
  }));
3033
3286
  return new Map(resolved.filter((entry) => entry[1] !== void 0).map(([backend, properties]) => [backend, properties]));
3034
3287
  }
3035
- function matchBackendProperty(field, properties) {
3036
- if (!properties) return;
3037
- const normalized = normalizeField(field);
3038
- return properties.find((property) => {
3039
- const reference = normalizeField(property.field);
3040
- return normalized === reference || normalized.endsWith(reference) || reference.endsWith(normalized);
3041
- });
3042
- }
3043
- function normalizeField(value) {
3044
- return value.replace(/\[locale\]/g, "").replace(/\[\]/g, "").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
3045
- }
3046
3288
 
3047
3289
  //#endregion
3048
3290
  //#region src/services/backendRouteExtractionService.ts
@@ -3077,16 +3319,7 @@ function extractBackendRouteCandidates(params) {
3077
3319
  }
3078
3320
 
3079
3321
  //#endregion
3080
- //#region src/utils/routeKey.ts
3081
- function stableKey$2(...parts) {
3082
- return createHash("sha1").update(parts.join("::")).digest("hex");
3083
- }
3084
- function buildRouteKey(method, routePath) {
3085
- return stableKey$2(method, routePath);
3086
- }
3087
-
3088
- //#endregion
3089
- //#region src/utils/pathNormalization.ts
3322
+ //#region src/utils/path/pathNormalization.ts
3090
3323
  function normalizePathForMatch(value) {
3091
3324
  return value.replace(/\[\]/g, "").replace(/\{[^}]+\}/g, "").replace(/[^a-z0-9]/gi, "").toLowerCase();
3092
3325
  }
@@ -4008,8 +4241,8 @@ async function analyzeCodebaseRouteContracts(params) {
4008
4241
  if (Node.isArrayLiteralExpression(expression)) routeDeclarations.push(...extractRoutesFromArray(routeFile, expression));
4009
4242
  }
4010
4243
  }
4011
- const routeDeclarationsByKey = dedupeByKey(routeDeclarations, (item) => stableKey$2(item.method, item.path, item.file, item.handlerRef || ""));
4012
- const selectedRouteDeclarations = selectedRouteKeys ? routeDeclarationsByKey.filter((route) => selectedRouteKeys.has(stableKey$2(route.method, route.path))) : routeDeclarationsByKey;
4244
+ const routeDeclarationsByKey = dedupeByKey(routeDeclarations, (item) => stableKey(item.method, item.path, item.file, item.handlerRef || ""));
4245
+ const selectedRouteDeclarations = selectedRouteKeys ? routeDeclarationsByKey.filter((route) => selectedRouteKeys.has(stableKey(route.method, route.path))) : routeDeclarationsByKey;
4013
4246
  taskProgressService.log(`${selectedRouteDeclarations.length} route definitions selected from ${routeDeclarationsByKey.length} discovered`);
4014
4247
  const documents = [];
4015
4248
  const dependencyFilesByHandler = /* @__PURE__ */ new Map();
@@ -4028,7 +4261,7 @@ async function analyzeCodebaseRouteContracts(params) {
4028
4261
  const routeLabel = `[${index + 1}/${selectedRouteDeclarations.length}] - ${routeDeclaration.method} ${routeDeclaration.path}`;
4029
4262
  taskProgressService.log(`${routeLabel} ...`);
4030
4263
  await yieldToEventLoop();
4031
- const routeKey = stableKey$2(routeDeclaration.method, routeDeclaration.path);
4264
+ const routeKey = stableKey(routeDeclaration.method, routeDeclaration.path);
4032
4265
  const routeContract = routeContracts?.get(routeKey);
4033
4266
  const operation = swagger?.paths?.[routeDeclaration.path]?.[routeDeclaration.method.toLowerCase()];
4034
4267
  if (!routeContract && (!operation || !swagger)) continue;
@@ -4208,7 +4441,7 @@ function resolveApiPropertySource(document, property, direction, matches = []) {
4208
4441
  return match ? `${match.sourceFile}:${match.line}` : matches[0] ? backendFieldSource(matches[0]) : document.routeFile || null;
4209
4442
  }
4210
4443
  function ensureBackendRouteRecord(backendRoutesByKey, document, candidate) {
4211
- const key = stableKey$2(document.key, candidate.backend, candidate.route);
4444
+ const key = stableKey(document.key, candidate.backend, candidate.route);
4212
4445
  const existing = backendRoutesByKey.get(key);
4213
4446
  if (existing) return existing;
4214
4447
  const created = {
@@ -4288,7 +4521,7 @@ async function buildCatalogue(documents) {
4288
4521
  for (const property of document.input) {
4289
4522
  const matches = matchCandidates(property, document, "input");
4290
4523
  const apiSource = resolveApiFieldSourceCandidate(document, property, "input");
4291
- const apiPropertyKey = stableKey$2(document.key, "input", property.path);
4524
+ const apiPropertyKey = stableKey(document.key, "input", property.path);
4292
4525
  const backendNames = dedupeByKey(matches.map((candidate) => candidate.backend), (value) => value);
4293
4526
  const isTransverseWithoutMapping = isTransversalInput(property) && !matches.length;
4294
4527
  const evidenceStatus = isTransverseWithoutMapping ? "confirmed" : evidenceStatusFromCandidates(matches);
@@ -4308,13 +4541,13 @@ async function buildCatalogue(documents) {
4308
4541
  evidence_status: evidenceStatus
4309
4542
  });
4310
4543
  if (!matches.length && !isTransverseWithoutMapping) mappingEvidence.push({
4311
- key: stableKey$2(apiPropertyKey, "needs_review"),
4544
+ key: stableKey(apiPropertyKey, "needs_review"),
4312
4545
  evidence_type: "static_analysis",
4313
4546
  status: "needs_review",
4314
4547
  confidence_score: 0,
4315
4548
  comment: `No backend property candidate matched ${property.path}`,
4316
4549
  input_property_key: apiPropertyKey,
4317
- backend_property_key: stableKey$2(document.key, "unmatched", "input", property.path),
4550
+ backend_property_key: stableKey(document.key, "unmatched", "input", property.path),
4318
4551
  source_file: document.routeFile || null
4319
4552
  });
4320
4553
  for (const match of matches) {
@@ -4323,7 +4556,7 @@ async function buildCatalogue(documents) {
4323
4556
  route: "unknown",
4324
4557
  sourceFile: match.sourceFile
4325
4558
  }).key;
4326
- const backendPropertyKey = stableKey$2(backendRouteKey, "input", match.resolvedProperty?.field || match.backendField);
4559
+ const backendPropertyKey = stableKey(backendRouteKey, "input", match.resolvedProperty?.field || match.backendField);
4327
4560
  backendPropertiesByKey.set(backendPropertyKey, {
4328
4561
  key: backendPropertyKey,
4329
4562
  backend_route_key: backendRouteKey,
@@ -4336,7 +4569,7 @@ async function buildCatalogue(documents) {
4336
4569
  provenance: "code_analysis"
4337
4570
  });
4338
4571
  mappingEvidence.push({
4339
- key: stableKey$2(apiPropertyKey, backendPropertyKey),
4572
+ key: stableKey(apiPropertyKey, backendPropertyKey),
4340
4573
  evidence_type: "static_analysis",
4341
4574
  status: match.requiresReview ? "needs_review" : match.confidence >= 90 ? "confirmed" : "inferred",
4342
4575
  confidence_score: match.confidence,
@@ -4351,7 +4584,7 @@ async function buildCatalogue(documents) {
4351
4584
  if (isIgnoredOutput(property)) continue;
4352
4585
  const matches = matchCandidates(property, document, "output");
4353
4586
  const apiSource = resolveApiFieldSourceCandidate(document, property, "output");
4354
- const apiPropertyKey = stableKey$2(document.key, "output", property.path);
4587
+ const apiPropertyKey = stableKey(document.key, "output", property.path);
4355
4588
  const backendNames = dedupeByKey(matches.map((candidate) => candidate.backend), (value) => value);
4356
4589
  const evidenceStatus = evidenceStatusFromCandidates(matches);
4357
4590
  const backendMappings = dedupeByKey(matches.map((match) => toBackendMapping(document, match)), (mapping) => `${mapping.backend}:${mapping.document || ""}:${mapping.method || "CALL"}:${mapping.route || ""}:${mapping.field}:${mapping.source_file}`);
@@ -4370,13 +4603,13 @@ async function buildCatalogue(documents) {
4370
4603
  ...matches.length ? {} : { unresolved_backend_candidates: unresolvedBackendCandidates(property, apiSource?.domainField, document, "output") }
4371
4604
  });
4372
4605
  if (!matches.length) mappingEvidence.push({
4373
- key: stableKey$2(apiPropertyKey, "needs_review"),
4606
+ key: stableKey(apiPropertyKey, "needs_review"),
4374
4607
  evidence_type: "static_analysis",
4375
4608
  status: "needs_review",
4376
4609
  confidence_score: 0,
4377
4610
  comment: `No backend property candidate matched ${property.path}`,
4378
4611
  output_property_key: apiPropertyKey,
4379
- backend_property_key: stableKey$2(document.key, "unmatched", "output", property.path),
4612
+ backend_property_key: stableKey(document.key, "unmatched", "output", property.path),
4380
4613
  source_file: document.routeFile || null
4381
4614
  });
4382
4615
  for (const match of matches) {
@@ -4385,7 +4618,7 @@ async function buildCatalogue(documents) {
4385
4618
  route: "unknown",
4386
4619
  sourceFile: match.sourceFile
4387
4620
  }).key;
4388
- const backendPropertyKey = stableKey$2(backendRouteKey, "output", match.resolvedProperty?.field || match.backendField);
4621
+ const backendPropertyKey = stableKey(backendRouteKey, "output", match.resolvedProperty?.field || match.backendField);
4389
4622
  backendPropertiesByKey.set(backendPropertyKey, {
4390
4623
  key: backendPropertyKey,
4391
4624
  backend_route_key: backendRouteKey,
@@ -4398,7 +4631,7 @@ async function buildCatalogue(documents) {
4398
4631
  provenance: "code_analysis"
4399
4632
  });
4400
4633
  mappingEvidence.push({
4401
- key: stableKey$2(apiPropertyKey, backendPropertyKey),
4634
+ key: stableKey(apiPropertyKey, backendPropertyKey),
4402
4635
  evidence_type: "static_analysis",
4403
4636
  status: match.requiresReview ? "needs_review" : match.confidence >= 90 ? "confirmed" : "inferred",
4404
4637
  confidence_score: match.confidence,
@@ -4437,239 +4670,13 @@ async function buildCatalogue(documents) {
4437
4670
  }
4438
4671
 
4439
4672
  //#endregion
4440
- //#region src/services/directus/directusSyncService.ts
4441
- const ROUTE_STATUSES = { published: "published" };
4442
- const LOCAL_SOURCE_FILE = "digital-api:tools/datasource-catalogue";
4443
- const DIRECTUS_RETRY_MAX_ATTEMPTS = 6;
4444
- const DIRECTUS_RETRY_INITIAL_DELAY_MS = 1e3;
4445
- const DIRECTUS_RETRY_MAX_DELAY_MS = 8e3;
4446
- const DIRECTUS_DELETE_WORKERS = 2;
4447
- function isRecord$3(value) {
4448
- return typeof value === "object" && value !== null && !Array.isArray(value);
4449
- }
4450
- function isServiceUnavailableError(error) {
4451
- if (!isRecord$3(error)) return false;
4452
- if ((isRecord$3(error.response) ? error.response.status : void 0) === 503) return true;
4453
- return Array.isArray(error.errors) && error.errors.some((item) => isRecord$3(item) && isRecord$3(item.extensions) && item.extensions.code === "SERVICE_UNAVAILABLE");
4454
- }
4455
- function isTransientNetworkError(error) {
4456
- if (error instanceof TypeError && error.message === "fetch failed") return true;
4457
- if (!isRecord$3(error)) return false;
4458
- if (error.message === "fetch failed") return true;
4459
- const cause = isRecord$3(error.cause) ? error.cause : void 0;
4460
- return cause?.code === "ECONNRESET" || cause?.code === "ECONNREFUSED" || cause?.code === "ETIMEDOUT" || cause?.code === "UND_ERR_CONNECT_TIMEOUT";
4461
- }
4462
- async function retryDirectusRequest(request, options = {}) {
4463
- const maxAttempts = options.maxAttempts ?? DIRECTUS_RETRY_MAX_ATTEMPTS;
4464
- const initialDelayMs = options.initialDelayMs ?? DIRECTUS_RETRY_INITIAL_DELAY_MS;
4465
- const maxDelayMs = options.maxDelayMs ?? DIRECTUS_RETRY_MAX_DELAY_MS;
4466
- const sleep = options.sleep || ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
4467
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) try {
4468
- return await request();
4469
- } catch (error) {
4470
- if (!isServiceUnavailableError(error) && !isTransientNetworkError(error) || attempt === maxAttempts) throw error;
4471
- const delayMs = Math.min(initialDelayMs * 2 ** (attempt - 1), maxDelayMs);
4472
- taskProgressService.report(`Directus request temporarily unavailable; retrying ${attempt + 1}/${maxAttempts} in ${(delayMs / 1e3).toFixed(1)}s`);
4473
- await sleep(delayMs);
4474
- }
4475
- throw new Error("Directus retry attempts exhausted");
4476
- }
4477
- function toRouteIdentifier(route) {
4478
- return `${route.method} ${route.path}`;
4479
- }
4480
- function toBackendRouteIdentifier(route) {
4481
- return toRouteIdentifier(route);
4482
- }
4483
- function groupBackendRoutesForDirectusSync(backendRoutes, apiRoutesByKey) {
4484
- const groups = /* @__PURE__ */ new Map();
4485
- for (const backendRoute of backendRoutes) {
4486
- const apiRoute = apiRoutesByKey.get(backendRoute.route_key);
4487
- const key = apiRoute ? `${backendRoute.backend}${apiRoute.method}${apiRoute.path}` : `${backendRoute.backend}missing-api-route${backendRoute.key}`;
4488
- const group = groups.get(key);
4489
- if (group) group.keys.push(backendRoute.key);
4490
- else groups.set(key, {
4491
- route: backendRoute,
4492
- keys: [backendRoute.key]
4493
- });
4494
- }
4495
- return [...groups.values()];
4496
- }
4497
- function selectCatalogueForDirectusPush(catalogue, routeSelector) {
4498
- if (!routeSelector) return catalogue;
4499
- const routes = catalogue.routes.filter((route) => route.method === routeSelector.method && route.path === routeSelector.path);
4500
- if (!routes.length) throw new Error(`Route not found in catalogue: ${routeSelector.method} ${routeSelector.path}`);
4501
- const routeKeys = new Set(routes.map((route) => route.key));
4502
- const routeInputProperties = catalogue.route_input_properties.filter((property) => routeKeys.has(property.route_key));
4503
- const routeOutputProperties = catalogue.route_output_properties.filter((property) => routeKeys.has(property.route_key));
4504
- const backendRoutes = catalogue.backend_routes.filter((route) => routeKeys.has(route.route_key));
4505
- const backendRouteKeys = new Set(backendRoutes.map((route) => route.key));
4506
- const backendProperties = catalogue.backend_properties.filter((property) => backendRouteKeys.has(property.backend_route_key));
4507
- const apiPropertyKeys = new Set([...routeInputProperties, ...routeOutputProperties].map((property) => property.key));
4508
- const backendPropertyKeys = new Set(backendProperties.map((property) => property.key));
4509
- return {
4510
- ...catalogue,
4511
- routes,
4512
- route_input_properties: routeInputProperties,
4513
- route_output_properties: routeOutputProperties,
4514
- backend_routes: backendRoutes,
4515
- backend_properties: backendProperties,
4516
- mapping_evidence: catalogue.mapping_evidence.filter((evidence) => backendPropertyKeys.has(evidence.backend_property_key) && (apiPropertyKeys.has(evidence.input_property_key || "") || apiPropertyKeys.has(evidence.output_property_key || ""))),
4517
- rules: catalogue.rules.filter((rule) => routeKeys.has(rule.route_key)),
4518
- aggregations: catalogue.aggregations.filter((aggregation) => routeKeys.has(aggregation.route_key))
4519
- };
4520
- }
4521
- function inferInputType(field) {
4522
- if (field.startsWith("params.")) return "PATH";
4523
- if (field.startsWith("query.")) return "QUERY";
4524
- if (field.startsWith("headers.")) return "HEADER";
4525
- return "BODY";
4526
- }
4527
- function normalizeLookupKey(value) {
4528
- return value.toUpperCase().replace(/[^A-Z0-9]/g, "");
4529
- }
4530
- function normalizeDirectusPath(value) {
4531
- return value.replace(/\?\.\[/g, "[").replace(/\?\./g, ".");
4532
- }
4673
+ //#region src/utils/string/toPascalCase.ts
4533
4674
  function toPascalCase(value) {
4534
- return value.toLowerCase().replace(/(^|[^a-z0-9]+)([a-z0-9])/g, (_match, _separator, character) => character.toUpperCase());
4535
- }
4536
- function buildTranslationSummary(route) {
4537
- const routeIdentifier = toRouteIdentifier(route);
4538
- const primarySource = route.analysis_files[0] || route.source_file || LOCAL_SOURCE_FILE;
4539
- return [{
4540
- languages_code: "en-US",
4541
- summary: routeIdentifier,
4542
- description: `Generated from ${path.basename(primarySource)} on 2026-07-16`
4543
- }];
4544
- }
4545
- async function createClient(baseUrl, token) {
4546
- const client = t$1(baseUrl).with(r()).with(e$3(token));
4547
- return { request: (request) => retryDirectusRequest(() => client.request(request)) };
4548
- }
4549
- function directusReadItems(collection, query) {
4550
- return n$1(collection, query);
4551
- }
4552
- function directusCreateItem(collection, payload) {
4553
- return n$2(collection, payload);
4554
- }
4555
- function directusUpdateItem(collection, id, payload) {
4556
- return i(collection, id, payload);
4557
- }
4558
- function directusDeleteItem(collection, id) {
4559
- return r$1(collection, id);
4560
- }
4561
- async function findOrphanedPropertyLinks(client) {
4562
- const [inputProperties, outputProperties, backendProperties, inputLinks, outputLinks] = await Promise.all([
4563
- client.request(directusReadItems("route_input_properties", {
4564
- fields: ["id"],
4565
- limit: -1
4566
- })),
4567
- client.request(directusReadItems("route_output_properties", {
4568
- fields: ["id"],
4569
- limit: -1
4570
- })),
4571
- client.request(directusReadItems("backend_properties", {
4572
- fields: ["id"],
4573
- limit: -1
4574
- })),
4575
- client.request(directusReadItems("route_input_properties_backend_properties", {
4576
- fields: [
4577
- "id",
4578
- "route_input_properties_id",
4579
- "backend_properties_id"
4580
- ],
4581
- limit: -1
4582
- })),
4583
- client.request(directusReadItems("route_output_properties_backend_properties", {
4584
- fields: [
4585
- "id",
4586
- "route_output_properties_id",
4587
- "backend_properties_id"
4588
- ],
4589
- limit: -1
4590
- }))
4591
- ]);
4592
- const ids = (items) => new Set(items.map((item) => String(item.id)));
4593
- const inputIds = ids(inputProperties);
4594
- const outputIds = ids(outputProperties);
4595
- const backendIds = ids(backendProperties);
4596
- return {
4597
- input: inputLinks.filter((link) => !inputIds.has(String(link.route_input_properties_id)) || !backendIds.has(String(link.backend_properties_id))),
4598
- output: outputLinks.filter((link) => !outputIds.has(String(link.route_output_properties_id)) || !backendIds.has(String(link.backend_properties_id)))
4599
- };
4600
- }
4601
- async function deleteOrphanedPropertyLinks(client, orphanedLinks) {
4602
- const links = [...orphanedLinks.input.map((link) => ({
4603
- collection: "route_input_properties_backend_properties",
4604
- id: link.id
4605
- })), ...orphanedLinks.output.map((link) => ({
4606
- collection: "route_output_properties_backend_properties",
4607
- id: link.id
4608
- }))];
4609
- let nextIndex = 0;
4610
- await Promise.all(Array.from({ length: Math.min(DIRECTUS_DELETE_WORKERS, links.length) }, async () => {
4611
- while (nextIndex < links.length) {
4612
- const link = links[nextIndex++];
4613
- await client.request(directusDeleteItem(link.collection, link.id));
4614
- }
4615
- }));
4616
- return links.length;
4617
- }
4618
- async function cleanupOrphanedPropertyLinks(client) {
4619
- taskProgressService.report("Cleaning orphaned property links");
4620
- const removed = await deleteOrphanedPropertyLinks(client, await findOrphanedPropertyLinks(client));
4621
- taskProgressService.report(`Removed ${removed} orphaned property links`);
4622
- return removed;
4623
- }
4624
- async function cleanDirectusOrphanedPropertyLinks(options) {
4625
- taskProgressService.report("Connecting to Directus");
4626
- const client = await createClient(options.baseUrl, options.token);
4627
- taskProgressService.report("Finding orphaned property links");
4628
- const orphanedLinks = await findOrphanedPropertyLinks(client);
4629
- const orphanedCount = orphanedLinks.input.length + orphanedLinks.output.length;
4630
- if (options.dryRun) {
4631
- taskProgressService.report(`${orphanedCount} orphaned property links found (dry-run)`);
4632
- return {
4633
- orphanedLinks: orphanedCount,
4634
- removedOrphanedLinks: 0
4635
- };
4636
- }
4637
- taskProgressService.report("Removing orphaned property links");
4638
- const removedOrphanedLinks = await deleteOrphanedPropertyLinks(client, orphanedLinks);
4639
- taskProgressService.report(`Removed ${removedOrphanedLinks} orphaned property links`);
4640
- return {
4641
- orphanedLinks: orphanedCount,
4642
- removedOrphanedLinks
4643
- };
4644
- }
4645
- async function loadDirectusReferenceData(client) {
4646
- const values = await client.request(directusReadItems("predefined_values", {
4647
- filter: { type: { _in: [
4648
- "backend",
4649
- "in_type",
4650
- "out_type"
4651
- ] } },
4652
- fields: [
4653
- "id",
4654
- "label",
4655
- "type"
4656
- ],
4657
- limit: -1
4658
- }));
4659
- const backendIds = /* @__PURE__ */ new Map();
4660
- const inTypeIds = /* @__PURE__ */ new Map();
4661
- const outTypeIds = /* @__PURE__ */ new Map();
4662
- for (const value of values) {
4663
- const normalizedKeys = [normalizeLookupKey(value.id), normalizeLookupKey(value.label)];
4664
- const target = value.type === "backend" ? backendIds : value.type === "in_type" ? inTypeIds : outTypeIds;
4665
- for (const key of normalizedKeys) target.set(key, value.id);
4666
- }
4667
- return {
4668
- backendIds,
4669
- inTypeIds,
4670
- outTypeIds
4671
- };
4675
+ return value.toLowerCase().replace(/(^|[^a-z0-9]+)([a-z0-9])/g, (_match, _separator, character) => character.toUpperCase());
4672
4676
  }
4677
+
4678
+ //#endregion
4679
+ //#region src/services/directus/queries/resolveBackendReference.ts
4673
4680
  async function resolveBackendReference(client, references, backend) {
4674
4681
  const lookupKey = normalizeLookupKey(backend);
4675
4682
  const existingId = references.backendIds.get(lookupKey);
@@ -4678,7 +4685,7 @@ async function resolveBackendReference(client, references, backend) {
4678
4685
  created: false
4679
4686
  };
4680
4687
  if (!isKnownBackendType(backend)) return { created: false };
4681
- const created = await client.request(directusCreateItem("predefined_values", {
4688
+ const created = await client.request(n$2("predefined_values", {
4682
4689
  id: backend,
4683
4690
  label: toPascalCase(backend),
4684
4691
  type: "backend"
@@ -4690,42 +4697,72 @@ async function resolveBackendReference(client, references, backend) {
4690
4697
  created: true
4691
4698
  };
4692
4699
  }
4693
- async function upsertRoute(client, route) {
4694
- const routeIdentifier = toRouteIdentifier(route);
4695
- const [existing] = await client.request(directusReadItems("routes", {
4696
- filter: { route: { _eq: routeIdentifier } },
4697
- fields: ["id", "route"],
4700
+
4701
+ //#endregion
4702
+ //#region src/services/directus/utils/normalizeDirectusPath.ts
4703
+ function normalizeDirectusPath(value) {
4704
+ return value.replace(/\?\.\[/g, "[").replace(/\?\./g, ".");
4705
+ }
4706
+
4707
+ //#endregion
4708
+ //#region src/services/directus/queries/upsertBackendProperty.ts
4709
+ async function upsertBackendProperty(client, property, backendRouteId, typeId) {
4710
+ const path = normalizeDirectusPath(property.field);
4711
+ const [existing] = await client.request(n$1("backend_properties", {
4712
+ filter: {
4713
+ route: { _eq: backendRouteId },
4714
+ path: { _eq: path },
4715
+ type: { _eq: typeId }
4716
+ },
4717
+ fields: ["id"],
4698
4718
  limit: 1
4699
4719
  }));
4700
- const payload = {
4701
- route: routeIdentifier,
4702
- status: ROUTE_STATUSES.published,
4703
- deprecated: false,
4704
- first_published_version: route.version,
4705
- last_published_version: route.version,
4706
- translations: buildTranslationSummary(route)
4707
- };
4708
- if (!existing) {
4709
- const created = await client.request(directusCreateItem("routes", payload));
4710
- return typeof created === "string" ? created : created.id;
4711
- }
4712
- await client.request(directusUpdateItem("routes", existing.id, payload));
4713
- return existing.id;
4720
+ if (existing) return existing.id;
4721
+ const created = await client.request(n$2("backend_properties", {
4722
+ route: backendRouteId,
4723
+ path: path || null,
4724
+ type: typeId,
4725
+ source_file: property.source_file || null,
4726
+ comments: property.provenance === "ai" ? "Inferred with AI" : null,
4727
+ content_name: null
4728
+ }));
4729
+ return typeof created === "string" ? created : created.id;
4730
+ }
4731
+
4732
+ //#endregion
4733
+ //#region src/services/directus/utils/toBackendRouteIdentifier.ts
4734
+ function toBackendRouteIdentifier(route) {
4735
+ return `${route.method} ${route.path}`;
4736
+ }
4737
+
4738
+ //#endregion
4739
+ //#region src/services/directus/queries/upsertBackendRoute.ts
4740
+ async function upsertBackendRoute(client, apiRoute, backendValueId) {
4741
+ const route = toBackendRouteIdentifier(apiRoute);
4742
+ const [existing] = await client.request(n$1("backend_routes", {
4743
+ filter: { route: { _eq: route } },
4744
+ fields: ["id"],
4745
+ limit: 1
4746
+ }));
4747
+ if (existing) return existing.id;
4748
+ const created = await client.request(n$2("backend_routes", {
4749
+ route,
4750
+ backend: backendValueId
4751
+ }));
4752
+ return typeof created === "string" ? created : created.id;
4714
4753
  }
4754
+
4755
+ //#endregion
4756
+ //#region src/services/directus/queries/upsertInputProperty.ts
4715
4757
  async function upsertInputProperty(client, property, routeId, inType) {
4716
4758
  const field = normalizeDirectusPath(property.field);
4717
- const [existing] = await client.request(directusReadItems("route_input_properties", {
4759
+ const [existing] = await client.request(n$1("route_input_properties", {
4718
4760
  filter: {
4719
4761
  route_id: { _eq: routeId },
4720
4762
  path: { _eq: field },
4721
4763
  in_type: { _eq: inType }
4722
4764
  },
4723
- fields: [
4724
- "id",
4725
- "route_id",
4726
- "path",
4727
- "in_type"
4728
- ],
4765
+ fields: ["id"],
4729
4766
  limit: 1
4730
4767
  }));
4731
4768
  const payload = {
@@ -4733,30 +4770,28 @@ async function upsertInputProperty(client, property, routeId, inType) {
4733
4770
  path: field,
4734
4771
  in_type: inType,
4735
4772
  description: property.description || null,
4736
- source_file: property.source_file || LOCAL_SOURCE_FILE,
4773
+ source_file: property.source_file || null,
4737
4774
  deprecated: false
4738
4775
  };
4739
4776
  if (!existing) {
4740
- const created = await client.request(directusCreateItem("route_input_properties", payload));
4777
+ const created = await client.request(n$2("route_input_properties", payload));
4741
4778
  return typeof created === "string" ? created : created.id;
4742
4779
  }
4743
- await client.request(directusUpdateItem("route_input_properties", existing.id, payload));
4780
+ await client.request(i("route_input_properties", existing.id, payload));
4744
4781
  return existing.id;
4745
4782
  }
4783
+
4784
+ //#endregion
4785
+ //#region src/services/directus/queries/upsertOutputProperty.ts
4746
4786
  async function upsertOutputProperty(client, property, routeId, outType) {
4747
4787
  const field = normalizeDirectusPath(property.field);
4748
- const [existing] = await client.request(directusReadItems("route_output_properties", {
4788
+ const [existing] = await client.request(n$1("route_output_properties", {
4749
4789
  filter: {
4750
4790
  route_id: { _eq: routeId },
4751
4791
  path: { _eq: field },
4752
4792
  out_type: { _eq: outType }
4753
4793
  },
4754
- fields: [
4755
- "id",
4756
- "route_id",
4757
- "path",
4758
- "out_type"
4759
- ],
4794
+ fields: ["id"],
4760
4795
  limit: 1
4761
4796
  }));
4762
4797
  const payload = {
@@ -4764,101 +4799,110 @@ async function upsertOutputProperty(client, property, routeId, outType) {
4764
4799
  path: field,
4765
4800
  out_type: outType,
4766
4801
  description: property.description || null,
4767
- source_file: property.source_file || LOCAL_SOURCE_FILE,
4802
+ source_file: property.source_file || null,
4768
4803
  deprecated: false,
4769
4804
  is_dynamic: field.includes("[]") || field.includes("{")
4770
4805
  };
4771
4806
  if (!existing) {
4772
- const created = await client.request(directusCreateItem("route_output_properties", payload));
4807
+ const created = await client.request(n$2("route_output_properties", payload));
4773
4808
  return typeof created === "string" ? created : created.id;
4774
4809
  }
4775
- await client.request(directusUpdateItem("route_output_properties", existing.id, payload));
4810
+ await client.request(i("route_output_properties", existing.id, payload));
4776
4811
  return existing.id;
4777
4812
  }
4778
- async function upsertBackendRoute(client, apiRoute, backendRoute, backendValueId) {
4779
- const routeValue = toBackendRouteIdentifier(apiRoute);
4780
- const [existing] = await client.request(directusReadItems("backend_routes", {
4781
- filter: { route: { _eq: routeValue } },
4782
- fields: [
4783
- "id",
4784
- "route",
4785
- "backend"
4786
- ],
4813
+
4814
+ //#endregion
4815
+ //#region src/services/directus/utils/buildTranslationSummary.ts
4816
+ function buildTranslationSummary(route) {
4817
+ const routeIdentifier = toBackendRouteIdentifier(route);
4818
+ const primarySource = route.analysis_files[0] || route.source_file;
4819
+ return [{
4820
+ languages_code: "en-US",
4821
+ summary: routeIdentifier,
4822
+ description: primarySource ? `Generated from ${path.basename(primarySource)}` : "Generated by Atlas"
4823
+ }];
4824
+ }
4825
+
4826
+ //#endregion
4827
+ //#region src/services/directus/queries/upsertRoute.ts
4828
+ async function upsertRoute(client, route) {
4829
+ const routeIdentifier = toBackendRouteIdentifier(route);
4830
+ const [existing] = await client.request(n$1("routes", {
4831
+ filter: { route: { _eq: routeIdentifier } },
4832
+ fields: ["id"],
4787
4833
  limit: 1
4788
4834
  }));
4789
4835
  const payload = {
4790
- route: routeValue,
4791
- backend: backendValueId
4836
+ route: routeIdentifier,
4837
+ status: "published",
4838
+ deprecated: false,
4839
+ first_published_version: route.version,
4840
+ last_published_version: route.version,
4841
+ translations: buildTranslationSummary(route)
4792
4842
  };
4793
4843
  if (!existing) {
4794
- const created = await client.request(directusCreateItem("backend_routes", payload));
4844
+ const created = await client.request(n$2("routes", payload));
4795
4845
  return typeof created === "string" ? created : created.id;
4796
4846
  }
4847
+ await client.request(i("routes", existing.id, payload));
4797
4848
  return existing.id;
4798
4849
  }
4799
- async function upsertBackendProperty(client, property, backendRouteId, typeId) {
4800
- const field = normalizeDirectusPath(property.field);
4801
- const [existing] = await client.request(directusReadItems("backend_properties", {
4802
- filter: {
4803
- route: { _eq: backendRouteId },
4804
- path: { _eq: field },
4805
- type: { _eq: typeId }
4806
- },
4807
- fields: [
4808
- "id",
4809
- "route",
4810
- "path",
4811
- "type",
4812
- "source_file"
4813
- ],
4814
- limit: 1
4815
- }));
4816
- const payload = {
4817
- route: backendRouteId,
4818
- path: field || null,
4819
- type: typeId,
4820
- source_file: property.source_file || LOCAL_SOURCE_FILE,
4821
- comments: property.provenance === "ai" ? "Inferred with AI" : null,
4822
- content_name: null
4823
- };
4824
- if (!existing) {
4825
- const created = await client.request(directusCreateItem("backend_properties", payload));
4826
- return typeof created === "string" ? created : created.id;
4850
+
4851
+ //#endregion
4852
+ //#region src/services/directus/utils/groupBackendRoutesForDirectusSync.ts
4853
+ function groupBackendRoutesForDirectusSync(backendRoutes, apiRoutesByKey) {
4854
+ const groups = /* @__PURE__ */ new Map();
4855
+ for (const backendRoute of backendRoutes) {
4856
+ const apiRoute = apiRoutesByKey.get(backendRoute.route_key);
4857
+ const key = apiRoute ? `${backendRoute.backend}\0${apiRoute.method}\0${apiRoute.path}` : `${backendRoute.backend}\0missing-api-route\0${backendRoute.key}`;
4858
+ const group = groups.get(key);
4859
+ if (group) group.keys.push(backendRoute.key);
4860
+ else groups.set(key, {
4861
+ route: backendRoute,
4862
+ keys: [backendRoute.key]
4863
+ });
4827
4864
  }
4828
- return existing.id;
4865
+ return [...groups.values()];
4829
4866
  }
4830
- async function ensureInputLink(client, inputPropertyId, backendPropertyId) {
4831
- const [existing] = await client.request(directusReadItems("route_input_properties_backend_properties", {
4832
- filter: {
4833
- route_input_properties_id: { _eq: inputPropertyId },
4834
- backend_properties_id: { _eq: backendPropertyId }
4835
- },
4836
- fields: ["id"],
4837
- limit: 1
4838
- }));
4839
- if (existing) return false;
4840
- await client.request(directusCreateItem("route_input_properties_backend_properties", {
4841
- route_input_properties_id: inputPropertyId,
4842
- backend_properties_id: backendPropertyId
4843
- }));
4844
- return true;
4867
+
4868
+ //#endregion
4869
+ //#region src/services/directus/utils/inferInputType.ts
4870
+ function inferInputType(field) {
4871
+ if (field.startsWith("params.")) return "PATH";
4872
+ if (field.startsWith("query.")) return "QUERY";
4873
+ if (field.startsWith("headers.")) return "HEADER";
4874
+ return "BODY";
4845
4875
  }
4846
- async function ensureOutputLink(client, outputPropertyId, backendPropertyId) {
4847
- const [existing] = await client.request(directusReadItems("route_output_properties_backend_properties", {
4848
- filter: {
4849
- route_output_properties_id: { _eq: outputPropertyId },
4850
- backend_properties_id: { _eq: backendPropertyId }
4851
- },
4852
- fields: ["id"],
4853
- limit: 1
4854
- }));
4855
- if (existing) return false;
4856
- await client.request(directusCreateItem("route_output_properties_backend_properties", {
4857
- route_output_properties_id: outputPropertyId,
4858
- backend_properties_id: backendPropertyId
4859
- }));
4860
- return true;
4876
+
4877
+ //#endregion
4878
+ //#region src/services/directus/utils/selectCatalogueForDirectusPush.ts
4879
+ function selectCatalogueForDirectusPush(catalogue, routeSelector) {
4880
+ if (!routeSelector) return catalogue;
4881
+ const routes = catalogue.routes.filter((route) => route.method === routeSelector.method && route.path === routeSelector.path);
4882
+ if (!routes.length) throw new Error(`Route not found in catalogue: ${routeSelector.method} ${routeSelector.path}`);
4883
+ const routeKeys = new Set(routes.map((route) => route.key));
4884
+ const routeInputProperties = catalogue.route_input_properties.filter((property) => routeKeys.has(property.route_key));
4885
+ const routeOutputProperties = catalogue.route_output_properties.filter((property) => routeKeys.has(property.route_key));
4886
+ const backendRoutes = catalogue.backend_routes.filter((route) => routeKeys.has(route.route_key));
4887
+ const backendRouteKeys = new Set(backendRoutes.map((route) => route.key));
4888
+ const backendProperties = catalogue.backend_properties.filter((property) => backendRouteKeys.has(property.backend_route_key));
4889
+ const apiPropertyKeys = new Set([...routeInputProperties, ...routeOutputProperties].map((property) => property.key));
4890
+ const backendPropertyKeys = new Set(backendProperties.map((property) => property.key));
4891
+ return {
4892
+ ...catalogue,
4893
+ routes,
4894
+ route_input_properties: routeInputProperties,
4895
+ route_output_properties: routeOutputProperties,
4896
+ backend_routes: backendRoutes,
4897
+ backend_properties: backendProperties,
4898
+ mapping_evidence: catalogue.mapping_evidence.filter((evidence) => backendPropertyKeys.has(evidence.backend_property_key) && (apiPropertyKeys.has(evidence.input_property_key || "") || apiPropertyKeys.has(evidence.output_property_key || ""))),
4899
+ rules: catalogue.rules.filter((rule) => routeKeys.has(rule.route_key)),
4900
+ aggregations: catalogue.aggregations.filter((aggregation) => routeKeys.has(aggregation.route_key))
4901
+ };
4861
4902
  }
4903
+
4904
+ //#endregion
4905
+ //#region src/services/directus/pushCatalogueToDirectus.ts
4862
4906
  async function pushCatalogueToDirectus(catalogue, options) {
4863
4907
  const pushedCollections = [
4864
4908
  "predefined_values",
@@ -4883,7 +4927,7 @@ async function pushCatalogueToDirectus(catalogue, options) {
4883
4927
  };
4884
4928
  }
4885
4929
  taskProgressService.report("Connecting to Directus");
4886
- const client = await createClient(options.baseUrl, options.token);
4930
+ const client = await createClient();
4887
4931
  taskProgressService.report("Loading predefined values from Directus");
4888
4932
  const refs = await loadDirectusReferenceData(client);
4889
4933
  const warnings = /* @__PURE__ */ new Set();
@@ -4917,7 +4961,7 @@ async function pushCatalogueToDirectus(catalogue, options) {
4917
4961
  continue;
4918
4962
  }
4919
4963
  if (backendReference.created) pushedItems += 1;
4920
- const backendRouteId = await upsertBackendRoute(client, route, backendRoute, backendReference.id);
4964
+ const backendRouteId = await upsertBackendRoute(client, route, backendReference.id);
4921
4965
  for (const key of keys) backendRouteIds.set(key, backendRouteId);
4922
4966
  pushedItems += 1;
4923
4967
  }
@@ -11724,7 +11768,7 @@ var require_dist$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
11724
11768
  }));
11725
11769
 
11726
11770
  //#endregion
11727
- //#region src/utils/routeReview.ts
11771
+ //#region src/utils/route/routeReview.ts
11728
11772
  var import_dist$1 = require_dist$1();
11729
11773
  function sanitizePathSegment(value) {
11730
11774
  return value.replace(/[{}]/g, "").replace(/[^a-z0-9._-]+/gi, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "") || "root";
@@ -11898,9 +11942,6 @@ function relativizeRouteReviewDocument(document, repoRoot) {
11898
11942
 
11899
11943
  //#endregion
11900
11944
  //#region src/services/catalogueArtifactService.ts
11901
- function stableKey$1(...parts) {
11902
- return createHash("sha1").update(parts.join("::")).digest("hex");
11903
- }
11904
11945
  async function writeRouteReviewDocument(catalogue, outputDir, repoRoot, routeKey) {
11905
11946
  const route = catalogue.routes.find((item) => item.key === routeKey);
11906
11947
  if (!route) throw new Error(`Unable to write review document for unknown route key ${routeKey}`);
@@ -11932,7 +11973,7 @@ async function writeCatalogueArtifacts(catalogue, outputDir, repoRoot = process.
11932
11973
  }
11933
11974
  function addBackendMapping(routeKey, direction, apiPropertyKey, mapping, backendRoutesByKey, backendPropertiesByKey, mappingEvidence) {
11934
11975
  const backendRoute = mapping.route || (mapping.document ? `DOCUMENT ${mapping.document}` : "unknown");
11935
- const backendRouteKey = stableKey$1(routeKey, mapping.backend, backendRoute);
11976
+ const backendRouteKey = stableKey(routeKey, mapping.backend, backendRoute);
11936
11977
  const backendRouteRecord = backendRoutesByKey.get(backendRouteKey) || {
11937
11978
  key: backendRouteKey,
11938
11979
  backend: mapping.backend,
@@ -11942,7 +11983,7 @@ function addBackendMapping(routeKey, direction, apiPropertyKey, mapping, backend
11942
11983
  provenance: "code_analysis"
11943
11984
  };
11944
11985
  backendRoutesByKey.set(backendRouteKey, backendRouteRecord);
11945
- const backendPropertyKey = stableKey$1(backendRouteKey, direction, mapping.field);
11986
+ const backendPropertyKey = stableKey(backendRouteKey, direction, mapping.field);
11946
11987
  backendPropertiesByKey.set(backendPropertyKey, {
11947
11988
  key: backendPropertyKey,
11948
11989
  backend_route_key: backendRouteKey,
@@ -11953,7 +11994,7 @@ function addBackendMapping(routeKey, direction, apiPropertyKey, mapping, backend
11953
11994
  provenance: mapping.provenance || "code_analysis"
11954
11995
  });
11955
11996
  mappingEvidence.push({
11956
- key: stableKey$1(apiPropertyKey, backendPropertyKey),
11997
+ key: stableKey(apiPropertyKey, backendPropertyKey),
11957
11998
  evidence_type: mapping.provenance === "ai" ? "inference" : "static_analysis",
11958
11999
  status: mapping.confidence >= 90 ? "confirmed" : "inferred",
11959
12000
  confidence_score: mapping.confidence,
@@ -11980,7 +12021,7 @@ function toCatalogueBackendMapping(mapping) {
11980
12021
  }
11981
12022
  function addDatasourceRoute(routeKey, datasource, backendRoutesByKey) {
11982
12023
  const backendRoute = datasource.operation?.route || "unknown";
11983
- const backendRouteKey = stableKey$1(routeKey, datasource.type, backendRoute);
12024
+ const backendRouteKey = stableKey(routeKey, datasource.type, backendRoute);
11984
12025
  backendRoutesByKey.set(backendRouteKey, {
11985
12026
  key: backendRouteKey,
11986
12027
  backend: datasource.type,
@@ -12000,7 +12041,7 @@ function mergeRouteReviewDocuments(documents) {
12000
12041
  const rules = [];
12001
12042
  const aggregations = [];
12002
12043
  for (const document of documents) {
12003
- const routeKey = stableKey$1(document.route.method, document.route.path);
12044
+ const routeKey = stableKey(document.route.method, document.route.path);
12004
12045
  const mappedBackendNames = [...document.inputs, ...document.outputs].flatMap((property) => property.mapping?.backends?.map((mapping) => mapping.type) || []);
12005
12046
  const backendNames = [.../* @__PURE__ */ new Set([...document.datasources.map((datasource) => datasource.type), ...mappedBackendNames])];
12006
12047
  routes.push({
@@ -12015,7 +12056,7 @@ function mergeRouteReviewDocuments(documents) {
12015
12056
  for (const datasource of document.datasources) addDatasourceRoute(routeKey, datasource, backendRoutesByKey);
12016
12057
  const appendProperties = (direction, properties) => {
12017
12058
  for (const property of properties) {
12018
- const apiPropertyKey = stableKey$1(routeKey, direction, property.field);
12059
+ const apiPropertyKey = stableKey(routeKey, direction, property.field);
12019
12060
  const backendMappings = property.mapping?.backends?.map(toCatalogueBackendMapping) || [];
12020
12061
  const catalogueProperty = {
12021
12062
  key: apiPropertyKey,
@@ -12110,21 +12151,16 @@ async function readCatalogueFromDirectory(inputDir) {
12110
12151
  }
12111
12152
 
12112
12153
  //#endregion
12113
- //#region src/services/directus/directusPushService.ts
12154
+ //#region src/services/directus/pushCatalogueDirectoryToDirectus.ts
12114
12155
  async function pushCatalogueDirectoryToDirectus(params) {
12115
12156
  const inputPath = path.resolve(params.cwd, params.catalogueDirectory);
12116
12157
  taskProgressService.report("Loading and reconstructing route YAML documents");
12117
12158
  const { catalogue, files } = await readCatalogueFromDirectory(inputPath);
12118
12159
  taskProgressService.report(`${files.length} route documents loaded, ${catalogue.backend_routes.length} backend routes reconstructed`);
12119
- const directusUrl = params.directusUrl || process.env.DIRECTUS_URL || process.env.CMS_API_URL || getUserConfig().directusUrl;
12120
- const directusToken = params.directusToken || process.env.DIRECTUS_TOKEN || process.env.CMS_DIRECTUS_TOKEN || process.env.CMS_API_TOKEN;
12121
- if (!directusToken) throw new Error("Directus push requested but DIRECTUS_TOKEN/directus-token is missing");
12122
12160
  return {
12123
12161
  inputPath,
12124
12162
  files,
12125
12163
  result: await pushCatalogueToDirectus(catalogue, {
12126
- baseUrl: directusUrl,
12127
- token: directusToken,
12128
12164
  dryRun: !params.write,
12129
12165
  routeSelector: params.routeSelector
12130
12166
  })
@@ -12132,7 +12168,13 @@ async function pushCatalogueDirectoryToDirectus(params) {
12132
12168
  }
12133
12169
 
12134
12170
  //#endregion
12135
- //#region src/utils/routeSelector.ts
12171
+ //#region src/utils/route/isRouteSelector.ts
12172
+ function isRouteSelector(value) {
12173
+ return /^[A-Z]+\s+\//i.test(value.trim());
12174
+ }
12175
+
12176
+ //#endregion
12177
+ //#region src/utils/route/routeSelector.ts
12136
12178
  function parseRouteSelector(value) {
12137
12179
  if (!value) return;
12138
12180
  const match = value.trim().match(/^([A-Z]+)\s+(\S+)$/i);
@@ -12146,18 +12188,12 @@ function parseRouteSelector(value) {
12146
12188
  //#endregion
12147
12189
  //#region src/commands/push.ts
12148
12190
  const DEFAULT_CATALOGUE_DIRECTORY = ".tmp/datasource-catalogue";
12149
- function isRouteSelectorArgument(value) {
12150
- return /^[A-Z]+\s+\//i.test(value.trim());
12151
- }
12152
- function isRecord$2(value) {
12153
- return typeof value === "object" && value !== null && !Array.isArray(value);
12154
- }
12155
12191
  function formatPushError(error) {
12156
12192
  if (error instanceof Error) return error.message;
12157
- if (!isRecord$2(error)) return String(error);
12193
+ if (!isRecord$1(error)) return String(error);
12158
12194
  const details = (Array.isArray(error.errors) ? error.errors : []).flatMap((item) => {
12159
- if (!isRecord$2(item) || typeof item.message !== "string") return [];
12160
- return [`${isRecord$2(item.extensions) && typeof item.extensions.code === "string" ? `${item.extensions.code}: ` : ""}${item.message}`];
12195
+ if (!isRecord$1(item) || typeof item.message !== "string") return [];
12196
+ return [`${isRecord$1(item.extensions) && typeof item.extensions.code === "string" ? `${item.extensions.code}: ` : ""}${item.message}`];
12161
12197
  });
12162
12198
  if (details.length) return `Directus API error: ${details.join("; ")}`;
12163
12199
  if (typeof error.message === "string") return error.message;
@@ -12219,20 +12255,18 @@ function createPushStepClassifier() {
12219
12255
  };
12220
12256
  };
12221
12257
  }
12222
- var push_default = (program) => void program.command("push").argument("[catalogue-directory-or-route]", "Directory or route such as \"GET /v1/offers\"", DEFAULT_CATALOGUE_DIRECTORY).option("--route <route>", "Push one route only, for example \"POST /v0/accommodations_arrangement/check\"").option("--directus-url <url>", "Directus base URL").option("--directus-token <token>", "Directus bearer token").option("--write", "Persist changes to Directus", false).description("Push generated per-route YAML documents to Directus").action(async (catalogueDirectoryOrRoute, options) => {
12258
+ var push_default = (program) => void program.command("push").argument("[catalogue-directory-or-route]", "Directory or route such as \"GET /v1/offers\"", DEFAULT_CATALOGUE_DIRECTORY).option("--route <route>", "Push one route only, for example \"POST /v0/accommodations_arrangement/check\"").option("--write", "Persist changes to Directus", false).description("Push generated per-route YAML documents to Directus").action(async (catalogueDirectoryOrRoute, options) => {
12223
12259
  intro("Datasource catalogue push");
12224
12260
  const { cwd } = getUserConfig();
12225
12261
  const progress = taskProgressService.createStepProgress(createPushStepClassifier());
12226
12262
  try {
12227
- const positionalRouteSelector = isRouteSelectorArgument(catalogueDirectoryOrRoute) ? parseRouteSelector(catalogueDirectoryOrRoute) : void 0;
12263
+ const positionalRouteSelector = isRouteSelector(catalogueDirectoryOrRoute) ? parseRouteSelector(catalogueDirectoryOrRoute) : void 0;
12228
12264
  if (options.route && positionalRouteSelector) throw new Error("Specify the route either as the positional argument or with --route, not both");
12229
12265
  const routeSelector = options.route ? parseRouteSelector(options.route) : positionalRouteSelector;
12230
12266
  const catalogueDirectory = positionalRouteSelector ? DEFAULT_CATALOGUE_DIRECTORY : catalogueDirectoryOrRoute;
12231
12267
  const pushResult = await progress.execute(() => pushCatalogueDirectoryToDirectus({
12232
12268
  cwd,
12233
12269
  catalogueDirectory,
12234
- directusUrl: options.directusUrl,
12235
- directusToken: options.directusToken,
12236
12270
  routeSelector,
12237
12271
  write: options.write
12238
12272
  }));
@@ -12344,9 +12378,6 @@ async function writeBackendTopologyArtifact(artifact, outputDir) {
12344
12378
  await fsPromises.writeFile(filePath, `${(0, import_dist$1.stringify)(artifact, { aliasDuplicateObjects: false })}\n`, "utf8");
12345
12379
  return filePath;
12346
12380
  }
12347
- function isRecord$1(value) {
12348
- return typeof value === "object" && value !== null && !Array.isArray(value);
12349
- }
12350
12381
  function parseBackendTopologyArtifact(value, expectedMethod, expectedPath, filePath) {
12351
12382
  if (!isRecord$1(value) || value.schema_version !== 3) throw new Error(`Invalid backend graph artifact schema: ${filePath}. Run generate:graph again.`);
12352
12383
  const route = value.route;
@@ -12426,7 +12457,7 @@ async function generateHandler(cwd, routeArgument, options) {
12426
12457
  completedTitle: "OpenAPI contract loaded"
12427
12458
  }, () => loadOpenApiDocument(options.openapiUrl));
12428
12459
  profile.add("OpenAPI", performance.now() - openApiStartedAt);
12429
- const selectedRouteKeys = routeSelector ? /* @__PURE__ */ new Set([buildRouteKey(routeSelector.method, routeSelector.path)]) : void 0;
12460
+ const selectedRouteKeys = routeSelector ? /* @__PURE__ */ new Set([stableKey(routeSelector.method, routeSelector.path)]) : void 0;
12430
12461
  const totalRoutes = routeSelector ? 1 : Object.values(openApiDocument.paths || {}).reduce((total, pathItem) => total + Object.keys(pathItem || {}).filter((method) => /^(get|post|put|patch|delete|head|options)$/i.test(method)).length, 0);
12431
12462
  const analysisStartedAt = performance.now();
12432
12463
  progress$3.finish("OpenAPI contract loaded");
@@ -12492,12 +12523,16 @@ var generate_default = (program) => void program.command("generate:catalog").arg
12492
12523
  });
12493
12524
 
12494
12525
  //#endregion
12495
- //#region src/commands/generateGraph.ts
12496
- function parseWorkerCount$1(value) {
12497
- const workers = Number(value);
12498
- if (!Number.isInteger(workers) || workers < 1) throw new Error("Worker count must be a positive integer");
12499
- return workers;
12526
+ //#region src/utils/number/parsePositiveInteger.ts
12527
+ function parsePositiveInteger(value, message = "Expected a positive integer") {
12528
+ const parsed = Number(value);
12529
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(message);
12530
+ return parsed;
12500
12531
  }
12532
+
12533
+ //#endregion
12534
+ //#region src/commands/generateGraph.ts
12535
+ const parseWorkerCount$1 = (value) => parsePositiveInteger(value, "Worker count must be a positive integer");
12501
12536
  async function generateGraphHandler(cwd, routeArgument, options) {
12502
12537
  intro("Code source Graph generation");
12503
12538
  const progress$2 = taskProgressService.createStepProgress((message) => {
@@ -12573,6 +12608,12 @@ var generateGraph_default = (program) => void program.command("generate:graph").
12573
12608
  return generateGraphHandler(cwd, routeArgument, options);
12574
12609
  });
12575
12610
 
12611
+ //#endregion
12612
+ //#region src/utils/number/formatPercentage.ts
12613
+ function formatPercentage(value, fractionDigits = 2) {
12614
+ return `${value.toFixed(fractionDigits)}%`;
12615
+ }
12616
+
12576
12617
  //#endregion
12577
12618
  //#region src/services/catalogueCoverageService.ts
12578
12619
  /**
@@ -12595,7 +12636,7 @@ function validateCoverage(coverage, catalogues) {
12595
12636
  });
12596
12637
  }
12597
12638
  function formatCoverageFailure(results) {
12598
- return ["Coverage regression detected:", ...results.filter((result) => !result.passed).map((result) => `- ${result.route}: ${result.actual.toFixed(2)}% is below ${result.expected.toFixed(2)}%`)].join("\n");
12639
+ return ["Coverage regression detected:", ...results.filter((result) => !result.passed).map((result) => `- ${result.route}: ${formatPercentage(result.actual)} is below ${formatPercentage(result.expected)}`)].join("\n");
12599
12640
  }
12600
12641
 
12601
12642
  //#endregion
@@ -12620,13 +12661,13 @@ async function generateTestHandler(cwd) {
12620
12661
  }, () => analyzeCodebaseRouteContracts({
12621
12662
  cwd,
12622
12663
  openApiDocument,
12623
- selectedRouteKeys: /* @__PURE__ */ new Set([buildRouteKey(routeSelector.method, routeSelector.path)])
12664
+ selectedRouteKeys: /* @__PURE__ */ new Set([stableKey(routeSelector.method, routeSelector.path)])
12624
12665
  }));
12625
12666
  if (documents.length !== 1) throw new Error(`Configured route was not found: ${expectation.route}`);
12626
12667
  catalogues.set(expectation.route, await buildCatalogue(documents));
12627
12668
  }
12628
12669
  const results = validateCoverage(coverage, catalogues);
12629
- log.message(results.map((result) => `${result.passed ? "✓" : "✗"} ${result.route}: ${result.actual.toFixed(2)}% (minimum ${result.expected.toFixed(2)}%)`).join("\n"));
12670
+ log.message(results.map((result) => `${result.passed ? "✓" : "✗"} ${result.route}: ${formatPercentage(result.actual)} (minimum ${formatPercentage(result.expected)})`).join("\n"));
12630
12671
  if (results.some((result) => !result.passed)) throw new Error(formatCoverageFailure(results));
12631
12672
  }
12632
12673
  var generateTest_default = (program) => void program.command("generate:test").description("Generate configured routes and verify their resolved-field coverage does not regress").action(async () => {
@@ -12642,6 +12683,25 @@ var generateTest_default = (program) => void program.command("generate:test").de
12642
12683
  }
12643
12684
  });
12644
12685
 
12686
+ //#endregion
12687
+ //#region src/utils/collection/forEachWithConcurrency.ts
12688
+ async function forEachWithConcurrency(items, workers, task) {
12689
+ let nextIndex = 0;
12690
+ const workerCount = Math.min(Math.max(1, workers), items.length);
12691
+ await Promise.all(Array.from({ length: workerCount }, async (_, workerId) => {
12692
+ while (nextIndex < items.length) {
12693
+ const item = items[nextIndex++];
12694
+ await task(item, workerId);
12695
+ }
12696
+ }));
12697
+ }
12698
+
12699
+ //#endregion
12700
+ //#region src/utils/source/formatSourceLocation.ts
12701
+ function formatSourceLocation(sourceFile, sourceLine) {
12702
+ return sourceLine ? `${sourceFile}:${sourceLine}` : sourceFile;
12703
+ }
12704
+
12645
12705
  //#endregion
12646
12706
  //#region ../../node_modules/@ai-sdk/provider/dist/index.js
12647
12707
  var marker$2 = "vercel.ai.error";
@@ -27940,6 +28000,25 @@ var originalGenerateCallId6 = createIdGenerator({
27940
28000
  });
27941
28001
  var defaultDownload2 = createDownload();
27942
28002
 
28003
+ //#endregion
28004
+ //#region src/utils/collection/chunk.ts
28005
+ function chunk(items, size) {
28006
+ if (!Number.isInteger(size) || size <= 0) throw new Error("Chunk size must be a positive integer");
28007
+ return Array.from({ length: Math.ceil(items.length / size) }, (_, index) => items.slice(index * size, (index + 1) * size));
28008
+ }
28009
+
28010
+ //#endregion
28011
+ //#region src/utils/path/normalizeFieldPath.ts
28012
+ function normalizeFieldPath(value) {
28013
+ return (value || "").replace(/\[\]/g, "").replace(/[^a-zA-Z0-9.]/g, "").toLowerCase();
28014
+ }
28015
+
28016
+ //#endregion
28017
+ //#region src/utils/path/getTerminalField.ts
28018
+ function getTerminalField(value) {
28019
+ return normalizeFieldPath(value).split(".").filter(Boolean).at(-1) || "";
28020
+ }
28021
+
27943
28022
  //#endregion
27944
28023
  //#region ../../node_modules/@ai-sdk/openai-compatible/dist/index.js
27945
28024
  function toCamelCase(str) {
@@ -29359,38 +29438,32 @@ const MAX_EXCERPT_CHARACTERS = 16e3;
29359
29438
  const EXCERPT_RADIUS = 7;
29360
29439
  const PROPERTIES_PER_BATCH = 8;
29361
29440
  const MAX_CANDIDATES_PER_BATCH = 48;
29362
- function normalizeFieldForRanking(field) {
29363
- return (field || "").replace(/\[\]/g, "").replace(/[^a-zA-Z0-9.]/g, "").toLowerCase();
29364
- }
29365
- function terminalFieldForRanking(field) {
29366
- return normalizeFieldForRanking(field).split(".").filter(Boolean).at(-1) || "";
29367
- }
29368
29441
  /**
29369
29442
  * Guards against a model picking the right reasoning but the wrong array index: the chosen
29370
29443
  * backend field must share a term with the API property, either as an exact terminal match
29371
29444
  * or as the container of a nested object property (e.g. "address.number" <-> "address").
29372
29445
  */
29373
29446
  function candidateSharesTermWithProperty(candidate, property) {
29374
- const propertySegments = normalizeFieldForRanking(property.field).split(".").filter(Boolean);
29375
- const domainSegments = normalizeFieldForRanking(property.domainField).split(".").filter(Boolean);
29447
+ const propertySegments = normalizeFieldPath(property.field).split(".").filter(Boolean);
29448
+ const domainSegments = normalizeFieldPath(property.domainField).split(".").filter(Boolean);
29376
29449
  const propertyTerminal = propertySegments.at(-1) || "";
29377
29450
  const propertyRoot = propertySegments[0] || "";
29378
- const backendTerminal = terminalFieldForRanking(candidate.backendField);
29379
- const apiPathTerminal = terminalFieldForRanking(candidate.apiPathCandidate);
29451
+ const backendTerminal = getTerminalField(candidate.backendField);
29452
+ const apiPathTerminal = getTerminalField(candidate.apiPathCandidate);
29380
29453
  if (backendTerminal && (backendTerminal === propertyTerminal || domainSegments.includes(backendTerminal))) return true;
29381
29454
  if (apiPathTerminal && apiPathTerminal === propertyTerminal) return true;
29382
29455
  return Boolean(propertySegments.length > 1 && backendTerminal && backendTerminal === propertyRoot);
29383
29456
  }
29384
29457
  function scopeCandidatesToProperties(document, properties) {
29385
29458
  const candidates = document.backendFieldCandidates.filter((candidate) => !candidate.direction || properties.some((property) => property.direction === candidate.direction)).map((candidate, index) => {
29386
- const apiPath = normalizeFieldForRanking(candidate.apiPathCandidate);
29387
- const backendTerminal = terminalFieldForRanking(candidate.backendField);
29459
+ const apiPath = normalizeFieldPath(candidate.apiPathCandidate);
29460
+ const backendTerminal = getTerminalField(candidate.backendField);
29388
29461
  return {
29389
29462
  candidate,
29390
29463
  index,
29391
29464
  relevance: Math.max(...properties.map((property) => {
29392
- const field = normalizeFieldForRanking(property.field);
29393
- const terminal = terminalFieldForRanking(property.field);
29465
+ const field = normalizeFieldPath(property.field);
29466
+ const terminal = getTerminalField(property.field);
29394
29467
  if (apiPath && (apiPath.endsWith(field) || field.endsWith(apiPath))) return 1e3;
29395
29468
  if (terminal && backendTerminal === terminal) return 100;
29396
29469
  return 0;
@@ -29446,7 +29519,7 @@ async function buildPrompt(document, properties) {
29446
29519
  direction: candidate.direction || null,
29447
29520
  mapper_type: candidate.mapperType || null,
29448
29521
  deterministic_review_reason: candidate.reviewReason || null,
29449
- source_file: candidate.sourceLine ? `${candidate.sourceFile}:${candidate.sourceLine}` : candidate.sourceFile,
29522
+ source_file: formatSourceLocation(candidate.sourceFile, candidate.sourceLine),
29450
29523
  static_confidence: candidate.confidence
29451
29524
  }));
29452
29525
  const sourceExcerpts = await readSourceExcerpts(document);
@@ -29585,9 +29658,6 @@ function parseSuggestions(content, document, properties, minimumConfidence) {
29585
29658
  }
29586
29659
  };
29587
29660
  }
29588
- function chunkProperties(properties) {
29589
- return Array.from({ length: Math.ceil(properties.length / PROPERTIES_PER_BATCH) }, (_, index) => properties.slice(index * PROPERTIES_PER_BATCH, (index + 1) * PROPERTIES_PER_BATCH));
29590
- }
29591
29661
  async function inferMissingMappings(document, properties, config) {
29592
29662
  if (!config.enabled || !properties.length || !document.backendFieldCandidates.length) return {
29593
29663
  suggestions: [],
@@ -29613,7 +29683,7 @@ async function inferMissingMappings(document, properties, config) {
29613
29683
  apiKey: process.env.LITELLM_API_KEY
29614
29684
  });
29615
29685
  const results = [];
29616
- for (const batch of chunkProperties(properties)) {
29686
+ for (const batch of chunk(properties, PROPERTIES_PER_BATCH)) {
29617
29687
  const scopedDocument = scopeCandidatesToProperties(document, batch);
29618
29688
  let content;
29619
29689
  try {
@@ -29663,11 +29733,8 @@ async function inferMissingMappings(document, properties, config) {
29663
29733
 
29664
29734
  //#endregion
29665
29735
  //#region src/services/catalogueInferenceService.ts
29666
- function stableKey(...parts) {
29667
- return createHash("sha1").update(parts.join("::")).digest("hex");
29668
- }
29669
29736
  function candidateSource(candidate) {
29670
- return candidate.sourceLine ? `${candidate.sourceFile}:${candidate.sourceLine}` : candidate.sourceFile;
29737
+ return formatSourceLocation(candidate.sourceFile, candidate.sourceLine);
29671
29738
  }
29672
29739
  function findBackendRoute(document, suggestion) {
29673
29740
  const candidates = document.backendRouteCandidates.filter((candidate) => candidate.backend === suggestion.candidate.backend);
@@ -29791,15 +29858,6 @@ function refreshStats(catalogue) {
29791
29858
  function formatInferenceFailure(error) {
29792
29859
  return (error instanceof Error ? error.message : String(error)).split(" Raw response:")[0];
29793
29860
  }
29794
- async function runWithConcurrency(items, workers, task) {
29795
- let nextIndex = 0;
29796
- await Promise.all(Array.from({ length: Math.min(Math.max(1, workers), items.length) }, async (_, workerId) => {
29797
- while (nextIndex < items.length) {
29798
- const item = items[nextIndex++];
29799
- await task(item, workerId);
29800
- }
29801
- }));
29802
- }
29803
29861
  async function inferCatalogueDirectory(params) {
29804
29862
  const inputPath = path.resolve(params.cwd, params.catalogueDirectory);
29805
29863
  const outputPath = path.resolve(params.cwd, params.outputDirectory || params.catalogueDirectory);
@@ -29808,7 +29866,8 @@ async function inferCatalogueDirectory(params) {
29808
29866
  ensureNeedsReviewEvidence(catalogue);
29809
29867
  const needsReviewBefore = [...catalogue.route_input_properties, ...catalogue.route_output_properties].filter((property) => property.evidence_status === "needs_review").length;
29810
29868
  const needsReviewPercentageBefore = calculateNeedsReviewPercentage(catalogue.stats.input_properties, catalogue.stats.output_properties, needsReviewBefore);
29811
- const selectedRoute = params.routeSelector ? catalogue.routes.find((route) => route.method.toUpperCase() === params.routeSelector?.method.toUpperCase() && route.path === params.routeSelector?.path) : void 0;
29869
+ const routeSelector = params.routeSelector;
29870
+ const selectedRoute = routeSelector ? catalogue.routes.find((route) => matchesRouteSelector(route, routeSelector)) : void 0;
29812
29871
  if (params.routeSelector && !selectedRoute) throw new Error(`Route not found in catalogue artifacts: ${params.routeSelector.method} ${params.routeSelector.path}`);
29813
29872
  const scopeProperties = [...catalogue.route_input_properties, ...catalogue.route_output_properties].filter((property) => !selectedRoute || property.route_key === selectedRoute.key);
29814
29873
  const needsReviewInScopeBefore = scopeProperties.filter((property) => property.evidence_status === "needs_review").length;
@@ -29876,7 +29935,7 @@ async function inferCatalogueDirectory(params) {
29876
29935
  duplicate: 0,
29877
29936
  candidate_mismatch: 0
29878
29937
  };
29879
- await runWithConcurrency(routeKeys, Math.min(params.inference.workers, 10), async (routeKey, workerId) => {
29938
+ await forEachWithConcurrency(routeKeys, Math.min(params.inference.workers, 10), async (routeKey, workerId) => {
29880
29939
  const route = catalogue.routes.find((item) => item.key === routeKey);
29881
29940
  const reportRoute = (phase) => {
29882
29941
  params.onWorkerProgress?.({
@@ -30029,6 +30088,14 @@ async function inferCatalogueDirectory(params) {
30029
30088
  };
30030
30089
  }
30031
30090
 
30091
+ //#endregion
30092
+ //#region src/utils/number/parsePositiveNumber.ts
30093
+ function parsePositiveNumber(value, message = "Expected a positive number") {
30094
+ const parsed = Number(value);
30095
+ if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(message);
30096
+ return parsed;
30097
+ }
30098
+
30032
30099
  //#endregion
30033
30100
  //#region src/commands/infer.ts
30034
30101
  function createInferenceStepClassifier() {
@@ -30056,11 +30123,6 @@ function createInferenceStepClassifier() {
30056
30123
  };
30057
30124
  };
30058
30125
  }
30059
- function parsePositiveNumber(value) {
30060
- const parsed = Number(value);
30061
- if (!Number.isFinite(parsed) || parsed <= 0) throw new InvalidArgumentError$2("Expected a positive number");
30062
- return parsed;
30063
- }
30064
30126
  function parseConfidence(value) {
30065
30127
  const parsed = parsePositiveNumber(value);
30066
30128
  if (parsed > 100) throw new InvalidArgumentError$2("Confidence must be between 1 and 100");
@@ -30071,7 +30133,7 @@ function parseWorkerCount(value) {
30071
30133
  if (workers > 10) throw new InvalidArgumentError$2("A maximum of 10 inference workers is supported");
30072
30134
  return workers;
30073
30135
  }
30074
- var infer_default = (program) => void program.command("infer").argument("[route-or-directory]", "Route selector (GET /v1/offers) or catalogue directory").option("-i, --input <directory>", "Directory containing generated route YAML documents", ".tmp/datasource-catalogue").option("-o, --output <directory>", "Output directory; defaults to updating the input directory").option("--model <name>", "AI model name").option("--ai-url <url>", "AI base URL").option("--min-confidence <number>", "Minimum confidence required to accept an ai/sdk mapping", parseConfidence).option("--timeout <milliseconds>", "Maximum duration of one route-level ai/sdk request", parsePositiveNumber).option("-w, --workers <count>", "Maximum number of concurrent route inference jobs (max 10)", parseWorkerCount).description("Use AI to review unresolved mappings in generated route YAML documents").action(async (routeOrDirectory, options) => {
30136
+ var infer_default = (program) => void program.command("infer").argument("[route-or-directory]", "Route selector (GET /v1/offers) or catalogue directory").option("-i, --input <directory>", "Directory containing generated route YAML documents", ".tmp/datasource-catalogue").option("-o, --output <directory>", "Output directory; defaults to updating the input directory").option("--model <name>", "AI model name").option("--ai-url <url>", "AI base URL").option("--min-confidence <number>", "Minimum confidence required to accept an ai/sdk mapping", parseConfidence).option("--timeout <milliseconds>", "Maximum duration of one route-level ai/sdk request", (value) => parsePositiveNumber(value)).option("-w, --workers <count>", "Maximum number of concurrent route inference jobs (max 10)", parseWorkerCount).description("Use AI to review unresolved mappings in generated route YAML documents").action(async (routeOrDirectory, options) => {
30075
30137
  intro("Datasource catalogue inference");
30076
30138
  const config = getUserConfig();
30077
30139
  const inference = config.inference;
@@ -30250,11 +30312,6 @@ async function buildNeedsReviewRouteReport(params) {
30250
30312
 
30251
30313
  //#endregion
30252
30314
  //#region src/commands/needsReview.ts
30253
- function parsePositiveInteger(value) {
30254
- const parsed = Number(value);
30255
- if (!Number.isInteger(parsed) || parsed <= 0) throw new InvalidArgumentError$2("Expected a positive integer");
30256
- return parsed;
30257
- }
30258
30315
  function formatNeedsReviewTable(rows) {
30259
30316
  const headers = [
30260
30317
  "#",
@@ -30272,7 +30329,7 @@ function formatNeedsReviewTable(rows) {
30272
30329
  String(row.outputNeedsReview),
30273
30330
  String(row.inputProperties + row.outputProperties),
30274
30331
  String(row.needsReview),
30275
- `${row.needsReviewPercentage.toFixed(2)}%`
30332
+ formatPercentage(row.needsReviewPercentage)
30276
30333
  ]);
30277
30334
  const widths = headers.map((header, column) => Math.max(header.length, ...values.map((row) => row[column].length)));
30278
30335
  const formatRow = (row) => row.map((cell, column) => column < 2 ? cell.padEnd(widths[column]) : cell.padStart(widths[column])).join(" | ");
@@ -30282,7 +30339,7 @@ function formatNeedsReviewTable(rows) {
30282
30339
  ...values.map(formatRow)
30283
30340
  ].join("\n");
30284
30341
  }
30285
- var needsReview_default = (program) => void program.command("needs-review").argument("[catalogue-directory]", "Directory containing generated route YAML documents", ".tmp/datasource-catalogue").addOption(new Option("--sort <metric>", "Sort candidates by review count or percentage").choices(["count", "percentage"]).default("count")).option("--limit <number>", "Maximum number of candidate routes to display", parsePositiveInteger).description("Display needs_review metrics grouped by route").action(async (catalogueDirectory, options) => {
30342
+ var needsReview_default = (program) => void program.command("needs-review").argument("[catalogue-directory]", "Directory containing generated route YAML documents", ".tmp/datasource-catalogue").addOption(new Option("--sort <metric>", "Sort candidates by review count or percentage").choices(["count", "percentage"]).default("count")).option("--limit <number>", "Maximum number of candidate routes to display", (value) => parsePositiveInteger(value)).description("Display needs_review metrics grouped by route").action(async (catalogueDirectory, options) => {
30286
30343
  intro("Datasource catalogue needs review report");
30287
30344
  const { cwd } = getUserConfig();
30288
30345
  const progress = taskProgressService.createStepProgress();
@@ -30307,7 +30364,7 @@ var needsReview_default = (program) => void program.command("needs-review").argu
30307
30364
  `Candidate routes: ${report.candidateRoutes}/${report.totalRoutes}`,
30308
30365
  `Properties: ${report.totalProperties}`,
30309
30366
  `Needs review: ${report.needsReview}`,
30310
- `Needs review percentage: ${report.needsReviewPercentage.toFixed(2)}%`,
30367
+ `Needs review percentage: ${formatPercentage(report.needsReviewPercentage)}`,
30311
30368
  options.limit ? `Rows displayed: ${report.rows.length}/${report.candidateRoutes}` : null
30312
30369
  ].filter(Boolean).join("\n"), "Summary");
30313
30370
  outro("Datasource catalogue needs review report completed");
@@ -30326,9 +30383,6 @@ function sortPropertiesByStatus(properties) {
30326
30383
  return rank(left.status) - rank(right.status) || left.field.localeCompare(right.field);
30327
30384
  });
30328
30385
  }
30329
- function normalizeFilePath(filePath) {
30330
- return filePath.replaceAll("\\", "/").replace(/^\.\//, "");
30331
- }
30332
30386
  function isGraphDocument(value) {
30333
30387
  if (typeof value !== "object" || value === null) return false;
30334
30388
  const document = value;
@@ -30338,9 +30392,6 @@ function collectImpactedRoutes(params) {
30338
30392
  const changedFiles = new Set(params.changedFiles.map(normalizeFilePath));
30339
30393
  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}`));
30340
30394
  }
30341
- async function readChangedFiles(filePath) {
30342
- return (await fsPromises.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
30343
- }
30344
30395
  async function readRouteGraphs(outputDirectory) {
30345
30396
  const graphFiles = await globby("**/*.graph.yaml", {
30346
30397
  cwd: outputDirectory,
@@ -30360,7 +30411,7 @@ async function readRouteGraphs(outputDirectory) {
30360
30411
  }
30361
30412
  function renderChangedRouteReport(params) {
30362
30413
  const changedFiles = params.changedFiles.sort();
30363
- const routeRows = params.routes.length ? params.routes.map((route) => `| ${route.method} ${route.path} | ${route.coverage.toFixed(2)}% | ${route.needsReview}/${route.fields} |`).join("\n") : "| _Aucune route impactée_ | — | — |";
30414
+ const routeRows = params.routes.length ? params.routes.map((route) => `| ${route.method} ${route.path} | ${formatPercentage(route.coverage)} | ${route.needsReview}/${route.fields} |`).join("\n") : "| _Aucune route impactée_ | — | — |";
30364
30415
  return [
30365
30416
  "<!-- datasource-catalogue-report -->",
30366
30417
  "## Datasource mapping report",
@@ -30415,6 +30466,12 @@ function renderChangedRouteReport(params) {
30415
30466
  ].join("\n");
30416
30467
  }
30417
30468
 
30469
+ //#endregion
30470
+ //#region src/utils/fs/readChangedFiles.ts
30471
+ async function readChangedFiles(filePath) {
30472
+ return (await fsPromises.readFile(filePath, "utf8")).split(/\r?\n/).map((line) => normalizeFilePath(line.trim())).filter(Boolean);
30473
+ }
30474
+
30418
30475
  //#endregion
30419
30476
  //#region src/commands/reportChanged.ts
30420
30477
  async function reportChangedHandler(cwd, options) {
@@ -30432,7 +30489,7 @@ async function reportChangedHandler(cwd, options) {
30432
30489
  const documents = await analyzeCodebaseRouteContracts({
30433
30490
  cwd,
30434
30491
  openApiDocument,
30435
- selectedRouteKeys: /* @__PURE__ */ new Set([buildRouteKey(route.method, route.path)]),
30492
+ selectedRouteKeys: /* @__PURE__ */ new Set([stableKey(route.method, route.path)]),
30436
30493
  routeAnalysisScope: ({ method, path: routePath }) => loadBackendTopologyAnalysisScope(outputDirectory, cwd, method, routePath)
30437
30494
  });
30438
30495
  if (documents.length !== 1) throw new Error("Route was not found in the OpenAPI contract");