Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 35 additions & 15 deletions lib/setModel.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import cds from '@sap/cds'
import fs from 'fs'
import path from 'path'

// Ensures only one CDS model compilation is ever in-flight.
// The moment setModel is called, cds.model is set to a promise.
export default async function setModel(path) {
export default async function setModel(projectPath) {
if (cds.model) {
// If cds.model is a promise, await it; if it's resolved, return it
if (typeof cds.model.then === 'function') await cds.model
return
}
// Assign a promise immediately to cds.model to prevent duplicate compilations
cds.model = (async () => {
const compiled = await compileModel(path)
const compiled = await compileModel(projectPath)
cds.model = compiled
return compiled
})()
Expand All @@ -20,16 +21,16 @@ export default async function setModel(path) {
}

// Loads and compiles the CDS model, returns the compiled model or throws on error
async function compileModel(path) {
cds.root = path
async function compileModel(projectPath) {
cds.root = projectPath
const startTime = Date.now()
const resolved = cds.resolve(path + '/*', { cache: {} }) // make sure NOT to use the cache
const resolved = cds.resolve(projectPath + '/*', { cache: {} }) // use CAP standard resolution for model compilation
let compiled = await cds.load(resolved, { docs: true, locations: true })
if (!compiled || (Array.isArray(compiled) && compiled.length === 0)) {
throw new Error(`Failed to load CDS model from path: ${path}`)
throw new Error(`Failed to load CDS model from path: ${projectPath}`)
}
if (!compiled.definitions || Object.keys(compiled.definitions).length === 0) {
throw new Error(`Compiled CDS model is invalid or empty for path: ${path}`)
throw new Error(`Compiled CDS model is invalid or empty for path: ${projectPath}`)
}
compiled = cds.compile.for.nodejs(compiled) // to include drafts, show effective types
const serviceInfo = cds.compile.to.serviceinfo(compiled)
Expand All @@ -41,7 +42,10 @@ async function compileModel(path) {
}

for (const name in compiled.definitions) {
Object.defineProperty(compiled.definitions[name], 'name', { value: name, enumerable: true })
Object.defineProperty(compiled.definitions[name], 'name', {
value: name,
enumerable: true
})
}

const _entities_in = service => {
Expand Down Expand Up @@ -81,19 +85,19 @@ async function compileModel(path) {
? parseInt(process.env.CDS_MCP_REFRESH_MS, 10)
: Math.max(compileDuration * 10, 20000)
changeWatcher = setInterval(async () => {
const hasChanged = await cdsFilesChanged(path)
const hasChanged = await cdsFilesChanged(projectPath)
if (hasChanged) {
await refreshModel(path)
await refreshModel(projectPath)
}
}, intervalMs).unref() // Uses CDS_MCP_REFRESH_MS if set, otherwise defaults to 10x compile duration or 20s
}
return compiled
}

// Refreshes the CDS model, only replaces cds.model if compilation succeeds
async function refreshModel(path) {
async function refreshModel(projectPath) {
try {
const compiled = await compileModel(path)
const compiled = await compileModel(projectPath)
cds.model = compiled
return compiled
} catch {
Expand All @@ -105,9 +109,25 @@ async function refreshModel(path) {
const cache = { cdsFiles: new Map() }
let changeWatcher = null

async function cdsFilesChanged(path) {
if (path.endsWith('/')) path = path.slice(0, -1)
const files = cds.resolve(path + '/*', { cache: {} }) || []
async function cdsFilesChanged(projectPath) {
// Recursively find all .cds files under root, ignoring node_modules
async function findCdsFiles(dir) {
let results = []
const entries = await fs.promises.readdir(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
if (entry.name === 'node_modules') continue
results = results.concat(await findCdsFiles(fullPath))
} else if (entry.isFile() && entry.name.endsWith('.cds')) {
results.push(fullPath)
}
}
return results
}

if (projectPath.endsWith('/')) projectPath = projectPath.slice(0, -1)
const files = await findCdsFiles(projectPath)
const currentTimestamps = new Map()
await Promise.all(
files.map(file =>
Expand Down