diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts new file mode 100644 index 0000000..3299881 --- /dev/null +++ b/flow-core/src/deps.ts @@ -0,0 +1,5 @@ +export { normalize } from "https://deno.land/std@0.224.0/path/mod.ts"; +export { assertEquals, assertThrows, assertNotStrictEquals } from "https://deno.land/std@0.224.0/assert/mod.ts"; +export { basename, dirname, join, resolve } from "https://deno.land/std@0.224.0/path/mod.ts"; +export { existsSync } from "https://deno.land/std@0.224.0/fs/mod.ts"; +export { ensureDir } from "https://deno.land/std@0.224.0/fs/ensure_dir.ts"; diff --git a/flow-core/src/mesh-constants.ts b/flow-core/src/mesh-constants.ts index 7efcaf7..3c1a90b 100644 --- a/flow-core/src/mesh-constants.ts +++ b/flow-core/src/mesh-constants.ts @@ -49,141 +49,3 @@ export const MESH = { FLOW_SERVICE_ONTOLOGY: "https://semantic-flow.github.io/ontology/flow-service/" } as const; -// Usage throughout codebase - - -export function getHandlePath(nodePath: string): string { - return `${nodePath}/${MESH.HANDLE_DIR}/`; -} - -export function getAssetsPath(nodePath: string): string { - return `${nodePath}/${MESH.ASSETS_DIR}/`; -} - -// config -export function getConfigFlowPath(nodePath: string): string { - return `${nodePath}/${MESH.CONFIG_FLOW_DIR}/`; -} - -export function getCurrentConfigSnapshotPath(nodePath: string): string { - return `${getConfigFlowPath(nodePath)}/${MESH.CURRENT_SNAPSHOT_DIR}/`; -} - -export function getCurrentConfigDistPath(nodePath: string): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getCurrentConfigSnapshotPath(nodePath)}${lastSegment}_${MESH.CONFIG}_${MESH.CURRENT}.jsonld`; -} - -export function getNextConfigSnapshotPath(nodePath: string): string { - return `${getConfigFlowPath(nodePath)}/${MESH.NEXT_SNAPSHOT_DIR}/`; -} - -export function getNextConfigDistPath(nodePath: string): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getNextConfigSnapshotPath(nodePath)}${lastSegment}_${MESH.CONFIG}_${MESH.NEXT}.jsonld`; -} - -export function getVersionConfigSnapshotPath(nodePath: string, version: number): string { - return `${getConfigFlowPath(nodePath)}/${MESH.VERSION_SNAPSHOT_PREFIX}${version}/`; -} - -export function getVersionConfigDistPath(nodePath: string, version: number): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getVersionConfigSnapshotPath(nodePath, version)}${lastSegment}_${MESH.CONFIG}_${MESH.VERSION_SNAPSHOT_PREFIX}${version}.jsonld`; -} - -// meta -export function getMetaFlowPath(nodePath: string): string { - return `${nodePath}/${MESH.META_FLOW_DIR}/`; -} - -export function getCurrentMetaSnapshotPath(nodePath: string): string { - return `${getMetaFlowPath(nodePath)}/${MESH.CURRENT_SNAPSHOT_DIR}/`; -} - -export function getCurrentMetaDistPath(nodePath: string): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getCurrentMetaSnapshotPath(nodePath)}${lastSegment}_${MESH.META}_${MESH.CURRENT}.jsonld`; -} - -export function getNextMetaSnapshotPath(nodePath: string): string { - return `${getMetaFlowPath(nodePath)}/${MESH.NEXT_SNAPSHOT_DIR}/`; -} - -export function getNextMetaDistPath(nodePath: string): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getNextMetaSnapshotPath(nodePath)}${lastSegment}_${MESH.META}_${MESH.NEXT}.jsonld`; -} - -export function getVersionMetaSnapshotPath(nodePath: string, version: number): string { - return `${getMetaFlowPath(nodePath)}/${MESH.VERSION_SNAPSHOT_PREFIX}${version}/`; -} - -export function getVersionMetaDistPath(nodePath: string, version: number): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getVersionMetaSnapshotPath(nodePath, version)}${lastSegment}_${MESH.META}_${MESH.VERSION_SNAPSHOT_PREFIX}${version}.jsonld`; -} - -// REF -export function getRefFlowPath(nodePath: string): string { - return `${nodePath}/${MESH.REF_FLOW_DIR}/`; -} - -export function getCurrentRefSnapshotPath(nodePath: string): string { - return `${getRefFlowPath(nodePath)}/${MESH.CURRENT_SNAPSHOT_DIR}/`; -} - -export function getCurrentRefDistPath(nodePath: string): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getCurrentRefSnapshotPath(nodePath)}${lastSegment}_${MESH.REF}_${MESH.CURRENT}.jsonld`; -} - -export function getNextRefSnapshotPath(nodePath: string): string { - return `${getRefFlowPath(nodePath)}/${MESH.NEXT_SNAPSHOT_DIR}/`; -} - -export function getNextRefDistPath(nodePath: string): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getNextRefSnapshotPath(nodePath)}${lastSegment}_${MESH.REF}_${MESH.NEXT}.jsonld`; -} - -export function getVersionRefSnapshotPath(nodePath: string, version: number): string { - return `${getRefFlowPath(nodePath)}/${MESH.VERSION_SNAPSHOT_PREFIX}${version}/`; -} - -export function getVersionRefDistPath(nodePath: string, version: number): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getVersionRefSnapshotPath(nodePath, version)}${lastSegment}_${MESH.REF}_${MESH.VERSION_SNAPSHOT_PREFIX}${version}.jsonld`; -} - -// data -export function getDataFlowPath(nodePath: string): string { - return `${nodePath}/${MESH.DATA_FLOW_DIR}/`; -} - -export function getCurrentDataSnapshotPath(nodePath: string): string { - return `${getDataFlowPath(nodePath)}/${MESH.CURRENT_SNAPSHOT_DIR}/`; -} - -export function getCurrentDataDistPath(nodePath: string): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getCurrentDataSnapshotPath(nodePath)}${lastSegment}_${MESH.DATA}_${MESH.CURRENT}.jsonld`; -} - -export function getNextDataSnapshotPath(nodePath: string): string { - return `${getDataFlowPath(nodePath)}/${MESH.NEXT_SNAPSHOT_DIR}/`; -} - -export function getNextDataDistPath(nodePath: string): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getNextDataSnapshotPath(nodePath)}${lastSegment}_${MESH.DATA}_${MESH.NEXT}.jsonld`; -} - -export function getVersionDataSnapshotPath(nodePath: string, version: number): string { - return `${getDataFlowPath(nodePath)}/${MESH.VERSION_SNAPSHOT_PREFIX}${version}/`; -} - -export function getVersionDataDistPath(nodePath: string, version: number): string { - const lastSegment = nodePath.substring(nodePath.lastIndexOf("/") + 1); - return `${getVersionDataSnapshotPath(nodePath, version)}${lastSegment}_${MESH.DATA}_${MESH.VERSION_SNAPSHOT_PREFIX}${version}.jsonld`; -} diff --git a/flow-core/src/utils/api-param-path-utils.ts b/flow-core/src/utils/api-param-path-utils.ts new file mode 100644 index 0000000..4a4f1ed --- /dev/null +++ b/flow-core/src/utils/api-param-path-utils.ts @@ -0,0 +1 @@ +// Utility functions for pseudo-path handling (using ~ instead of /) for API parameters diff --git a/flow-core/src/utils/mesh-path-utils.ts b/flow-core/src/utils/mesh-path-utils.ts new file mode 100644 index 0000000..4e946cf --- /dev/null +++ b/flow-core/src/utils/mesh-path-utils.ts @@ -0,0 +1,145 @@ +/** + * Utility functions for mesh path operations + */ + +import { MESH } from '../mesh-constants.ts'; +import { getLastPathSegment } from './path-utils.ts'; + +// Usage throughout codebase + + +export function getHandlePath(nodePath: string): string { + return `${nodePath}/${MESH.HANDLE_DIR}/`; +} + +export function getAssetsPath(nodePath: string): string { + return `${nodePath}/${MESH.ASSETS_DIR}/`; +} + +// config +export function getConfigFlowPath(nodePath: string): string { + return `${nodePath}/${MESH.CONFIG_FLOW_DIR}/`; +} + +export function getCurrentConfigSnapshotPath(nodePath: string): string { + return `${getConfigFlowPath(nodePath)}/${MESH.CURRENT_SNAPSHOT_DIR}/`; +} + +export function getCurrentConfigDistPath(nodePath: string): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getCurrentConfigSnapshotPath(nodePath)}${lastSegment}_${MESH.CONFIG}_${MESH.CURRENT}.jsonld`; +} + +export function getNextConfigSnapshotPath(nodePath: string): string { + return `${getConfigFlowPath(nodePath)}/${MESH.NEXT_SNAPSHOT_DIR}/`; +} + +export function getNextConfigDistPath(nodePath: string): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getNextConfigSnapshotPath(nodePath)}${lastSegment}_${MESH.CONFIG}_${MESH.NEXT}.jsonld`; +} + +export function getVersionConfigSnapshotPath(nodePath: string, version: number): string { + return `${getConfigFlowPath(nodePath)}/${MESH.VERSION_SNAPSHOT_PREFIX}${version}/`; +} + +export function getVersionConfigDistPath(nodePath: string, version: number): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getVersionConfigSnapshotPath(nodePath, version)}${lastSegment}_${MESH.CONFIG}_${MESH.VERSION_SNAPSHOT_PREFIX}${version}.jsonld`; +} + +// meta +export function getMetaFlowPath(nodePath: string): string { + return `${nodePath}/${MESH.META_FLOW_DIR}/`; +} + +export function getCurrentMetaSnapshotPath(nodePath: string): string { + return `${getMetaFlowPath(nodePath)}/${MESH.CURRENT_SNAPSHOT_DIR}/`; +} + +export function getCurrentMetaDistPath(nodePath: string): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getCurrentMetaSnapshotPath(nodePath)}${lastSegment}_${MESH.META}_${MESH.CURRENT}.jsonld`; +} + +export function getNextMetaSnapshotPath(nodePath: string): string { + return `${getMetaFlowPath(nodePath)}/${MESH.NEXT_SNAPSHOT_DIR}/`; +} + +export function getNextMetaDistPath(nodePath: string): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getNextMetaSnapshotPath(nodePath)}${lastSegment}_${MESH.META}_${MESH.NEXT}.jsonld`; +} + +export function getVersionMetaSnapshotPath(nodePath: string, version: number): string { + return `${getMetaFlowPath(nodePath)}/${MESH.VERSION_SNAPSHOT_PREFIX}${version}/`; +} + +export function getVersionMetaDistPath(nodePath: string, version: number): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getVersionMetaSnapshotPath(nodePath, version)}${lastSegment}_${MESH.META}_${MESH.VERSION_SNAPSHOT_PREFIX}${version}.jsonld`; +} + +// REF +export function getRefFlowPath(nodePath: string): string { + return `${nodePath}/${MESH.REF_FLOW_DIR}/`; +} + +export function getCurrentRefSnapshotPath(nodePath: string): string { + return `${getRefFlowPath(nodePath)}/${MESH.CURRENT_SNAPSHOT_DIR}/`; +} + +export function getCurrentRefDistPath(nodePath: string): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getCurrentRefSnapshotPath(nodePath)}${lastSegment}_${MESH.REF}_${MESH.CURRENT}.jsonld`; +} + +export function getNextRefSnapshotPath(nodePath: string): string { + return `${getRefFlowPath(nodePath)}/${MESH.NEXT_SNAPSHOT_DIR}/`; +} + +export function getNextRefDistPath(nodePath: string): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getNextRefSnapshotPath(nodePath)}${lastSegment}_${MESH.REF}_${MESH.NEXT}.jsonld`; +} + +export function getVersionRefSnapshotPath(nodePath: string, version: number): string { + return `${getRefFlowPath(nodePath)}/${MESH.VERSION_SNAPSHOT_PREFIX}${version}/`; +} + +export function getVersionRefDistPath(nodePath: string, version: number): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getVersionRefSnapshotPath(nodePath, version)}${lastSegment}_${MESH.REF}_${MESH.VERSION_SNAPSHOT_PREFIX}${version}.jsonld`; +} + +// data +export function getDataFlowPath(nodePath: string): string { + return `${nodePath}/${MESH.DATA_FLOW_DIR}/`; +} + +export function getCurrentDataSnapshotPath(nodePath: string): string { + return `${getDataFlowPath(nodePath)}/${MESH.CURRENT_SNAPSHOT_DIR}/`; +} + +export function getCurrentDataDistPath(nodePath: string): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getCurrentDataSnapshotPath(nodePath)}${lastSegment}_${MESH.DATA}_${MESH.CURRENT}.jsonld`; +} + +export function getNextDataSnapshotPath(nodePath: string): string { + return `${getDataFlowPath(nodePath)}/${MESH.NEXT_SNAPSHOT_DIR}/`; +} + +export function getNextDataDistPath(nodePath: string): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getNextDataSnapshotPath(nodePath)}${lastSegment}_${MESH.DATA}_${MESH.NEXT}.jsonld`; +} + +export function getVersionDataSnapshotPath(nodePath: string, version: number): string { + return `${getDataFlowPath(nodePath)}/${MESH.VERSION_SNAPSHOT_PREFIX}${version}/`; +} + +export function getVersionDataDistPath(nodePath: string, version: number): string { + const lastSegment = getLastPathSegment(nodePath); + return `${getVersionDataSnapshotPath(nodePath, version)}${lastSegment}_${MESH.DATA}_${MESH.VERSION_SNAPSHOT_PREFIX}${version}.jsonld`; +} diff --git a/flow-core/src/utils/path-utils.ts b/flow-core/src/utils/path-utils.ts index 220288b..5fa9771 100644 --- a/flow-core/src/utils/path-utils.ts +++ b/flow-core/src/utils/path-utils.ts @@ -1,5 +1,7 @@ // Utility functions for path handling in flow-core +import { normalize } from '../deps.ts'; + /** * Converts a node relative path to a meta relative path by prepending "../../" * This is used to navigate from a node's directory to its meta directory. @@ -10,34 +12,38 @@ export function convertNodeRelativePathToMetaRelativePath(nodeRelativePath: string): string { return `../../${nodeRelativePath}`; } - /** - * Normalizes a node path to ensure it does not start with a slash or "./" - * and uses consistent separators. + * Normalizes a folder path by ensuring it ends with a slash. + * If the path is empty, it returns "/". * - * @param nodePath - The node path to normalize - * @returns The normalized node path + * @param folderPath - The folder path to normalize + * @returns The normalized folder path */ -export function normalizeNodePath(nodePath: string): string { - if (!nodePath) return ''; - let normalized = nodePath; - if (normalized.startsWith('/')) { - normalized = normalized.slice(1); - } - if (normalized.startsWith('./')) { - normalized = normalized.slice(2); - } - // Additional normalization can be added here if needed - return normalized; + +export function normalizeFolderPath(folderPath: string): string { + if (!folderPath) return "/"; + const path = normalize(folderPath); + return path.endsWith("/") ? path : path + "/"; } /** - * Extracts the last segment of a node path. + * Safely extracts the last segment of a path. + * Removes trailing slashes, then returns the substring after the last slash. + * If no slash is present, returns the entire path. * - * @param nodePath - The node path string + * @param path - The filesystem path string * @returns The last segment of the path */ -export function getLastSegment(nodePath: string): string { - const parts = nodePath.split('/'); - return parts[parts.length - 1] || ''; +export function getLastPathSegment(path: string): string { + if (!path) return path; + // Remove trailing slashes + let trimmedPath = path; + while (trimmedPath.endsWith('/')) { + trimmedPath = trimmedPath.slice(0, -1); + } + const lastSlashIndex = trimmedPath.lastIndexOf('/'); + if (lastSlashIndex === -1) { + return trimmedPath; + } + return trimmedPath.substring(lastSlashIndex + 1); } diff --git a/flow-service/src/config/loaders/jsonld-loader.ts b/flow-service/src/config/loaders/jsonld-loader.ts index dfd8055..8ff70ee 100644 --- a/flow-service/src/config/loaders/jsonld-loader.ts +++ b/flow-service/src/config/loaders/jsonld-loader.ts @@ -8,8 +8,8 @@ import type { ServiceConfigInput, NodeConfigInput } from '../types.ts'; import { ConfigError } from '../types.ts'; import { handleCaughtError } from '../../utils/logger.ts'; -import { getCurrentConfigDistPath } from '../../../../flow-core/src/mesh-constants.ts'; -import { dirname, resolve } from 'https://deno.land/std@0.208.0/path/mod.ts'; +import { getCurrentConfigDistPath } from '../../../../flow-core/src/utils/mesh-path-utils.ts'; +import { dirname, resolve } from '../../../../flow-core/src/deps.ts'; /** * Loads a service configuration from a JSON-LD file at the specified path. diff --git a/flow-service/src/routes/meshes.ts b/flow-service/src/routes/meshes.ts index fc561e8..3b4c179 100644 --- a/flow-service/src/routes/meshes.ts +++ b/flow-service/src/routes/meshes.ts @@ -1,8 +1,9 @@ import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'; import { logger } from '../utils/logger.ts'; -import { getMetaFlowPath, getNextMetaDistPath, getCurrentMetaDistPath, MESH, getHandlePath, getAssetsPath } from '../../../flow-core/src/mesh-constants.ts'; -import { join, dirname } from 'jsr:@std/path'; -import { existsSync } from "https://deno.land/std@0.224.0/fs/mod.ts"; +import { getMetaFlowPath, getNextMetaDistPath, getCurrentMetaDistPath, getHandlePath, getAssetsPath } from '../../../flow-core/src/utils/mesh-path-utils.ts'; +import { normalizeFolderPath } from '../../../flow-core/src/utils/path-utils.ts'; +import { MESH } from '../../../flow-core/src/mesh-constants.ts'; +import { join, dirname, existsSync } from '../../../flow-core/src/deps.ts'; import { ServiceConfigAccessor } from '../config/index.ts'; import { composeMetadataContent } from '../services/metadata-composer.ts'; import { initializeMeshRegistry } from '../utils/mesh-utils.ts'; @@ -120,7 +121,8 @@ export const createMeshesRoutes = (config: ServiceConfigAccessor): OpenAPIHono = meshes.openapi(registerMeshRoute, async (c) => { const { name, parentPath } = c.req.valid('json'); - const path = join(parentPath, name); + const normalizedParentPath = normalizeFolderPath(parentPath); + const path = join(normalizedParentPath, name); if (meshRegistry[name]) { return c.json({ diff --git a/flow-service/src/utils/logger.ts b/flow-service/src/utils/logger.ts index 2e86eb9..b9a2cfb 100644 --- a/flow-service/src/utils/logger.ts +++ b/flow-service/src/utils/logger.ts @@ -1,6 +1,5 @@ import * as Sentry from 'npm:@sentry/deno'; -import { dirname } from 'https://deno.land/std@0.208.0/path/mod.ts'; -import { ensureDir } from 'https://deno.land/std@0.208.0/fs/mod.ts'; +import { ensureDir, dirname } from '../../../flow-core/src/deps.ts'; // Environment-based configuration const isDevelopment = Deno.env.get('FLOW_ENV') !== 'production' diff --git a/flow-service/src/utils/mesh-utils.ts b/flow-service/src/utils/mesh-utils.ts index 85cc727..d45c129 100644 --- a/flow-service/src/utils/mesh-utils.ts +++ b/flow-service/src/utils/mesh-utils.ts @@ -1,8 +1,6 @@ -import { basename, dirname } from 'jsr:@std/path'; import { ServiceConfigAccessor } from '../config/index.ts'; import { logger } from './logger.ts'; - -import { existsSync } from "https://deno.land/std@0.224.0/fs/mod.ts"; +import { basename, dirname, existsSync } from '../../../flow-core/src/deps.ts'; export const initializeMeshRegistry = (config: ServiceConfigAccessor, meshRegistry: Record) => { const meshPaths = config.meshPaths; diff --git a/flow-service/src/utils/startup-logger.ts b/flow-service/src/utils/startup-logger.ts index 4c6b63e..c33f294 100644 --- a/flow-service/src/utils/startup-logger.ts +++ b/flow-service/src/utils/startup-logger.ts @@ -6,8 +6,7 @@ import type { ServiceConfigAccessor } from '../config/resolution/service-config-resolver.ts'; import { MESH } from '../../../flow-core/src/mesh-constants.ts'; - -import { resolve } from "https://deno.land/std@0.224.0/path/mod.ts"; +import { resolve } from "../../../flow-core/src/deps.ts"; /** * Logs the service startup configuration details with a timestamp in US locale. diff --git a/flow-service/tests/integration/meshes-routes.integration.test.ts b/flow-service/tests/integration/meshes-routes.integration.test.ts index 92b2737..8a38405 100644 --- a/flow-service/tests/integration/meshes-routes.integration.test.ts +++ b/flow-service/tests/integration/meshes-routes.integration.test.ts @@ -1,4 +1,4 @@ -import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts"; +import { assertEquals } from '../../../flow-core/src/deps.ts'; Deno.test("Health endpoint is reachable", async () => { const response = await fetch("http://localhost:8080/api/health"); diff --git a/flow-service/tests/integration/test-formatting-direct.ts b/flow-service/tests/integration/test-formatting-direct.ts index 8ca72a6..e70201f 100644 --- a/flow-service/tests/integration/test-formatting-direct.ts +++ b/flow-service/tests/integration/test-formatting-direct.ts @@ -7,8 +7,7 @@ // Import the formatting functions directly from the logger module // We'll need to extract them for testing -import { dirname } from 'https://deno.land/std@0.208.0/path/mod.ts'; -import { ensureDir } from 'https://deno.land/std@0.208.0/fs/mod.ts'; +import { dirname, ensureDir } from '../../../flow-core/src/deps.ts'; type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL'; diff --git a/flow-service/tests/unit/merge-configs.test.ts b/flow-service/tests/unit/merge-configs.test.ts index 50bdd36..8880ff7 100644 --- a/flow-service/tests/unit/merge-configs.test.ts +++ b/flow-service/tests/unit/merge-configs.test.ts @@ -6,7 +6,7 @@ */ // @ts-nocheck - Unit tests intentionally test runtime behavior with various data types -import { assertEquals, assertNotStrictEquals } from "https://deno.land/std@0.224.0/assert/mod.ts"; +import { assertEquals, assertNotStrictEquals } from '../../../flow-core/src/deps.ts'; import { mergeConfigs } from "../../src/utils/merge-configs.ts"; Deno.test("mergeConfigs - Basic object merging", () => {