Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk): added support for services #6

Merged
merged 4 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions .changeset/curvy-geese-compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@eventcatalog/sdk": patch
---

feat(sdk): added support for services
1 change: 1 addition & 0 deletions src/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@
* @module docs
*/
export * from './events';
export * from './services';
3 changes: 2 additions & 1 deletion src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export const rmEventById = (directory: string) => async (id: string, version?: s
* Version an event by it's id.
*
* Takes the latest event and moves it to a versioned directory.
* All files with this event are also versioned (e.g /events/InventoryAdjusted/schema.json)
*
* @example
* ```ts
Expand All @@ -149,7 +150,7 @@ export const rmEventById = (directory: string) => async (id: string, version?: s
*
* // moves the latest InventoryAdjusted event to a versioned directory
* // the version within that event is used as the version number.
* await verionEvent('InventoryAdjusted');
* await versionEvent('InventoryAdjusted');
*
* ```
*/
Expand Down
50 changes: 50 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { join } from 'node:path';
import { rmEvent, rmEventById, writeEvent, versionEvent, getEvent, addFileToEvent, addSchemaToEvent } from './events';
import { writeService, getService, versionService, rmService, rmServiceById, addFileToService } from './services';

/**
* Init the SDK for EventCatalog
Expand Down Expand Up @@ -59,5 +60,54 @@ export default (path: string) => {
* @returns
*/
addSchemaToEvent: addSchemaToEvent(join(path, 'events')),

/**
* ================================
* SERVICES
* ================================
*/

/**
* Adds a service to EventCatalog
*
* @param service - The service to write
* @param options - Optional options to write the event
*
*/
writeService: writeService(join(path, 'services')),
/**
* Returns a service from EventCatalog
* @param id - The id of the service to retrieve
* @param version - Optional id of the version to get
* @returns
*/
getService: getService(join(path, 'services')),
/**
* Moves a given service id to the version directory
* @param directory
*/
versionService: versionService(join(path, 'services')),
/**
* Remove a service from EventCatalog (modeled on the standard POSIX rm utility)
*
* @param path - The path to your service, e.g. `/InventoryService`
*
*/
rmService: rmService(join(path, 'services')),
/**
* Remove an service by an service id
*
* @param id - The id of the service you want to remove
*
*/
rmServiceById: rmServiceById(join(path, 'services')),
/**
* Adds a file to the given service
* @param id - The id of the service to add the file to
* @param file - File contents to add including the content and the file name
* @param version - Optional version of the service to add the file to
* @returns
*/
addFileToService: addFileToService(join(path, 'services')),
};
};
214 changes: 214 additions & 0 deletions src/services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import matter from 'gray-matter';
import { copyDir, findFileById, getFiles, searchFilesForId, versionExists } from './internal/utils';
import type { Service } from './types';
import fs from 'node:fs/promises';
import { dirname, join } from 'node:path';

/**
* Returns a service from EventCatalog.
*
* You can optionally specify a version to get a specific version of the service
*
* @example
* ```ts
* import utils from '@eventcatalog/utils';
*
* const { getService } = utils('/path/to/eventcatalog');
*
* // Gets the latest version of the event
* cont event = await getService('InventoryService');
*
* // Gets a version of the event
* cont event = await getService('InventoryService', '0.0.1');
* ```
*/
export const getService =
(directory: string) =>
async (id: string, version?: string): Promise<Service> => {
const file = await findFileById(directory, id, version);

if (!file) throw new Error(`No service found for the given id: ${id}` + (version ? ` and version ${version}` : ''));

const { data, content } = matter.read(file);

return {
...data,
markdown: content.trim(),
} as Service;
};

/**
* Write an event to EventCatalog.
*
* You can optionally overide the path of the event.
*
* @example
* ```ts
* import utils from '@eventcatalog/utils';
*
* const { writeService } = utils('/path/to/eventcatalog');
*
* // Write a service
* // Event would be written to services/InventoryService
* await writeService({
* id: 'InventoryService',
* name: 'Inventory Service',
* version: '0.0.1',
* summary: 'Service that handles the inventory',
* markdown: '# Hello world',
* });
*
* // Write an event to the catalog but override the path
* // Event would be written to services/Inventory/InventoryService
* await writeService({
* id: 'InventoryService',
* name: 'Inventory Adjusted',
* version: '0.0.1',
* summary: 'This is a summary',
* markdown: '# Hello world',
* }, { path: "/Inventory/InventoryService"});
* ```
*/
export const writeService =
(directory: string) =>
async (service: Service, options: { path: string } = { path: '' }) => {
// Get the path
const path = options.path || `/${service.id}`;
const exists = await versionExists(directory, service.id, service.version);

if (exists) {
throw new Error(`Failed to write service as the version ${service.version} already exists`);
}

const { markdown, ...frontmatter } = service;
const document = matter.stringify(markdown.trim(), frontmatter);
await fs.mkdir(join(directory, path), { recursive: true });
await fs.writeFile(join(directory, path, 'index.md'), document);
};

