UNPKG

@emanuelsan/mosaic-js

Version:

Composable Markdown-based AI instruction engine for Node.js

80 lines 3.65 kB
import path from 'path'; import { findMarkdownFileById } from './findMarkdownFileById'; import { Effect, Context, Schema } from 'effect'; // Context export class Directory extends Context.Tag('Directory')() { } // export type TemplateSelectorType = 'relative' | 'root' | 'id'; const TemplateSelectorTypeSchema = Schema.Literal('relative', 'root', 'id'); const SelectorValidationSchema = Schema.Union(Schema.Struct({ valid: Schema.Literal(true), type: TemplateSelectorTypeSchema, }), Schema.Struct({ valid: Schema.Literal(false), })); /** * Effectful function that validates a MosaicJS template selector string and determines its type. * Accepts a selector and returns an Effect that, when executed, yields an object indicating whether the selector is valid, * and if valid, specifies its type ('relative', 'root', or 'id'). * * Supported selector formats: * - Relative path (e.g., 'some-dir/core-instructions') * - Root path prefixed with '@' (e.g., '@root-block', '@namespace/path') * - ID selector prefixed with '#' (e.g., '#some-id') * * Returns: Effect<SelectorValidation> */ export const isValidTemplateSelector = (selector) => Effect.sync(() => { // Type 1: relative path (no prefix) const relativePath = /^[a-zA-Z0-9_-]+(\/[a-zA-Z0-9_-]+)*$/; // Type 2: root path (starts with @) const rootPath = /^@[a-zA-Z0-9_-]+(\/[a-zA-Z0-9_-]+)*$/; // Type 3: id selector (starts with #) const idSelector = /^#[a-zA-Z0-9_-]+$/; if (relativePath.test(selector)) return { valid: true, type: 'relative' }; if (rootPath.test(selector)) return { valid: true, type: 'root' }; if (idSelector.test(selector)) return { valid: true, type: 'id' }; return { valid: false }; }); /** * Effectful function that normalizes a validated MosaicJS template selector to a relative path string. * * This is an unsafe function: it expects the provided selector to be already validated and of a correct type. * The function uses the selector type to determine how to normalize: * - For 'id' selectors (e.g., '#some-id'), it resolves the corresponding markdown file and returns the path relative to the template directory. * - For 'root' selectors (e.g., '@root-block'), it removes the '@' prefix and returns the path as relative to root. * - For 'relative' selectors, it returns the selector as-is. * * Context: Requires access to the template directory from the Directory context. * Returns: Effect<string | null> */ export const normalizeSelector = (selector, type) => Effect.gen(function* () { // Get the template directory from the context const directory = yield* Directory; const templateDir = yield* directory.templateDir; if (type === 'id') { const id = selector.slice(1); // remove '#' const foundPath = yield* Effect.sync(() => findMarkdownFileById(templateDir, id)); if (!foundPath) return null; // Return the path relative to the templateDir, without extension const relativePath = path .relative(templateDir, foundPath) .replace(/\\/g, '/'); return relativePath.replace(/\.md$/, ''); } if (type === 'root') { // Remove '@' prefix, return as relative to root return selector.slice(1); } // Already relative, just return as-is return selector; }); export const normalizeToRelativeSelector = (selector) => Effect.gen(function* () { const validation = yield* isValidTemplateSelector(selector); return validation.valid ? yield* normalizeSelector(selector, validation.type) : null; }); //# sourceMappingURL=normalizeToRelativeSelector.js.map