UNPKG

mcard-js

Version:

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

1,531 lines (1,510 loc) 127 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/model/hash/HashValidator.ts var HashValidator; var init_HashValidator = __esm({ "src/model/hash/HashValidator.ts"() { "use strict"; HashValidator = class { /** * Compute hash of content using specified algorithm */ static async computeHash(content, algorithm = "sha256") { const data = typeof content === "string" ? new TextEncoder().encode(content) : content; let algoName = "SHA-256"; switch (algorithm.toLowerCase()) { case "sha1": algoName = "SHA-1"; break; case "sha-1": algoName = "SHA-1"; break; case "sha256": algoName = "SHA-256"; break; case "sha-256": algoName = "SHA-256"; break; case "sha384": algoName = "SHA-384"; break; case "sha-384": algoName = "SHA-384"; break; case "sha512": algoName = "SHA-512"; break; case "sha-512": algoName = "SHA-512"; break; default: console.warn(`Algorithm ${algorithm} not natively supported or mapped, defaulting to SHA-256`); algoName = "SHA-256"; } const buffer = new Uint8Array(data).buffer; const hashBuffer = await crypto.subtle.digest(algoName, buffer); return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join(""); } /** * Validate that content matches expected hash */ static async validate(content, expectedHash) { const computedHash = await this.computeHash(content); return computedHash === expectedHash; } }; } }); // src/model/GTime.ts var VALID_HASH_ALGORITHMS, GTime; var init_GTime = __esm({ "src/model/GTime.ts"() { "use strict"; VALID_HASH_ALGORITHMS = ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"]; GTime = class { static DEFAULT_ALGORITHM = "sha256"; /** * Generate a GTime stamp for the current moment * Format: HASH_ALGO|TIMESTAMP|REGION_CODE */ static stampNow(hashAlgorithm = this.DEFAULT_ALGORITHM) { const algo = hashAlgorithm.toLowerCase(); const timestamp = (/* @__PURE__ */ new Date()).toISOString(); const region = "UTC"; return `${algo}|${timestamp}|${region}`; } /** * Parse a GTime string */ static parse(gtime) { const parts = gtime.split("|"); if (parts.length !== 3) { throw new Error(`Invalid GTime format: ${gtime}`); } return { algorithm: parts[0], timestamp: new Date(parts[1]), region: parts[2] }; } /** * Get the hash algorithm from a GTime string */ static getHashAlgorithm(gtime) { return this.parse(gtime).algorithm; } /** * Get the timestamp from a GTime string */ static getTimestamp(gtime) { return this.parse(gtime).timestamp; } /** * Get the region code from a GTime string */ static getRegionCode(gtime) { return this.parse(gtime).region; } /** * Check if the provided hash function is valid. * Matches Python's GTime.is_valid_hash_function() */ static isValidHashFunction(hashFunction) { if (!hashFunction || typeof hashFunction !== "string") { return false; } return VALID_HASH_ALGORITHMS.includes(hashFunction.toLowerCase()); } /** * Check if the provided region code is valid. * Matches Python's GTime.is_valid_region_code() */ static isValidRegionCode(regionCode) { return Boolean(regionCode && regionCode === regionCode.toUpperCase()); } /** * Check if the provided timestamp is in ISO format. * Matches Python's GTime.is_iso_format() */ static isIsoFormat(timestamp) { if (!timestamp || typeof timestamp !== "string") { return false; } try { const date = new Date(timestamp); if (isNaN(date.getTime())) { return false; } const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/; return isoPattern.test(timestamp); } catch { return false; } } }; } }); // src/model/constants.ts var EVENT_CONSTANTS, ALGORITHM_HIERARCHY; var init_constants = __esm({ "src/model/constants.ts"() { "use strict"; EVENT_CONSTANTS = { TYPE: "type", HASH: "hash", FIRST_G_TIME: "first_g_time", CONTENT_SIZE: "content_size", COLLISION_TIME: "collision_time", UPGRADED_FUNCTION: "upgraded_function", UPGRADED_HASH: "upgraded_hash", DUPLICATE_TIME: "duplicate_time", DUPLICATE_EVENT_TYPE: "duplicate", COLLISION_EVENT_TYPE: "collision" }; ALGORITHM_HIERARCHY = { "sha1": { strength: 1, next: "sha224" }, "sha224": { strength: 2, next: "sha256" }, "sha256": { strength: 3, next: "sha384" }, "sha384": { strength: 4, next: "sha512" }, "sha512": { strength: 5, next: "custom" }, "custom": { strength: 6, next: null } }; } }); // src/model/EventProducer.ts var EventProducer_exports = {}; __export(EventProducer_exports, { generateCollisionEvent: () => generateCollisionEvent, generateDuplicationEvent: () => generateDuplicationEvent }); async function generateCollisionEvent(card) { const currentHashFunction = GTime.getHashAlgorithm(card.g_time); const nextAlgo = nextHashFunction(currentHashFunction); const upgradedHash = await HashValidator.computeHash(card.content, nextAlgo); const event = { [EVENT_CONSTANTS.TYPE]: EVENT_CONSTANTS.COLLISION_EVENT_TYPE, [EVENT_CONSTANTS.HASH]: card.hash, [EVENT_CONSTANTS.FIRST_G_TIME]: card.g_time, [EVENT_CONSTANTS.COLLISION_TIME]: card.g_time, // Using original card's time as per Python logic reference [EVENT_CONSTANTS.CONTENT_SIZE]: card.content.length, [EVENT_CONSTANTS.UPGRADED_FUNCTION]: nextAlgo, [EVENT_CONSTANTS.UPGRADED_HASH]: upgradedHash }; return JSON.stringify(event); } function generateDuplicationEvent(card) { const event = { [EVENT_CONSTANTS.TYPE]: EVENT_CONSTANTS.DUPLICATE_EVENT_TYPE, [EVENT_CONSTANTS.HASH]: card.hash, [EVENT_CONSTANTS.DUPLICATE_TIME]: card.g_time }; return JSON.stringify(event); } function nextHashFunction(current) { const currentLower = current.toLowerCase(); const entry = ALGORITHM_HIERARCHY[currentLower]; if (entry && entry.next) { return entry.next; } return "sha256"; } var init_EventProducer = __esm({ "src/model/EventProducer.ts"() { "use strict"; init_constants(); init_GTime(); init_HashValidator(); } }); // src/index.browser.ts var index_browser_exports = {}; __export(index_browser_exports, { ALGORITHM_HIERARCHY: () => ALGORITHM_HIERARCHY, CardCollection: () => CardCollection, ContentHandle: () => ContentHandle, ContentTypeInterpreter: () => ContentTypeInterpreter, EVENT_CONSTANTS: () => EVENT_CONSTANTS, Either: () => Either, ErrorCodes: () => ErrorCodes, FaroSidecar: () => FaroSidecar, GTime: () => GTime, HandleValidationError: () => HandleValidationError, HashValidator: () => HashValidator, IO: () => IO, IndexedDBEngine: () => IndexedDBEngine, LensProtocol: () => LensProtocol, MCard: () => MCard, MCardStore: () => MCardStore, Maybe: () => Maybe, Reader: () => Reader, ServiceWorkerPTR: () => ServiceWorkerPTR, SqliteWasmEngine: () => SqliteWasmEngine, State: () => State, ValidationRegistry: () => ValidationRegistry, VerificationStatus: () => VerificationStatus, Writer: () => Writer, computeHash: () => computeHash, validateHandle: () => validateHandle, validationRegistry: () => validationRegistry }); module.exports = __toCommonJS(index_browser_exports); // src/model/MCard.ts init_HashValidator(); init_GTime(); // src/model/detectors/BinaryDetector.ts var BinarySignatureDetector = class _BinarySignatureDetector { contentTypeName = "binary"; // Signatures map: [Signature Bytes, Mime Type] static SIGNATURES = [ [new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), "image/png"], [new Uint8Array([255, 216, 255]), "image/jpeg"], [new Uint8Array([71, 73, 70, 56, 55, 97]), "image/gif"], // GIF87a [new Uint8Array([71, 73, 70, 56, 57, 97]), "image/gif"], // GIF89a [new Uint8Array([66, 77]), "image/bmp"], // BM [new Uint8Array([0, 0, 1, 0]), "image/x-icon"], [new Uint8Array([0, 0, 2, 0]), "image/x-icon"], [new Uint8Array([37, 80, 68, 70]), "application/pdf"], // %PDF [new Uint8Array([80, 75, 3, 4]), "application/zip"], // PK.. [new Uint8Array([31, 139, 8]), "application/gzip"], [new Uint8Array([82, 97, 114, 33, 26, 7, 0]), "application/x-rar-compressed"], [new Uint8Array([55, 122, 188, 175, 39, 28]), "application/x-7z-compressed"], [new Uint8Array([83, 81, 76, 105, 116, 101, 32, 102, 111, 114, 109, 97, 116, 32, 51, 0]), "application/x-sqlite3"] ]; // Extension-to-MIME mapping for binary types (used when byte detection fails) static EXT_TO_MIME = { // Video ".mp4": "video/mp4", ".webm": "video/webm", ".avi": "video/x-msvideo", ".mov": "video/quicktime", ".mkv": "video/x-matroska", ".wmv": "video/x-ms-wmv", ".flv": "video/x-flv", ".m4v": "video/x-m4v", // Audio ".mp3": "audio/mpeg", ".ogg": "audio/ogg", ".flac": "audio/flac", ".aac": "audio/aac", ".m4a": "audio/mp4", ".wma": "audio/x-ms-wma", // Images (backup for when signature detection fails) ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".ico": "image/x-icon", ".webp": "image/webp", ".svg": "image/svg+xml", // Documents ".pdf": "application/pdf", // Archives ".zip": "application/zip", ".gz": "application/gzip", ".rar": "application/x-rar-compressed", ".7z": "application/x-7z-compressed", ".tar": "application/x-tar", // Database ".db": "application/x-sqlite3", ".sqlite": "application/x-sqlite3", ".sqlite3": "application/x-sqlite3", // Fonts ".woff": "font/woff", ".woff2": "font/woff2", ".ttf": "font/ttf", ".otf": "font/otf", ".eot": "application/vnd.ms-fontobject" }; detect(contentSample, lines, firstLine, fileExtension) { const mime = this.getMimeType(contentSample, lines, firstLine, fileExtension); return mime && mime !== "application/octet-stream" ? 0.95 : 0; } getMimeType(contentSample, lines, firstLine, fileExtension) { const bytes = this.toBytes(contentSample); const detected = this.detectFromBytes(bytes); if (detected !== "application/octet-stream") { return detected; } if (fileExtension) { let ext = fileExtension.toLowerCase(); const lastDotIndex = ext.lastIndexOf("."); if (lastDotIndex > -1) { ext = ext.substring(lastDotIndex); } else if (!ext.startsWith(".")) { if (ext.includes("/") || ext.includes("\\")) { return "application/octet-stream"; } ext = "." + ext; } const extMime = _BinarySignatureDetector.EXT_TO_MIME[ext]; if (extMime) { return extMime; } } return "application/octet-stream"; } /** * Detect MIME type directly from bytes. */ detectFromBytes(bytes) { if (this.startsWith(bytes, new Uint8Array([82, 73, 70, 70]))) { return this.detectRiffFormat(bytes); } for (const [sig, mime] of _BinarySignatureDetector.SIGNATURES) { if (this.startsWith(bytes, sig)) { if (mime === "application/zip") { return this.detectZipType(bytes); } return mime; } } return "application/octet-stream"; } toBytes(content) { if (content instanceof Uint8Array) return content; return new TextEncoder().encode(content); } startsWith(data, prefix) { if (data.length < prefix.length) return false; for (let i = 0; i < prefix.length; i++) { if (data[i] !== prefix[i]) return false; } return true; } detectRiffFormat(bytes) { if (bytes.length < 12) return "application/octet-stream"; const format = new TextDecoder().decode(bytes.slice(8, 12)); if (format === "WAVE") return "audio/wav"; if (format === "WEBP") return "image/webp"; return "application/octet-stream"; } detectZipType(bytes) { const header = new TextDecoder().decode(bytes.slice(0, 2048)); if (header.includes("[Content_Types].xml") && header.includes("_rels/.rels")) { if (header.includes("word/")) return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; if (header.includes("xl/")) return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; if (header.includes("ppt/")) return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; } return "application/zip"; } }; // src/model/detectors/LanguageDetector.ts var ProgrammingLanguageDetector = class { contentTypeName = "code"; detect(contentSample, lines, firstLine, fileExtension) { const mime = this.getMimeType(contentSample, lines, firstLine, fileExtension); return mime && mime !== "text/plain" ? 0.95 : 0; } getMimeType(contentSample, lines, firstLine, fileExtension) { const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); if (this.isPython(firstLine, text, lines)) { return "text/x-python"; } const cType = this.detectCFamily(text); if (cType) return cType; const jsType = this.detectJsType(text); if (jsType) return jsType; if (this.isTypescript(text)) { return "text/typescript"; } return "text/plain"; } isPython(firstLine, text, lines) { if (/^\s*import\s+(\w+|\w+\.\w+)/m.test(text) || /^\s*from\s+(\w+|\w+\.\w+)\s+import\s+/m.test(text)) { const stdLibs = ["os", "sys", "re", "json", "math", "random", "datetime"]; if (stdLibs.some((lib) => text.includes(`import ${lib}`) || text.includes(`from ${lib}`))) { return true; } } if (firstLine.startsWith("#!") && firstLine.toLowerCase().includes("python")) return true; if (text.includes("if __name__ ==") && text.includes("__main__")) return true; if (/^\s*def\s+\w+\s*\(/.test(text) && !text.includes("function")) return true; if (/^\s*class\s+\w+\s*[\(:]/m.test(text)) return true; if (/^\s*@\w+/m.test(text)) return true; let count = 0; const patterns = [ /\bif\b.*?:/, /\belif\b.*?:/, /\belse\s*:/, /\bfor\b.*?\bin\b.*?:/, /\bwhile\b.*?:/, /\btry\s*:/, /\bexcept\b.*?:/, /\bfinally\s*:/, /\bNone\b/, /\bTrue\b/, /\bFalse\b/, /f["'].*?\{.*?\}["']/, // f-string /\bdef\b/, /\bclass\b/, /\bimport\b/, /\bfrom\b/, /\blambda\b.*?:/ ]; for (const p of patterns) { if (p.test(text)) count++; } const nonEmptyLines = lines.filter((l) => l.trim().length > 0).length; if (nonEmptyLines <= 5 && count >= 1) { return true; } return count >= 3; } detectCFamily(text) { const cPatterns = [ /#include\s*<.*?>/, /#include\s*".*?"/, /\b(int|void|char|float|double)\s+main\s*\(.*\)\s*\{/, /\bstruct\s+\w+\s*\{/, /#define\s+\w+/, /printf\(.*?\);/, /scanf\(.*?\);/ ]; const cppPatterns = [ /\bclass\s+\w+\s*\{/, /\bnamespace\s+\w+\s*\{/, /\btemplate\s*<.*?>/, /::/, /\bstd::/, /\bcout\s*<</, /\bcin\s*>>/, /\bnew\s+\w+/, /\bdelete\s+\w+/, /#include\s*<iostream>/ ]; let cCount = 0; let cppCount = 0; cPatterns.forEach((p) => { if (p.test(text)) cCount++; }); cppPatterns.forEach((p) => { if (p.test(text)) cppCount++; }); if (cppCount >= 2 || cppCount >= 1 && text.includes("std::")) return "text/x-c++"; if (cCount >= 2) return "text/x-c"; return null; } detectJsType(text) { const jsPatterns = [ /function\s+\w+\s*\(/.test(text), // function foo( /\bconst\s+\w+\s*=/.test(text), /\blet\s+\w+\s*=/.test(text), /\bvar\s+\w+\s*=/.test(text), /\bimport\s+.*\s+from/.test(text), /\bexport\s+/.test(text), /\=\>\s*\{/.test(text), // Arrow func /console\.log\(/.test(text) ]; const jsxPatterns = [ /<\w+(>|\s+.*?>)[\s\S]*?<\/\w+>/m.test(text), /<\w+\s+\/>/m.test(text), /className=/.test(text), /React\.createElement/.test(text) ]; const jsCount = jsPatterns.filter(Boolean).length; const jsxCount = jsxPatterns.filter(Boolean).length; if (jsxCount > 0 && (text.includes("import React") || text.includes('from "react"'))) return "text/jsx"; if (jsxCount >= 2) return "text/jsx"; if (jsCount >= 2) { const stripped = text.trim(); if (stripped.startsWith("{") && stripped.endsWith("}") || stripped.startsWith("[") && stripped.endsWith("]")) { try { JSON.parse(text); if (jsCount < 2) return null; } catch { } } return "text/javascript"; } return null; } isTypescript(text) { const tsPatterns = [ /:\s*(string|number|boolean|any|void|null|undefined)\b/, /\binterface\s+\w+\s*\{/, /\bclass\s+\w+\s+implements\s+\w+/, /\btype\s+\w+\s*=/, /\b(public|private|protected)\s+/, /\bnamespace\s+\w+\s*\{/, /<\w+>/ // Generics (simple check) ]; let count = 0; tsPatterns.forEach((p) => { if (p.test(text)) count++; }); return count >= 2; } }; // src/model/detectors/MarkupDetectors.ts var XMLDetector = class _XMLDetector { contentTypeName = "xml"; static XML_DECLARATION = /^\s*<\?xml/i; static BASIC_TAG_PAIR = /<(\w+)[^>]*>.*?<\/\1>/s; detect(contentSample, lines, firstLine, fileExtension) { const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); let confidence = 0; if (fileExtension && fileExtension.toLowerCase() === ".xml") { confidence = Math.max(confidence, 0.95); } if (_XMLDetector.XML_DECLARATION.test(firstLine) || text.trim().startsWith("<?xml")) { confidence = Math.max(confidence, 0.95); } if (text.includes("<") && text.includes(">") && text.includes("</")) { confidence = Math.max(confidence, 0.5); if (_XMLDetector.BASIC_TAG_PAIR.test(text)) { confidence = Math.max(confidence, 0.7); } } if (text.toLowerCase().includes("<!doctype html")) { if (confidence > 0.3) confidence -= 0.4; } return Math.min(Math.max(confidence, 0), 1); } getMimeType(contentSample, lines, firstLine, fileExtension) { const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); if (fileExtension === ".xml") return "application/xml"; if (text.toLowerCase().includes("<svg")) return "image/svg+xml"; if (text.toLowerCase().includes("<html") || text.toLowerCase().includes("<!doctype html")) return "text/html"; if (this.detect(contentSample, lines, firstLine, fileExtension) > 0.5) return "application/xml"; return "text/plain"; } }; var MarkdownDetector = class _MarkdownDetector { contentTypeName = "markdown"; static MD_PATTERNS = [ /^#{1,6}\s+\S+/, // ATX Headers /^\s*[\*\+\-]\s+\S+/, // List items /^\s*\d+\.\s+\S+/, // Ordered list items /`{1,3}[^`]+`{1,3}/, // Inline code /\[[^\]]+\]\([^\)]+\)/, // Links /!\[[^\]]+\]\([^\)]+\)/, // Images /^\s*>.*/ // Blockquotes ]; static SETEXT_HEADER = /^.*\n(?:={3,}|-{3,})\s*$/m; detect(contentSample, lines, firstLine, fileExtension) { const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); let confidence = 0; if (fileExtension && [".md", ".markdown"].includes(fileExtension.toLowerCase())) { confidence = Math.max(confidence, 0.95); } let mdFeatures = 0; if (_MarkdownDetector.SETEXT_HEADER.test(text)) mdFeatures += 2; for (const line of lines.slice(0, 20)) { if (_MarkdownDetector.MD_PATTERNS.some((p) => p.test(line))) { mdFeatures++; } } const hasCodeFence = text.includes("```"); if (hasCodeFence) mdFeatures++; if (mdFeatures > 1 && hasCodeFence) confidence = Math.max(confidence, 0.85); if (mdFeatures > 3 && hasCodeFence) confidence = Math.max(confidence, 0.95); else if (mdFeatures > 1) confidence = Math.max(confidence, 0.6); else if (mdFeatures > 3) confidence = Math.max(confidence, 0.8); else if (mdFeatures > 5) confidence = Math.max(confidence, 0.9); const stripped = text.trim(); if (stripped.startsWith("{") && stripped.endsWith("}") || stripped.startsWith("[") && stripped.endsWith("]")) { try { JSON.parse(text); if (confidence > 0.3) confidence -= 0.4; } catch { } } if (stripped.startsWith("<") && text.includes("<?xml")) { if (confidence > 0.3) confidence -= 0.4; } return Math.min(Math.max(confidence, 0), 1); } getMimeType(contentSample, lines, firstLine, fileExtension) { return this.detect(contentSample, lines, firstLine, fileExtension) > 0.5 ? "text/markdown" : "text/plain"; } }; var PlainTextDetector = class _PlainTextDetector { contentTypeName = "text"; static IMAGE_EXTS = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"]; detect(contentSample, lines, firstLine, fileExtension) { if (!contentSample && lines.length === 0) return 0.1; if (fileExtension) { const ext = fileExtension.toLowerCase(); if (_PlainTextDetector.IMAGE_EXTS.includes(ext) || ext === ".pdf") return 0; } const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); if (text.includes(",") && lines.length < 5) { const commaLines = lines.filter((l) => l.includes(",")).length; if (commaLines > 0 && commaLines === lines.length) { return 0.8; } } return 0.15; } getMimeType(contentSample, lines, firstLine, fileExtension) { return "text/plain"; } }; // src/model/detectors/DataFormatDetectors.ts var SQLDetector = class _SQLDetector { contentTypeName = "sql"; // Keywords (case insensitive checking handled in method) static KEYWORDS = [ "SELECT ", "INSERT ", "UPDATE ", "DELETE ", "CREATE ", "DROP ", "ALTER ", "FROM ", "WHERE ", "JOIN ", "TABLE ", "INTO ", "VALUES ", "SET ", "PRIMARY KEY" ]; detect(contentSample, lines, firstLine, fileExtension) { const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); let confidence = 0; if (fileExtension && fileExtension.toLowerCase() === ".sql") { confidence = Math.max(confidence, 0.95); } let hits = 0; const upperText = text.toUpperCase(); for (const line of lines.slice(0, 10)) { const upperLine = line.toUpperCase(); for (const kw of _SQLDetector.KEYWORDS) { if (upperLine.includes(kw)) { hits++; } } } if (hits >= 2) confidence = Math.max(confidence, 0.85); else if (hits === 1) confidence = Math.max(confidence, 0.6); return Math.min(confidence, 1); } getMimeType(contentSample, lines, firstLine, fileExtension) { return this.detect(contentSample, lines, firstLine, fileExtension) > 0.5 ? "text/x-sql" : "text/plain"; } }; var JSONDetector = class { contentTypeName = "json"; detect(contentSample, lines, firstLine, fileExtension) { const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); if (fileExtension && fileExtension.toLowerCase() === ".json") { return this.verifyJsonStructure(text) ? 0.95 : 0.6; } const stripped = text.trim(); if (!(stripped.startsWith("{") && stripped.endsWith("}") || stripped.startsWith("[") && stripped.endsWith("]"))) { return 0; } for (const line of lines.slice(0, 5)) { const l = line.trim(); if (l.startsWith("//") || l.startsWith("/*")) return 0; } try { JSON.parse(text); return 0.9; } catch (e) { return 0; } } getMimeType(contentSample, lines, firstLine, fileExtension) { return this.detect(contentSample, lines, firstLine, fileExtension) > 0.5 ? "application/json" : "text/plain"; } verifyJsonStructure(text) { try { JSON.parse(text); return true; } catch { return false; } } }; var YAMLDetector = class _YAMLDetector { contentTypeName = "yaml"; static YAML_START_PATTERNS = [/^---\s*$/, /^%YAML/]; static KEY_VALUE_PATTERN = /^\s*[\w.-]+:\s+(?![=\{\[])/; static LIST_ITEM_PATTERN = /^\s*-\s+[\w\'\"]/; detect(contentSample, lines, firstLine, fileExtension) { const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); let confidence = 0; if (fileExtension && [".yaml", ".yml"].includes(fileExtension.toLowerCase())) { confidence = Math.max(confidence, 0.95); } if (_YAMLDetector.YAML_START_PATTERNS.some((p) => p.test(firstLine))) { confidence = Math.max(confidence, 0.9); } let yamlFeatures = 0; if (_YAMLDetector.YAML_START_PATTERNS.some((p) => new RegExp(p.source, "m").test(text))) { yamlFeatures += 2; } for (const line of lines.slice(0, 20)) { const stripped = line.trim(); if (_YAMLDetector.KEY_VALUE_PATTERN.test(stripped)) yamlFeatures++; else if (_YAMLDetector.LIST_ITEM_PATTERN.test(stripped)) yamlFeatures++; } const firstNonEmpty = lines.find((l) => l.trim().length > 0) || ""; if (firstNonEmpty.trim() === "---") { if (yamlFeatures > 1) confidence = Math.max(confidence, 0.5); if (yamlFeatures > 3) confidence = Math.max(confidence, 0.75); if (yamlFeatures > 5) confidence = Math.max(confidence, 0.9); } else { if (fileExtension && [".yaml", ".yml"].includes(fileExtension.toLowerCase())) { } } return Math.min(Math.max(confidence, 0), 1); } getMimeType(contentSample, lines, firstLine, fileExtension) { const conf = this.detect(contentSample, lines, firstLine, fileExtension); return conf > 0.5 ? "application/x-yaml" : "text/plain"; } }; var CSVDetector = class { contentTypeName = "csv"; detect(contentSample, lines, firstLine, fileExtension) { const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); if (fileExtension && fileExtension.toLowerCase() === ".csv") { return this.verifyCsvStructure(lines) ? 0.95 : 0.6; } return this.analyzeCsvContent(lines); } getMimeType(contentSample, lines, firstLine, fileExtension) { return this.detect(contentSample, lines, firstLine, fileExtension) > 0.5 ? "text/csv" : "text/plain"; } verifyCsvStructure(lines) { const sampleLines = lines.slice(0, 10).filter((l) => l.trim().length > 0); if (sampleLines.length === 0) return false; if (!sampleLines.every((l) => l.includes(","))) return false; const counts = sampleLines.map((l) => (l.match(/,/g) || []).length); const uniqueCounts = [...new Set(counts)]; if (uniqueCounts.length === 1 && uniqueCounts[0] > 0) return true; if (sampleLines.length > 1) { const dataCounts = counts.slice(1); const uniqueData = [...new Set(dataCounts)]; if (uniqueData.length === 1 && uniqueData[0] > 0) return true; } return false; } analyzeCsvContent(lines) { if (!lines || lines.length === 0) return 0; const sampleLines = lines.slice(0, 10).filter((l) => l.trim().length > 0); if (sampleLines.length === 0 || !sampleLines.every((l) => l.includes(","))) return 0; const counts = sampleLines.map((l) => (l.match(/,/g) || []).length); const uniqueCounts = [...new Set(counts)]; if (uniqueCounts.length === 1 && uniqueCounts[0] > 0) return 0.9; if (sampleLines.length > 1) { const dataCounts = counts.slice(1); const uniqueData = [...new Set(dataCounts)]; if (uniqueData.length === 1 && uniqueData[0] > 0) return 0.8; } if (counts.every((c) => c > 0)) return 0.5; return 0; } }; // src/model/detectors/OBJDetector.ts var OBJDetector = class _OBJDetector { contentTypeName = "obj"; // Check for 'v ' (vertex), 'f ' (face), 'vn ', 'vt ' static COMMANDS = ["v ", "vt ", "vn ", "f ", "g ", "o ", "s ", "mtllib ", "usemtl "]; detect(contentSample, lines, firstLine, fileExtension) { const text = typeof contentSample === "string" ? contentSample : new TextDecoder().decode(contentSample); let confidence = 0; if (fileExtension && fileExtension.toLowerCase() === ".obj") { confidence = Math.max(confidence, 0.95); } const validLines = lines.filter((l) => l.trim().length > 0 && !l.trim().startsWith("#")); let commandCount = 0; for (const line of validLines.slice(0, 20)) { const trimmed = line.trim(); for (const cmd of _OBJDetector.COMMANDS) { if (trimmed.startsWith(cmd)) { commandCount++; break; } } } if (commandCount >= 2) { if (commandCount > 10) confidence = Math.max(confidence, 0.9); else if (commandCount > 5) confidence = Math.max(confidence, 0.8); else confidence = Math.max(confidence, 0.7); } const codeKeywords = ["def ", "class ", "import ", "function ", "var ", "let ", "const "]; if (codeKeywords.some((k) => text.includes(k))) { return 0; } return Math.min(confidence, 1); } getMimeType(contentSample, lines, firstLine, fileExtension) { return this.detect(contentSample, lines, firstLine, fileExtension) > 0.5 ? "application/3d-obj" : "text/plain"; } }; // src/model/detectors/registry.ts var DetectorRegistry = class { detectors; constructor() { this.detectors = [ new BinarySignatureDetector(), // Programming languages new ProgrammingLanguageDetector(), // Structured data new XMLDetector(), new JSONDetector(), new OBJDetector(), // Markup new MarkdownDetector(), // Data formats (lower priority) new SQLDetector(), new CSVDetector(), new YAMLDetector(), // Fallback new PlainTextDetector() ]; } /** * Detect content type and return the most likely MIME type. */ detect(contentSample, lines, firstLine, fileExtension) { const hasComma = lines.some((l) => l.includes(",")); if (hasComma) { if (lines.length < 3) { const commaLines = lines.filter((l) => l.includes(",")).length; if (commaLines > 0 && commaLines === lines.length) { const delimCounts = lines.filter((l) => l.trim()).map((l) => (l.match(/,/g) || []).length); if (delimCounts.length > 0 && delimCounts.every((c) => c <= 2)) { return "text/plain"; } } } } let bestConfidence = 0; let bestMime = "text/plain"; for (const detector of this.detectors) { const confidence = detector.detect(contentSample, lines, firstLine, fileExtension); if (confidence > bestConfidence) { const mime = detector.getMimeType(contentSample, lines, firstLine, fileExtension); if (mime) { bestConfidence = confidence; bestMime = mime; if (confidence >= 0.99) break; } } } return bestMime; } }; var registry = new DetectorRegistry(); // src/model/ContentTypeInterpreter.ts var ContentTypeInterpreter = class { static MIME_TO_EXT = { "text/plain": ".txt", "application/json": ".json", "text/csv": ".csv", "text/x-python": ".py", "text/javascript": ".js", "text/jsx": ".jsx", "text/typescript": ".ts", "text/x-c": ".c", "text/x-c++": ".cpp", "application/xml": ".xml", "text/html": ".html", "application/x-yaml": ".yaml", "text/markdown": ".md", "text/x-sql": ".sql", "image/png": ".png", "image/jpeg": ".jpg", "image/gif": ".gif", "image/bmp": ".bmp", "image/x-icon": ".ico", "image/webp": ".webp", "image/svg+xml": ".svg", "application/pdf": ".pdf", "application/zip": ".zip", "application/gzip": ".gz", "application/x-rar-compressed": ".rar", "application/x-7z-compressed": ".7z", "application/x-sqlite3": ".db", "audio/wav": ".wav" // Add more as needed }; /** * Convenience method to detect MIME type only. * Matches the API expected by MCard/PCard/VCard. */ static detect(content) { return this.detectContentType(content).mimeType; } /** * Detect content type and suggest extension. * * @param content Content string or binary buffer * @param fileExtension Optional file extension hint * @returns Object containing detected mimeType and suggested extension */ static detectContentType(content, fileExtension) { let lines = []; let firstLine = ""; let textSample = ""; if (typeof content === "string") { textSample = content.slice(0, 8192); } else { textSample = new TextDecoder("utf-8", { fatal: false }).decode(content.slice(0, 8192)); } lines = textSample.split("\n").slice(0, 20); firstLine = lines[0] || ""; const mimeType = registry.detect(content, lines, firstLine, fileExtension); let extension = this.getExtension(mimeType); if (fileExtension && extension) { if (fileExtension.toLowerCase() === extension || fileExtension.toLowerCase() === `.${extension}`) { extension = fileExtension; } } if (!extension && fileExtension) { extension = fileExtension; } if (!extension) { extension = ".txt"; } return { mimeType, extension }; } static getExtension(mimeType) { return this.MIME_TO_EXT[mimeType] || ""; } /** * Check if content should be treated as binary. */ static isBinaryContent(content, mimeType) { if (mimeType) { if (mimeType.startsWith("text/") || mimeType.includes("json") || mimeType.includes("xml") || mimeType.includes("javascript") || mimeType.includes("ecmascript")) { return false; } return true; } if (typeof content === "string") return false; const detection = this.detectContentType(content); return !detection.mimeType.startsWith("text/") && !detection.mimeType.includes("json") && !detection.mimeType.includes("xml"); } static isKnownLongLineExtension(extension) { if (!extension) return false; const ext = extension.toLowerCase(); return [".min.js", ".min.css", ".map", ".svg", ".json", ".geojson"].some((e) => ext.endsWith(e)); } static isUnstructuredBinary(sample) { if (sample.length < 512) return false; let nullCount = 0; let controlCount = 0; const len = Math.min(sample.length, 32 * 1024); for (let i = 0; i < len; i++) { const byte = sample[i]; if (byte === 0) { nullCount++; } if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) { controlCount++; } } const nullRatio = nullCount / len; const controlRatio = controlCount / len; return nullRatio > 0.1 || controlRatio > 0.2; } static hasPathologicalLines(sample, isKnownType) { if (isKnownType || sample.length < 32768) return false; for (let i = 0; i < sample.length; i++) { if (sample[i] === 10 || sample[i] === 13) return false; } return true; } }; // src/types/dots.ts function createMCardDOTSMetadata(tightRefs = [], looseRefs = []) { return { role: "Carrier" /* CARRIER */, eosRole: "InvariantContent" /* INVARIANT_CONTENT */, plane: "Data" /* DATA */, tightRefs, looseRefs }; } // src/model/MCard.ts var MCard = class _MCard { content; hash; g_time; contentType; // Defaulting to specific string or null hashFunction; constructor(content, hash, g_time, contentType, hashFunction) { this.content = content; this.hash = hash; this.g_time = g_time; this.contentType = contentType; this.hashFunction = hashFunction; } /** * Create a new MCard from content */ static async create(content, hashAlgorithm = "sha256") { if (content === null || content === void 0) { throw new Error("Content cannot be null or undefined"); } const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content; if (bytes.length === 0) { throw new Error("Content cannot be empty"); } const hash = await HashValidator.computeHash(bytes, hashAlgorithm); const g_time = GTime.stampNow(hashAlgorithm); const contentType = ContentTypeInterpreter.detect(bytes); return new _MCard(bytes, hash, g_time, contentType, hashAlgorithm); } /** * Create an MCard from existing data (e.g., from database) */ static fromData(content, hash, g_time) { const alg = GTime.getHashAlgorithm(g_time); const contentType = ContentTypeInterpreter.detect(content); return new _MCard(content, hash, g_time, contentType, alg); } /** * Get content as text (UTF-8 decoded) */ getContentAsText() { return new TextDecoder().decode(this.content); } /** * Get content as raw bytes */ getContent() { return this.content; } /** * Convert to plain object */ toObject() { return { hash: this.hash, content: this.getContentAsText(), g_time: this.g_time, contentType: this.contentType, hashFunction: this.hashFunction }; } /** * Get DOTS vocabulary metadata for this MCard * * Returns the DOTS role information that positions this MCard * in the Double Operadic Theory of Systems framework. * * MCard is always a CARRIER object in the Data Plane. * * @param tightRefs - Optional array of prerequisite MCard hashes (vertical composition) * @param looseRefs - Optional array of alternative MCard hashes (horizontal composition) * @returns DOTSMetadata describing this card's role in the compositional system * * @example * ```typescript * const card = await MCard.create('Hello World'); * const meta = card.getDOTSMetadata(); * console.log(meta.role); // 'Carrier' * console.log(meta.plane); // 'Data' * ``` */ getDOTSMetadata(tightRefs = [], looseRefs = []) { return createMCardDOTSMetadata(tightRefs, looseRefs); } }; // src/index.browser.ts init_GTime(); // src/model/Handle.ts var MAX_HANDLE_LENGTH = 255; function isValidStartChar(char) { return /^\p{L}$/u.test(char); } function isValidBodyChar(char) { return /^[\p{L}\p{N}_./ -]$/u.test(char); } var HandleValidationError = class extends Error { constructor(message) { super(message); this.name = "HandleValidationError"; } }; function validateHandle(handle) { if (!handle) { throw new HandleValidationError("Handle cannot be empty."); } const normalized = handle.trim().normalize("NFC").toLowerCase(); if (normalized.length === 0) { throw new HandleValidationError("Handle cannot be empty after normalization."); } if (normalized.length > MAX_HANDLE_LENGTH) { throw new HandleValidationError( `Handle '${handle}' is too long (${normalized.length} chars). Maximum is ${MAX_HANDLE_LENGTH}.` ); } if (!isValidStartChar(normalized[0])) { throw new HandleValidationError( `Invalid handle '${handle}'. Must start with a letter (any language).` ); } for (let i = 1; i < normalized.length; i++) { if (!isValidBodyChar(normalized[i])) { throw new HandleValidationError( `Invalid character '${normalized[i]}' at position ${i} in handle '${handle}'.` ); } } return normalized; } var ContentHandle = class { handle; currentHash; createdAt; updatedAt; constructor(handle, currentHash, createdAt, updatedAt) { this.handle = validateHandle(handle); this.currentHash = currentHash; this.createdAt = createdAt ?? /* @__PURE__ */ new Date(); this.updatedAt = updatedAt ?? this.createdAt; } /** * Update handle to point to new hash * @returns Previous hash for history tracking */ update(newHash) { const previousHash = this.currentHash; this.currentHash = newHash; this.updatedAt = /* @__PURE__ */ new Date(); return previousHash; } toObject() { return { handle: this.handle, currentHash: this.currentHash, createdAt: this.createdAt.toISOString(), updatedAt: this.updatedAt.toISOString() }; } }; // src/monads/Maybe.ts var Maybe = class _Maybe { constructor(_value, _isNothing) { this._value = _value; this._isNothing = _isNothing; } /** * Create a Just (has value) */ static just(value) { return new _Maybe(value, false); } /** * Create a Nothing (no value) */ static nothing() { return new _Maybe(null, true); } /** * Check if this is Nothing */ get isNothing() { return this._isNothing; } /** * Check if this is Just */ get isJust() { return !this._isNothing; } /** * Get the value (throws if Nothing) */ get value() { if (this._isNothing) { throw new Error("Cannot get value from Nothing"); } return this._value; } /** * Monadic bind - chain operations * Short-circuits on Nothing */ bind(fn) { if (this._isNothing) { return _Maybe.nothing(); } return fn(this._value); } /** * Map a function over the value */ map(fn) { if (this._isNothing) { return _Maybe.nothing(); } return _Maybe.just(fn(this._value)); } /** * Get value or default */ getOrElse(defaultValue) { return this._isNothing ? defaultValue : this._value; } }; // src/model/CardCollection.ts var CardCollection = class { engine; constructor(engine) { this.engine = engine; } // =========== Standard Operations =========== /** * Add a card to the collection * Handles duplicates (same content, same hash) and collisions (diff content, same hash) */ async add(card) { const existingCard = await this.engine.get(card.hash); if (existingCard) { const isDuplicate = this.areContentsEqual(existingCard.content, card.content); if (isDuplicate) { const { generateDuplicationEvent: generateDuplicationEvent2 } = await Promise.resolve().then(() => (init_EventProducer(), EventProducer_exports)); const eventStr = generateDuplicationEvent2(card); const eventCard = await MCard.create(eventStr); await this.engine.add(eventCard); return card.hash; } else { const { generateCollisionEvent: generateCollisionEvent2 } = await Promise.resolve().then(() => (init_EventProducer(), EventProducer_exports)); const eventStr = await generateCollisionEvent2(card); const eventCard = await MCard.create(eventStr); await this.engine.add(eventCard); const eventObj = JSON.parse(eventStr); const nextAlgo = eventObj.upgraded_function; if (!nextAlgo) { throw new Error("Failed to determine next hash algorithm for collision"); } const upgradedCard = await MCard.create(card.content, nextAlgo); return this.engine.add(upgradedCard); } } return this.engine.add(card); } areContentsEqual(a, b) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } /** * Get a card by hash */ async get(hash) { return this.engine.get(hash); } /** * Delete a card by hash */ async delete(hash) { return this.engine.delete(hash); } /** * Get a page of cards */ async getPage(pageNumber = 1, pageSize = 10) { return this.engine.getPage(pageNumber, pageSize); } /** * Count total cards */ async count() { return this.engine.count(); } // =========== Handle Operations =========== /** * Add a card and register a handle for it */ async addWithHandle(card, handle) { const hash = await this.add(card); await this.engine.registerHandle(handle, hash); return hash; } /** * Get card by handle */ async getByHandle(handle) { return this.engine.getByHandle(handle); } /** * Resolve handle to hash */ async resolveHandle(handle) { return this.engine.resolveHandle(handle); } /** * Update handle to point to new card */ async updateHandle(handle, newCard) { const hash = await this.add(newCard); await this.engine.updateHandle(handle, hash); return hash; } /** * Get version history for a handle */ async getHandleHistory(handle) { return this.engine.getHandleHistory(handle); } // =========== Monadic Operations =========== /** * Monadic get - returns Maybe<MCard> */ async getM(hash) { const card = await this.get(hash); return card ? Maybe.just(card) : Maybe.nothing(); } /** * Monadic getByHandle - returns Maybe<MCard> */ async getByHandleM(handle) { const card = await this.getByHandle(handle); return card ? Maybe.just(card) : Maybe.nothing(); } /** * Monadic resolveHandle - returns Maybe<string> */ async resolveHandleM(handle) { const hash = await this.resolveHandle(handle); return hash ? Maybe.just(hash) : Maybe.nothing(); } /** * Resolve handle and get card in one monadic operation */ async resolveAndGetM(handle) { const maybeHash = await this.resolveHandleM(handle); if (maybeHash.isNothing) return Maybe.nothing(); return this.getM(maybeHash.value); } /** * Prune version history for a handle. * @param handle The handle string. * @param options Options for pruning (olderThan date, or deleteAll). * @returns Number of deleted entries. */ async pruneHandleHistory(handle, options = {}) { if (this.engine.pruneHandleHistory) { return this.engine.pruneHandleHistory(handle, options); } return 0; } // =========== Search & Bulk Operations =========== async clear() { return this.engine.clear(); } async searchByString(query, pageNumber = 1, pageSize = 10) { return this.engine.search(query, pageNumber, pageSize); } async searchByContent(query, pageNumber = 1, pageSize = 10) { return this.engine.search(query, pageNumber, pageSize); } async searchByHash(hashPrefix) { return this.engine.searchByHash(hashPrefix); } async getAllMCardsRaw() { return this.engine.getAll(); } async getAllCards(pageSize = 10, processCallback) { const cards = []; let pageNumber = 1; let total = 0; while (true) { const page = await this.getPage(pageNumber, pageSize); if (!page.items || page.items.length === 0) break; for (const card of page.items) { if (processCallback) { processCallback(card); } cards.push(card); } total = page.totalItems; if (!page.hasNext) break; pageNumber++; } return { cards, total }; } async printAllCards() { const cards = await this.getAllMCardsRaw(); cards.forEach((card) => { console.log(`Hash: ${card.hash}`); try { const text = new TextDecoder().decode(card.content); const preview = text.slice(0, 100).replace(/\n/g, " "); console.log(`Content: ${preview}${text.length > 100 ? "..." : ""}`); } catch { console.log(`Content (binary): ${card.content.length} bytes`); } console.log("---"); }); } }; // src/index.browser.ts init_constants(); // src/storage/IndexedDBEngine.ts var import_idb = require("idb"); var IndexedDBEngine = class { db = null; dbName; constructor(dbName = "mcard-db") { this.dbName = dbName; } /** * Initialize the database connection */ async init() { this.db = await (0, import_idb.openDB)(this.dbName, 1, { upgrade(db) { if (!db.objectStoreNames.contains("cards")) { db.createObjectStore("cards"