diff --git a/lib/setModel.js b/lib/setModel.js index ab7b274..e2f51a4 100644 --- a/lib/setModel.js +++ b/lib/setModel.js @@ -40,6 +40,10 @@ async function compileModel(path) { Object.assign(def, info) } + for (const name in compiled.definitions) { + Object.defineProperty(compiled.definitions[name], 'name', { value: name, enumerable: true }) + } + const _entities_in = service => { const exposed = [], { entities } = service diff --git a/lib/tools.js b/lib/tools.js index ccd37d2..afcc25b 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -5,22 +5,22 @@ import { z } from 'zod' import setModel from './setModel.js' import fuzzyTopN from './fuzzyTopN.js' -const PROJECT_PATH = { - projectPath: z.string().describe('Root path of the project') -} - const tools = { search_cds_definitions: { title: 'Search for CDS definitions', description: 'Get details of CDS definitions, returns Core Schema Notation (CSN). Use this if you want to see elements, parameters, file locations, URL paths, etc., helpful when constructing queries or OData URLs or when modifying CDS models.', inputSchema: { - ...PROJECT_PATH, - name: z.string().optional().describe('Name of the definition (fuzzy search, no regex or special characters)'), - kind: z.string().optional().describe('Kind of the definition (service, entity, action, ...)'), - topN: z.number().default(1).describe('Number of results') + projectPath: z.string().describe('Root path of the project'), + name: z + .string() + .optional() + .describe('Name of the definition (fuzzy search (Levenshtein distance), no regex or special characters)'), + kind: z.string().optional().describe('Filter for kind of the definition (service, entity, action, ...)'), + topN: z.number().default(1).describe('Number of results'), + namesOnly: z.boolean().optional().describe('If true, only return the names of the definitions') }, - handler: async ({ projectPath, name, kind, topN }) => { + handler: async ({ projectPath, name, kind, topN, namesOnly }) => { await setModel(projectPath) const defNames = kind ? Object.entries(cds.model.definitions) @@ -29,26 +29,10 @@ const tools = { .map(([k]) => k) : Object.keys(cds.model.definitions) const scores = name ? fuzzyTopN(name, defNames, topN) : fuzzyTopN('', defNames, topN) - return scores.map(s => Object.assign({ name: s.item }, cds.model.definitions[s.item])) - } - }, - list_all_cds_definition_names: { - title: 'List all CDS definitions names', - description: - 'Get an overview of available CDS definitions, for details use `search_cds_definitions`. Helpful for initial exploration, e.g. to get all service names.', - inputSchema: { - ...PROJECT_PATH, - kind: z.string().optional().describe('Kind of the definition (service, entity, action, ...)') - }, - handler: async ({ projectPath, kind }) => { - await setModel(projectPath) - const defNames = kind - ? Object.entries(cds.model.definitions) - // eslint-disable-next-line no-unused-vars - .filter(([_k, v]) => v.kind === kind) - .map(([k]) => k) - : Object.keys(cds.model.definitions) - return defNames + if (namesOnly) { + return scores.map(s => s.item) + } + return scores.map(s => cds.model.definitions[s.item]) } } } diff --git a/tests/sample/db/schema.cds b/tests/sample/db/schema.cds index ff7017c..a6bf0fd 100644 --- a/tests/sample/db/schema.cds +++ b/tests/sample/db/schema.cds @@ -1,6 +1,7 @@ using { Currency, managed, sap } from '@sap/cds/common'; namespace sap.capire.bookshop; +@odata.draft.enabled entity Books : managed { key ID : Integer; @mandatory title : localized String(111); diff --git a/tests/tools.test.js b/tests/tools.test.js index 0836a56..8353860 100644 --- a/tests/tools.test.js +++ b/tests/tools.test.js @@ -38,23 +38,38 @@ test.describe('tools', () => { assert(Array.isArray(books[0].endpoints), 'Should contain endpoints') assert.equal(books[0].endpoints[0].kind, 'odata', 'Should contain odata endpoint kind') assert.equal(books[0].endpoints[0].path, 'odata/v4/admin/Books', 'Should contain endpoint path') + + // Check that keys are present and correct + assert(books[0].elements.ID, 'Books entity should have key ID') + assert(books[0].elements.ID.key === true, 'ID should be marked as key') + // Check draft fields + assert(books[0].elements.IsActiveEntity, 'Draft-enabled entity should have IsActiveEntity') + assert(books[0].elements.IsActiveEntity.key === true, 'IsActiveEntity should be marked as key') + assert(books[0].elements.HasActiveEntity, 'Draft-enabled entity should have HasActiveEntity') + assert(books[0].elements.HasDraftEntity, 'Draft-enabled entity should have HasDraftEntity') }) - test('list_all_cds_definition_names: should list all entities', async () => { - const entities = await tools.list_all_cds_definition_names.handler({ + test('search_cds_definitions: should list all entities (namesOnly)', async () => { + const entities = await tools.search_cds_definitions.handler({ projectPath: sampleProjectPath, - kind: 'entity' + kind: 'entity', + topN: 100, + namesOnly: true }) assert(Array.isArray(entities), 'Entities should be an array') assert(entities.length > 0, 'Should find at least one entity') + assert(typeof entities[0] === 'string', 'Should return only names') }) - test('list_all_cds_definition_names: should list all services', async () => { - const services = await tools.list_all_cds_definition_names.handler({ + test('search_cds_definitions: should list all services (namesOnly)', async () => { + const services = await tools.search_cds_definitions.handler({ projectPath: sampleProjectPath, - kind: 'service' + kind: 'service', + topN: 100, + namesOnly: true }) assert(Array.isArray(services), 'Services should be an array') assert(services.length > 0, 'Should find at least one service') + assert(typeof services[0] === 'string', 'Should return only names') }) })