UNPKG

mcard-js

Version:

MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers

187 lines 10.1 kB
/** * VCard Application Vocabulary - Data-driven patterns for side effects. * * Resource types are defined as DATA in vcard_ext/, making the system * fully extensible without code changes. */ import { HashValidator } from './hash/HashValidator'; import { RESOURCE_REGISTRY, ResourceCategory } from './vcard_ext/index.js'; export { ResourceCategory, RESOURCE_REGISTRY }; // ============================================================================= // Core Resource Factory // ============================================================================= export class Resource { static async create(resourceType, ...argsAndOptions) { if (!(resourceType in RESOURCE_REGISTRY)) { throw new Error(`Unknown resource type: ${resourceType}. Available: ${Object.keys(RESOURCE_REGISTRY).join(', ')}`); } const rtype = RESOURCE_REGISTRY[resourceType]; // Separate positional args from options const positionalArgs = []; let options = {}; for (const arg of argsAndOptions) { if (typeof arg === 'object' && !Array.isArray(arg) && arg !== null) { options = { ...options, ...arg }; } else { positionalArgs.push(arg); } } options = { ...rtype.defaultOptions, ...options }; // Map positional args using type's argNames const templateVars = { ...options }; for (let i = 0; i < positionalArgs.length && i < rtype.argNames.length; i++) { templateVars[rtype.argNames[i]] = positionalArgs[i]; } const uri = Resource._buildUri(rtype.uriTemplate, templateVars); const hashContent = Resource._buildHashContent(rtype.hashTemplate, templateVars); const contentHash = await HashValidator.computeHash(new TextEncoder().encode(hashContent), 'sha256'); const status = Resource._determineStatus(resourceType, templateVars, options); const qosMetrics = { category: rtype.category }; for (const [k, v] of Object.entries(templateVars)) { if (v !== undefined && v !== null && !k.startsWith('_')) { qosMetrics[k] = v; } } return { uri, contentHash, status, qosMetrics }; } static _buildUri(template, vars) { const preserveUri = vars._preserveUri || false; let uri = template; for (const [key, value] of Object.entries(vars)) { if (value !== undefined && value !== null && !key.startsWith('_')) { let strVal = String(value); if (!preserveUri && (key === 'endpoint' || key === 'collectorUrl')) { strVal = strVal.replace('http://', '').replace('https://', ''); } uri = uri.replace(`{${key}}`, strVal); } } return uri; } static _buildHashContent(template, vars) { let content = template; for (const [key, value] of Object.entries(vars)) { content = content.replace(`{${key}}`, value !== undefined ? String(value) : 'undefined'); } return content; } static _determineStatus(resourceType, vars, options) { const required = options.required !== false; if (resourceType === 'env') { const name = vars.name || ''; const actualValue = typeof process !== 'undefined' ? (process.env[name] ?? options.default) : undefined; if (actualValue !== undefined) return 'verified'; return required ? 'invalid' : 'pending'; } return 'pending'; } static types() { return Object.keys(RESOURCE_REGISTRY); } static register(type) { RESOURCE_REGISTRY[type.name] = type; } } // ============================================================================= // Helper Functions // ============================================================================= export async function createLgtmStack(baseUrl, serviceName, options = {}) { const { environment = 'production', ports = {} } = options; const p = { grafana: 3000, prometheus: 9090, loki: 3100, tempo: 4317, ...ports }; return Promise.all([ Resource.create('grafana', `${baseUrl}:${p.grafana}`, { description: `Grafana for ${serviceName}` }), Resource.create('prometheus', `${baseUrl}:${p.prometheus}`, { jobName: serviceName }), Resource.create('loki', `${baseUrl}:${p.loki}`, { labels: { service: serviceName, environment } }), Resource.create('tempo', `${baseUrl}:${p.tempo}`, { serviceName }) ]); } // ============================================================================= // Convenience Wrappers // ============================================================================= export var AccessMode; (function (AccessMode) { AccessMode["READ"] = "read"; AccessMode["WRITE"] = "write"; AccessMode["READ_WRITE"] = "rw"; AccessMode["EXECUTE"] = "execute"; })(AccessMode || (AccessMode = {})); export class EnvResource { static async create(name, options = {}) { return Resource.create('env', name, options); } static resolve(ref) { if (!ref.uri.startsWith('env://')) throw new Error(`Not an env:// URI: ${ref.uri}`); return typeof process !== 'undefined' ? process.env[ref.uri.replace('env://', '')] : undefined; } } export class FileResource { static async create(path, options = {}) { return Resource.create('file', path, { ...options, mode: options.mode || AccessMode.READ }); } static async createDirectory(path, options = {}) { return Resource.create('directory', path, options); } } export class StorageResource { static async createSqlite(path, options = {}) { return Resource.create('sqlite', path, options); } static async createPostgres(conn, options = {}) { return Resource.create('postgres', conn, options); } static async createS3(bucket, key, options = {}) { return Resource.create('s3', bucket, key, options); } static async createLitefs(path, options = {}) { return Resource.create('litefs', path, options); } static async createTurso(db, options = {}) { return Resource.create('turso', db, options); } } export class NetworkResource { static async createApi(endpoint, options = {}) { return Resource.create('api', endpoint, options); } static async createWebhook(endpoint, options = {}) { return Resource.create('webhook', endpoint, options); } } export class ObservabilityResource { static async createGrafana(endpoint, options = {}) { return Resource.create('grafana', endpoint, { ...options, endpoint }); } static async createPrometheus(endpoint, options = {}) { return Resource.create('prometheus', endpoint, { ...options, endpoint }); } static async createLoki(endpoint, options = {}) { return Resource.create('loki', endpoint, { ...options, endpoint }); } static async createTempo(endpoint, options = {}) { return Resource.create('tempo', endpoint, { ...options, endpoint }); } static async createFaro(collectorUrl, appName, options = {}) { return Resource.create('faro', collectorUrl, appName, { ...options, collectorUrl }); } static async createOtlp(endpoint, options = {}) { return Resource.create('otlp', endpoint, { ...options, endpoint }); } static async createLgtmStack(baseUrl, serviceName, options) { return createLgtmStack(baseUrl, serviceName, options); } } // ============================================================================= // Application Resources Manager // ============================================================================= export class ApplicationResources { resources = []; async add(resourceType, ...argsAndOptions) { this.resources.push(await Resource.create(resourceType, ...argsAndOptions)); return this; } async addEnv(name, options) { return this.add('env', name, options || {}); } async addFile(path, options) { return this.add('file', path, options || {}); } async addDirectory(path, options) { return this.add('directory', path, options || {}); } async addSqlite(path, options) { return this.add('sqlite', path, options || {}); } async addLitefs(path, options) { return this.add('litefs', path, options || {}); } async addTurso(db, options) { return this.add('turso', db, options || {}); } async addApi(endpoint, options) { return this.add('api', endpoint, options || {}); } async addGrafana(endpoint, options) { return this.add('grafana', endpoint, options || {}); } async addPrometheus(endpoint, options) { return this.add('prometheus', endpoint, options || {}); } async addLoki(endpoint, options) { return this.add('loki', endpoint, options || {}); } async addTempo(endpoint, options) { return this.add('tempo', endpoint, options || {}); } async addFaro(url, app, options) { this.resources.push(await ObservabilityResource.createFaro(url, app, options)); return this; } async addOtlp(endpoint, options) { return this.add('otlp', endpoint, options || {}); } async addLgtmStack(baseUrl, serviceName, options) { this.resources.push(...await createLgtmStack(baseUrl, serviceName, options)); return this; } getAll() { return this.resources; } getByCategory(category) { const cat = typeof category === 'string' ? category : category; return this.resources.filter(r => r.qosMetrics?.category === cat); } getObservabilityResources() { return this.resources.filter(r => r.qosMetrics?.category === 'observability'); } getInvalid() { return this.resources.filter(r => r.status === 'invalid'); } validate() { const invalid = this.getInvalid(); return { valid: invalid.length === 0, total: this.resources.length, verified: this.resources.filter(r => r.status === 'verified').length, pending: this.resources.filter(r => r.status === 'pending').length, invalid: invalid.length, invalidResources: invalid.map(r => r.uri) }; } } //# sourceMappingURL=vcard_vocabulary.js.map