/**
* Version a service by it's id.
*
* Takes the latest service and moves it to a versioned directory.
* All files with this service are also versioned. (e.g /services/InventoryService/openapi.yml)
*
* @example
* ```ts
* import utils from '@eventcatalog/utils';
*
* const { versionService } = utils('/path/to/eventcatalog');
*
* // moves the latest InventoryService service to a versioned directory
* // the version within that service is used as the version number.
* await versionService('InventoryService');
*
* ```
*/
export const versionService = (directory: string) => async (id: string) => {
// Find all the events in the directory
const files = await getFiles(`${directory}/**/index.md`);
const matchedFiles = await searchFilesForId(files, id);

if (matchedFiles.length === 0) {
throw new Error(`No service found with id: ${id}`);
}

// Service that is in the route of the project
const file = matchedFiles[0];
const eventDirectory = dirname(file);
const { data: { version = '0.0.1' } = {} } = matter.read(file);
const targetDirectory = join(eventDirectory, 'versioned', version);

await fs.mkdir(targetDirectory, { recursive: true });

// Copy the service to the versioned directory
await copyDir(directory, eventDirectory, targetDirectory, (src) => {
return !src.includes('versioned');
});

// Remove all the files in the root of the resource as they have now been versioned
await fs.readdir(eventDirectory).then(async (resourceFiles) => {
await Promise.all(
resourceFiles.map(async (file) => {
if (file !== 'versioned') {
await fs.rm(join(eventDirectory, file), { recursive: true });
}
})
);
});
};

/**
* Delete a service at it's given path.
*
* @example
* ```ts
* import utils from '@eventcatalog/utils';
*
* const { rmService } = utils('/path/to/eventcatalog');
*
* // Removes the service at services/InventoryService
* await rmService('/InventoryService');
* ```
*/
export const rmService = (directory: string) => async (path: string) => {
await fs.rm(join(directory, path), { recursive: true });
};

/**
* Delete a service by it's id.
*
* Optionally specify a version to delete a specific version of the service.
*
* @example
* ```ts
* import utils from '@eventcatalog/utils';
*
* const { rmServiceById } = utils('/path/to/eventcatalog');
*
* // deletes the latest InventoryService event
* await rmServiceById('InventoryService');
*
* // deletes a specific version of the InventoryService event
* await rmServiceById('InventoryService', '0.0.1');
* ```
*/
export const rmServiceById = (directory: string) => async (id: string, version?: string) => {
// Find all the events in the directory
const files = await getFiles(`${directory}/**/index.md`);

const matchedFiles = await searchFilesForId(files, id, version);

if (matchedFiles.length === 0) {
throw new Error(`No service found with id: ${id}`);
}

await Promise.all(matchedFiles.map((file) => fs.rm(file)));
};

/**
* Add a file to a service by it's id.
*
* Optionally specify a version to add a file to a specific version of the service.
*
* @example
* ```ts
* import utils from '@eventcatalog/utils';
*
* const { addFileToService } = utils('/path/to/eventcatalog');
*
* // adds a file to the latest InventoryService event
* await addFileToService('InventoryService', { content: 'Hello world', fileName: 'hello.txt' });
*
* // adds a file to a specific version of the InventoryService event
* await addFileToService('InventoryService', { content: 'Hello world', fileName: 'hello.txt' }, '0.0.1');
*
* ```
*/
export const addFileToService =
(directory: string) => async (id: string, file: { content: string; fileName: string }, version?: string) => {
const pathToEvent = await findFileById(directory, id, version);
if (!pathToEvent) throw new Error('Cannot find directory to write file to');
const contentDirectory = dirname(pathToEvent);
await fs.writeFile(join(contentDirectory, file.fileName), file.content);
};
4 changes: 2 additions & 2 deletions src/test/events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import utils from '../index';
import path from 'node:path';
import fs from 'node:fs';

const CATALOG_PATH = path.join(__dirname, 'catalog');
const CATALOG_PATH = path.join(__dirname, 'catalog-events');

const { writeEvent, getEvent, rmEvent, rmEventById, versionEvent, addFileToEvent, addSchemaToEvent } = utils(CATALOG_PATH);

Expand All @@ -15,7 +15,7 @@ beforeEach(() => {
});

afterEach(() => {
// fs.rmSync(CATALOG_PATH, { recursive: true, force: true });
fs.rmSync(CATALOG_PATH, { recursive: true, force: true });
});

describe('Events SDK', () => {
Expand Down
Loading