diff --git a/.eleventy.js b/.eleventy.js index 32d9149..f06484a 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -1,3 +1,6 @@ +const markdownIt = require("markdown-it"); +const markdownItMermaid = require("markdown-it-mermaid"); + module.exports = function (eleventyConfig) { // Passthrough copy for static assets eleventyConfig.addPassthroughCopy("src/assets"); @@ -54,6 +57,16 @@ module.exports = function (eleventyConfig) { return array.slice(0, n); }); + // Markdown: enable Mermaid diagrams + const mermaidPlugin = markdownItMermaid.default || markdownItMermaid; + const markdownLib = markdownIt({ + html: true, + breaks: false, + linkify: true + }).use(mermaidPlugin); + + eleventyConfig.setLibrary("md", markdownLib); + // Collections // Blog posts / Proposals collection eleventyConfig.addCollection("posts", function (collectionApi) { @@ -62,12 +75,13 @@ module.exports = function (eleventyConfig) { }); }); - // Projects collection (blog posts + CLQMS projects) + // Projects collection (blog posts + CLQMS + TinyLink) eleventyConfig.addCollection("projects", function (collectionApi) { const blogPosts = collectionApi.getFilteredByGlob("src/blog/**/*.md"); const clqmsPosts = collectionApi.getFilteredByTag("clqms"); + const tinylinkPosts = collectionApi.getFilteredByTag("tinylink"); - const allProjects = [...blogPosts, ...clqmsPosts].sort((a, b) => { + const allProjects = [...blogPosts, ...clqmsPosts, ...tinylinkPosts].sort((a, b) => { return new Date(b.date) - new Date(a.date); }); @@ -81,6 +95,13 @@ module.exports = function (eleventyConfig) { }); }); + // TinyLink collection sorted by order + eleventyConfig.addCollection("tinylink", function (collectionApi) { + return collectionApi.getFilteredByTag("tinylink").sort((a, b) => { + return (Number(a.data.order) ?? 99) - (Number(b.data.order) ?? 99); + }); + }); + return { dir: { input: "src", diff --git a/package-lock.json b/package-lock.json index 876299b..373128c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "5panda.11ty", "version": "1.0.0", "license": "MIT", + "dependencies": { + "markdown-it": "^14.1.1", + "markdown-it-mermaid": "^0.2.5" + }, "devDependencies": { "@11ty/eleventy": "^3.1.2", "@tailwindcss/postcss": "^4.1.18", @@ -659,7 +663,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { @@ -1072,6 +1075,34 @@ "semver": "bin/semver" } }, + "node_modules/d3": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", + "integrity": "sha512-yFk/2idb8OHPKkbAL8QaOaqENNoMhIaSHZerk3oQsECwkObkCpJyjYwCe+OHiq6UEdhe1m8ZGARRRO3ljFjlKg==", + "license": "BSD-3-Clause" + }, + "node_modules/dagre-d3-renderer": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/dagre-d3-renderer/-/dagre-d3-renderer-0.4.26.tgz", + "integrity": "sha512-vOWj1uA4/APTrfDyfHaH/xpfXhPh9rszW+HOaEwPCeA6Afl06Lobfh7OpESuVMQW2QGuY4UQ7pte/p0WhdDs7w==", + "license": "MIT", + "dependencies": { + "d3": "3.5.17", + "dagre-layout": "^0.8.0", + "graphlib": "^2.1.1", + "lodash": "^4.17.4" + } + }, + "node_modules/dagre-layout": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dagre-layout/-/dagre-layout-0.8.8.tgz", + "integrity": "sha512-ZNV15T9za7X+fV8Z07IZquUKugCxm5owoiPPxfEx6OJRD331nkiIaF3vSt0JEY5FkrY0KfRQxcpQ3SpXB7pLPQ==", + "license": "MIT", + "dependencies": { + "graphlibrary": "^2.2.0", + "lodash": "^4.17.5" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -1892,6 +1923,24 @@ "dev": true, "license": "ISC" }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/graphlibrary": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/graphlibrary/-/graphlibrary-2.2.0.tgz", + "integrity": "sha512-XTcvT55L8u4MBZrM37zXoUxsgxs/7sow7YSygd9CIwfWTVO8RVu7AYXhhCiTuFEf+APKgx6Jk4SuQbYR0vYKmQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.5" + } + }, "node_modules/gray-matter": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", @@ -2026,6 +2075,15 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -2970,7 +3028,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "dev": true, "license": "MIT", "dependencies": { "uc.micro": "^2.0.0" @@ -3030,6 +3087,12 @@ "node": ">=4" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, "node_modules/luxon": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", @@ -3051,10 +3114,9 @@ } }, "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "dev": true, + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1", @@ -3068,11 +3130,19 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/markdown-it-mermaid": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/markdown-it-mermaid/-/markdown-it-mermaid-0.2.5.tgz", + "integrity": "sha512-ZUTFRX+cXEtWmn/9LMlpVklPJiDrHPWyHE/wamC2wm0Ojh1qOcuKWfWW3BqP83+7w6C59rS7M3OrGTs/u9mQTA==", + "license": "MIT", + "dependencies": { + "mermaid": "^7.1.2" + } + }, "node_modules/markdown-it/node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -3111,7 +3181,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true, "license": "MIT" }, "node_modules/memorystream": { @@ -3123,6 +3192,20 @@ "node": ">= 0.10.0" } }, + "node_modules/mermaid": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-7.1.2.tgz", + "integrity": "sha512-bDLu3fQuf3/R0fNkNzB0GTaF7+6SxnZpfTs9DVQF1ougsuP23MBzvEIGfL0ML8zeyg7+J2D+0AaoLVhskW5ulw==", + "license": "MIT", + "dependencies": { + "d3": "3.5.17", + "dagre-d3-renderer": "^0.4.25", + "dagre-layout": "^0.8.0", + "he": "^1.1.1", + "lodash": "^4.17.4", + "moment": "^2.20.1" + } + }, "node_modules/mime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", @@ -3196,6 +3279,15 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/moo": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", @@ -3801,7 +3893,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4639,7 +4730,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true, "license": "MIT" }, "node_modules/unbox-primitive": { diff --git a/package.json b/package.json index 12e3351..f935035 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,9 @@ "postcss": "^8.5.6", "postcss-cli": "^11.0.1", "tailwindcss": "^4.1.18" + }, + "dependencies": { + "markdown-it": "^14.1.1", + "markdown-it-mermaid": "^0.2.5" } -} \ No newline at end of file +} diff --git a/src/_layouts/base.njk b/src/_layouts/base.njk index 832a1a6..9deea34 100644 --- a/src/_layouts/base.njk +++ b/src/_layouts/base.njk @@ -52,6 +52,7 @@ Projects @@ -90,6 +91,11 @@ 🏥CLQMS +
  • + + 🔗TinyLink + +
  • @@ -153,6 +159,16 @@

    © {% year %} 5Panda. Built with 11ty & Tailwind CSS.

    + + - \ No newline at end of file + diff --git a/src/index.njk b/src/index.njk index 9f1a54b..05d31a9 100644 --- a/src/index.njk +++ b/src/index.njk @@ -110,3 +110,36 @@ description: Innovative projects and ideas {% endif %} + + + {% if collections.tinylink.length > 0 %} +
    +
    +
    + TinyLink +

    Integration Hub

    +

    + Bi-directional instrument integration and message translation workflows. +

    +
    + + {% if collections.tinylink.length > 4 %} + + {% endif %} +
    +
    + {% endif %} diff --git a/src/projects/tinylink/001-design.md b/src/projects/tinylink/001-design.md new file mode 100644 index 0000000..4e1ef52 --- /dev/null +++ b/src/projects/tinylink/001-design.md @@ -0,0 +1,84 @@ +--- +layout: post.njk +tags: tinylink +title: "TinyLink: Integration Design" +description: "Bi-directional integration hub design for instrument results and CLQMS01 workflows." +date: 2026-04-07 +order: 1 +--- + +# TinyLink Integration Design + +## Overview +TinyLink is a bi-directional integration hub between laboratory instruments and the CLQMS01 host. It receives instrument results, wraps raw payloads into a canonical envelope, translates them to JSON, and pushes them to CLQMS01. It also retrieves test requests from CLQMS01 and delivers them to instruments through download and query workflows (planned). + +## Goals +- Provide reliable, automated data exchange between instruments and CLQMS01. +- Normalize and translate messages between instrument formats and JSON for CLQMS01. +- Support both download (new requests) and query (SampleID) workflows. + +## Non-Goals +- User interface or manual data entry. +- Business rule orchestration beyond mapping and routing. +- Long-term analytics or reporting. + +## High-Level Architecture +```mermaid +graph LR + subgraph Instrument + INST[Instrument] + end + + subgraph App + RCV[Receiver / Raw Capture] + XLT[Translator] + ROUTE[Message Router - planned] + POLL[Scheduler / Poller - planned] + end + + subgraph CLQMS01 + CLQ[CLQMS01 Host] + end + + %% Result flow + INST -->|Result data| RCV + RCV --> XLT + XLT -->|JSON| CLQ + + %% Download request flow + POLL -->|Check new requests| CLQ + CLQ -->|New requests| ROUTE + ROUTE -->|Instrument message| INST + + %% Query flow + INST -->|Query + SampleID| ROUTE + ROUTE -->|SampleID| CLQ + CLQ -->|Ordered tests| XLT + XLT -->|Instrument response| INST +``` + +## Data Flows +- Result flow (Instrument → CLQMS01): receive instrument output, wrap raw payload into a canonical envelope, translate to JSON, send to CLQMS01. +- Download request flow (CLQMS01 → Instrument): poll CLQMS01 for new requests, map to instrument message, send to instrument. (planned) +- Query flow (Instrument → CLQMS01 → Instrument): instrument sends query with SampleID, fetch ordered tests from CLQMS01, translate to instrument response, send back. (planned) + +## Current vs Planned +- Current: result ingest → raw capture → translator → queue → delivery worker → CLQMS01. +- Planned: scheduler/poller for download requests and message router for download/query workflows. + +## Key Components +- Receiver/Raw Capture: accepts instrument output and records it as raw payload for translation. +- Translator: maps instrument fields to JSON schema and vice versa. +- Message Router: routes messages to the correct workflow and destination. (planned) +- Scheduler/Poller: periodically checks CLQMS01 for new requests. (planned) +- CLQMS01 Adapter: handles request/response and authentication to the host. + +## Reliability and Error Handling +- Retry on transient network failures. +- Log and flag translation/normalization errors with raw payload references. +- Idempotency safeguards for resends when possible. + +## Security and Configuration +- CLQMS01 host and API key stored in config. +- Instrument connection details stored in config. +- No secrets committed to source control.