From b1ec29950fa55871360c0ce7043188b4a9b405f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 16:12:59 +0000 Subject: [PATCH 1/3] docs(site): render mermaid code fences as diagrams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fumadocs was showing ```mermaid blocks (used on 7 pages: protocol diagram, kernel architecture, data-flow, permissions matrix, field-type decision tree, contracts, metadata-service) as plain code. Add a client-side Mermaid component (theme-aware, falls back to the source block on bad syntax) and a remark plugin that rewrites mermaid fences into — no content changes required. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018r9mjpF7jr9BKdzEmUE4uZ --- apps/docs/components/mermaid.tsx | 60 +++ apps/docs/mdx-components.tsx | 2 + apps/docs/package.json | 5 +- apps/docs/source.config.ts | 21 +- pnpm-lock.yaml | 799 +++++++++++++++++++++++++++++++ 5 files changed, 885 insertions(+), 2 deletions(-) create mode 100644 apps/docs/components/mermaid.tsx diff --git a/apps/docs/components/mermaid.tsx b/apps/docs/components/mermaid.tsx new file mode 100644 index 0000000000..cf7e4788d2 --- /dev/null +++ b/apps/docs/components/mermaid.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { useEffect, useId, useState } from 'react'; +import { useTheme } from 'next-themes'; + +/** + * Client-side Mermaid renderer. Code fences with the `mermaid` language are + * rewritten to by the remark plugin in source.config.ts, + * so authors keep writing standard ```mermaid blocks. + */ +export function Mermaid({ chart }: { chart: string }) { + const id = useId(); + const [svg, setSvg] = useState(''); + const { resolvedTheme } = useTheme(); + + useEffect(() => { + let cancelled = false; + + async function render() { + const { default: mermaid } = await import('mermaid'); + mermaid.initialize({ + startOnLoad: false, + securityLevel: 'strict', + fontFamily: 'inherit', + theme: resolvedTheme === 'dark' ? 'dark' : 'default', + }); + try { + const rendered = await mermaid.render( + // mermaid requires a DOM-safe element id + `mermaid-${id.replace(/[^a-zA-Z0-9]/g, '')}`, + chart, + ); + if (!cancelled) setSvg(rendered.svg); + } catch { + // Leave the diagram source visible instead of a blank hole on bad syntax. + if (!cancelled) setSvg(''); + } + } + + void render(); + return () => { + cancelled = true; + }; + }, [chart, id, resolvedTheme]); + + if (!svg) { + return ( +
+        {chart}
+      
+ ); + } + + return ( +
+ ); +} diff --git a/apps/docs/mdx-components.tsx b/apps/docs/mdx-components.tsx index 20beb4cd77..2691bc287a 100644 --- a/apps/docs/mdx-components.tsx +++ b/apps/docs/mdx-components.tsx @@ -1,9 +1,11 @@ import defaultMdxComponents from 'fumadocs-ui/mdx'; import type { MDXComponents } from 'mdx/types'; +import { Mermaid } from '@/components/mermaid'; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, + Mermaid, ...components, }; } diff --git a/apps/docs/package.json b/apps/docs/package.json index f5788bc45c..c28e36d47c 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -17,10 +17,13 @@ "fumadocs-mdx": "15.0.12", "fumadocs-ui": "16.10.5", "lucide-react": "^1.22.0", + "mermaid": "^11.16.0", "next": "16.2.9", + "next-themes": "^0.4.6", "react": "^19.2.7", "react-dom": "^19.2.7", - "tailwind-merge": "^3.6.0" + "tailwind-merge": "^3.6.0", + "unist-util-visit": "^5.1.0" }, "devDependencies": { "@objectstack/spec": "workspace:*", diff --git a/apps/docs/source.config.ts b/apps/docs/source.config.ts index af739d36bc..67bb441e2b 100644 --- a/apps/docs/source.config.ts +++ b/apps/docs/source.config.ts @@ -2,6 +2,25 @@ import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; import { metaSchema, pageSchema } from 'fumadocs-core/source/schema'; import { z } from 'zod'; import path from 'node:path'; +import { visit } from 'unist-util-visit'; + +/** + * Rewrite ```mermaid code fences into elements so + * diagrams render as SVG (components/mermaid.tsx) instead of code blocks. + */ +function remarkMermaid() { + return (tree: any) => { + visit(tree, 'code', (node: any, index: number | undefined, parent: any) => { + if (node.lang !== 'mermaid' || !parent || index === undefined) return; + parent.children[index] = { + type: 'mdxJsxFlowElement', + name: 'Mermaid', + attributes: [{ type: 'mdxJsxAttribute', name: 'chart', value: node.value }], + children: [], + }; + }); + }; +} export const docs = defineDocs({ dir: path.resolve(process.cwd(), '../../content/docs'), @@ -31,6 +50,6 @@ export const blog = defineDocs({ export default defineConfig({ mdxOptions: { - // MDX options + remarkPlugins: (v) => [...v, remarkMermaid], }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e659a91ad0..d7e038cc06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,9 +54,15 @@ importers: lucide-react: specifier: ^1.22.0 version: 1.22.0(react@19.2.7) + mermaid: + specifier: ^11.16.0 + version: 11.16.0 next: specifier: 16.2.9 version: 16.2.9(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: specifier: ^19.2.7 version: 19.2.7 @@ -66,6 +72,9 @@ importers: tailwind-merge: specifier: ^3.6.0 version: 3.6.0 + unist-util-visit: + specifier: ^5.1.0 + version: 5.1.0 devDependencies: '@objectstack/spec': specifier: workspace:* @@ -2214,6 +2223,9 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@authenio/xml-encryption@2.0.2': resolution: {integrity: sha512-cTlrKttbrRHEw3W+0/I609A2Matj5JQaRvfLtEIGZvlN0RaPi+3ANsMeqAyCAVlH/lUIW2tmtBlSMni74lcXeg==} engines: {node: '>=12'} @@ -2529,6 +2541,9 @@ packages: '@better-fetch/fetch@1.3.1': resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + '@changesets/apply-release-plan@7.1.1': resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} @@ -2584,6 +2599,9 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + '@cloudflare/workers-types@4.20260520.1': resolution: {integrity: sha512-wdmf9Fwabp06OgK9ZyCl8Q77GZ94k9J7OA9qA65FbgxZ/hd3hYRkVNiY7Bvsx4tKim+sWmKqGOblecZInAvoRg==} @@ -2862,6 +2880,12 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.3': + resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} + '@img/colour@1.1.0': resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} @@ -3117,6 +3141,9 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@mermaid-js/parser@1.2.0': + resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==} + '@modelcontextprotocol/sdk@1.29.0': resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} engines: {node: '>=18'} @@ -4192,6 +4219,99 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -4210,6 +4330,9 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -4343,6 +4466,9 @@ packages: '@ungap/structured-clone@1.3.2': resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==} + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + '@vercel/oidc@3.2.0': resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} engines: {node: '>= 20'} @@ -5007,6 +5133,14 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -5068,6 +5202,12 @@ packages: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -5095,6 +5235,162 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.34.0: + resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} + dayjs@1.11.21: resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} @@ -5149,6 +5445,9 @@ packages: defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -5199,6 +5498,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.4.11: + resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==} + domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -5298,6 +5600,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-toolkit@1.49.0: + resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} + esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -5821,6 +6126,9 @@ packages: resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + happy-dom@20.10.2: resolution: {integrity: sha512-5p9Sxis3eowDJKqx90QCsgbNA02XXqJ59NOHvD4V6cxp+rP4d/xOyVx7uY3hS8hiUbY1VeiFH8lbJ81AyuDVLQ==} engines: {node: '>=20.0.0'} @@ -5947,6 +6255,9 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -5975,6 +6286,13 @@ packages: inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + interpret@2.2.0: resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==} engines: {node: '>= 0.10'} @@ -6195,12 +6513,19 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + keytar@7.9.0: resolution: {integrity: sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==} keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -6243,6 +6568,12 @@ packages: resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==} engines: {node: '>=22.0.0'} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + lazystream@1.0.1: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} @@ -6360,6 +6691,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -6479,6 +6813,11 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -6549,6 +6888,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@11.16.0: + resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -7117,6 +7459,9 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + package-manager-detector@1.7.0: + resolution: {integrity: sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==} + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -7146,6 +7491,9 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -7277,6 +7625,12 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + postcss-load-config@6.0.1: resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} engines: {node: '>= 18'} @@ -7598,6 +7952,9 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rolldown@1.0.3: resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -7611,6 +7968,9 @@ packages: rou3@0.7.12: resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -7622,6 +7982,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -7901,6 +8264,9 @@ packages: babel-plugin-macros: optional: true + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + sucrase@3.35.1: resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} engines: {node: '>=16 || 14 >=14.17'} @@ -8073,6 +8439,10 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -8251,6 +8621,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@14.0.1: + resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). @@ -8570,6 +8944,11 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.7.0 + tinyexec: 1.2.4 + '@authenio/xml-encryption@2.0.2': dependencies: '@xmldom/xmldom': 0.8.13 @@ -9065,6 +9444,8 @@ snapshots: '@better-fetch/fetch@1.3.1': {} + '@braintree/sanitize-url@7.1.2': {} + '@changesets/apply-release-plan@7.1.1': dependencies: '@changesets/config': 3.1.4 @@ -9208,6 +9589,8 @@ snapshots: human-id: 4.2.0 prettier: 2.8.8 + '@chevrotain/types@11.1.2': {} + '@cloudflare/workers-types@4.20260520.1': optional: true @@ -9415,6 +9798,14 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.3': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + '@img/colour@1.1.0': optional: true @@ -9646,6 +10037,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@mermaid-js/parser@1.2.0': + dependencies: + '@chevrotain/types': 11.1.2 + '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': dependencies: '@hono/node-server': 1.19.14(hono@4.12.27) @@ -10668,6 +11063,123 @@ snapshots: '@types/cookie@0.6.0': optional: true + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.4': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -10684,6 +11196,8 @@ snapshots: '@types/estree@1.0.9': {} + '@types/geojson@7946.0.16': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -10832,6 +11346,11 @@ snapshots: '@ungap/structured-clone@1.3.2': {} + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@vercel/oidc@3.2.0': {} '@vitest/coverage-v8@4.1.9(vitest@4.1.9)': @@ -11505,6 +12024,10 @@ snapshots: commander@4.1.1: {} + commander@7.2.0: {} + + commander@8.3.0: {} + commondir@1.0.1: {} compress-commons@4.1.2: @@ -11551,6 +12074,14 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + crc-32@1.2.2: {} crc32-stream@4.0.3: @@ -11578,6 +12109,190 @@ snapshots: csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.34.0 + + cytoscape-fcose@2.2.0(cytoscape@3.34.0): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.34.0 + + cytoscape@3.34.0: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + dayjs@1.11.21: {} debug@4.3.4: @@ -11616,6 +12331,10 @@ snapshots: defu@6.1.7: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + delayed-stream@1.0.0: {} delegates@1.0.0: @@ -11656,6 +12375,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.4.11: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -11751,6 +12474,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.4 + es-toolkit@1.49.0: {} + esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -12385,6 +13110,8 @@ snapshots: graphql@16.14.2: optional: true + hachure-fill@0.5.2: {} + happy-dom@20.10.2: dependencies: '@types/node': 26.0.1 @@ -12614,6 +13341,8 @@ snapshots: immediate@3.0.6: {} + import-meta-resolve@4.2.0: {} + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -12634,6 +13363,10 @@ snapshots: inline-style-parser@0.2.7: {} + internmap@1.0.1: {} + + internmap@2.0.3: {} + interpret@2.2.0: {} ioredis-mock@8.13.1(@types/ioredis-mock@8.2.7(ioredis@5.11.1))(ioredis@5.11.1): @@ -12844,6 +13577,10 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 + katex@0.16.47: + dependencies: + commander: 8.3.0 + keytar@7.9.0: dependencies: node-addon-api: 4.3.0 @@ -12854,6 +13591,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + kleur@4.1.5: optional: true @@ -12884,6 +13623,10 @@ snapshots: kysely@0.29.2: {} + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + lazystream@1.0.1: dependencies: readable-stream: 2.3.8 @@ -12971,6 +13714,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.18.1: {} + lodash.defaults@4.2.0: {} lodash.difference@4.5.0: {} @@ -13085,6 +13830,8 @@ snapshots: markdown-table@3.0.4: {} + marked@16.4.2: {} + math-intrinsics@1.1.0: {} mdast-util-find-and-replace@3.0.2: @@ -13260,6 +14007,30 @@ snapshots: merge2@1.4.1: {} + mermaid@11.16.0: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.3 + '@mermaid-js/parser': 1.2.0 + '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.34.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) + cytoscape-fcose: 2.2.0(cytoscape@3.34.0) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.21 + dompurify: 3.4.11 + es-toolkit: 1.49.0 + katex: 0.16.47 + khroma: 2.1.0 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.4.0 + ts-dedent: 2.3.0 + uuid: 14.0.1 + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -13959,6 +14730,8 @@ snapshots: dependencies: quansync: 0.2.11 + package-manager-detector@1.7.0: {} + pako@1.0.11: {} parse-entities@4.0.2: @@ -13998,6 +14771,8 @@ snapshots: path-browserify@1.0.1: {} + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-expression-matcher@1.6.1: {} @@ -14099,6 +14874,13 @@ snapshots: pluralize@8.0.0: {} + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.16)(tsx@4.22.4)(yaml@2.9.0): dependencies: lilconfig: 3.1.3 @@ -14474,6 +15256,8 @@ snapshots: glob: 7.2.3 optional: true + robust-predicates@3.0.3: {} + rolldown@1.0.3: dependencies: '@oxc-project/types': 0.133.0 @@ -14528,6 +15312,13 @@ snapshots: rou3@0.7.12: {} + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + router@2.2.0: dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -14544,6 +15335,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -14890,6 +15683,8 @@ snapshots: client-only: 0.0.1 react: 19.2.7 + stylis@4.4.0: {} + sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -15100,6 +15895,8 @@ snapshots: dependencies: typescript: 6.0.3 + ts-dedent@2.3.0: {} + ts-interface-checker@0.1.13: {} ts-morph@28.0.0: @@ -15301,6 +16098,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@14.0.1: {} + uuid@8.3.2: {} validate-npm-package-license@3.0.4: From 8a3e5cad3f81debe83a59bd9c372ca2986247bb2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 17:14:35 +0000 Subject: [PATCH 2/3] docs(site): drop numeric stats; first round of persona-review fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback, remove count-style statistics from module overviews and landing (field type counts, hook event counts, namespace tallies) in favor of qualitative descriptions — counts drift from the implementation and don't help readers. Also lands the first fixes from the reader-persona review pass: - ai/actions-as-tools: remove walkthrough of HITL demo tests that do not exist in examples/app-todo (only mcp-actions/seed tests exist); point at the real delete_completed danger action instead - ai/agents, ai/natural-language-queries: fix orphaned 'see above' references and same-page anchors that now live on sibling pages Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018r9mjpF7jr9BKdzEmUE4uZ --- content/docs/ai/actions-as-tools.mdx | 22 +-- content/docs/ai/agents.mdx | 7 +- content/docs/ai/natural-language-queries.mdx | 5 + content/docs/automation/index.mdx | 8 +- content/docs/data-modeling/field-types.mdx | 174 +++++------------- content/docs/data-modeling/fields.mdx | 4 +- content/docs/data-modeling/index.mdx | 6 +- .../docs/getting-started/quick-reference.mdx | 2 +- content/docs/index.mdx | 4 +- content/docs/kernel/index.mdx | 2 +- content/docs/references/index.mdx | 4 +- content/docs/ui/index.mdx | 6 +- 12 files changed, 77 insertions(+), 167 deletions(-) diff --git a/content/docs/ai/actions-as-tools.mdx b/content/docs/ai/actions-as-tools.mdx index b3608e55a0..40bd3ff0af 100644 --- a/content/docs/ai/actions-as-tools.mdx +++ b/content/docs/ai/actions-as-tools.mdx @@ -116,21 +116,13 @@ The queue is exposed via four REST endpoints (`GET`, `GET/:id`, `POST/:id/approv ### End-to-end example -Two runnable demos live in `examples/app-todo/test/`: - -- **`ai-hitl.test.ts`** — drives the tool registry directly (no LLM dependency). Asserts the full lifecycle: `variant:'danger'` action registered as a tool → invocation returns `pending_approval` without executing → row persisted → `approvePendingAction(id, actor)` re-runs the handler → row transitions to `executed`. Reject path is also covered. - - ```bash - pnpm --filter @example/app-todo test:hitl - ``` - -- **`ai-hitl-llm.test.ts`** — same scenario but with a real model behind Vercel AI Gateway. The LLM is given the auto-generated tool description and asked to "delete all completed tasks"; the framework returns `pending_approval` so the model summarises the wait instead of retrying. After we call approve from the test, the deletion completes. - - ```bash - AI_GATEWAY_API_KEY=vck_... pnpm --filter @example/app-todo test:hitl:llm - ``` - - Gated on `AI_GATEWAY_API_KEY` (or `OPENAI_API_KEY`); exits 0 with a notice if no key is provided, so it can be wired into CI without leaking spend. +The full lifecycle: a `variant:'danger'` action registered as a tool → +invocation returns `pending_approval` without executing → row persisted → +`approvePendingAction(id, actor)` re-runs the handler → row transitions to +`executed` (the reject path mirrors it). The todo example's +`delete_completed` action (`examples/app-todo/src/actions/task.actions.ts`) +is authored exactly for this: `variant: 'danger'` + `confirmText` route it +through the approval queue. A trimmed view of the integration path: diff --git a/content/docs/ai/agents.mdx b/content/docs/ai/agents.mdx index 2be563d757..3f56f5ccf4 100644 --- a/content/docs/ai/agents.mdx +++ b/content/docs/ai/agents.mdx @@ -18,7 +18,8 @@ is in — the user never picks from a roster: Both agents are part of the **cloud / Enterprise** in-UI AI runtime (cloud ADR-0025). The **open edition** ships neither — it uses `@objectstack/mcp` (BYO-AI) for data -query and source-mode authoring instead (see the note above). +query and source-mode authoring instead (see the callout in the +[AI Overview](/docs/ai)). Within the cloud / EE runtime there is no per-turn intent classifier and no agent dropdown: the surface binds the agent (data console → `ask`, Studio → `build`). A @@ -64,7 +65,7 @@ them via the chat endpoint. Agent tools are **references** to existing Actions, Flows, or queries — you do not define ad-hoc tool names with inline parameter schemas here. See -[Actions as Tools](#actions-as-tools-explicit-opt-in) for how an Action becomes +[Actions as Tools](/docs/ai/actions-as-tools) for how an Action becomes LLM-callable. @@ -219,7 +220,7 @@ Use the data tools to query records and aggregate metrics.`, }, // Built-in data tools (query_records / get_record / aggregate_data) are - // available to agents automatically once registered — see "Actions as Tools". + // available to agents automatically once registered — see "Natural Language Queries". tools: [ { type: 'flow', name: 'analyze_pipeline', description: 'Analyze sales pipeline health' }, ], diff --git a/content/docs/ai/natural-language-queries.mdx b/content/docs/ai/natural-language-queries.mdx index 101abde7e6..cac5f36b84 100644 --- a/content/docs/ai/natural-language-queries.mdx +++ b/content/docs/ai/natural-language-queries.mdx @@ -7,6 +7,11 @@ description: How agents query live data through the built-in data tools (query_r Part of the [AI module](/docs/ai) — how natural-language questions become ObjectQL queries at runtime. +The data tools described here ship with `@objectstack/service-ai`, i.e. the +**cloud / Enterprise** in-UI AI runtime (see the callout in the +[AI Overview](/docs/ai)). On the open edition, point your own AI at the same +objects and queries through `@objectstack/mcp` instead. + Agents query your data through the built-in **data tools** — `query_records`, `get_record`, and `aggregate_data` — which the LLM calls with structured arguments. These run as ordinary [ObjectQL](/docs/protocol/objectql) queries over diff --git a/content/docs/automation/index.mdx b/content/docs/automation/index.mdx index 46e863b4eb..572fcd9979 100644 --- a/content/docs/automation/index.mdx +++ b/content/docs/automation/index.mdx @@ -1,6 +1,6 @@ --- title: Automation -description: 18 hook events, 20+ extensible flow node types, scheduled flows, approvals, and durable webhooks — the process engine that reacts to your data. +description: Hooks, flows, workflows, approvals, scheduled jobs, and durable webhooks — the process engine that reacts to your data. --- # Automation @@ -27,8 +27,8 @@ export const OpportunityStageHook: Hook = { ## The building blocks -- **Hooks** intercept **18 lifecycle events** — before/after each of find, findOne, count, aggregate, insert, update, delete, updateMany, deleteMany. Hooks support priority ordering, fire-and-forget `async` mode, CEL `condition` guards, and a `retryPolicy` with backoff ([Hooks](/docs/automation/hooks)). -- **Flows** are node-based processes with **20 built-in node types** (decision, loop, create/update/delete record, http, notify, script, screen, wait, subflow, parallel/join gateways, …) — and the set is **registry-extensible**: plugins can contribute new node types via `registerNodeExecutor`. Flows launch on record changes, on a schedule, from a screen, or via API ([Flows](/docs/automation/flows)). +- **Hooks** intercept every record operation with before/after events — reads, writes, deletes, and their bulk variants. Hooks support priority ordering, fire-and-forget `async` mode, CEL `condition` guards, and a `retryPolicy` with backoff ([Hooks](/docs/automation/hooks)). +- **Flows** are node-based processes built from nodes like decision, loop, record operations, http, notify, script, screen, wait, subflow, and parallel/join gateways — and the set is **registry-extensible**: plugins can contribute new node types via `registerNodeExecutor`. Flows launch on record changes, on a schedule, from a screen, or via API ([Flows](/docs/automation/flows)). - **Workflows** model a record's lifecycle as a **finite state machine**: states, transitions, and guards ([Workflows](/docs/automation/workflows)). - **Approvals** are flow nodes with approver resolution, approve/reject decisions, and escalation ([Approvals](/docs/automation/approvals)). - **Webhooks** deliver events to external systems through a **durable outbox** — exponential/linear/fixed retry with dead-lettering, HMAC signing, and an admin redeliver endpoint ([Webhook Delivery](/docs/automation/webhooks)). @@ -39,7 +39,7 @@ Rule of thumb: model *state* with workflows, model *steps* with flows, use hooks ## What's in this module - + diff --git a/content/docs/data-modeling/field-types.mdx b/content/docs/data-modeling/field-types.mdx index a4861246b9..c956eb5224 100644 --- a/content/docs/data-modeling/field-types.mdx +++ b/content/docs/data-modeling/field-types.mdx @@ -1,11 +1,11 @@ --- title: Field Type Gallery -description: Complete reference for all 48 ObjectStack field types with per-type configuration properties +description: Complete reference for every ObjectStack field type with per-type configuration properties --- # Field Type Gallery -ObjectStack provides **49 field types** covering every data modeling need — from basic text and numbers to AI vectors and rich media. This guide organizes them by category with per-type configuration details. +ObjectStack provides a **comprehensive set of field types** covering every data modeling need — from basic text and numbers to AI vectors and rich media. This guide organizes them by category with per-type configuration details. **Source:** `packages/spec/src/data/field.zod.ts` @@ -24,7 +24,6 @@ Single-line plain text input. | `maxLength` | `number` | — | Maximum character length | | `minLength` | `number` | — | Minimum character length | | `format` | `string` | — | Validation format pattern | -| `caseSensitive` | `boolean` | — | Whether text comparisons are case-sensitive | ```typescript { name: 'first_name', label: 'First Name', type: 'text', maxLength: 100 } @@ -303,10 +302,19 @@ Reference to a record in another object (foreign key). |:---|:---|:---|:---| | `reference` | `string` | **required** | Target object name (snake_case) | | `referenceFilters` | `string[]` | — | Filters applied to lookup dialogs (e.g. `"active = true"`) | -| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted | +| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted (a *required* lookup left at the default `set_null` is escalated to `restrict`, since a NOT NULL foreign key cannot be cleared) | ```typescript -{ name: 'assigned_to', label: 'Assigned To', type: 'lookup', reference: 'user' } +{ name: 'company', label: 'Company', type: 'lookup', reference: 'account' } +``` + +### `user` +Person picker — a lookup specialized to the built-in `sys_user` object. Stored identically to `lookup` (foreign key to `sys_user.id`; `multiple: true` stores an array) and resolved through the same `$expand` machinery. Supports `defaultValue: 'current_user'` to stamp the acting user's id on insert. + +```typescript +Field.user({ label: 'Assignee' }) +Field.user({ label: 'Watchers', multiple: true }) +Field.user({ label: 'Owner', defaultValue: 'current_user' }) ``` ### `master_detail` @@ -419,15 +427,16 @@ Audio file attachment. ## Computed Types ### `formula` -Calculated field using an expression. +Calculated field using a CEL expression (see [Expressions](/docs/data-modeling/formulas)). | Property | Type | Default | Description | |:---|:---|:---|:---| -| `expression` | `string` | **required** | Calculation expression | -| `cached` | `object` | — | Cache settings for computed result | +| `expression` | `string \| Expression` | **required** | CEL calculation expression | +| `returnType` | `'number' \| 'text' \| 'boolean' \| 'date'` | — | Declared value type of the computed result | +| `dependencies` | `string[]` | — | Fields the formula depends on | ```typescript -{ name: 'total', label: 'Total', type: 'formula', expression: 'quantity * unit_price' } +{ name: 'total', label: 'Total', type: 'formula', expression: 'record.quantity * record.unit_price', returnType: 'number' } ``` ### `summary` @@ -497,23 +506,14 @@ Name-keyed map of embedded sub-objects (`Record`). Insertion ## Enhanced Types ### `location` -Geographic coordinates with optional map display. - -| Property | Type | Default | Description | -|:---|:---|:---|:---| -| `displayMap` | `boolean` | — | Show interactive map | -| `allowGeocoding` | `boolean` | — | Enable address-to-coordinates conversion | +Geographic coordinates. Stored as `{ latitude, longitude, altitude?, accuracy? }` (latitude −90..90, longitude −180..180). No per-type config properties. ```typescript -{ name: 'headquarters', label: 'Location', type: 'location', displayMap: true, allowGeocoding: true } +{ name: 'headquarters', label: 'Location', type: 'location' } ``` ### `address` -Structured postal address. - -| Property | Type | Default | Description | -|:---|:---|:---|:---| -| `addressFormat` | `'us' \| 'uk' \| 'international'` | — | Address format template | +Structured postal address. Stored as `{ street, city, state, postalCode, country, countryCode, formatted }` (all parts optional). No per-type config properties. ```typescript { name: 'billing_address', label: 'Billing Address', type: 'address' } @@ -525,11 +525,9 @@ Source code with syntax highlighting. | Property | Type | Default | Description | |:---|:---|:---|:---| | `language` | `string` | — | Programming language for highlighting | -| `theme` | `string` | — | Editor theme | -| `lineNumbers` | `boolean` | — | Show line numbers | ```typescript -{ name: 'snippet', label: 'Code Snippet', type: 'code', language: 'typescript', lineNumbers: true } +{ name: 'snippet', label: 'Code Snippet', type: 'code', language: 'typescript' } ``` ### `json` @@ -540,16 +538,10 @@ Raw JSON data. ``` ### `color` -Color picker with format options. - -| Property | Type | Default | Description | -|:---|:---|:---|:---| -| `colorFormat` | `'hex' \| 'rgb' \| 'rgba' \| 'hsl'` | — | Color value format | -| `allowAlpha` | `boolean` | — | Allow alpha transparency | -| `presetColors` | `string[]` | — | Preset color palette | +Color picker. Stores the color value as a string. No per-type config properties. ```typescript -{ name: 'brand_color', label: 'Brand Color', type: 'color', colorFormat: 'hex', presetColors: ['#FF0000', '#00FF00', '#0000FF'] } +{ name: 'brand_color', label: 'Brand Color', type: 'color' } ``` ### `rating` @@ -557,11 +549,12 @@ Star rating input. | Property | Type | Default | Description | |:---|:---|:---|:---| -| `maxRating` | `number` | `5` | Maximum rating value | -| `allowHalf` | `boolean` | `false` | Allow half-star ratings | +| `max` | `number` | `5` | Maximum rating value (set by the `Field.rating(max)` factory) | ```typescript -{ name: 'satisfaction', label: 'Rating', type: 'rating', maxRating: 5, allowHalf: true } +{ name: 'satisfaction', label: 'Rating', type: 'rating', max: 5 } +// or with the factory: +// satisfaction: Field.rating(5, { label: 'Rating' }) ``` ### `slider` @@ -571,12 +564,10 @@ Range slider input. |:---|:---|:---|:---| | `min` | `number` | — | Minimum value | | `max` | `number` | — | Maximum value | -| `step` | `number` | — | Step increment | -| `showValue` | `boolean` | — | Display current value | -| `marks` | `object` | — | Tick marks on slider | +| `step` | `number` | `1` | Step increment | ```typescript -{ name: 'confidence', label: 'Confidence', type: 'slider', min: 0, max: 100, step: 5, showValue: true } +{ name: 'confidence', label: 'Confidence', type: 'slider', min: 0, max: 100, step: 5 } ``` ### `signature` @@ -587,17 +578,10 @@ Digital signature capture. ``` ### `qrcode` -QR/barcode generator and scanner. - -| Property | Type | Default | Description | -|:---|:---|:---|:---| -| `barcodeFormat` | `'qr' \| 'ean13' \| 'ean8' \| 'code128' \| 'code39' \| 'upca' \| 'upce'` | — | Barcode format type | -| `qrErrorCorrection` | `'L' \| 'M' \| 'Q' \| 'H'` | — | QR error correction level (only when `barcodeFormat` is `'qr'`) | -| `displayValue` | `boolean` | — | Display human-readable value below barcode/QR code | -| `allowScanning` | `boolean` | — | Enable camera scanning | +QR/barcode value rendered as a scannable code. No per-type config properties. ```typescript -{ name: 'asset_tag', label: 'Asset Tag', type: 'qrcode', allowScanning: true } +{ name: 'asset_tag', label: 'Asset Tag', type: 'qrcode' } ``` ### `progress` @@ -623,17 +607,14 @@ Vector embeddings for semantic search and similarity. | Property | Type | Default | Description | |:---|:---|:---|:---| -| `vectorConfig.dimensions` | `number` | **required** | Vector dimensionality (e.g., 1536 for OpenAI) | -| `vectorConfig.distanceMetric` | `'cosine' \| 'euclidean' \| 'dotProduct' \| 'manhattan'` | `'cosine'` | Distance calculation method | -| `vectorConfig.normalized` | `boolean` | `false` | Whether vectors are pre-normalized | -| `vectorConfig.indexed` | `boolean` | `true` | Enable vector indexing | -| `vectorConfig.indexType` | `'hnsw' \| 'ivfflat' \| 'flat'` | — | Index algorithm | +| `dimensions` | `number` | **required** | Vector dimensionality (e.g., 1536 for OpenAI embeddings) | + +The nested `vectorConfig` object (with `distanceMetric`, `indexed`, `indexType`, …) is retained for back-compat only and is a runtime no-op — set the flat `dimensions` property instead. ```typescript -{ - name: 'embedding', label: 'Embedding', type: 'vector', - vectorConfig: { dimensions: 1536, distanceMetric: 'cosine', indexed: true, indexType: 'hnsw' } -} +{ name: 'embedding', label: 'Embedding', type: 'vector', dimensions: 1536 } +// or with the factory: +// embedding: Field.vector(1536, { label: 'Embedding' }) ``` --- @@ -647,7 +628,7 @@ These properties are available on **all** field types: | `name` | `string` | **required** | Machine name (snake_case) | | `label` | `string` | — | Human-readable display label | | `description` | `string` | — | Field description / help text | -| `type` | `FieldType` | **required** | One of the 49 field types | +| `type` | `FieldType` | **required** | One of the supported field types | | `required` | `boolean` | `false` | Whether the field is required | | `unique` | `boolean` | `false` | Enforce uniqueness | | `multiple` | `boolean` | `false` | Allow array of values | @@ -660,11 +641,9 @@ These properties are available on **all** field types: | `defaultValue` | `any` | — | Default value for new records | | `group` | `string` | — | Field grouping / section | | `inlineHelpText` | `string` | — | Inline help tooltip | -| `trackFeedHistory` | `boolean` | — | Track changes in activity feed | -| `encryptionConfig` | `object` | — | Field-level encryption settings | -| `maskingRule` | `object` | — | Data masking configuration | +| `trackHistory` | `boolean` | — | Render this field's value changes as entries on the record activity timeline | +| `requiredPermissions` | `string[]` | — | Capabilities required to read/edit this field (masked on read, denied on write) | | `dependencies` | `string[]` | — | Names of fields this field depends on (formulas, visibility rules, etc.) | -| `dataQuality` | `object` | — | Data quality validation rules | | `visibleWhen` | `Expression` | — | Show field only when predicate is true | | `readonlyWhen` | `Expression` | — | Make field read-only when predicate is true | | `requiredWhen` | `Expression` | — | Require field when predicate is true | @@ -672,71 +651,6 @@ These properties are available on **all** field types: --- -## Field Type Decision Tree - -Use this guide to choose the right field type: - -``` -Is it text? -├── Short text (< 255 chars) → text -├── Long text → textarea -├── Formatted text → markdown | html | richtext -├── Email address → email -├── URL → url -├── Phone number → phone -├── Login password (one-way hash) → password -├── Reversible secret (API key/token) → secret -└── Source code → code - -Is it a number? -├── Integer or decimal → number -├── Money amount → currency -└── Percentage → percent - -Is it a date/time? -├── Date only → date -├── Date + time → datetime -└── Time only → time - -Is it a choice? -├── Single choice (dropdown) → select -├── Single choice (visible) → radio -├── Multiple choices (dropdown) → multiselect -└── Multiple choices (visible) → checkboxes - -Is it a reference? -├── Simple foreign key → lookup -├── Parent-child (cascade delete) → master_detail -└── Self-referential hierarchy → tree - -Is it a file? -├── Any file → file -├── Image → image -├── Profile picture → avatar -├── Video → video -└── Audio → audio - -Is it computed? -├── Formula calculation → formula -├── Roll-up summary → summary -└── Auto-incrementing ID → autonumber - -Is it embedded structured data (stored as JSON, no separate table)? -├── Single embedded sub-object → composite -├── Repeating embedded array → repeater -└── Name-keyed map of sub-objects → record - -Is it specialized? -├── Geographic location → location -├── Postal address → address -├── Yes/No toggle → boolean | toggle -├── Star rating → rating -├── Range slider → slider -├── Color picker → color -├── Raw JSON → json -├── Tags/Labels → tags -├── Progress bar → progress -├── Signature → signature -├── QR/Barcode → qrcode -└── AI embedding → vector -``` +## Choosing a Field Type + +Not sure which type fits your data? Follow the [Field Type Decision Tree](/docs/data-modeling/field-type-decision-tree) — a flowchart, quick-reference tables, and a use-case mapping for every type in this gallery. diff --git a/content/docs/data-modeling/fields.mdx b/content/docs/data-modeling/fields.mdx index fdeb4bdd62..36d16ac68c 100644 --- a/content/docs/data-modeling/fields.mdx +++ b/content/docs/data-modeling/fields.mdx @@ -1,11 +1,11 @@ --- title: Field Metadata -description: Configure field types and properties — 49 field types for text, numbers, dates, relationships, and more +description: Configure field types and properties — text, numbers, dates, relationships, files, and more --- # Field Metadata -A **Field** defines an individual property within an Object. ObjectStack provides 49 field types covering text, numbers, dates, selections, relationships, files, calculations, and specialized types like vectors and QR codes. +A **Field** defines an individual property within an Object. ObjectStack provides a comprehensive set of field types covering text, numbers, dates, selections, relationships, files, calculations, and specialized types like vectors and QR codes. ## Basic Usage diff --git a/content/docs/data-modeling/index.mdx b/content/docs/data-modeling/index.mdx index 3e0d63dd3d..4fa4713aff 100644 --- a/content/docs/data-modeling/index.mdx +++ b/content/docs/data-modeling/index.mdx @@ -1,6 +1,6 @@ --- title: Data Modeling -description: Objects, 49 field types, relationships, validation, CEL formulas, and a compiled query AST — the ObjectQL layer every other module builds on. +description: Objects, fields, relationships, validation, CEL formulas, and a compiled query AST — the ObjectQL layer every other module builds on. --- # Data Modeling @@ -31,7 +31,7 @@ That one definition is enough to get a persisted table, CRUD + query endpoints, ## What the data layer actually gives you -- **49 field types** — from `text`, `currency`, and `lookup`/`master_detail` relationships to `formula`, `summary`, `signature`, `qrcode`, and `vector` (with HNSW/IVFFlat index config for AI embeddings). See the [Field Types gallery](/docs/data-modeling/field-types). +- **A full spectrum of field types** — from `text`, `currency`, and `lookup`/`master_detail` relationships to `formula`, `summary`, `signature`, and `vector` (with index config for AI embeddings). See the [Field Types gallery](/docs/data-modeling/field-types). - **Validation as metadata** — required/format rules, CEL script validation with access to `previous.`, uniqueness via indexes, and severity levels — enforced identically in API, UI, and automation. - **CEL expressions** — formula fields and computed defaults share one expression language ([Expressions](/docs/data-modeling/formulas)). - **A compiled query AST** — queries are JSON documents validated against the protocol, then compiled by a driver into native queries with joins, aggregations, window functions, HAVING, and subqueries. The [query cheat sheet](/docs/data-modeling/queries) covers the syntax; the [spec](/docs/protocol/objectql/query-syntax) is normative. @@ -44,7 +44,7 @@ That one definition is enough to get a persisted table, CRUD + query endpoints, - + diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index 552e99e71d..b8c940da1e 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -18,7 +18,7 @@ Core business logic and data modeling schemas. | Protocol | Source File | Key Schemas | Purpose | |:---------|:-----------|:------------|:--------| -| **[Field](/docs/references/data/field)** | `field.zod.ts` | Field, FieldType, SelectOption | 49 field types for data modeling | +| **[Field](/docs/references/data/field)** | `field.zod.ts` | Field, FieldType, SelectOption | Field types for data modeling | | **[Object](/docs/references/data/object)** | `object.zod.ts` | Object, ObjectCapabilities | Object/table definitions | | **[Query](/docs/references/data/query)** | `query.zod.ts` | Query, QueryOptions | Query AST with joins, aggregations | | **[Filter](/docs/references/data/filter)** | `filter.zod.ts` | QueryFilter, FilterCondition | Advanced filtering operators | diff --git a/content/docs/index.mdx b/content/docs/index.mdx index a9fb7a0fc3..389830bcef 100644 --- a/content/docs/index.mdx +++ b/content/docs/index.mdx @@ -5,9 +5,7 @@ description: Technical documentation for ObjectStack. # ObjectStack Documentation -ObjectStack is an AI-native business backend protocol for structured, auditable business applications. You author your application — data model, logic, permissions, and UI — as typed metadata in TypeScript (`defineStack()`), compile it to a self-contained artifact (`objectstack.json`), and the runtime derives everything else: the database schema across four interchangeable drivers, a generated REST + realtime API, permission-checked automation, and server-driven UI. AI agents act through the same typed, permission-aware surface — never through raw SQL or scraped UI. - -One protocol definition, 15 namespaces, 49 field types, one artifact — every engine (SQL, React, MCP) is a consumer of the same source of truth. +ObjectStack is an AI-native business backend protocol for structured, auditable business applications. You author your application — data model, logic, permissions, and UI — as typed metadata in TypeScript (`defineStack()`), compile it to a self-contained artifact (`objectstack.json`), and the runtime derives everything else: the database schema on interchangeable drivers, a generated REST + realtime API, permission-checked automation, and server-driven UI. AI agents act through the same typed, permission-aware surface — never through raw SQL or scraped UI. Every engine (SQL, React, MCP) is a consumer of the same source of truth. ## Start here diff --git a/content/docs/kernel/index.mdx b/content/docs/kernel/index.mdx index 948fd02cfb..4d038c2ad0 100644 --- a/content/docs/kernel/index.mdx +++ b/content/docs/kernel/index.mdx @@ -23,7 +23,7 @@ The kernel is ObjectStack's runtime: it loads your metadata artifact, hosts plug | [`services.storage`](/docs/kernel/runtime-services/storage-service) | stable | File storage | | [`services.audit`](/docs/kernel/runtime-services/audit-service) | experimental | Append-only audit sink (query history via `services.data`) | -- **Service contracts** (28 interface files in `packages/spec/src/contracts/` — `IDataEngine`, `IAuthService`, `ICacheService`, `IMetadataService`, `IStorageService`, …) define what any implementation must provide, so services are swappable. Fifteen runtime service packages ship in this repo (cache, cluster, datasource, i18n, job, knowledge, messaging, realtime, …). +- **Service contracts** (`IDataEngine`, `IAuthService`, `ICacheService`, `IMetadataService`, `IStorageService`, … in `packages/spec/src/contracts/`) define what any implementation must provide, so services are swappable — the repo ships runtime service packages for cache, cluster, datasource, i18n, jobs, knowledge, messaging, realtime, and more. - **Cluster semantics** define how the runtime behaves beyond a single node — including the per-partition locking the webhook outbox relies on. ## What's in this module diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index ece83adb17..5197cb2863 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -43,7 +43,7 @@ Defines the "Shape of Data" and business logic. | File | Schema | Purpose | | :--- | :--- | :--- | -| `field.zod.ts` | `FieldSchema` | Field definitions with 49 types (text, number, select, lookup, formula, vector, location, etc.) | +| `field.zod.ts` | `FieldSchema` | Field definitions (text, number, select, lookup, formula, vector, location, etc.) | | `object.zod.ts` | `ObjectSchema` | Object/table definitions with fields, indexes, and capabilities | | `query.zod.ts` | `QuerySchema` | Abstract query AST supporting window functions, HAVING, DISTINCT, subqueries | | `validation.zod.ts` | `ValidationRuleSchema` | Validation rules for data integrity | @@ -63,7 +63,7 @@ Defines the "Shape of Data" and business logic. | `driver/mongo.zod.ts` | `MongoConfigSchema` | MongoDB driver configuration | **Key Features:** -- 49 field types including AI/ML vectors and GPS locations +- Field types spanning text, relationships, formulas, AI/ML vectors, and GPS locations - Advanced query capabilities (window functions, HAVING, DISTINCT, subqueries) - Validation rules and formulas - Lifecycle hooks for business logic diff --git a/content/docs/ui/index.mdx b/content/docs/ui/index.mdx index ecae436daf..6e9035c597 100644 --- a/content/docs/ui/index.mdx +++ b/content/docs/ui/index.mdx @@ -1,6 +1,6 @@ --- title: UI Engine -description: Apps, 9 view render types, dashboards, themes, and public portals — server-driven UI declared as metadata and rendered by the ObjectUI runtime. +description: Apps, views, dashboards, themes, and public portals — server-driven UI declared as metadata and rendered by the ObjectUI runtime. --- # UI Engine @@ -29,7 +29,7 @@ export const CrmApp = App.create({ ## The building blocks - **Apps** group navigation, branding, and entry points for one audience. -- **Views** present object records in **9 render types**: `grid` (the standard data table), `kanban`, `gallery`, `calendar`, `timeline`, `gantt`, `map`, `chart`, and `tree`. Forms are their own view kind with per-mode layouts ([Views](/docs/ui/views)). +- **Views** present object records as `grid` (the standard data table), `kanban`, `gallery`, `calendar`, `timeline`, `gantt`, `map`, `chart`, or `tree`. Forms are their own view kind with per-mode layouts ([Views](/docs/ui/views)). - **Pages** compose free-form layouts from widgets; **Dashboards** combine charts, reports, and datasets for analytics. - **Themes** define palettes, typography, and spacing as metadata — the CRM example ships light and dark theme variants. - **Portals** open a scoped slice of the app to external audiences, including **anonymous entry routes** for public data collection ([Forms](/docs/ui/forms), [public data collection](/docs/ui/public-data-collection)). @@ -39,7 +39,7 @@ export const CrmApp = App.create({ - + From f0f60a28ae45baafb352dfe7bc65a8c7e97cc27d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 17:47:44 +0000 Subject: [PATCH 3/3] =?UTF-8?q?docs(site):=20full-corpus=20persona=20revie?= =?UTF-8?q?w=20=E2=80=94=20fix=20what=20real=20readers=20would=20hit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seven reader-persona passes (first-time developer, schema modeler, logic author + API integrator, security admin, UI builder + AI admin, plugin developer, production SRE) read every hand-written page and verified claims against source before fixing. Highlights: - hooks: ctx.input is flat (installFlatInput) — the documented ctx.input.doc pattern never worked; fixed across hooks, events, error-handling-server, and the plugin tutorial - plugin tutorial: manifest example was missing required id (threw at parse) and registered a kernel hook name that never fires — a reader shipped a silently dead plugin; rewritten against engine.registerHook - permissions: removed the false implicit manager-visibility claim (role hierarchy grants nothing by itself); owner-type sharing rules and group/guest recipients marked declared-but-unenforced; FLS semantics corrected to default-visible for undeclared fields; no-sharingModel = no record filter warning added - data modeling: removed invented cdc/partitioning/recordTypes object config (schema hard-rejects), phantom field props (pruned 2026-06), wrong autonumber key; new expand (related records) query section - api: removed ADR-0019-deleted workflow approve/reject endpoints, invented webhook rate-limiting; realtime status stated honestly (in-process pub/sub; WS/SSE transport plugin-provided); error handling documents the two real wire formats - protocol: maskingRule/encryptionConfig sections re-scoped to their real status (System schemas exist; field wiring pruned, channel is type:'secret' + FLS) - deployment: env-var table repaired and completed (OS_SECRET_KEY, cluster vars), stale CLI paths and ten dead links fixed - onboarding: REST route /api/v1/data/:object, send_email→notify node, npx os pointers, nine dead ADR links - runtime-services: binding note — services.* is the contract surface; open-framework hooks use ctx.api / ctx.getService Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018r9mjpF7jr9BKdzEmUE4uZ --- content/docs/ai/chatbot-integration.mdx | 11 +- content/docs/ai/knowledge-rag.mdx | 5 +- content/docs/api/client-sdk.mdx | 12 +- content/docs/api/environment-routing.mdx | 2 +- content/docs/api/error-catalog.mdx | 4 +- content/docs/api/error-handling-client.mdx | 2 +- content/docs/api/error-handling-server.mdx | 12 +- content/docs/api/index.mdx | 2 +- content/docs/api/plugin-endpoints.mdx | 10 +- content/docs/automation/approvals.mdx | 2 +- content/docs/automation/flows.mdx | 1 + content/docs/automation/hook-bodies.mdx | 2 +- content/docs/automation/hooks.mdx | 10 +- content/docs/automation/webhooks.mdx | 24 ++-- content/docs/concepts/architecture.mdx | 4 +- content/docs/concepts/metadata-lifecycle.mdx | 18 +-- content/docs/data-modeling/fields.mdx | 16 +-- content/docs/data-modeling/formulas.mdx | 25 ++-- content/docs/data-modeling/objects.mdx | 32 +---- content/docs/data-modeling/queries.mdx | 27 ++++ content/docs/data-modeling/relationships.mdx | 8 ++ content/docs/data-modeling/schema-design.mdx | 21 ++-- .../docs/data-modeling/validation-rules.mdx | 119 +++++------------- .../docs/deployment/cloud-artifact-api.mdx | 3 +- .../docs/deployment/environment-variables.mdx | 19 ++- content/docs/deployment/troubleshooting.mdx | 2 +- content/docs/deployment/vercel.mdx | 2 +- content/docs/getting-started/cli.mdx | 10 +- .../docs/getting-started/common-patterns.mdx | 48 ++++--- content/docs/getting-started/examples.mdx | 2 +- .../docs/getting-started/quick-reference.mdx | 4 +- content/docs/getting-started/quick-start.mdx | 8 +- .../docs/kernel/contracts/cache-service.mdx | 11 +- content/docs/kernel/contracts/index.mdx | 8 +- .../kernel/contracts/metadata-service.mdx | 4 +- content/docs/kernel/events.mdx | 8 +- content/docs/kernel/index.mdx | 2 +- .../docs/kernel/runtime-services/examples.mdx | 12 +- .../docs/kernel/runtime-services/index.mdx | 9 ++ content/docs/kernel/services-checklist.mdx | 41 +++--- content/docs/permissions/access-recipes.mdx | 4 +- content/docs/permissions/authorization.mdx | 10 +- .../docs/permissions/field-level-security.mdx | 20 ++- content/docs/permissions/index.mdx | 9 +- .../docs/permissions/permission-metadata.mdx | 2 +- .../docs/permissions/permissions-matrix.mdx | 43 ++++--- content/docs/permissions/roles.mdx | 33 +++-- content/docs/permissions/sharing-rules.mdx | 25 +++- content/docs/plugins/anatomy.mdx | 2 +- content/docs/plugins/development.mdx | 56 +++++---- content/docs/plugins/index.mdx | 2 +- content/docs/plugins/packages.mdx | 81 ++++++------ content/docs/protocol/diagram.mdx | 6 +- .../docs/protocol/objectos/error-handling.mdx | 4 +- content/docs/protocol/objectql/security.mdx | 22 +++- content/docs/protocol/objectql/types.mdx | 35 ++---- content/docs/protocol/objectui/concept.mdx | 2 +- .../docs/releases/implementation-status.mdx | 8 +- content/docs/ui/apps.mdx | 4 +- content/docs/ui/dashboards.mdx | 2 +- content/docs/ui/pages.mdx | 18 +-- content/docs/ui/views.mdx | 5 +- 62 files changed, 516 insertions(+), 439 deletions(-) diff --git a/content/docs/ai/chatbot-integration.mdx b/content/docs/ai/chatbot-integration.mdx index 5953999dfe..f863efe123 100644 --- a/content/docs/ai/chatbot-integration.mdx +++ b/content/docs/ai/chatbot-integration.mdx @@ -26,12 +26,13 @@ This guide maps every chatbot configuration knob to its framework endpoint so you can drop the plugin into an app without reverse-engineering the contract. -## 1. Enable the AI tier on the framework side +## 1. Enable the AI tier on the backend -The `default` and `full` plugin tier presets both include the `ai` -capability, so `objectstack dev` boots the AI services unless you opt out -with `--preset minimal`. Provide a Vercel AI Gateway model and key via env -vars: +On a cloud / EE dev host (where `@objectstack/service-ai` is available — +see the callout above), the `default` and `full` plugin tier presets both +include the `ai` capability, so `objectstack dev` boots the AI services +unless you opt out with `--preset minimal`. Provide a Vercel AI Gateway +model and key via env vars: ```bash AI_GATEWAY_API_KEY=vck_*** \ diff --git a/content/docs/ai/knowledge-rag.mdx b/content/docs/ai/knowledge-rag.mdx index 31caee0eb8..0edb580c18 100644 --- a/content/docs/ai/knowledge-rag.mdx +++ b/content/docs/ai/knowledge-rag.mdx @@ -40,7 +40,10 @@ kernel.use(new KnowledgeMemoryPlugin()); // kernel.use(new KnowledgeRagflowPlugin({ endpoint, apiKey })); ``` -Then register the AI tool so agents can call it: +Then register the AI tool so agents can call it. (This step uses +`@objectstack/service-ai`, i.e. the **cloud / Enterprise** in-UI AI runtime — +see the callout in the [AI Overview](/docs/ai). The Knowledge Protocol itself +and the adapter plugins above ship in the open framework.) ```ts import { registerKnowledgeTools } from '@objectstack/service-ai'; diff --git a/content/docs/api/client-sdk.mdx b/content/docs/api/client-sdk.mdx index 1ccf28de4f..7f091c683f 100644 --- a/content/docs/api/client-sdk.mdx +++ b/content/docs/api/client-sdk.mdx @@ -122,7 +122,7 @@ The `@objectstack/client` SDK aims to implement the ObjectStack API protocol spe | **storage** | ✅ | 2 | File upload & download | | **i18n** | ✅ | 3 | Internationalization | | **notifications** | 🟡 | 7 | Legacy notification helpers; receipt/inbox cut-over pending | -| **realtime** | ✅ | 6 | WebSocket subscriptions | +| **realtime** | ✅ | 6 | Connection/subscription/presence calls (server transport is plugin-provided) | | **ai** | ✅ | 3 | AI services (NLQ, suggest, insights) | @@ -553,18 +553,18 @@ For detailed information about the client's protocol implementation: diff --git a/content/docs/api/environment-routing.mdx b/content/docs/api/environment-routing.mdx index 4cc050bb3c..49640173e1 100644 --- a/content/docs/api/environment-routing.mdx +++ b/content/docs/api/environment-routing.mdx @@ -73,7 +73,7 @@ const client = new ObjectStackClient({ const env = client.project('env_prod'); -await env.data.find('customer', { top: 20 }); +await env.data.find('customer', { limit: 20 }); await env.meta.getItems('object'); await env.packages.list(); ``` diff --git a/content/docs/api/error-catalog.mdx b/content/docs/api/error-catalog.mdx index 25ddd34508..58837f70e4 100644 --- a/content/docs/api/error-catalog.mdx +++ b/content/docs/api/error-catalog.mdx @@ -5,7 +5,7 @@ description: Complete reference for all ObjectStack error codes with causes, fix # Error Code Catalog -ObjectStack uses a structured error system with **9 error categories** and **41+ standardized error codes**. Every error includes a machine-readable code, HTTP status mapping, and retry guidance. +ObjectStack uses a structured error system with **9 error categories** and **51 standardized error codes**. Every error includes a machine-readable code, HTTP status mapping, and retry guidance. **Source:** `packages/spec/src/api/errors.zod.ts` @@ -391,7 +391,7 @@ import type { EnhancedApiError } from '@objectstack/spec/api'; async function handleApiCall() { try { - const result = await client.records.create('task', { title: 'New Task' }); + const result = await client.data.create('task', { title: 'New Task' }); return result; } catch (error) { const apiError = error as EnhancedApiError; diff --git a/content/docs/api/error-handling-client.mdx b/content/docs/api/error-handling-client.mdx index 10051297ff..597186f04b 100644 --- a/content/docs/api/error-handling-client.mdx +++ b/content/docs/api/error-handling-client.mdx @@ -8,7 +8,7 @@ description: Best practices for handling ObjectStack errors in client applicatio This guide covers best practices for handling ObjectStack API errors in client applications — from parsing error responses to displaying validation errors in forms and implementing retry strategies. -**Error Format:** All ObjectStack API errors follow the `ErrorResponseSchema` structure. See [Wire Format](/docs/api/wire-format#7-error-response-format) for the full JSON shape. +**Error Format:** The patterns in this guide target the spec error envelope (`ErrorResponseSchema`). Note that today's kernel REST data routes emit a flatter `{ error, code }` shape — see the [API Overview](/docs/api#error-handling) for the two wire formats in use and [Wire Format](/docs/api/wire-format#7-error-response-format) for full JSON examples; adapt the parser accordingly. --- diff --git a/content/docs/api/error-handling-server.mdx b/content/docs/api/error-handling-server.mdx index d2855b15d1..e51de23aa1 100644 --- a/content/docs/api/error-handling-server.mdx +++ b/content/docs/api/error-handling-server.mdx @@ -77,8 +77,8 @@ object (imported from `@objectstack/spec/data`) with `object`, `events` (an array of lifecycle events such as `beforeInsert` / `afterUpdate`), and a `handler: async (ctx) => { ... }`. Errors thrown from a hook handler abort the operation and propagate to the API layer. The handler receives a `HookContext`: -the pending record lives on `ctx.input.doc`, the pre-change snapshot on -`ctx.previous`, and the persisted result (after hooks) on `ctx.result`. +the pending record's fields are exposed directly on `ctx.input`, the pre-change +snapshot on `ctx.previous`, and the persisted result (after hooks) on `ctx.result`. ### Before Hooks (Validation / Guard) @@ -90,7 +90,7 @@ export const ValidateStatusTransition: Hook = { object: 'task', events: ['beforeUpdate'], handler: async (ctx) => { - const changes = ctx.input.doc as Record; + const changes = ctx.input as Record; const previous = ctx.previous ?? {}; // Guard: prevent modifying completed tasks @@ -182,7 +182,7 @@ export const ValidateTaskData: Hook = { object: 'task', events: ['beforeInsert'], handler: async (ctx) => { - const data = ctx.input.doc as Record; + const data = ctx.input as Record; const schema = z.object({ title: z.string().min(1, 'Title is required').max(200), priority: z.enum(['low', 'medium', 'high', 'critical']), @@ -218,7 +218,7 @@ export const TaskWriteWithLogging: Hook = { onError: 'abort', // failure aborts the write (default); use 'log' to continue handler: async (ctx) => { try { - await runBusinessRules(ctx.input.doc); + await runBusinessRules(ctx.input); } catch (error) { const httpStatus = (error as AppError).httpStatus ?? 500; const logEntry = { @@ -496,7 +496,7 @@ export const ValidateBusinessRules: Hook = { object: 'task', events: ['beforeInsert'], handler: async (ctx) => { - const result = safeParsePretty(TaskBusinessRules, ctx.input.doc); + const result = safeParsePretty(TaskBusinessRules, ctx.input); if (!result.success) { throw new AppError( 'validation_error', diff --git a/content/docs/api/index.mdx b/content/docs/api/index.mdx index 4bd6c4939e..075e2f9d71 100644 --- a/content/docs/api/index.mdx +++ b/content/docs/api/index.mdx @@ -14,7 +14,7 @@ ObjectStack exposes a fully typed REST API. All endpoints use JSON request/respo | Surface | Status in this repo | | :--- | :--- | | **REST** | ✅ Auto-generated from the protocol (`@objectstack/rest`) — CRUD, query, batch, metadata, packages | -| **Realtime** | ✅ WebSocket subscriptions plus SSE streaming (`@objectstack/service-realtime`) | +| **Realtime** | ⚠️ In-process pub/sub service (`@objectstack/service-realtime`, single-instance); the `/realtime/*` REST routes and WebSocket/SSE transport are plugin-provided — none ships in the open framework | | **MCP** | ✅ Objects and opted-in actions exposed as Model Context Protocol tools ([AI module](/docs/ai)) | | **GraphQL** | ⚠️ Route is wired but **bring-your-own service**: `/graphql` returns 501 unless an implementation of the `IGraphQLService` contract is registered — none ships in the open framework | | **OData** | ⚠️ Vocabulary only: REST list endpoints accept OData-style operators (e.g. `$top`), but there is no standalone OData endpoint | diff --git a/content/docs/api/plugin-endpoints.mdx b/content/docs/api/plugin-endpoints.mdx index f449bad69f..55ee4d12dd 100644 --- a/content/docs/api/plugin-endpoints.mdx +++ b/content/docs/api/plugin-endpoints.mdx @@ -33,14 +33,16 @@ The following endpoints become available when the corresponding plugin is instal | GET | `/workflow/:object/config` | Get workflow configuration | | GET | `/workflow/:object/:recordId/state` | Get record's workflow state | | POST | `/workflow/:object/:recordId/transition` | Execute state transition | -| POST | `/workflow/:object/:recordId/approve` | Approve workflow step | -| POST | `/workflow/:object/:recordId/reject` | Reject workflow step | + +Approve/reject are **not** workflow routes (ADR-0019): approval is a flow node, and decisions are recorded on the approvals runtime via `POST /approvals/requests/:id/approve` and `POST /approvals/requests/:id/reject`. ### Automation (`/automation`) — Plugin Required | Method | Endpoint | Description | |:-------|:---------|:------------| -| POST | `/automation/trigger/:name` | Trigger an automation flow by name | +| POST | `/automation/:name/trigger` | Trigger an automation flow by name (legacy alias: `/automation/trigger/:name`) | + +The automation dispatcher also exposes flow CRUD (`GET`/`POST /automation`, `GET`/`PUT`/`DELETE /automation/:name`) and run observability/resume routes — see [Durable pause & resume](/docs/automation/flows#durable-pause--resume-adr-0019). ### Views (`/ui`) — Plugin Required @@ -60,7 +62,7 @@ The following endpoints become available when the corresponding plugin is instal | POST | `/realtime/disconnect` | Close connection | | POST | `/realtime/subscribe` | Subscribe to channel | | POST | `/realtime/unsubscribe` | Unsubscribe | -| POST | `/realtime/presence` | Set presence | +| PUT | `/realtime/presence/:channel` | Set presence | | GET | `/realtime/presence/:channel` | Get channel presence | ### Notifications (`/notifications`) — Plugin Required diff --git a/content/docs/automation/approvals.mdx b/content/docs/automation/approvals.mdx index 175285a35e..e538ee99ea 100644 --- a/content/docs/automation/approvals.mdx +++ b/content/docs/automation/approvals.mdx @@ -130,5 +130,5 @@ Separating *who configures* from *who approves* from *as whom it runs* is the sa ## See also - Decision records: **ADR-0049** (`runAs`), **ADR-0066** (unified authorization). -- Guide: [Business Logic](/docs/automation/hooks) (Flows, Approval Nodes); [Who can see data / automation / interface](/docs/permissions/access-recipes). +- Guide: [Hooks](/docs/automation/hooks) and [Flows](/docs/automation/flows); [Who can see data / automation / interface](/docs/permissions/access-recipes). - Reference: [Flow](/docs/references/automation/flow), [Approval](/docs/references/automation/approval). diff --git a/content/docs/automation/flows.mdx b/content/docs/automation/flows.mdx index e5b83b3ffa..667e001d46 100644 --- a/content/docs/automation/flows.mdx +++ b/content/docs/automation/flows.mdx @@ -109,6 +109,7 @@ Each node performs a specific action in the flow. | `delete_record` | Delete records | | `get_record` | Query records | | `http` | Make an HTTP API call | +| `notify` | Send an outbound notification via the messaging service | | `script` | Run a custom script action (dispatched by `config.actionType`) | | `screen` | Display a user form/screen (durable pause) | | `wait` | Pause for a timer or named signal (durable pause; timers auto-resume) | diff --git a/content/docs/automation/hook-bodies.mdx b/content/docs/automation/hook-bodies.mdx index c3c6282ef4..814043c8ba 100644 --- a/content/docs/automation/hook-bodies.mdx +++ b/content/docs/automation/hook-bodies.mdx @@ -238,7 +238,7 @@ objectstack.config.ts ## See also - [Formula Reference](/docs/data-modeling/formulas) — the L1 expression engine. -- [Business Logic](/docs/automation/hooks) — broader patterns for hooks, actions, flows. +- [Hooks](/docs/automation/hooks) and [Flows](/docs/automation/flows) — broader patterns for hooks, actions, flows. - [Cloud Deployment](/docs/deployment) — how artifacts travel from Studio to objectos. - `packages/spec/src/data/hook-body.zod.ts` — canonical Zod schema. - `packages/runtime/src/sandbox/script-runner.ts` — engine decision rationale. diff --git a/content/docs/automation/hooks.mdx b/content/docs/automation/hooks.mdx index 5d11601910..25b204f933 100644 --- a/content/docs/automation/hooks.mdx +++ b/content/docs/automation/hooks.mdx @@ -16,8 +16,10 @@ such as `beforeFind`/`afterFind` are also available). ## Before Hook -Mutate the incoming record before it is saved. The pending record lives on -`ctx.input` (`ctx.input.doc` for insert/update): +Mutate the incoming record before it is saved. The engine exposes the pending +record's fields **directly on `ctx.input`** (a flat view over the internal +`{ data, options }` wrapper — reads and writes of record fields route through +`ctx.input.data`): ```typescript import { Hook } from '@objectstack/spec/data'; @@ -28,7 +30,7 @@ export const AccountBeforeWrite: Hook = { events: ['beforeInsert', 'beforeUpdate'], handler: async (ctx) => { - const record = ctx.input.doc as Record; + const record = ctx.input as Record; // Normalize phone numbers if (record.phone) { @@ -80,7 +82,7 @@ ctx = { id, // tracing id object, // target object name event, // e.g. 'beforeInsert' | 'afterUpdate' - input, // mutable input (e.g. input.doc for insert/update) + input, // mutable input — record fields exposed flat (raw wrapper: input.data, input.options) result, // mutable operation result (after* events) previous, // record state before the operation (update/delete) session, // { userId, tenantId, roles, accessToken, isSystem } diff --git a/content/docs/automation/webhooks.mdx b/content/docs/automation/webhooks.mdx index c312df08f3..4b939355b3 100644 --- a/content/docs/automation/webhooks.mdx +++ b/content/docs/automation/webhooks.mdx @@ -373,15 +373,17 @@ better-auth session (any authenticated user). Responses: `200` on success, ## 9. Concurrency & rate-limiting -Each dispatcher partition runs at most `maxConcurrency` (default 16) HTTP -requests in flight. Beyond that, claimed rows wait in an in-memory -queue. This caps per-node outbound load. - -For per-receiver rate limiting (some receivers like Slack publish strict -limits), the dispatcher tracks `requests_in_last_second` per -`hostname(url)` and delays further requests to that host with the same -exponential backoff used for retries. This shields well-behaved -receivers from being driven into 429s by our bulk traffic. +Outbound load is shaped by the claim loop, not an explicit concurrency knob: +within a held partition the dispatcher claims up to `batchSize` rows (default +32) per tick and POSTs them **sequentially**, so at most one request per +partition is in flight at a time (default 8 partitions per node). Both +`batchSize`, `partitionCount`, and the tick `intervalMs` are configurable via +`HttpDispatcherOptions`. + +> **Not yet implemented.** Per-receiver rate limiting (tracking request rate +> per `hostname(url)` and delaying further requests to a busy host) is future +> work — see Phase 4 of the plan below. Today nothing shields a receiver from +> bulk traffic beyond the sequential-per-partition send loop. ## 10. Observability @@ -469,7 +471,7 @@ Every design choice matches one or more established systems: - **GitHub webhooks** — `X-…-Signature: sha256=` signing scheme, redelivery, per-delivery detail with request/response bytes. - **Shopify webhooks** — HMAC-SHA256 header, `X-Shopify-Topic` event - hint (we use `type` in payload instead — simpler). + hint (we carry the event hint in the `X-Objectstack-Event` header instead). - **AWS EventBridge / SQS** — at-least-once durability, dead-letter queue model (our `status = dead`). - **Kubernetes admission webhooks** — egress validation and TLS-only @@ -477,7 +479,7 @@ Every design choice matches one or more established systems: ## 15. Migration plan -Three phases. Each phase delivers visible user value. +Four phases. Each phase delivers visible user value. > **How it actually shipped.** The implementation did **not** introduce new > `system/*` Zod schemas. There is no `system/webhook-delivery.zod.ts` and no diff --git a/content/docs/concepts/architecture.mdx b/content/docs/concepts/architecture.mdx index 665cb8f242..074094bc41 100644 --- a/content/docs/concepts/architecture.mdx +++ b/content/docs/concepts/architecture.mdx @@ -183,7 +183,7 @@ export const HighValueCustomerFlow = defineFlow({ nodes: [ { id: 'start', type: 'start', label: 'Customer created' }, { id: 'assign', type: 'update_record', label: 'Assign owner' }, - { id: 'notify', type: 'send_email', label: 'Alert leadership' }, + { id: 'notify', type: 'notify', label: 'Alert leadership' }, { id: 'end', type: 'end', label: 'End' }, ], edges: [ @@ -430,7 +430,7 @@ export const OpportunityWonFlow = defineFlow({ nodes: [ { id: 'start', type: 'start', label: 'Stage changed' }, { id: 'invoice', type: 'create_record', label: 'Create invoice' }, - { id: 'notify', type: 'send_email', label: 'Notify sales team' }, + { id: 'notify', type: 'notify', label: 'Notify sales team' }, { id: 'end', type: 'end', label: 'End' }, ], edges: [ diff --git a/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx index ed95d0ba6e..26d168225c 100644 --- a/content/docs/concepts/metadata-lifecycle.mdx +++ b/content/docs/concepts/metadata-lifecycle.mdx @@ -5,7 +5,7 @@ description: How metadata flows through Repository → Change Log → Cache → # Metadata Lifecycle & HMR -This page documents the metadata data path introduced by [ADR-0008](/adr/0008-metadata-repository-and-change-log) and refined by [ADR-0005](/adr/0005-metadata-customization-overlay). It is the canonical event stream that powers Studio Hot Module Replacement (HMR), REST writes, and future cloud editing. +This page documents the metadata data path introduced by [ADR-0008](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0008-metadata-repository-and-change-log.md) and refined by [ADR-0005](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0005-metadata-customization-overlay.md). It is the canonical event stream that powers Studio Hot Module Replacement (HMR), REST writes, and future cloud editing. **TL;DR** — Every write goes through a single `MetadataRepository.put()` call. The repository appends to a change log, emits a watch event with a monotonic `seq`, and broadcasts over SSE to the Studio UI. The Studio's `useMetadataHmr` hook displays the latest `seq` in the status badge and re-fetches affected views. @@ -54,7 +54,7 @@ Reads walk top-to-bottom: the first non-null layer wins. Writes always route to | **Cache** | `@objectstack/metadata` | In-memory snapshot of the registry, keyed by `MetaRef`. Invalidated by change-log events. | | **Registry** | `@objectstack/metadata` | Typed registry the Kernel and plugins query. Built from the cache. | -`MetaRef = (type, name, org)`. As of [ADR-0008 §0 amendment (2026-04-13)](/adr/0008-metadata-repository-and-change-log#0-2026-04-13-amendment--drop-project-and-branch-from-metaref), `project` and `branch` are removed from the runtime tuple. Project survives only as an artifact-packaging concept on the `objectstack.json` envelope; branching is left to Git. +`MetaRef = (type, name, org)`. As of [ADR-0008 §0 amendment (2026-04-13)](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0008-metadata-repository-and-change-log.md#0-2026-04-13-amendment--drop-project-and-branch-from-metaref), `project` and `branch` are removed from the runtime tuple. Project survives only as an artifact-packaging concept on the `objectstack.json` envelope; branching is left to Git. --- @@ -102,9 +102,9 @@ In shared-database multi-tenancy, **most metadata types must not be per-org cust | `object`, `field` | ❌ | Defines the table schema. Overriding would break existing data. | | `datasource` | ❌ | Connection strings; multi-tenant isolation is enforced at a higher layer. | -There is no `workflow` metadata type (per [ADR-0020](/adr/0020-state-machine-converge-and-enforce), record state machines are a `state_machine` validation). The runtime gate is implemented in `OVERLAY_ALLOWED_TYPES` (derived from the registry) and enforced by `SysMetadataRepository.put()`. Denied types return `403 not_overridable`. +There is no `workflow` metadata type (per [ADR-0020](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0020-state-machine-converge-and-enforce.md), record state machines are a `state_machine` validation). The runtime gate is implemented in `OVERLAY_ALLOWED_TYPES` (derived from the registry) and enforced by `SysMetadataRepository.put()`. Denied types return `403 not_overridable`. -See [ADR-0005](/adr/0005-metadata-customization-overlay) for the full design and amendments. +See [ADR-0005](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0005-metadata-customization-overlay.md) for the full design and amendments. --- @@ -126,11 +126,11 @@ The hash is `sha256:` + 64-hex of a canonical (sorted-keys, no-undefined) JSON s ## Cross-references -- [ADR-0005: Metadata customization overlay](/adr/0005-metadata-customization-overlay) — overlay semantics, whitelist, conflict rules. -- [ADR-0008 §0 amendment (2026-04): project/branch removal](/adr/0008-metadata-repository-and-change-log#0-2026-04-13-amendment--drop-project-and-branch-from-metaref) — why `MetaRef.project` and `MetaRef.branch` are dead. -- [ADR-0008: Metadata Repository & Change Log](/adr/0008-metadata-repository-and-change-log) — the four-primitive architecture. -- [IMetadataService Contract](/guides/contracts/metadata-service) — the runtime API plugins call. -- [North Star](/concepts/north-star) — the product shape these primitives serve. +- [ADR-0005: Metadata customization overlay](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0005-metadata-customization-overlay.md) — overlay semantics, whitelist, conflict rules. +- [ADR-0008 §0 amendment (2026-04): project/branch removal](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0008-metadata-repository-and-change-log.md#0-2026-04-13-amendment--drop-project-and-branch-from-metaref) — why `MetaRef.project` and `MetaRef.branch` are dead. +- [ADR-0008: Metadata Repository & Change Log](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0008-metadata-repository-and-change-log.md) — the four-primitive architecture. +- [IMetadataService Contract](/docs/kernel/contracts/metadata-service) — the runtime API plugins call. +- [North Star](/docs/concepts/north-star) — the product shape these primitives serve. --- diff --git a/content/docs/data-modeling/fields.mdx b/content/docs/data-modeling/fields.mdx index 36d16ac68c..bfa4d1075f 100644 --- a/content/docs/data-modeling/fields.mdx +++ b/content/docs/data-modeling/fields.mdx @@ -172,7 +172,7 @@ order: Field.masterDetail('order', { | `reference` (1st arg) | `string` | Target object name | | `referenceFilters` | `string[]` | Filter conditions for the lookup | | `deleteBehavior` | `enum` | `'set_null'`, `'cascade'`, `'restrict'` | -| `inlineEdit` | `boolean` | Render child records inline on the parent create/edit form | +| `inlineEdit` | `boolean \| 'grid' \| 'form'` | Render child records inline on the parent create/edit form (`true` = auto-pick, `'grid'`, or `'form'`) | | `inlineColumns` | `array` | Optional explicit columns for the inline grid | | `inlineAmountField` | `string` | Optional numeric child field for the inline running total | @@ -248,9 +248,9 @@ when multiple relationships target the same parent object. | `progress` | — | Progress bar | ```typescript -office: Field.address({ label: 'Office Address', addressFormat: 'international' }), -coordinates: Field.location({ label: 'Location', displayMap: true }), -brand_color: Field.color({ label: 'Color', colorFormat: 'hex' }), +office: Field.address({ label: 'Office Address' }), +coordinates: Field.location({ label: 'Location' }), +brand_color: Field.color({ label: 'Color' }), satisfaction: Field.rating(5, { label: 'Rating' }), approval_signature: Field.signature({ label: 'Signature' }), embedding: Field.vector(1536, { label: 'Embedding' }), @@ -296,9 +296,11 @@ These properties are available on all field types: | Property | Type | Default | Description | | :--- | :--- | :--- | :--- | -| `encryptionConfig` | `object` | — | Field-level encryption settings | -| `maskingRule` | `object` | — | Data masking rules for display | -| `auditTrail` | `boolean` | `false` | Track all changes to this field | +| `requiredPermissions` | `string[]` | — | Capabilities required to read/edit this field — masked on read, denied on write unless the caller holds all of them (ADR-0066 D3) | +| `trackHistory` | `boolean` | — | Render this field's value changes as entries on the record activity timeline | + +For values that must be encrypted at rest (API keys, tokens, DB passwords), use the +`secret` field type — see the [Field Type Gallery](/docs/data-modeling/field-types#secret). ### Conditional Logic diff --git a/content/docs/data-modeling/formulas.mdx b/content/docs/data-modeling/formulas.mdx index 323a75dcea..9a152b3a51 100644 --- a/content/docs/data-modeling/formulas.mdx +++ b/content/docs/data-modeling/formulas.mdx @@ -153,13 +153,22 @@ All functions are pure given a pinned `now`, which is what makes | Function | Returns | Description | |:---|:---|:---| | `now()` | timestamp | Pinned wall-clock at evaluation start | -| `today()` | timestamp | `now()` truncated to UTC start-of-day | -| `daysFromNow(n)` | timestamp | `now() + n` days (preserves wall-clock time, not start-of-day) | -| `daysAgo(n)` | timestamp | `now() - n` days | +| `today()` | timestamp | Calendar day in the business timezone, as UTC midnight | +| `daysFromNow(n)` / `daysAgo(n)` | timestamp | `today() ± n` days (calendar-day, not wall-clock) | +| `addDays(d, n)` / `addMonths(d, n)` | timestamp | Shift a *given* date; `addMonths` clamps to month end (Jan 31 + 1mo → Feb 28) | +| `date(s)` / `datetime(s)` | timestamp | Parse an ISO date / date-time string (aliases) | +| `daysBetween(a, b)` | int | Whole days from `a` to `b` (negative when `b` is earlier) | | `isBlank(v)` | bool | True for `null`, `undefined`, `''`, `[]` | +| `isEmpty(v)` | bool | True for `null` or zero-length string/list/map | | `coalesce(v, fallback)` | dyn | `v` when non-null, else `fallback` | | `trim(v)` | string | `v` with leading/trailing whitespace removed | | `joinNonEmpty(list, sep)` | string | `list` joined by `sep`, skipping blank entries | +| `upper(s)` / `lower(s)` | string | Case conversion | +| `contains(s, sub)` / `startsWith(s, p)` / `endsWith(s, p)` | bool | Substring checks (free-function form) | +| `matches(s, regex)` | bool | Regex test | +| `len(v)` | int | Length of a string / list / map (mirrors CEL's built-in `size()`) | +| `abs(x)` / `round(x)` | number | Numeric helpers | +| `min(a, b)` / `max(a, b)` | dyn | Smaller / larger operand (numeric comparison) | Add new helpers in [`packages/formula/src/stdlib.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/formula/src/stdlib.ts). @@ -213,7 +222,7 @@ Building the string by hand with `+` would also work, but CEL throws on ```ts { name: 'rating', - type: 'picklist', + type: 'select', visibleWhen: P`record.status == 'qualified'`, } @@ -232,13 +241,13 @@ identical SHA-1 across builds while still being "fresh" relative to whoever installs the package. ```ts -import { defineDataset, cel } from '@objectstack/spec'; +import { defineSeed } from '@objectstack/spec/data'; +import { cel } from '@objectstack/spec'; +import { Opportunity } from './objects/opportunity.object'; -export const opportunityDataset = defineDataset({ - object: 'opportunity', +export const opportunitySeed = defineSeed(Opportunity, { records: [ { - id: 'opp_acme', name: 'Acme Q3 Renewal', close_date: cel`daysFromNow(45)`, created_at: cel`now()`, diff --git a/content/docs/data-modeling/objects.mdx b/content/docs/data-modeling/objects.mdx index 7aafbd7a7d..82f84db261 100644 --- a/content/docs/data-modeling/objects.mdx +++ b/content/docs/data-modeling/objects.mdx @@ -30,7 +30,7 @@ export const Account = ObjectSchema.create({ ], }), annual_revenue: Field.currency({ label: 'Annual Revenue', scale: 2 }), - owner: Field.lookup('user', { label: 'Owner', required: true }), + owner: Field.user({ label: 'Owner', required: true }), }, enable: { @@ -66,8 +66,8 @@ export const Account = ObjectSchema.create({ | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| `displayNameField` | `string` | optional | Field used as record display name (defaults to `'name'`) | -| `titleFormat` | `string` | optional | Title expression (e.g. `'{name} - {code}'`) | +| `nameField` | `string` | optional | The stored field used as the record display name, e.g. `'name'` or `'title'` (ADR-0079). The deprecated alias `displayNameField` is still accepted. | +| `titleFormat` | `string` | optional | Deprecated (ADR-0079 → `nameField`). Render-only title template (e.g. `'{{record.name}} - {{record.code}}'`); an explicit `nameField` takes precedence | | `highlightFields` | `string[]` | optional | Most-important fields in priority order — default list columns, cards, previews, detail highlight strip (ADR-0085; formerly `compactLayout` — the old spelling was retired and is now rejected) | | `stageField` | `string \| false` | optional | Linear lifecycle field; `false` declares the status field non-linear and suppresses stage heuristics (ADR-0085) | | `recordName` | `object` | optional | Record name auto-generation config | @@ -138,27 +138,6 @@ versioning: { } ``` -#### Change Data Capture - -```typescript -cdc: { - enabled: true, - events: ['insert', 'update', 'delete'], - destination: 'kafka://my-topic', -} -``` - -#### Table Partitioning - -```typescript -partitioning: { - enabled: true, - strategy: 'range', // 'range' | 'hash' | 'list' - key: 'created_at', - interval: '1 month', // Required for range strategy -} -``` - ### Record Name Auto-generate unique record identifiers: @@ -206,7 +185,6 @@ indexes: [ | `abstract` | `boolean` | Abstract base, cannot be instantiated (default: `false`) | | `sharingModel` | `enum` | Org-Wide Default record visibility (ADR-0055/0056). Canonical: `'private'`, `'public_read'`, `'public_read_write'`, `'controlled_by_parent'` (detail visibility derived from its master). Legacy aliases: `'read'`=public_read, `'read_write'`/`'full'`=public_read_write | | `keyPrefix` | `string` | Short prefix for record IDs (e.g. `'001'`) | -| `recordTypes` | `string[]` | Record type names for this object | | `validations` | `ValidationRule[]` | Object-level validation rules (see [Validation](/docs/data-modeling/validation)) | ## Naming Conventions @@ -249,7 +227,7 @@ export const ProjectTask = ObjectSchema.create({ ], }), due_date: Field.date({ label: 'Due Date' }), - assignee: Field.lookup('user', { label: 'Assignee' }), + assignee: Field.user({ label: 'Assignee' }), project: Field.lookup('project', { label: 'Project', required: true }), estimated_hours: Field.number({ label: 'Estimated Hours', min: 0 }), }, @@ -273,7 +251,7 @@ export const ProjectTask = ObjectSchema.create({ type: 'script', severity: 'warning', message: 'Due date should be in the future', - condition: 'due_date < today()', + condition: 'record.due_date < today()', events: ['insert'], }, ], diff --git a/content/docs/data-modeling/queries.mdx b/content/docs/data-modeling/queries.mdx index 4c69076512..b010f605df 100644 --- a/content/docs/data-modeling/queries.mdx +++ b/content/docs/data-modeling/queries.mdx @@ -201,6 +201,33 @@ position from the previous page. There is no `keyset`/`after` query property. --- +## Expand (Related Records) + +Load related records through `lookup` / `master_detail` fields with `expand`. +Each key is a relationship field name; the value is a nested query that can +select fields, filter, and expand further (default max depth: 3). + +```typescript +{ + object: 'task', + fields: ['title', 'assignee'], + expand: { + assignee: { object: 'user', fields: ['name', 'email'] }, + project: { + object: 'project', + where: { is_active: { $eq: true } }, // AND-merged with the batch lookup + expand: { org: { object: 'org' } } // nested expand + } + } +} +``` + +The engine resolves `expand` via batch `$in` queries (driver-agnostic), so it +works on every driver. Per-parent `limit` / `offset` / `orderBy` are **not** +applied on this path. + +--- + ## Aggregations ### Available Functions diff --git a/content/docs/data-modeling/relationships.mdx b/content/docs/data-modeling/relationships.mdx index 19925f8b8f..632d61237c 100644 --- a/content/docs/data-modeling/relationships.mdx +++ b/content/docs/data-modeling/relationships.mdx @@ -50,6 +50,14 @@ Child records automatically appear in related lists when a lookup points to the - Account has many Contacts (Contact.account → Account) - Account detail page shows "Contacts" related list +### Other relationship types + +- **Master-detail** (`Field.masterDetail`) — parent-child ownership with cascade delete and optional inline line-item editing on the parent form; see [`master_detail`](/docs/data-modeling/field-types#master_detail). +- **Tree** (`type: 'tree'`) — self-referential hierarchies (categories, org charts); see [`tree`](/docs/data-modeling/field-types#tree). +- **User** (`Field.user`) — a person picker, i.e. a lookup specialized to the built-in `sys_user` object; see [`user`](/docs/data-modeling/field-types#user). +- **Roll-ups over children** — aggregate child records onto the parent with a [`summary` field](/docs/data-modeling/fields#calculation-types). +- **Querying across relationships** — load related records with `expand`; see the [query cheat sheet](/docs/data-modeling/queries#expand-related-records). + --- ## See also diff --git a/content/docs/data-modeling/schema-design.mdx b/content/docs/data-modeling/schema-design.mdx index cceddbf273..16c7f9bcc6 100644 --- a/content/docs/data-modeling/schema-design.mdx +++ b/content/docs/data-modeling/schema-design.mdx @@ -119,12 +119,16 @@ Generates sequential numbers automatically: ```typescript Field.autonumber({ label: 'Account Number', - format: 'ACC-{0000}', // ACC-0001, ACC-0002, ... + autonumberFormat: 'ACC-{0000}', // ACC-0001, ACC-0002, ... }) -// The format string supports a single zero-pad token, e.g.: -// 'INV-{000}' // INV-001, INV-002, ... -// Date tokens like {YYYY}/{MM}/{DD} are not interpolated. +// The format string is literal text plus {…} tokens: +// {0000} — the sequence counter, zero-padded (at most one) +// {YYYY} {YY} {MM} {DD} {YYYYMMDD} — generation date in the business timezone +// {field_name} — another field on the same record +// The counter is scoped to whatever renders before the {0000} slot, so +// 'AD{YYYYMMDD}{0000}' resets daily and '{plan_no}{000}' counts per parent — +// no separate reset config. A fixed prefix like 'INV-{000}' keeps one global counter. ``` ### User Fields @@ -200,7 +204,7 @@ export const Account = ObjectSchema.create({ vat_id: { type: 'text', group: 'billing' }, billing_address: { type: 'address', group: 'billing' }, created_at: { type: 'datetime', readonly: true, group: 'system' }, - created_by: { type: 'lookup', reference: 'user', readonly: true, group: 'system' }, + created_by: { type: 'user', reference: 'sys_user', readonly: true, group: 'system' }, }, }); ``` @@ -311,7 +315,7 @@ export const Account = ObjectSchema.create({ fields: { account_number: Field.autonumber({ label: 'Account Number', - format: 'ACC-{0000}', + autonumberFormat: 'ACC-{0000}', }), name: Field.text({ @@ -338,10 +342,9 @@ export const Account = ObjectSchema.create({ billing_address: Field.address({ label: 'Billing Address', - addressFormat: 'international', }), - owner: Field.lookup('user', { + owner: Field.user({ label: 'Account Owner', required: true, }), @@ -367,7 +370,7 @@ export const Account = ObjectSchema.create({ type: 'script', severity: 'error', message: 'Revenue must be positive', - condition: 'annual_revenue < 0', + condition: 'record.annual_revenue < 0', }, ], }); diff --git a/content/docs/data-modeling/validation-rules.mdx b/content/docs/data-modeling/validation-rules.mdx index ad540d00e7..02d221e72f 100644 --- a/content/docs/data-modeling/validation-rules.mdx +++ b/content/docs/data-modeling/validation-rules.mdx @@ -5,7 +5,7 @@ description: Default validation behavior, required properties, and constraints f # Field Validation Rules -Every ObjectStack field type has built-in validation behavior that runs automatically at the schema level. This reference documents the **default constraints**, **required properties**, and **validation semantics** for each of the 48 field types. +Every ObjectStack field type has built-in validation behavior that runs automatically at the schema level. This reference documents the **default constraints**, **required properties**, and **validation semantics** for each field type. **Source:** `packages/spec/src/data/field.zod.ts` @@ -22,7 +22,7 @@ These properties apply to **all** field types and are validated by the base `Fie |:---|:---|:---|:---| | `name` | `string` | — | Must match `^[a-z_][a-z0-9_]*$` (snake_case) | | `label` | `string` | — | Human-readable display name | -| `type` | `FieldType` | — | Must be one of the 48 defined types | +| `type` | `FieldType` | — | Must be a member of the `FieldType` enum | | `required` | `boolean` | `false` | Rejects `null`/`undefined` at runtime when `true` | | `unique` | `boolean` | `false` | Enforces database-level uniqueness constraint | | `multiple` | `boolean` | `false` | Stores value as array (applicable for select, lookup, file, image) | @@ -31,7 +31,7 @@ These properties apply to **all** field types and are validated by the base `Fie | `sortable` | `boolean` | `true` | Whether field appears in list view sort options | | `index` | `boolean` | `false` | Creates standard database index | | `externalId` | `boolean` | `false` | Marks field as external ID for upsert operations | -| `auditTrail` | `boolean` | `false` | Tracks all changes with user and timestamp | +| `trackHistory` | `boolean` | — | Render this field's value changes as entries on the record activity timeline | | `visibleWhen` | `string \| Expression` | — | CEL predicate; field is shown only when `TRUE` | | `readonlyWhen` | `string \| Expression` | — | CEL predicate; field is read-only when `TRUE` | | `requiredWhen` | `string \| Expression` | — | CEL predicate; field is required when `TRUE` | @@ -48,7 +48,6 @@ These properties apply to **all** field types and are validated by the base `Fie | `maxLength` | `number` | — | Rejects values exceeding character count | | `minLength` | `number` | — | Rejects values below character count | | `format` | `string` | — | Validates against format pattern (e.g., regex) | -| `caseSensitive` | `boolean` | — | Controls case-sensitivity in uniqueness checks | **Default constraints:** None. Unbounded text unless `maxLength` is set. @@ -66,7 +65,6 @@ These properties apply to **all** field types and are validated by the base `Fie | Property | Type | Default | Validation Behavior | |:---|:---|:---|:---| | `format` | `string` | `email` | Validates RFC 5322 email format | -| `caseSensitive` | `boolean` | — | Controls case-sensitivity in uniqueness checks | **Default constraints:** Must conform to valid email format. @@ -93,7 +91,7 @@ These properties apply to **all** field types and are validated by the base `Fie | `maxLength` | `number` | — | Maximum password length | | `minLength` | `number` | — | Minimum password length | -**Default constraints:** Value is never returned in read operations. Stored encrypted. +**Default constraints:** Value is never returned in read operations. Stored as a one-way hash owned by the auth subsystem — for reversible encrypted-at-rest secrets, use the `secret` type instead. --- @@ -257,10 +255,10 @@ These properties apply to **all** field types and are validated by the base `Fie | Property | Type | Default | Validation Behavior | |:---|:---|:---|:---| | `reference` | `string` | — | **Required.** Parent object name | -| `deleteBehavior` | `enum` | `set_null` | Typically `cascade` for master-detail | +| `deleteBehavior` | `enum` | `cascade` | Master-detail cascades at runtime unless set to `restrict` | | `writeRequiresMasterRead` | `boolean` | — | Require read access to master record | -**Default constraints:** Enforces parent-child ownership. Child records cascade-delete with parent by convention. +**Default constraints:** Enforces parent-child ownership. Child records cascade-delete with the parent by default. ### `tree` @@ -337,7 +335,6 @@ These properties apply to **all** field types and are validated by the base `Fie |:---|:---|:---|:---| | `expression` | `string` | — | **Required.** Formula expression | | `dependencies` | `string[]` | — | Fields this formula depends on | -| `cached` | `object` | — | Caching configuration with TTL and invalidation triggers | **Default constraints:** Read-only. Value computed at runtime from `expression`. Not directly writable. @@ -347,8 +344,7 @@ These properties apply to **all** field types and are validated by the base `Fie label: 'Full Name', type: 'formula', expression: 'record.first_name + " " + record.last_name', - dependencies: ['first_name', 'last_name'], - cached: { enabled: true, ttl: 3600, invalidateOn: ['first_name', 'last_name'] } + dependencies: ['first_name', 'last_name'] } ``` @@ -377,28 +373,17 @@ These properties apply to **all** field types and are validated by the base `Fie ### `location` -| Property | Type | Default | Validation Behavior | -|:---|:---|:---|:---| -| `displayMap` | `boolean` | — | Show map widget in UI | -| `allowGeocoding` | `boolean` | — | Enable address-to-coordinate conversion | - -**Default constraints:** Stored as `{ latitude, longitude, altitude?, accuracy? }`. Latitude: -90 to 90. Longitude: -180 to 180. +**Default constraints:** Stored as `{ latitude, longitude, altitude?, accuracy? }`. Latitude: -90 to 90. Longitude: -180 to 180. No per-type config properties. ### `address` -| Property | Type | Default | Validation Behavior | -|:---|:---|:---|:---| -| `addressFormat` | `enum` | — | `us`, `uk`, or `international` | - -**Default constraints:** Stored as structured object with `street`, `city`, `state`, `postalCode`, `country`, `countryCode`, `formatted`. +**Default constraints:** Stored as structured object with `street`, `city`, `state`, `postalCode`, `country`, `countryCode`, `formatted` (all parts optional). No per-type config properties. ### `code` | Property | Type | Default | Validation Behavior | |:---|:---|:---|:---| | `language` | `string` | — | Programming language for syntax highlighting | -| `theme` | `string` | — | Editor theme (e.g., `dark`, `monokai`) | -| `lineNumbers` | `boolean` | — | Show line numbers in editor | **Default constraints:** Stored as plain text. Language used for UI syntax highlighting only. @@ -408,22 +393,15 @@ These properties apply to **all** field types and are validated by the base `Fie ### `color` -| Property | Type | Default | Validation Behavior | -|:---|:---|:---|:---| -| `colorFormat` | `enum` | — | `hex`, `rgb`, `rgba`, or `hsl` | -| `allowAlpha` | `boolean` | — | Allow transparency channel | -| `presetColors` | `string[]` | — | Predefined color swatches | - -**Default constraints:** Validated against the specified `colorFormat`. Defaults to hex if not specified. +**Default constraints:** Stores the color value as a string. No per-type config properties. ### `rating` | Property | Type | Default | Validation Behavior | |:---|:---|:---|:---| -| `maxRating` | `number` | `5` | Maximum rating value | -| `allowHalf` | `boolean` | — | Allow half-step increments | +| `max` | `number` | `5` | Maximum rating value (set by the `Field.rating(max)` factory) | -**Default constraints:** Integer between 0 and `maxRating`. Half-steps allowed when `allowHalf` is true. +**Default constraints:** Integer between 0 and `max`. ### `slider` @@ -432,8 +410,6 @@ These properties apply to **all** field types and are validated by the base `Fie | `min` | `number` | — | Minimum slider value | | `max` | `number` | — | Maximum slider value | | `step` | `number` | `1` | Step increment | -| `showValue` | `boolean` | — | Display current value | -| `marks` | `Record` | — | Custom labels at specific values | **Default constraints:** Numeric value between `min` and `max` in `step` increments. @@ -443,14 +419,7 @@ These properties apply to **all** field types and are validated by the base `Fie ### `qrcode` -| Property | Type | Default | Validation Behavior | -|:---|:---|:---|:---| -| `barcodeFormat` | `enum` | — | `qr`, `ean13`, `ean8`, `code128`, `code39`, `upca`, `upce` | -| `qrErrorCorrection` | `enum` | — | `L` (7%), `M` (15%), `Q` (25%), `H` (30%) — only for `qr` format | -| `displayValue` | `boolean` | — | Show human-readable value below code | -| `allowScanning` | `boolean` | — | Enable camera scanning input | - -**Default constraints:** `qrErrorCorrection` only applies when `barcodeFormat` is `qr`. Value validated against barcode format rules. +**Default constraints:** Stores the encoded value; rendered as a scannable code. No per-type config properties. ### `progress` @@ -473,51 +442,16 @@ These properties apply to **all** field types and are validated by the base `Fie | Property | Type | Default | Validation Behavior | |:---|:---|:---|:---| -| `vectorConfig.dimensions` | `number` | — | **Required.** Vector size (1–10,000) | -| `vectorConfig.distanceMetric` | `enum` | `cosine` | `cosine`, `euclidean`, `dotProduct`, `manhattan` | -| `vectorConfig.normalized` | `boolean` | `false` | Whether vectors are unit-length | -| `vectorConfig.indexed` | `boolean` | `true` | Create vector index for similarity search | -| `vectorConfig.indexType` | `enum` | — | `hnsw`, `ivfflat`, or `flat` | +| `dimensions` | `number` | — | **Required.** Vector size (1–10,000) | -**Default constraints:** Must be a numeric array of exactly `dimensions` length. Indexed by default for fast similarity search. +**Default constraints:** Must be a numeric array of exactly `dimensions` length. The nested `vectorConfig` object is retained for back-compat only and is a runtime no-op — set the flat `dimensions` property instead. ```typescript { name: 'content_embedding', label: 'Content Embedding', type: 'vector', - vectorConfig: { - dimensions: 1536, - distanceMetric: 'cosine', - indexed: true, - indexType: 'hnsw' - } -} -``` - ---- - -## Data Quality Rules - -Any field can optionally include `dataQuality` rules for governance and monitoring: - -| Property | Type | Default | Validation Behavior | -|:---|:---|:---|:---| -| `dataQuality.uniqueness` | `boolean` | `false` | Enforce unique values across all records | -| `dataQuality.completeness` | `number` | `0` | Minimum ratio of non-null values (0–1) | -| `dataQuality.accuracy.source` | `string` | — | Reference data source for validation | -| `dataQuality.accuracy.threshold` | `number` | — | Minimum accuracy threshold (0–1) | - -```typescript -{ - name: 'social_security', - label: 'SSN', - type: 'text', - dataQuality: { - uniqueness: true, - completeness: 0.95, - accuracy: { source: 'government_db', threshold: 0.98 } - } + dimensions: 1536 } ``` @@ -525,13 +459,16 @@ Any field can optionally include `dataQuality` rules for governance and monitori ## Security & Compliance Properties -Fields supporting sensitive data can leverage encryption and masking: +For sensitive data, use the properties and types the platform actually enforces: | Property | Type | Default | Description | |:---|:---|:---|:---| -| `encryptionConfig` | `EncryptionConfig` | — | Field-level encryption (GDPR/HIPAA/PCI-DSS) | -| `maskingRule` | `MaskingRule` | — | Data masking rules for PII protection | -| `auditTrail` | `boolean` | `false` | Track all changes with user and timestamp | +| `requiredPermissions` | `string[]` | — | Capabilities required to read/edit the field — masked on read, denied on write unless the caller holds all of them (ADR-0066 D3) | +| `trackHistory` | `boolean` | — | Render the field's value changes as entries on the record activity timeline | + +For reversible encrypted-at-rest values (API keys, tokens, DB passwords), use the +`secret` field type; for credentials, use `password` (one-way hash). See the +[Field Type Gallery](/docs/data-modeling/field-types). --- @@ -544,7 +481,7 @@ Fields supporting sensitive data can leverage encryption and masking: | `email` | — | RFC 5322 email format | | `url` | — | Valid URL with protocol | | `phone` | — | E.164 / national format | -| `password` | — | Stored encrypted, never returned | +| `password` | — | One-way hash, never returned | | `markdown` | — | `maxLength` | | `html` | — | Sanitized, `maxLength` | | `richtext` | — | Sanitized, `maxLength` | @@ -572,14 +509,14 @@ Fields supporting sensitive data can leverage encryption and masking: | `summary` | `summaryOperations` | Read-only, roll-up from children | | `autonumber` | — | Read-only, auto-incremented | | `location` | — | Lat: -90–90, Lng: -180–180 | -| `address` | — | Structured object, `addressFormat` | +| `address` | — | Structured object (street, city, …) | | `code` | — | Plain text, `language` for highlighting | | `json` | — | Must be valid JSON | -| `color` | — | Validated against `colorFormat` | -| `rating` | — | 0 to `maxRating` (default 5) | +| `color` | — | Stored as a string | +| `rating` | — | 0 to `max` (default 5) | | `slider` | — | `min` to `max` in `step` increments | | `signature` | — | Base64 image, typically immutable | | `qrcode` | — | Format-specific validation | | `progress` | — | Numeric, typically 0–100 | | `tags` | — | String array, trimmed, deduplicated | -| `vector` | `vectorConfig` | Numeric array of exact `dimensions` length | +| `vector` | `dimensions` | Numeric array of exact `dimensions` length | diff --git a/content/docs/deployment/cloud-artifact-api.mdx b/content/docs/deployment/cloud-artifact-api.mdx index 8727d5187b..4f5bcf3edc 100644 --- a/content/docs/deployment/cloud-artifact-api.mdx +++ b/content/docs/deployment/cloud-artifact-api.mdx @@ -110,8 +110,7 @@ calls, not data-plane calls. | File | Purpose | |:---|:---| -| `packages/cli/src/commands/publish.ts` | CLI publish command and endpoint construction. | -| `packages/cli/src/commands/rollback.ts` | CLI revision activation command. | +| `packages/cli/src/commands/package/publish.ts` | CLI `os package publish` command and endpoint construction (the legacy `publish.ts` / `rollback.ts` commands were removed — #2237). | | `packages/runtime/src/http-dispatcher.ts` | Kernel-resolution seam for per-request environment resolution. The concrete id/hostname registry ships in the host distribution `@objectstack/objectos-runtime` (not part of this open-source repo). | | `packages/cloud-connection/src/runtime-config-plugin.ts` | Console runtime-config for active/default environment state. | | `packages/spec/src/system/environment-artifact.zod.ts` | Normative artifact envelope schema. | diff --git a/content/docs/deployment/environment-variables.mdx b/content/docs/deployment/environment-variables.mdx index 4e93e18aea..3945318e18 100644 --- a/content/docs/deployment/environment-variables.mdx +++ b/content/docs/deployment/environment-variables.mdx @@ -28,6 +28,9 @@ read at startup unless noted otherwise. Boolean variables accept `true` / `false | `OS_MODE` | enum | `standalone` | Runtime mode. `standalone` \| `cloud`. | | `OS_BOOT_EMPTY` | flag | `0` | When `1`, boot the kernel with no metadata artifact (CLI internal). | | `OS_MIGRATE_AND_EXIT` | flag | `0` | When `1`, run pending migrations and exit (suitable for one-shot jobs). | +| `OS_DISABLE_CONSOLE` | flag | `0` | When `1`, do not mount the Console portal under `/_console`. | +| `OS_EAGER_SCHEMAS` | flag | `0` | When `1`, eagerly materialise all object schemas at boot (slower start, faster first request). | +| `OS_SKIP_SCHEMA_SYNC` | flag | `0` | When `1`, skip the implicit `db:sync` on boot. Use after running migrations manually. | > **Port conflicts behave differently by mode.** In **dev** (`os dev`, or > `NODE_ENV=development`) a busy port auto-hops to the next free one (up to @@ -35,11 +38,8 @@ read at startup unless noted otherwise. Boolean variables accept `true` / `false > (`os start`) a busy port is a hard error (exit 1) — it never silently > drifts, because a shifted port breaks reverse-proxy upstreams, `OS_AUTH_URL` > callbacks, and `OS_TRUSTED_ORIGINS` (CORS). Pin the port explicitly in -> production (`PORT=8080 os start`) and keep `OS_AUTH_URL` / `OS_TRUSTED_ORIGINS` +> production (`OS_PORT=8080 os start`) and keep `OS_AUTH_URL` / `OS_TRUSTED_ORIGINS` > in sync with it. -| `OS_DISABLE_CONSOLE` | flag | `0` | When `1`, do not mount the Console portal under `/_console`. | -| `OS_EAGER_SCHEMAS` | flag | `0` | When `1`, eagerly materialise all object schemas at boot (slower start, faster first request). | -| `OS_SKIP_SCHEMA_SYNC` | flag | `0` | When `1`, skip the implicit `db:sync` on boot. Use after running migrations manually. | --- @@ -54,6 +54,17 @@ read at startup unless noted otherwise. Boolean variables accept `true` / `false --- +## Secrets & Clustering + +| Variable | Type | Default | Description | +|:---|:---|:---|:---| +| `OS_SECRET_KEY` | string | — | 32-byte master key (64 hex chars or base64) for `sys_secret` encryption — encrypted settings, `secret` fields, datasource credentials. **Required** for containerized or multi-node deployments; on a single durable host `os start` mints and persists a dev key instead. See [Deployment Modes](/docs/deployment#environment-variables). | +| `OS_DEV_CRYPTO_KEY` | string | — | Development convenience crypto key, consulted after `OS_SECRET_KEY`. Do not use in production. | +| `OS_CLUSTER_DRIVER` | string | `memory` | Cluster coordination driver id. When set to anything other than `memory`, the runtime treats the deployment as multi-node (and requires `OS_SECRET_KEY`). Non-memory drivers (e.g. `redis`) ship in the EE distribution. | +| `OS_REDIS_URL` | url | — | Connection URL passed to a non-memory cluster driver (e.g. `OS_CLUSTER_DRIVER=redis`). | + +--- + ## Authentication | Variable | Type | Default | Description | diff --git a/content/docs/deployment/troubleshooting.mdx b/content/docs/deployment/troubleshooting.mdx index c2570180af..dca477db9e 100644 --- a/content/docs/deployment/troubleshooting.mdx +++ b/content/docs/deployment/troubleshooting.mdx @@ -125,7 +125,7 @@ The custom error map provides "Did you mean?" suggestions for common typos. ``` $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $between, -$contains, $startsWith, $endsWith, +$contains, $notContains, $startsWith, $endsWith, $null, $exists, $and, $or, $not ``` diff --git a/content/docs/deployment/vercel.mdx b/content/docs/deployment/vercel.mdx index 3e8cdfd78c..7145b59613 100644 --- a/content/docs/deployment/vercel.mdx +++ b/content/docs/deployment/vercel.mdx @@ -171,7 +171,7 @@ storage for artifacts. - [ ] `api/_kernel.ts` boots the kernel with the correct driver - [ ] `vercel.json` sets `VITE_USE_MOCK_SERVER=false` and `VITE_SERVER_URL=` (empty) - [ ] Rewrite rule routes `/api/*` to `/api` and excludes `/api/` from SPA rewrite -- [ ] `DATABASE_URL` is configured in Vercel environment variables (for production drivers) +- [ ] `OS_DATABASE_URL` is configured in Vercel environment variables (for production drivers) - [ ] CORS is configured if frontend and API are on different origins --- diff --git a/content/docs/getting-started/cli.mdx b/content/docs/getting-started/cli.mdx index 01129678fc..3e96df2ae8 100644 --- a/content/docs/getting-started/cli.mdx +++ b/content/docs/getting-started/cli.mdx @@ -13,7 +13,7 @@ Command Line Interface for building metadata-driven applications with the Object pnpm add -D @objectstack/cli ``` -The CLI is available as `objectstack` or the shorter alias `os`. +The CLI is available as `objectstack` or the shorter alias `os`. Installed as a dev dependency, the bins are project-local — invoke them as `npx os …` / `pnpm exec os …` or via your package scripts. ## Your First App in 2 Minutes @@ -67,12 +67,6 @@ os compile # Build production artifact → dist/objectstack.json | `os dev [package]` | | Start development mode with hot reload | | `os serve [config]` | | Start the ObjectStack server with plugin auto-detection | -### Production - -| Command | Description | -|---------|-------------| -| `os start` | Serve a pre-compiled `objectstack.json` artifact (no `objectstack.config.ts` required) | - #### `os init` Scaffolds a new ObjectStack project with configuration, TypeScript setup, and initial metadata files. @@ -269,6 +263,7 @@ os start | `--environment-id ` | `OS_ENVIRONMENT_ID` | Environment identifier (default `env_local`) | | `-p, --port ` | `PORT` / `OS_PORT` | Listen port (default `3000`). **Production fails loudly if the port is busy** — see note below. | | `--ui` / `--no-ui` | — | Mount the Console portal at `/_console/`. Enabled by default (so you can install marketplace apps); pass `--no-ui` to disable it. | +| `-v, --verbose` | — | Verbose output | > **Port conflicts: production never auto-shifts.** Unlike `os dev` (which > hops to the next free port for local convenience), `os start` exits with an @@ -276,7 +271,6 @@ os start > your reverse-proxy upstream, `OS_AUTH_URL` callbacks, and `OS_TRUSTED_ORIGINS` > (CORS). **Pin the port explicitly** (`PORT=8080 os start`) and keep > `OS_AUTH_URL` / `OS_TRUSTED_ORIGINS` in sync when you change it. -| `-v, --verbose` | — | Verbose output | **Resolution priority (artifact):** `--artifact` > `OS_ARTIFACT_PATH` > `/dist/objectstack.json`. **Resolution priority (database):** `--database` > `OS_DATABASE_URL` > `DATABASE_URL` (legacy) > `file:/data/objectstack.db`. diff --git a/content/docs/getting-started/common-patterns.mdx b/content/docs/getting-started/common-patterns.mdx index cb5b139421..5780732a93 100644 --- a/content/docs/getting-started/common-patterns.mdx +++ b/content/docs/getting-started/common-patterns.mdx @@ -406,41 +406,35 @@ Restrict field visibility and editability based on user profiles. name: { label: 'Name', type: 'text', required: true }, email: { label: 'Email', type: 'email', required: true }, department: { label: 'Department', type: 'lookup', reference: 'department' }, - // Sensitive fields with encryption salary: { label: 'Salary', type: 'currency', hidden: true, // Hidden from default views - encryptionConfig: { - enabled: true, - algorithm: 'aes-256-gcm', - scope: 'field', - keyManagement: { - provider: 'local', - rotationPolicy: { enabled: true, frequencyDays: 90 } - } - } - }, - ssn: { label: 'SSN', type: 'text', - hidden: true, - maskingRule: { - field: 'ssn', - strategy: 'partial', - pattern: '\\d{4}$' - }, - encryptionConfig: { - enabled: true, - algorithm: 'aes-256-gcm', - scope: 'field', - keyManagement: { - provider: 'local', - rotationPolicy: { enabled: true, frequencyDays: 90 } - } - } }, + // `secret` values are stored encrypted and never returned in plain reads + api_token: { label: 'API Token', type: 'secret' }, } }] } ``` +Who can actually read or edit a field is governed by permission sets — grant per-object CRUD, then narrow individual fields: + +```typescript +import { definePermissionSet } from '@objectstack/spec'; + +export const HrRepPermission = definePermissionSet({ + name: 'hr_rep', + label: 'HR Rep', + isProfile: true, + objects: { + employee: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: false }, + }, + fields: { + // . — field-level security + 'employee.salary': { readable: true, editable: false }, // Read-only + }, +}); +``` + --- ## Putting It All Together diff --git a/content/docs/getting-started/examples.mdx b/content/docs/getting-started/examples.mdx index 55dc1417df..389c9b244f 100644 --- a/content/docs/getting-started/examples.mdx +++ b/content/docs/getting-started/examples.mdx @@ -197,7 +197,7 @@ const tasks = defineSeed(Task, { export const TodoSeedData = [tasks]; ``` -**3. The server auto-detects** what you need: ObjectQL engine → InMemory driver → Hono HTTP server → REST API at `/api/v1/todo_task`. +**3. The server auto-detects** what you need: ObjectQL engine → InMemory driver → Hono HTTP server → REST API at `/api/v1/data/todo_task`. --- diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index b8c940da1e..4f4482a463 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -6,7 +6,7 @@ description: Fast lookup table for all ObjectStack protocols # Quick Reference Guide -Fast lookup for all 175 ObjectStack protocols organized by category. +Fast lookup for the ObjectStack protocols organized by category. Click on any protocol name to view its complete API reference. @@ -293,7 +293,7 @@ import type { Field, Object, Query, View } from '@objectstack/spec'; **New to ObjectStack?** Start with the [Introduction](/docs/getting-started) to learn core concepts before diving into specific protocols. -- [Protocol Reference Index](/docs/guides) - Full protocol documentation +- [Protocol Reference Index](/docs/references) - Full protocol documentation - [Data Modeling](/docs/data-modeling) - Deep dive into data modeling - [UI Engine](/docs/ui) - UI development guide - [Contributing Guide](https://github.com/objectstack-ai/framework/blob/main/CONTRIBUTING.md) - How to contribute to protocols diff --git a/content/docs/getting-started/quick-start.mdx b/content/docs/getting-started/quick-start.mdx index 8481546064..b861e752b5 100644 --- a/content/docs/getting-started/quick-start.mdx +++ b/content/docs/getting-started/quick-start.mdx @@ -3,7 +3,7 @@ title: Quick Start description: Learn to build enterprise applications with the ObjectStack Protocol --- -# Developer Guide +# Quick Start This guide teaches you how to **build real applications** using the ObjectStack Protocol. Each section is hands-on, with code examples derived from the [HotCRM reference app](https://github.com/objectstack-ai/hotcrm). @@ -22,6 +22,8 @@ npx @objectstack/cli init my-app cd my-app ``` +The `os` commands below use the CLI that `init` installs into your project — if `os` isn't on your PATH, run them as `npx os …` (or `pnpm exec os …`). + ### Add AI skills to your editor Install the ObjectStack skill bundle so your AI assistant (Claude Code, Copilot, Cursor, …) knows the protocol's schemas and conventions: @@ -76,7 +78,7 @@ export default myAppItem; This is the same metadata that powers the REST API, Studio editors, and the MCP tools exposed to AI agents — define it once, and ObjectStack derives the rest. -### Launch the Studio +### Launch the Console ```bash os dev --ui @@ -144,7 +146,7 @@ Follow these guides in order for the best experience: ## Project Structure -A typical ObjectStack application (generated by `os init`): +A typical ObjectStack application layout (`os init` scaffolds only the minimal core of this — see [Example Apps](/docs/getting-started/examples)): ``` my-app/ diff --git a/content/docs/kernel/contracts/cache-service.mdx b/content/docs/kernel/contracts/cache-service.mdx index a28c28fc36..50fe0aa532 100644 --- a/content/docs/kernel/contracts/cache-service.mdx +++ b/content/docs/kernel/contracts/cache-service.mdx @@ -147,8 +147,10 @@ async function updateObjectDefinition( name: string, changes: Partial ): Promise { - // Update source of truth - const updated = await metadataService.updateObject(name, changes); + // Update source of truth — register() saves the full definition + const current = await metadataService.getObject(name); + const updated = { ...(current as ObjectDefinition), ...changes }; + await metadataService.register('object', name, updated); // Update cache immediately await cacheService.set(`meta:object:${name}`, updated, 3600); @@ -160,11 +162,12 @@ async function updateObjectDefinition( ### Cache Invalidation on Events ```typescript -metadataService.onChange((event) => { +// watch() is optional on IMetadataService — check before subscribing +const handle = metadataService.watch?.('object', (event) => { // Invalidate the affected key when metadata changes. // The contract deletes individual keys — track related keys // (e.g. cached queries) yourself to invalidate them. - cacheService.delete(`meta:object:${event.objectName}`); + cacheService.delete(`meta:object:${event.name}`); }); ``` diff --git a/content/docs/kernel/contracts/index.mdx b/content/docs/kernel/contracts/index.mdx index bc0c675d5e..1dd213da85 100644 --- a/content/docs/kernel/contracts/index.mdx +++ b/content/docs/kernel/contracts/index.mdx @@ -104,7 +104,7 @@ export interface IDataEngine { ``` -**Convention:** Contract interfaces are prefixed with `I` (e.g., `IDataEngine`). Services are registered and resolved by **string name** through the kernel service registry (e.g., `ctx.registerService('dataEngine', impl)`). +**Convention:** Contract interfaces are prefixed with `I` (e.g., `IDataEngine`). Services are registered and resolved by **string name** through the kernel service registry (e.g., `ctx.registerService('data', impl)` — see the `CoreServiceName` enum for the standard names). --- @@ -125,7 +125,7 @@ export const myPlugin: Plugin = { // Init phase: register the services this plugin provides init(ctx) { - ctx.registerService('dataEngine', new MyDataEngine(ctx)); + ctx.registerService('data', new MyDataEngine(ctx)); // Consume a service another plugin registered const cache = ctx.getService('cache'); @@ -157,6 +157,6 @@ flowchart LR **See also:** -- [Plugin Development Guide](/guides/plugin-development) for implementing contracts -- [Kernel Services Guide](/guides/kernel-services) for runtime architecture +- [Plugin Development](/docs/plugins/development) for implementing contracts +- [Service Registry](/docs/kernel/services) for runtime architecture diff --git a/content/docs/kernel/contracts/metadata-service.mdx b/content/docs/kernel/contracts/metadata-service.mdx index 1b8b213d2d..40b03be06e 100644 --- a/content/docs/kernel/contracts/metadata-service.mdx +++ b/content/docs/kernel/contracts/metadata-service.mdx @@ -13,7 +13,7 @@ The Metadata Service manages all object and field definitions at runtime. It ser -For the **data path** behind this contract — Repository → Change Log → Cache → Registry, plus HMR semantics — see [Metadata Lifecycle & HMR](/concepts/metadata-lifecycle). +For the **data path** behind this contract — Repository → Change Log → Cache → Registry, plus HMR semantics — see [Metadata Lifecycle & HMR](/docs/concepts/metadata-lifecycle). --- @@ -261,7 +261,7 @@ console.log(result.failed); // failed ## UI Metadata (Views & Dashboards) -UI metadata types (`view`, `dashboard`, `page`, `app`, `theme`) are first-class citizens in the Metadata Service. The previously separate `IUIService` was removed in 11 — use the Metadata Service for views/dashboards. +UI metadata types (`view`, `dashboard`, `page`, `app`, `theme`) are first-class citizens in the Metadata Service. The previously separate `IUIService` was removed in v11 — use the Metadata Service for views/dashboards. ### Reading UI Metadata diff --git a/content/docs/kernel/events.mdx b/content/docs/kernel/events.mdx index ef2313ad78..d9844512c0 100644 --- a/content/docs/kernel/events.mdx +++ b/content/docs/kernel/events.mdx @@ -81,15 +81,15 @@ Every data hook is a single-argument handler `(ctx: HookContext) => void | Promi ```typescript // Enrich a record before it is created engine.registerHook('beforeInsert', async (ctx) => { - if (ctx.object === 'order' && ctx.input.doc.amount > 10000) { + if (ctx.object === 'order' && ctx.input.amount > 10000) { ctx.logger?.warn?.('Large order detected!'); - ctx.input.doc.requires_approval = true; + ctx.input.requires_approval = true; } }, { object: 'order' }); // React after a record is created engine.registerHook('afterInsert', async (ctx) => { - await notifyWebhook(ctx.object, ctx.input.doc.id); + await notifyWebhook(ctx.object, ctx.input.id); }, { object: 'order' }); ``` @@ -102,7 +102,7 @@ What happens when a data hook throws is governed by the hook's **`onError`** pol ```typescript engine.registerHook('beforeInsert', async (ctx) => { - if (ctx.object === 'invoice' && !ctx.input.doc.customer_id) { + if (ctx.object === 'invoice' && !ctx.input.customer_id) { throw new Error('Invoice must have a customer'); // Aborts the insert (default onError) } }); diff --git a/content/docs/kernel/index.mdx b/content/docs/kernel/index.mdx index 4d038c2ad0..1e74f7de56 100644 --- a/content/docs/kernel/index.mdx +++ b/content/docs/kernel/index.mdx @@ -5,7 +5,7 @@ description: The ObjectKernel runtime — plugin host, event bus, service regist # Kernel & Services -The kernel is ObjectStack's runtime: it loads your metadata artifact, hosts plugins, wires up services, and enforces the lifecycle that keeps the system stable. This module documents the **ObjectOS layer** in practice — the [System Protocol](/docs/protocol/objectos) is its normative spec. If you write hooks or plugins, this is where the APIs you call and the contracts they implement are documented. +The kernel is ObjectStack's runtime: it loads your metadata artifact, hosts plugins, wires up services, and enforces the lifecycle that keeps the system stable. This module documents the **ObjectOS layer** in practice — the [System Protocol](/docs/protocol/objectos) is its normative spec. If you write hooks or plugins, this is where the capability surface you call — `ctx.api`, `ctx.getService(...)`, and the `services.*` contract they implement — is documented. ## How it fits together diff --git a/content/docs/kernel/runtime-services/examples.mdx b/content/docs/kernel/runtime-services/examples.mdx index 112e4ce689..d87393952d 100644 --- a/content/docs/kernel/runtime-services/examples.mdx +++ b/content/docs/kernel/runtime-services/examples.mdx @@ -9,10 +9,8 @@ description: Practical examples for flow nodes, hooks, and plugin event subscrip ```ts export async function run(ctx: any) { - const order = await ctx.services?.data?.findOne('sales_order', { - where: { id: ctx.input.orderId }, - }); - const lines = await ctx.services?.data?.find('sales_order_line', { + const { record: order } = await ctx.services.data.get('sales_order', ctx.input.orderId); + const lines = await ctx.services.data.find('sales_order_line', { where: { sales_order_id: order.id }, orderBy: [{ field: 'line_no', order: 'asc' }], limit: 200, @@ -20,7 +18,7 @@ export async function run(ctx: any) { return { order, - lines: lines ?? [], + lines: lines.records ?? [], }; } ``` @@ -53,8 +51,8 @@ export const ExamplePlugin: Plugin = { ctx.logger.info('Kernel ready, initializing subscriptions'); }); - ctx.hook('service:registered', async (serviceName: string) => { - ctx.logger.debug('Service registered', { serviceName }); + ctx.hook('kernel:shutdown', async () => { + ctx.logger.info('Shutdown signal received, cleaning up'); }); }, }; diff --git a/content/docs/kernel/runtime-services/index.mdx b/content/docs/kernel/runtime-services/index.mdx index 4f6135040c..25e06e50e2 100644 --- a/content/docs/kernel/runtime-services/index.mdx +++ b/content/docs/kernel/runtime-services/index.mdx @@ -5,6 +5,15 @@ description: Reference entry for runtime `services.*` APIs used by flow nodes, h # Runtime Service APIs + +**Binding note.** These pages document the stable `services.*` contract surface +(signatures match the client SDK and `packages/spec/src/contracts/`). In this +repo's runtime, hook bodies reach the equivalent capabilities through `ctx.api` +(scoped data operations) and `ctx.getService(...)` (any registered service) — +a literal `services.*` object is not injected into hook contexts by the open +framework today. Managed runtimes provide the `services.*` binding directly. + + This chapter documents the runtime `services.*` APIs used in hook/action/flow/plugin code: - `services.data` diff --git a/content/docs/kernel/services-checklist.mdx b/content/docs/kernel/services-checklist.mdx index b5cfb12ed9..d735e13e9d 100644 --- a/content/docs/kernel/services-checklist.mdx +++ b/content/docs/kernel/services-checklist.mdx @@ -61,9 +61,9 @@ The ObjectStack protocol defines **17 kernel services** registered via the `Core | 11 | **i18n** | `core` | 3 | ✅ Built-in (in-memory fallback) | `@objectstack/service-i18n` | | 12 | **file-storage** | `optional` | — | ❌ Plugin Required | TBD plugin | | 13 | **search** | `optional` | — | ❌ Plugin Required | TBD plugin | -| 14 | **cache** | `core` | — | ❌ Plugin Required | TBD plugin | -| 15 | **queue** | `core` | — | ❌ Plugin Required | TBD plugin | -| 16 | **job** | `core` | — | ❌ Plugin Required | TBD plugin | +| 14 | **cache** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-cache` | +| 15 | **queue** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-queue` | +| 16 | **job** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-job` | | 17 | **graphql** | `optional` | — | ❌ Plugin Required | TBD plugin | @@ -260,7 +260,6 @@ The i18n service is a **core built-in service** with automatic in-memory fallbac | **Production** | `I18nServicePlugin` | File-based `FileI18nAdapter` loads JSON locale files from disk | | **Built-in Fallback** | Kernel | In-memory Map-backed stub, auto-injected when no plugin provides `i18n` | | **Development** | `DevPlugin` | In-memory Map-backed stub, supports `loadTranslations()` | -| **Mock / MSW** | `MswPlugin` | Routes via `HttpDispatcher.dispatch()` catch-all — requires one of the above | ```typescript // Production (overrides built-in fallback) @@ -315,15 +314,17 @@ AppPlugin will: --- -## 12–17. Infrastructure Services ❌ Plugin Required +## 12–17. Infrastructure Services + +`cache`, `queue`, and `job` are `core` services: like `i18n`, the kernel auto-injects an in-memory fallback when no plugin registers them (see `CORE_FALLBACK_FACTORIES` in `packages/core/src/fallbacks/`). The `optional` services (`file-storage`, `search`, `graphql`) stay disabled until a plugin provides them. | Service | Description | |:--------|:------------| | **file-storage** | Unified upload/download/delete. Drivers: local FS, S3, MinIO. | | **search** | Full-text search. Drivers: in-memory, Elasticsearch, Meilisearch. | -| **cache** | General-purpose cache. Drivers: in-memory LRU, Redis. | -| **queue** | Message queue. Drivers: in-memory, BullMQ / Redis Streams. | -| **job** | Scheduled task execution. Cron-based, concurrency control. | +| **cache** | General-purpose cache. In-memory fallback; Redis via `@objectstack/service-cache`. | +| **queue** | Message queue. In-memory fallback; BullMQ / Redis via `@objectstack/service-queue`. | +| **job** | Scheduled task execution. In-memory fallback; cron-based, concurrency control. | | **graphql** | Auto-generate GraphQL schema from Object metadata. | --- @@ -368,24 +369,26 @@ AppPlugin will: ```typescript import type { Plugin } from '@objectstack/core'; +let authService: AuthServiceImpl; + export const authPlugin: Plugin = { name: 'plugin-auth', - provides: ['auth'], // CoreServiceName value - + async init(ctx) { + // Register the 'auth' CoreServiceName value const engine = ctx.getService('data'); - const authService = new AuthServiceImpl(engine); + authService = new AuthServiceImpl(engine); ctx.registerService('auth', authService); }, - + async start(ctx) { const auth = ctx.getService('auth'); await auth.onReady(); }, - - async stop(ctx) { - const auth = ctx.getService('auth'); - await auth.shutdown(); + + // destroy() takes no arguments — capture what you need in module scope + async destroy() { + await authService.shutdown(); }, }; ``` @@ -401,17 +404,17 @@ When a plugin registers a service, the discovery endpoint automatically updates: diff --git a/content/docs/permissions/access-recipes.mdx b/content/docs/permissions/access-recipes.mdx index 1f88eb83e8..a78bcf9962 100644 --- a/content/docs/permissions/access-recipes.mdx +++ b/content/docs/permissions/access-recipes.mdx @@ -37,7 +37,7 @@ definePermissionSet({ }); ``` -- **"My own"** vs **"my team's"** vs **"the org's"** is the RLS *depth* axis (`readScope` / `writeScope`: `own | own_and_reports | unit | unit_and_below | org`, ADR-0057). A manager set uses `unit`; a rep uses `own`. +- **"My own"** vs **"my team's"** vs **"the org's"** is the RLS *depth* axis (`readScope` / `writeScope`: `own | own_and_reports | unit | unit_and_below | org`, ADR-0057). A manager set uses `unit`; a rep uses `own`. The hierarchy-relative scopes (`own_and_reports` / `unit` / `unit_and_below`) need the enterprise hierarchy resolver and **fail closed to `own`** without it — see [Access depth](/docs/permissions/profiles#access-depth--readscope--writescope-adr-0057-d1). - Grants combine **most-permissively** across a user's sets; the tenant-isolation policy `AND`s on top; the superuser bypass (`viewAllRecords` / `modifyAllRecords`) short-circuits RLS where the object's posture allows it (ADR-0066). - For genuinely sensitive objects, set `access: { default: 'private' }` so they are **not** covered by blanket wildcard grants — access needs an explicit grant. @@ -58,7 +58,7 @@ Two separate questions: ## Why -Keeping capability, assignment and requirement decoupled means resources stay stable (`requiredPermissions: ['export_data']`) while admins re-assign who holds `export_data` at runtime, with no code change. The fixed precedence (AND-gates → most-permissive grant union → RLS → explicit deny) is specified in ADR-0066, so combinations are predictable rather than ad-hoc. +Keeping capability, assignment and requirement decoupled means resources stay stable (`requiredPermissions: ['export_data']`) while admins re-assign who holds `export_data` at runtime, with no code change. The fixed precedence (AND-gates → most-permissive grant union → RLS; an explicit deny/muting layer is reserved but [not yet implemented](/docs/permissions/authorization#combination-semantics-the-fixed-order)) is specified in ADR-0066, so combinations are predictable rather than ad-hoc. ## Runnable example diff --git a/content/docs/permissions/authorization.mdx b/content/docs/permissions/authorization.mdx index 298baea874..dc5654752e 100644 --- a/content/docs/permissions/authorization.mdx +++ b/content/docs/permissions/authorization.mdx @@ -45,8 +45,8 @@ site — the file you read when behavior surprises you. | 1 | **Anonymous deny** | No identity → HTTP 401 on `/data/*`. **Default-on** (ADR-0056 D2): public data serving requires an explicit `api.requireAuth: false` opt-out, which logs a boot warning. Control plane (`/auth`, `/health`, `/discovery`) is exempt; share-links validate their token then read as SYSTEM. | `packages/rest/src/rest-server.ts` `enforceAuth` (default in `packages/spec/src/api/rest-server.zod.ts`) | fail-closed | | 2 | **Public-form grant** | An anonymous form submission carries a declaration-derived `publicFormGrant` authorizing ONLY create + read-back on the form's declared target object — never anything else (ADR-0056 Option A). No `guest_portal` profile needed. | `packages/plugins/plugin-security/src/security-plugin.ts` (ObjectQL middleware) | scope-limited allow | | 3 | **Object CRUD** | `allowRead/Create/Edit/Delete` (+ the destructive lifecycle class `allowTransfer/Restore/Purge`, gated ahead of the M2 operations — #1883) resolved across the caller's permission sets. | `packages/plugins/plugin-security/src/permission-evaluator.ts` `checkObjectPermission` | fail-closed 403 | -| 4 | **OWD / sharing** | Org-wide default (`private` / `public_read` / `public_read_write` / `controlled_by_parent`), manual record shares, criteria/owner sharing rules, business-unit hierarchy widening (ADR-0057 D5: hierarchy lives on `sys_business_unit`, not roles). | `packages/plugins/plugin-sharing/src/sharing-service.ts` + `sharing-rule-service.ts` | owner-only baseline | -| 5 | **Row-level security** | CEL predicates (`using` read filter, `check` write post-image) compiled into the query. An applicable-but-uncompilable policy **denies** — it is never silently dropped. Tenant isolation is a wildcard RLS rule AND-ed on top. | `packages/plugins/plugin-security/src/rls-compiler.ts` + `security-plugin.ts` | fail-closed | +| 4 | **OWD / sharing** | Org-wide default (`private` / `public_read` / `public_read_write` / `controlled_by_parent`), manual record shares, criteria sharing rules (owner-type rules are declared but seed-skipped — [not enforced](/docs/permissions/sharing-rules#owner-based-sharing-rules)), business-unit hierarchy widening (ADR-0057 D5: scope-depth hierarchy lives on `sys_business_unit`, not roles). | `packages/plugins/plugin-sharing/src/sharing-service.ts` + `sharing-rule-service.ts` | owner-only baseline | +| 5 | **Row-level security** | CEL predicates (`using` read filter, `check` write post-image) compiled into the query. If **no** applicable policy compiles, the result is a deny-all sentinel (fail-closed); an uncompilable policy alongside compilable ones is excluded from the OR-union **with a logged warning** — exclusion can only narrow access, never widen it. Tenant isolation is a wildcard RLS rule AND-ed on top. | `packages/plugins/plugin-security/src/rls-compiler.ts` + `security-plugin.ts` | fail-closed | | 6 | **Field-level security** | Read mask (strip non-readable fields) + write deny per `fields` rules. | `packages/plugins/plugin-security/src/field-masker.ts` | see posture note below | Two orthogonal identity-layer gates run before all of this: the ADR-0069 @@ -169,8 +169,10 @@ The complete, prioritized gap map lives in issue **#2561** (the production - **Deny/muting layer** (ADR-0066 ⑦⑧) — union-only grants can't take access away; needed for large-org governance and packaged-set adjustment. -- **Capability registry** (ADR-0066 D1) — `systemPermissions` strings become - `sys_permission` records, with an authoring lint for unregistered +- **Capability registry** (ADR-0066 D1) — **landed**: capabilities are seeded + as first-class `sys_capability` records + (`packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts`); + the remaining gap is the authoring lint for unregistered `systemPermissions` references (ADR-0066 ⑨). - **Per-operation `requiredPermissions`** (ADR-0066 ⑤) — read-open / write-gated objects. diff --git a/content/docs/permissions/field-level-security.mdx b/content/docs/permissions/field-level-security.mdx index 44a422b002..d87e597a7a 100644 --- a/content/docs/permissions/field-level-security.mdx +++ b/content/docs/permissions/field-level-security.mdx @@ -1,6 +1,6 @@ --- title: "Field-Level Security" -description: "Control visibility and editability of specific fields — readable/editable rules, hidden vs. read-only semantics, and fail-closed server-side enforcement." +description: "Control visibility and editability of specific fields — readable/editable rules, hidden vs. read-only semantics, and server-side enforcement of declared rules." --- # Field-Level Security @@ -36,7 +36,7 @@ fields: { { readable: true, editable: true } ``` -## Server-side enforcement (fail-closed) +## Server-side enforcement of declared rules The client-side ObjectForm / inline grid hides non-editable fields from the UI — but that is a **UX layer only**. The SecurityPlugin middleware @@ -73,9 +73,19 @@ field exists. Throwing makes the boundary observable in both directions — legitimate UIs get an actionable error to fix; probing clients learn nothing they could not already infer. -**Allow-list semantics.** Fields without an explicit rule pass through -untouched. Permission sets only constrain fields they explicitly -enumerate. +**Default-visible (block-list) semantics.** Fields without an explicit +rule pass through untouched — readable *and* writable. Permission sets +only constrain fields they explicitly enumerate, and field grants union +most-permissively across a user's sets (one set's `readable: true` +out-votes another set's `false`). Until a subtractive muting layer lands +(ADR-0066 ⑧), a `{ readable: false }` rule masks the field only as long +as **no other set the user holds** declares it `readable: true` — so +protect sensitive fields by granting them only in the sets that need +them, and treat a sensitive field on a broadly-granted object as a +review smell — see the +[FLS posture warning](/docs/permissions/authorization#combination-semantics-the-fixed-order). +Declared rules themselves are enforced fail-closed: a masked field is +stripped on read and a write to a non-editable field throws. **Bulk inserts.** Arrays are checked row-by-row; a single offending field in any row rejects the whole batch atomically. diff --git a/content/docs/permissions/index.mdx b/content/docs/permissions/index.mdx index 4691c2efa5..f75f9121bd 100644 --- a/content/docs/permissions/index.mdx +++ b/content/docs/permissions/index.mdx @@ -13,7 +13,10 @@ The model has five layers, each with schema **and** runtime support: **profiles* (one per user) and additive **permission sets** for object/field rights, a **role hierarchy** for reporting structure, **sharing rules + Organization-Wide Defaults** for record visibility, **row-level security** (compiled into every query), and -**field-level security** (masked server-side, fail-closed). Object access scopes +**field-level security** (declared rules masked and enforced server-side; fields +without a declared rule stay visible by default — see the +[FLS posture note](/docs/permissions/authorization#combination-semantics-the-fixed-order)). +Object access scopes go from `own` through `own_and_reports`, `unit`, `unit_and_below`, to `org`. Access rules are metadata like everything else — this is a real sharing rule from @@ -36,7 +39,7 @@ export const HighValueOpportunitySharingRule = defineSharingRule({ Because AI agents act through the same permission-aware surface, these rules bound agent access exactly as they bound users ([Actions as Tools](/docs/ai/actions-as-tools)). -> **Implementation status — Phase-1 RBAC is live.** REST → ObjectQL now propagates a populated `ExecutionContext` (userId, tenantId, roles, permissions) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks actually fire on every authenticated request. The default `member_default` permission set ships a wildcard RLS rule `organization_id == current_user.organization_id` plus explicit per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the two global tables that lack an `organization_id` column. RLS expressions, the physical column, and `RLSUserContext.organization_id` all use the same canonical name — there is no `tenantField` rewrite indirection (schemas with a different physical tenant column should fork the defaults). The legacy `objectql.registerTenantMiddleware` has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics also reuses the same read scope: `@objectstack/service-analytics` auto-bridges to `security.getReadFilter(object, context)` when the security service is registered, so dataset-bound dashboards/reports do not bypass RLS. End-to-end verified on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic is denied by default** (the ADR-0056 D2 default-deny flip landed: `requireAuth` defaults to `true`); an explicit `requireAuth: false` opt-out logs a boot-time warning, and public forms self-authorize via a declaration-derived `publicFormGrant` (ADR-0056 Option A — see [Public Forms](/docs/ui/forms)). Organization-Wide Defaults (`private` / `public_read` / `public_read_write` / `controlled_by_parent`) and Sharing Rules (owner + criteria, with `role_and_subordinates` hierarchy widening) are live and dogfood-proven (ADR-0056); the Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See `CHANGELOG.md` and [Implementation Status](/docs/releases/implementation-status) for the latest matrix. +> **Implementation status — Phase-1 RBAC is live.** REST → ObjectQL now propagates a populated `ExecutionContext` (userId, tenantId, roles, permissions) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks actually fire on every authenticated request. The default `member_default` permission set ships a wildcard RLS rule `organization_id == current_user.organization_id` plus explicit per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the two global tables that lack an `organization_id` column. RLS expressions, the physical column, and `RLSUserContext.organization_id` all use the same canonical name — there is no `tenantField` rewrite indirection (schemas with a different physical tenant column should fork the defaults). The legacy `objectql.registerTenantMiddleware` has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics also reuses the same read scope: `@objectstack/service-analytics` auto-bridges to `security.getReadFilter(object, context)` when the security service is registered, so dataset-bound dashboards/reports do not bypass RLS. End-to-end verified on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic is denied by default** (the ADR-0056 D2 default-deny flip landed: `requireAuth` defaults to `true`); an explicit `requireAuth: false` opt-out logs a boot-time warning, and public forms self-authorize via a declaration-derived `publicFormGrant` (ADR-0056 Option A — see [Public Forms](/docs/ui/forms)). Organization-Wide Defaults (`private` / `public_read` / `public_read_write` / `controlled_by_parent`) and criteria Sharing Rules (with `role_and_subordinates` hierarchy widening) are live and dogfood-proven (ADR-0056); **owner-type sharing rules and `group`/`guest` recipients are declared but not enforced** — the seeder skips them with a warning ([Sharing Rules](/docs/permissions/sharing-rules#owner-based-sharing-rules)); the Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See `CHANGELOG.md` and [Implementation Status](/docs/releases/implementation-status) for the latest matrix. ## What's in this module @@ -75,7 +78,7 @@ ObjectStack implements a multi-layered security model: ├─────────────────────────────────────┤ │ Record-Level Security │ ← Which records can be accessed? │ - Organization-Wide Defaults │ -│ - Role Hierarchy │ +│ - Scope-Depth Grants (readScope) │ │ - Sharing Rules │ │ - Manual Sharing │ ├─────────────────────────────────────┤ diff --git a/content/docs/permissions/permission-metadata.mdx b/content/docs/permissions/permission-metadata.mdx index 674661ce11..41874559c1 100644 --- a/content/docs/permissions/permission-metadata.mdx +++ b/content/docs/permissions/permission-metadata.mdx @@ -121,7 +121,7 @@ fields: { | `readable` | User can see the field value | | `editable` | User can modify the field value | -**Note:** `editable: true` requires `readable: true`. A field that is not readable is completely hidden from the user. +**Note:** `readable` defaults to `true` and `editable` to `false`. The schema does not force `editable: true` to be paired with `readable: true`, but a field with `readable: false` is stripped from every read regardless of `editable` — always declare them together, and treat `{ readable: false }` as completely hiding the field. ## Tab Permissions diff --git a/content/docs/permissions/permissions-matrix.mdx b/content/docs/permissions/permissions-matrix.mdx index c61684373b..c1ba4103ce 100644 --- a/content/docs/permissions/permissions-matrix.mdx +++ b/content/docs/permissions/permissions-matrix.mdx @@ -8,7 +8,7 @@ description: Visual reference for ObjectStack's security model — permission ty This page provides a comprehensive visual reference for ObjectStack's security model — from object-level permissions to field-level security, sharing rules, and role hierarchies. -**Security Model:** ObjectStack uses a layered security model inspired by Salesforce. Permissions are evaluated in order: Object Permission → Organization-Wide Defaults → Record Ownership → Role Hierarchy → Sharing Rules → Field-Level Security. +**Security Model:** ObjectStack uses a layered security model inspired by Salesforce. Permissions are evaluated in order: Object Permission → Organization-Wide Defaults → Record Ownership / Scope Depth → Sharing (rules + manual shares) → Row-Level Security → Field-Level Security. Unlike Salesforce, the role hierarchy is **not** an automatic layer — it widens access only where a sharing rule's `role_and_subordinates` recipient or a scope-depth grant explicitly invokes it. --- @@ -135,9 +135,13 @@ Sharing rules extend access beyond ownership and role hierarchy. The declarative | Mechanism | `type` | Description | Example | |:---|:---|:---|:---| -| **Owner-Based** | `owner` | Share records owned by a specific group/role with another recipient | All accounts owned by "West Region" team are shared with "Sales Directors" | +| **Owner-Based** | `owner` | Share records owned by a specific group/role with another recipient — **[experimental — not enforced]**: owner rules are skipped at seed time and materialize no shares yet | All accounts owned by "West Region" team are shared with "Sales Directors" | | **Criteria-Based** | `criteria` | Share records matching a CEL predicate over field values | All opportunities where `record.amount > 100000` are shared with "VP Sales" | + +**Enforcement status:** only **criteria** rules are enforced today. Declared `owner`-type rules, and rules with `group` / `guest` recipients, are skipped at seed time with a warning (ADR-0049 — never silently advertised as live). See [Sharing Rules](/docs/permissions/sharing-rules#owner-based-sharing-rules). + + **Beyond declarative rules:** Two other sharing mechanisms exist but are **not** `SharingRule` types. **Manual sharing** is a runtime grant — `sys_record_share` rows created with `source: 'manual'` (see `packages/plugins/plugin-sharing/src/sharing-service.ts`). **Territories** are a separate matrix model (`TerritorySchema` in `packages/spec/src/security/territory.zod.ts`) with their own account/opportunity/case access levels, parallel to the role hierarchy. Owner/criteria rules are re-evaluated on insert/update via internal sharing rule hooks. @@ -180,7 +184,7 @@ OWD sets the baseline access level for each object across the entire organizatio | **Public Read/Write** | `read_write` | All users | All users | Low-sensitivity data (e.g., tasks, wiki pages) | | **Public Read Only** | `read` | All users | Owner + shared | Moderate sensitivity (e.g., accounts, contacts) | | **Private** | `private` | Owner + shared | Owner + shared | High sensitivity (e.g., opportunities, HR records) | -| **Full Access** | `full` | All users | All users (incl. transfer/share) | Fully collaborative data | +| **Full Access** | `full` | All users | All users | Legacy alias — enforced identically to `public_read_write` | Since ADR-0056 (D1), `object.sharingModel` accepts the **canonical OWD vocabulary** — `private`, `public_read`, `public_read_write`, and `controlled_by_parent` — *in addition to* the legacy `read` / `read_write` / `full` spellings shown above (both are valid; the canonical values are preferred for new objects). `controlled_by_parent` is for child objects in a master-detail relationship, where access is **derived from the parent record** (a line is visible/editable only if its master is). These models are enforced by `plugin-sharing` + `plugin-security` and dogfood-proven over the real HTTP stack. @@ -209,7 +213,10 @@ defineStack({ ## 6. Role Hierarchy -The role hierarchy determines which users can see records owned by users below them. +The role hierarchy is a reporting structure that sharing rules can reference to +widen record access. It grants **nothing automatically**: a user's position +above another role does not by itself expose that role's records (ADR-0057 D5 — +the visibility hierarchy does not live on roles). ```mermaid graph TD @@ -258,11 +265,19 @@ graph TD style ENG_MGR fill:#0284c7,color:#fff ``` -**How it works:** Each role can see all records owned by users in roles below it. For example: -- **CEO** can see all records in the organization -- **VP Sales** can see records owned by Directors and Sales Reps -- **Director - East** can see records owned by East Sales Reps only -- **Sales Rep** can only see their own records (plus sharing rules) +**How it is consumed:** the `parent` graph powers the `role_and_subordinates` +sharing-rule recipient — one rule can grant access to a role *and every role +beneath it* (see [Recipient types](/docs/permissions/sharing-rules#recipient-types)). +Manager-over-subordinate record visibility is built explicitly, per grant: + +- **VP Sales sees Directors' and Reps' records** — author a criteria sharing + rule with `sharedWith: { type: 'role', value: 'vp_sales' }`, or grant the VP's + permission set `readScope: 'unit_and_below'` (scope depth, ADR-0057). +- **Cascade one rule down a branch** — share with + `{ type: 'role_and_subordinates', value: 'director_east' }` to reach the + director and everyone below that role. +- **Sales Rep** sees only their own records (plus whatever sharing rules and + manual shares grant) — this part needs no configuration under a `private` OWD. ### Configuration Example @@ -293,7 +308,7 @@ flowchart TD C -->|Public Read/Write| ALLOW[✅ Access Granted] C -->|No| D{Is owner?} D -->|Yes| ALLOW - D -->|No| E{Role hierarchy grants?} + D -->|No| E{Scope-depth grant? readScope} E -->|Yes| ALLOW E -->|No| F{Sharing rule matches?} F -->|Yes| ALLOW @@ -311,11 +326,11 @@ flowchart TD | 1 | **Object Permission** | Does the profile grant any access to this object? | | 2 | **OWD** | Is the object public? If so, grant access immediately | | 3 | **Record Ownership** | Does the user own this record? | -| 4 | **Role Hierarchy** | Does the user's role sit above the owner's role? | -| 5 | **Sharing Rules** | Do any sharing rules grant access to this record? | -| 6 | **Manual Sharing** | Was this record explicitly shared with the user? | +| 4 | **Scope Depth** | Does a `readScope` / `writeScope` grant (`own_and_reports` / `unit` / `unit_and_below` / `org`) widen the owner-match to cover the record's owner? | +| 5 | **Sharing Rules** | Did a criteria sharing rule materialize a share for this user on this record? | +| 6 | **Manual Sharing** | Was this record explicitly shared with the user (`sys_record_share`, `source: 'manual'`)? | | 7 | **Field-Level Security** | Which fields is the user allowed to see/edit? | -**Performance:** Steps 2–6 are compiled into SQL WHERE clauses (RLS) at query time, not evaluated record-by-record. This ensures security checks are efficient even on tables with millions of rows. +**Performance:** For reads, steps 2–6 are compiled into a query filter (owner-match ∪ materialized shares, AND-ed with RLS) at query time, not evaluated record-by-record. By-id writes are verified with a pre-image check: the target row is re-read through the write-scope filter before the mutation. This keeps security checks efficient even on tables with millions of rows. diff --git a/content/docs/permissions/roles.mdx b/content/docs/permissions/roles.mdx index fb79ea1ba1..4218de8a50 100644 --- a/content/docs/permissions/roles.mdx +++ b/content/docs/permissions/roles.mdx @@ -1,11 +1,15 @@ --- title: "Role Hierarchy" -description: "Roles control record-level access through a hierarchy derived from each role's parent link — access is granted upward to managers over their subordinates' records." +description: "Roles bundle permissions (via role-based permission-set grants) and form a reporting hierarchy derived from each role's parent link — consumed by role_and_subordinates sharing-rule recipients." --- # Role Hierarchy -Roles control record-level access through a hierarchy. +Roles serve two purposes: they are an **assignment target for permission sets** +(grant a set to a role via a `sys_role_permission_set` row — see +[Permission Sets](/docs/permissions/permission-sets#assigning-permission-sets)), +and their `parent` links form a **reporting hierarchy** that sharing rules can +reference to widen record access. The hierarchy is derived from the `parent` link on each role — there is no `RoleHierarchy` container schema. Author roles as individual `*.role.ts` @@ -43,12 +47,27 @@ export const roles: Role[] = [ Sales Rep Service Agent ``` -**Grant Access UP:** Users see records owned by: -- Themselves -- Their subordinates -- Their subordinates' subordinates (all levels down) +**There is no implicit "managers see subordinates' records" grant.** Unlike +Salesforce, the role hierarchy by itself does not widen record visibility +(ADR-0057 D5: the visibility hierarchy does not live on roles). Upward +visibility is always an explicit, opt-in grant: -**Example:** Sales Director sees all records owned by Sales Managers and Sales Reps. +- **Sharing rules** — a criteria rule whose recipient is + `{ type: 'role', value: 'sales_director' }` or + `{ type: 'role_and_subordinates', value: 'sales_manager' }`. The + `role_and_subordinates` recipient is where the `parent` graph is consumed: + it expands to everyone holding the named role *or any role beneath it*. + See [Sharing Rules](/docs/permissions/sharing-rules#recipient-types). +- **Scope-depth grants** — an owner-scoped object grant with + `readScope: 'own_and_reports'` (manager chain via `sys_user.manager_id`) or + `'unit'` / `'unit_and_below'` (business units). These require the paid + hierarchy resolver and **fail closed to `own`** without it — see + [Access depth](/docs/permissions/profiles#access-depth--readscope--writescope-adr-0057-d1). + +**Example:** to let a Sales Director see records owned by Sales Managers and +Sales Reps, author a criteria sharing rule with the director role as recipient, +or grant the director's permission set `readScope: 'unit_and_below'` — neither +happens automatically. --- diff --git a/content/docs/permissions/sharing-rules.mdx b/content/docs/permissions/sharing-rules.mdx index 2db72a9e54..e43a66fa98 100644 --- a/content/docs/permissions/sharing-rules.mdx +++ b/content/docs/permissions/sharing-rules.mdx @@ -29,11 +29,19 @@ export const OrganizationDefaults = { | Level | Description | |-------|-------------| -| `private` | Owner only (+ role hierarchy) | +| `private` | Owner only (+ scope-depth grants and record shares) | | `public_read` | All users can read | | `public_read_write` | All users can read and edit | | `controlled_by_parent` | Controlled by parent object | +> **The default is open, not private.** An object that declares **no +> `sharingModel`** — or that has no `owner_id` field for the owner-match to key +> on — gets **no record-level OWD filter at all** (it behaves as public within +> the tenant; only tenant-isolation RLS still applies). If records should be +> owner-scoped, declare `sharingModel: 'private'` explicitly on the object. +> See `effectiveSharingModel` in +> `packages/plugins/plugin-sharing/src/sharing-service.ts`. + ## Criteria-Based Sharing Rules Share records based on field criteria: @@ -69,7 +77,7 @@ export const AccountTeamSharingRule = defineSharingRule({ | `type` | Shares with | |:--|:--| | `user` | A single user | -| `group` | All members of a public group | +| `group` | All members of a public group — **declared but not enforced**: there is no runtime recipient mapping yet, so a rule with a `group` recipient is skipped at seed time with a warning (ADR-0049) | | `role` | Everyone assigned that role | | `role_and_subordinates` | Everyone in that role **and every role below it** in the hierarchy (ADR-0056 D6) — configurable per rule, so one rule can cascade down a branch of the org chart | @@ -82,8 +90,21 @@ The recipient set is expanded by `@objectstack/plugin-sharing` when the rule is evaluated (`afterInsert` / `afterUpdate`); `role_and_subordinates` walks the `sys_role.parent` graph (cycle-safe). +A criteria `condition` must be compilable by the CEL → filter pushdown compiler. +A condition the compiler cannot lower (functions, cross-object traversal) is +**skipped and logged — never seeded as a permissive match-all** (ADR-0049), so a +bad condition under-shares rather than over-shares. + ## Owner-Based Sharing Rules +> **[Experimental — not enforced.]** Owner-based (`type: 'owner'`) rules depend +> on live role membership and have no static `criteria_json` equivalent, so the +> seeder **skips them with a warning** — they materialize **no** record shares +> today (see `packages/plugins/plugin-sharing/src/bootstrap-declared-sharing-rules.ts` +> and the `SharingRuleSchema` notes in `packages/spec/src/security/sharing.zod.ts`). +> Do not rely on an owner-based rule to deliver access; use a criteria rule or a +> scope-depth grant until this ships. + Share based on record owner characteristics: ```typescript diff --git a/content/docs/plugins/anatomy.mdx b/content/docs/plugins/anatomy.mdx index 8e65a5c5a1..805aa5da49 100644 --- a/content/docs/plugins/anatomy.mdx +++ b/content/docs/plugins/anatomy.mdx @@ -71,7 +71,7 @@ ObjectStack uses `type` discrimination to optimize runtime behavior, allowing th * **Use Cases:** Admin Console, Low-code Studio, SPA Dashboard. * **Behavior:** * **Passive:** Driven by `plugin-hono-server` (or other HTTP adapters). - * **Static Assets:** Must verify `staticPath` pointing to a build output (e.g., `dist/`). + * **Static Assets:** Must provide `staticPath` pointing to a build output (e.g., `dist/`). * **Routing:** Automatically mounted to `/{slug}` with SPA fallback support. * **Assets:** Files are served locally under `/{slug}/assets`. diff --git a/content/docs/plugins/development.mdx b/content/docs/plugins/development.mdx index 88caf129c7..373ce6c211 100644 --- a/content/docs/plugins/development.mdx +++ b/content/docs/plugins/development.mdx @@ -31,7 +31,7 @@ A plugin is a self-contained module that extends the ObjectStack kernel with: mkdir objectstack-plugin-hello cd objectstack-plugin-hello npm init -y -npm install @objectstack/spec +npm install @objectstack/core @objectstack/spec npm install -D typescript vitest @types/node ``` @@ -62,7 +62,8 @@ A plugin package describes itself with a manifest validated by `ManifestSchema`. import { ManifestSchema } from '@objectstack/spec/kernel'; export const manifest = ManifestSchema.parse({ - name: 'objectstack-plugin-hello', + id: 'com.example.hello', // required — reverse-domain package id + name: 'objectstack-plugin-hello', // required — human-readable name version: '1.0.0', description: 'A simple example plugin that adds a greeting service.', type: 'plugin', @@ -77,7 +78,7 @@ A runtime plugin's behaviour (services, hooks, objects) is declared in code via ## Step 3: Implement the Plugin -Create `src/index.ts`. A runtime plugin implements the `Plugin` interface: services are registered inside `init(ctx)` via `ctx.registerService`, and lifecycle hooks are wired with `ctx.hook(...)`. +Create `src/index.ts`. A runtime plugin implements the `Plugin` interface: services are registered inside `init(ctx)` via `ctx.registerService`. Kernel lifecycle hooks (`kernel:ready`, custom events) are wired with `ctx.hook(...)`; record lifecycle hooks (`beforeInsert`, `afterUpdate`, …) are registered on the **data engine** — see [Events & Hooks](/docs/kernel/events) for the two hook systems. ```typescript import type { Plugin, PluginContext } from '@objectstack/core'; @@ -96,20 +97,25 @@ export function createHelloPlugin(): Plugin { return { name: 'hello_world', version: '1.0.0', + // Ensure the data engine plugin initializes before this one + dependencies: ['com.objectstack.engine.objectql'], init(ctx: PluginContext) { // Register services so other plugins can consume them ctx.registerService('greeting', greetingService); - // Hook into record lifecycle - ctx.hook('data:beforeInsert', (context: { object: string; data: Record }) => { - if (context.object === 'contact' && context.data.first_name) { + // Hook into the record lifecycle via the data engine. + // Data hooks receive a single HookContext; mutate the flat hookCtx.input + // in before* hooks to change the operation. + const engine = ctx.getService('data'); + engine.registerHook('beforeInsert', async (hookCtx: any) => { + if (hookCtx.input.first_name) { // Auto-generate a greeting field - context.data.welcome_message = greetingService.greet( - context.data.first_name as string + hookCtx.input.welcome_message = greetingService.greet( + hookCtx.input.first_name as string ); } - }); + }, { object: 'contact' }); // the engine only runs this hook for 'contact' } }; } @@ -151,15 +157,22 @@ import { describe, it, expect } from 'vitest'; import type { PluginContext } from '@objectstack/core'; import { createHelloPlugin } from './index'; -// Minimal fake PluginContext that captures registered services and hooks. +// Minimal fake PluginContext with a stub data engine that captures hooks. function createFakeContext() { const services: Record = {}; - const hooks: Record void> = {}; + const hooks: Record void | Promise> = {}; + const hookOptions: Record = {}; + const engine = { + registerHook: (event: string, handler: (hookCtx: any) => void, options?: unknown) => { + hooks[event] = handler; + hookOptions[event] = options; + }, + }; const ctx = { registerService: (name: string, service: unknown) => { services[name] = service; }, - hook: (name: string, handler: (...args: any[]) => void) => { hooks[name] = handler; }, + getService: (name: string) => (name === 'data' ? engine : services[name]), } as unknown as PluginContext; - return { ctx, services, hooks }; + return { ctx, services, hooks, hookOptions }; } describe('HelloPlugin', () => { @@ -180,20 +193,19 @@ describe('HelloPlugin', () => { expect(greeting.greet('Alice')).toBe('Hello, Alice! Welcome to ObjectStack.'); }); - it('should add welcome message on contact create', () => { + it('should add welcome message on contact create', async () => { const { ctx, hooks } = createFakeContext(); createHelloPlugin().init(ctx); - const context = { object: 'contact', data: { first_name: 'Bob' } as Record }; - hooks['data:beforeInsert'](context); - expect(context.data.welcome_message).toBe('Hello, Bob! Welcome to ObjectStack.'); + const hookCtx = { object: 'contact', input: { doc: { first_name: 'Bob' } as Record } }; + await hooks['beforeInsert'](hookCtx); + expect(hookCtx.input.welcome_message).toBe('Hello, Bob! Welcome to ObjectStack.'); }); - it('should not modify non-contact objects', () => { - const { ctx, hooks } = createFakeContext(); + it('should scope the hook to the contact object', () => { + // The engine (not the handler) filters by object — assert the option + const { ctx, hookOptions } = createFakeContext(); createHelloPlugin().init(ctx); - const context = { object: 'task', data: { title: 'Test' } as Record }; - hooks['data:beforeInsert'](context); - expect(context.data).not.toHaveProperty('welcome_message'); + expect(hookOptions['beforeInsert']).toEqual({ object: 'contact' }); }); }); ``` diff --git a/content/docs/plugins/index.mdx b/content/docs/plugins/index.mdx index 3fc97005ab..6747044f74 100644 --- a/content/docs/plugins/index.mdx +++ b/content/docs/plugins/index.mdx @@ -38,7 +38,7 @@ ObjectStack is built on a **microkernel architecture** where nearly everything b │ │ (data) │ │ (security) │ │ (server) │ │ (driver)│ │ │ └────────────┘ └────────────┘ └────────────┘ └─────────┘ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ -│ │ REST API │ │ MSW │ │ Custom │ │ +│ │ REST API │ │ Dev │ │ Custom │ │ │ │ (api) │ │ (testing) │ │ (your own) │ │ │ └────────────┘ └────────────┘ └────────────┘ │ └─────────────────────────────────────────────────────────────┘ diff --git a/content/docs/plugins/packages.mdx b/content/docs/plugins/packages.mdx index 01454514d5..f57e51585e 100644 --- a/content/docs/plugins/packages.mdx +++ b/content/docs/plugins/packages.mdx @@ -13,10 +13,10 @@ ObjectStack is organized into **70 package manifests** across multiple categorie |:---|:---:|:---| | **Core runtime** | 9 | `spec`, `core`, `runtime`, `types`, `metadata`, `objectql`, `rest`, `formula`, `platform-objects` | | **Client / DX** | 5 | `client`, `client-react`, `cli`, `create-objectstack`, `vscode-objectstack` | -| **Framework adapters** | 7 | `express`, `fastify`, `hono`, `nestjs`, `nextjs`, `nuxt`, `sveltekit` | +| **Framework adapters** | 1 | `hono` (other frameworks: build a thin adapter on `HttpDispatcher` — see below) | | **Drivers** | 4 | `driver-memory`, `driver-sql`, `driver-sqlite-wasm`, `driver-mongodb` | -| **Plugins** | 22 | `plugin-auth`, `plugin-security`, `plugin-org-scoping`, `plugin-audit`, `plugin-approvals`, `plugin-sharing`, `plugin-email`, `plugin-webhooks`, `plugin-reports`, `plugin-hono-server`, `mcp`, `plugin-msw`, `plugin-dev`, trigger plugins, and knowledge/embedder plugins | -| **Platform services** | 16 | `service-analytics`, `service-automation`, `service-cache`, `service-cluster`, `service-cluster-redis`, `service-datasource`, `service-feed`, `service-i18n`, `service-job`, `service-knowledge`, `service-messaging`, `service-package`, `service-queue`, `service-realtime`, `service-settings`, `service-storage` | +| **Plugins** | 18 | `plugin-auth`, `plugin-security`, `plugin-org-scoping`, `plugin-audit`, `plugin-approvals`, `plugin-sharing`, `plugin-email`, `plugin-webhooks`, `plugin-reports`, `plugin-hono-server`, `plugin-dev`, `mcp`, trigger plugins (`trigger-api`, `trigger-record-change`, `trigger-schedule`), and knowledge/embedder plugins (`knowledge-memory`, `knowledge-ragflow`, `embedder-openai`) | +| **Platform services** | 15 | `service-analytics`, `service-automation`, `service-cache`, `service-cluster`, `service-cluster-redis`, `service-datasource`, `service-i18n`, `service-job`, `service-knowledge`, `service-messaging`, `service-package`, `service-queue`, `service-realtime`, `service-settings`, `service-storage` | ## Core Packages @@ -44,7 +44,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data'; - **Purpose**: ObjectKernel with dependency injection, lifecycle hooks, and event bus - **Exports**: `ObjectKernel`, `LiteKernel`, `Plugin` interface, service management - **When to use**: Bootstrap your application, manage plugins and services -- **README**: [View README](/packages/core/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/core/README.md) ```typescript import { ObjectKernel } from '@objectstack/core'; @@ -58,7 +58,7 @@ const kernel = new ObjectKernel(); - **Purpose**: High-level runtime bootstrap and plugin composition - **Exports**: Runtime configuration, plugin loaders, capability system - **When to use**: Use with `defineStack()` for application setup -- **README**: [View README](/packages/runtime/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/runtime/README.md) ### @objectstack/objectql @@ -67,7 +67,7 @@ const kernel = new ObjectKernel(); - **Purpose**: ObjectQL query engine with filters, aggregations, and window functions - **Exports**: Query parser, filter engine, schema registry - **When to use**: Advanced query operations, custom data access patterns -- **README**: [View README](/packages/objectql/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/objectql/README.md) ### @objectstack/metadata @@ -76,7 +76,7 @@ const kernel = new ObjectKernel(); - **Purpose**: Load, validate, and manage metadata from files or runtime - **Exports**: Metadata loaders, serializers, overlay system, validation - **When to use**: Dynamic metadata loading, multi-source metadata composition -- **README**: [View README](/packages/metadata/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/metadata/README.md) ### @objectstack/rest @@ -85,7 +85,7 @@ const kernel = new ObjectKernel(); - **Purpose**: Automatic REST API generation based on object definitions - **Exports**: REST server, route generators, middleware - **When to use**: Expose ObjectStack data via REST API -- **README**: [View README](/packages/rest/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/rest/README.md) ### @objectstack/formula @@ -113,7 +113,7 @@ const kernel = new ObjectKernel(); - **Purpose**: Type-safe client for ObjectStack REST API with batching and error handling - **Exports**: `ObjectStackClient`, query builders, error classes - **When to use**: Any JavaScript/TypeScript application (Node, React, Vue, Angular, etc.) -- **README**: [View README](/packages/client/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/client/README.md) ```typescript import { ObjectStackClient } from '@objectstack/client'; @@ -127,7 +127,7 @@ const client = new ObjectStackClient({ baseUrl: 'https://api.example.com' }); - **Purpose**: React hooks for queries, mutations, real-time subscriptions - **Exports**: `useQuery`, `useMutation`, `useRealtimeConnection`, `useView`, `useObject`, `useMetadata`, etc. - **When to use**: React applications -- **README**: [View README](/packages/client-react/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/client-react/README.md) ```typescript import { useQuery, useMutation } from '@objectstack/client-react'; @@ -143,7 +143,7 @@ import { useQuery, useMutation } from '@objectstack/client-react'; - **Purpose**: Fast in-memory data storage with full ObjectQL support - **When to use**: Development, testing, demos (data is lost on restart) -- **README**: [View README](/packages/plugins/driver-memory/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/driver-memory/README.md) ```typescript import { InMemoryDriver } from '@objectstack/driver-memory'; @@ -156,7 +156,7 @@ import { InMemoryDriver } from '@objectstack/driver-memory'; - **Purpose**: Production-ready SQL database support with migrations - **Supports**: PostgreSQL, MySQL, SQLite, and all Knex-compatible databases - **When to use**: Traditional relational database deployments -- **README**: [View README](/packages/plugins/driver-sql/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/driver-sql/README.md) ```typescript import { SqlDriver } from '@objectstack/driver-sql'; @@ -173,7 +173,7 @@ const driver = new SqlDriver({ - **Purpose**: SQLite running entirely in WebAssembly (no native bindings), with optional `fs`-backed persistence - **Modes**: In-memory (`:memory:`) or a file path persisted via the `persist` option - **When to use**: Environments without native SQLite, edge/browser runtimes, lightweight local-first storage -- **README**: [View README](/packages/plugins/driver-sqlite-wasm/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/driver-sqlite-wasm/README.md) ```typescript import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; @@ -186,7 +186,7 @@ const driver = new SqliteWasmDriver({ filename: ':memory:' }); - **Purpose**: Native MongoDB driver for ObjectQL with document-flavored objects - **When to use**: Existing MongoDB infrastructure, document-shaped data -- **README**: [View README](/packages/plugins/driver-mongodb/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/driver-mongodb/README.md) --- @@ -200,7 +200,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Aggregations, time series, funnels, dashboards - **When to use**: Business intelligence, reporting, metrics dashboards -- **README**: [View README](/packages/services/service-analytics/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-analytics/README.md) ### @objectstack/service-automation @@ -208,7 +208,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Autolaunched, screen, and scheduled flows with visual builder support - **When to use**: Business process automation, approval workflows, scheduled tasks -- **README**: [View README](/packages/services/service-automation/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-automation/README.md) ### @objectstack/service-cache @@ -217,15 +217,14 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Adapters**: Memory (dev), Redis (production) - **Features**: TTL, namespaces, pattern matching, statistics - **When to use**: Performance optimization, reduce database load -- **README**: [View README](/packages/services/service-cache/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-cache/README.md) -### @objectstack/service-feed +### @objectstack/service-messaging -**Feed/Chatter Service** — Activity feed with comments, reactions, subscriptions. +**Messaging Service** — Outbound notification dispatch (ADR-0012). -- **Features**: Comments, @mentions, reactions, field change tracking, presence -- **When to use**: Collaboration features, activity streams, social features -- **README**: [View README](/packages/services/service-feed/README.md) +- **Features**: `MessagingChannel` registry, `emit()` fan-out, always-on inbox channel; email/webhook/push/IM channels plug in +- **When to use**: Notifying users across channels from flows, hooks, and plugins ### @objectstack/service-i18n @@ -233,7 +232,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Multi-language support, interpolation, pluralization, fallback chains - **When to use**: Multi-language applications, global deployments -- **README**: [View README](/packages/services/service-i18n/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-i18n/README.md) ### @objectstack/service-job @@ -241,7 +240,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Cron expressions, intervals, one-time jobs, retry logic, history - **When to use**: Background tasks, scheduled reports, cleanup jobs -- **README**: [View README](/packages/services/service-job/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-job/README.md) ### @objectstack/service-queue @@ -249,7 +248,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Priority queues, retry, rate limiting, worker pools, job events - **When to use**: Async processing, email sending, report generation, webhooks -- **README**: [View README](/packages/services/service-queue/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-queue/README.md) ### @objectstack/service-realtime @@ -257,7 +256,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Channels, presence, broadcasting, typing indicators, cursor tracking - **When to use**: Real-time dashboards, collaborative editing, live notifications -- **README**: [View README](/packages/services/service-realtime/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-realtime/README.md) ### @objectstack/service-storage @@ -265,7 +264,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Upload, download, signed URLs, multipart uploads, metadata - **When to use**: File attachments, document management, media storage -- **README**: [View README](/packages/services/service-storage/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-storage/README.md) ### @objectstack/service-package @@ -273,7 +272,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Upsert by `(id, version)`, SHA-256 integrity hash, `latest` resolution, bulk list/delete - **When to use**: Marketplace backends, internal tenant-facing registries, CI-driven metadata distribution -- **README**: [View README](/packages/services/service-package/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-package/README.md) ### @objectstack/service-settings @@ -281,7 +280,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Per-org / per-user settings, Zod-validated namespaces, default fallbacks - **When to use**: Application/feature configuration that must be editable at runtime -- **README**: [View README](/packages/services/service-settings/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-settings/README.md) --- @@ -293,7 +292,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Email/password, OAuth providers, session management, RBAC - **When to use**: User authentication and authorization -- **README**: [View README](/packages/plugins/plugin-auth/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-auth/README.md) ### @objectstack/plugin-security @@ -301,7 +300,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Role-based access control, object/field permissions, row-level security, `owner_id` auto-stamp - **When to use**: Multi-user applications with access control requirements -- **README**: [View README](/packages/plugins/plugin-security/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-security/README.md) ### @objectstack/plugin-org-scoping @@ -309,7 +308,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: `organization_id` auto-stamp on insert, per-org seed-data replay, default-org bootstrap, orphan-row claim hook - **When to use**: Multi-organization SaaS where every row is scoped to an `sys_organization`. Enable by setting `OS_MULTI_ORG_ENABLED=true`; registered automatically before `plugin-security` -- **README**: [View README](/packages/plugins/plugin-org-scoping/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-org-scoping/README.md) ### @objectstack/plugin-audit @@ -317,7 +316,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: CRUD audit logs, field-level changes, security events, compliance reports - **When to use**: SOC 2, HIPAA, GDPR compliance, security monitoring -- **README**: [View README](/packages/plugins/plugin-audit/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-audit/README.md) ### @objectstack/mcp @@ -325,7 +324,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: AI tools, data resources, prompt templates for Claude, Cursor, Cline - **When to use**: AI agent integration, MCP-compatible tools -- **README**: [View README](/packages/mcp/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/mcp/README.md) ### @objectstack/plugin-hono-server @@ -333,7 +332,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Lightweight HTTP server, middleware support, edge-compatible - **When to use**: Serve ObjectStack REST API with Hono -- **README**: [View README](/packages/plugins/plugin-hono-server/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-hono-server/README.md) ### @objectstack/plugin-dev @@ -341,7 +340,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern - **Features**: Metadata validation, schema introspection, debugging tools - **When to use**: Development and debugging -- **README**: [View README](/packages/plugins/plugin-dev/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-dev/README.md) ### @objectstack/plugin-approvals @@ -389,7 +388,7 @@ The open edition ships the **Hono** adapter. Hono runs on Node.js, Bun, Deno, an **Hono Adapter** — the supported HTTP adapter; edge-native and multi-runtime. - **Use case**: Node.js, Bun, Deno, Cloudflare Workers, Vercel Edge -- **README**: [View README](/packages/adapters/hono/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/adapters/hono/README.md) --- @@ -401,7 +400,7 @@ The open edition ships the **Hono** adapter. Hono runs on Node.js, Bun, Deno, an - **Commands**: `serve`, `dev`, `start`, `doctor`, `compile`, `build`, `validate`, `generate`, `package`, `meta`, … (binary is `os` / `objectstack`) - **When to use**: Development, deployment, project management -- **README**: [View README](/packages/cli/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/cli/README.md) ```bash npx os serve --dev @@ -413,7 +412,7 @@ npx os serve --dev - **Templates**: `blank` (default, bundled), plus remote templates `todo`, `compliance`, `content`, `contracts`, `procurement` - **When to use**: Start a new ObjectStack project -- **README**: [View README](/packages/create-objectstack/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/create-objectstack/README.md) ```bash npx create-objectstack my-app @@ -425,7 +424,7 @@ npx create-objectstack my-app - **Features**: Syntax highlighting, autocomplete, validation for metadata files - **When to use**: Enhanced development experience in VS Code -- **README**: [View README](/packages/vscode-objectstack/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/vscode-objectstack/README.md) --- @@ -437,7 +436,7 @@ npx create-objectstack my-app - **Exports**: Type helpers, utility types, branded types - **When to use**: Imported automatically by other packages -- **README**: [View README](/packages/types/README.md) +- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/types/README.md) --- diff --git a/content/docs/protocol/diagram.mdx b/content/docs/protocol/diagram.mdx index ec977f0a15..1277ca2b19 100644 --- a/content/docs/protocol/diagram.mdx +++ b/content/docs/protocol/diagram.mdx @@ -278,7 +278,7 @@ These rules ensure clean architecture and prevent circular dependencies. **See also:** -- [Data Flow Guide](/guides/data-flow) for detailed sequence diagrams -- [Kernel Services](/guides/kernel-services) for runtime architecture -- [API Protocol](/references/api/contract) for endpoint contracts +- [Data Flow Guide](/docs/api/data-flow) for detailed sequence diagrams +- [Kernel Services](/docs/kernel/services) for runtime architecture +- [API Protocol](/docs/references/api/contract) for endpoint contracts diff --git a/content/docs/protocol/objectos/error-handling.mdx b/content/docs/protocol/objectos/error-handling.mdx index 1a5b42b030..80a1b64b8b 100644 --- a/content/docs/protocol/objectos/error-handling.mdx +++ b/content/docs/protocol/objectos/error-handling.mdx @@ -1047,12 +1047,12 @@ for (let i = 0; i < 1000000; i++) { icon={} title="HTTP API" description="Learn REST endpoint conventions and CRUD operations" - href="/docs/transport/http-api" + href="/docs/protocol/objectos/http-protocol" /> } title="Real-Time Protocols" description="Implement WebSocket subscriptions and event streaming" - href="/docs/transport/realtime" + href="/docs/protocol/objectos/realtime-protocol" /> diff --git a/content/docs/protocol/objectql/security.mdx b/content/docs/protocol/objectql/security.mdx index 59c61e7f5f..c891e54948 100644 --- a/content/docs/protocol/objectql/security.mdx +++ b/content/docs/protocol/objectql/security.mdx @@ -251,7 +251,15 @@ fields: ## 4. Data Masking -Masking is configured **on the field** via `maskingRule` (`MaskingRuleSchema` in `packages/spec/src/system/masking.zod.ts`). The strategy enum is one of: `redact`, `partial`, `hash`, `tokenize`, `randomize`, `nullify`, `substitute`. +> **Status: schema exists, field wiring removed.** `MaskingRuleSchema` +> (`packages/spec/src/system/masking.zod.ts`, strategies `redact`, `partial`, +> `hash`, `tokenize`, `randomize`, `nullify`, `substitute`) remains in the +> System namespace, but the per-field `maskingRule` property was **pruned from +> the Field schema in 2026-06** as dead surface — it was never read by the +> runtime (see `docs/audits/2026-06-dead-surface-disposition-plan.md`). To keep +> a value out of reader hands today, use FLS (`readable: false`), `hidden`, or +> a `type: 'secret'` field. The examples below show the *schema shapes* for +> implementers, not a wired runtime capability. ### Partial Masking @@ -374,7 +382,15 @@ publicSharing: ## 6. Field-Level Encryption -Sensitive fields can be encrypted at rest via `encryptionConfig` (`EncryptionConfigSchema` in `packages/spec/src/system/encryption.zod.ts`). Supported algorithms are `aes-256-gcm` (default), `aes-256-cbc`, and `chacha20-poly1305`. Keys are held by a key-management provider (`local`, `aws-kms`, `azure-key-vault`, `gcp-kms`, `hashicorp-vault`). +> **Status: schema exists, field wiring removed.** `EncryptionConfigSchema` +> (`packages/spec/src/system/encryption.zod.ts`, algorithms `aes-256-gcm` +> default / `aes-256-cbc` / `chacha20-poly1305`; key providers `local`, +> `aws-kms`, `azure-key-vault`, `gcp-kms`, `hashicorp-vault`) remains in the +> System namespace, but the per-field `encryptionConfig` property was **pruned +> from the Field schema in 2026-06** — at-rest field encryption was never +> implemented by the runtime. The supported channel for secret values is a +> `type: 'secret'` field (one-way handling via the settings crypto provider). +> The example below shows the *schema shape* for implementers. ```yaml fields: @@ -440,7 +456,7 @@ rowLevelSecurity: ### Defense in Depth -Combine OWD (`sharingModel: private`), an RLS `using` policy, FLS (`readable: false`) on sensitive fields, and `encryptionConfig` for data at rest. +Combine OWD (`sharingModel: private`), an RLS `using` policy, FLS (`readable: false`) on sensitive fields, and `type: 'secret'` for values that must never round-trip to clients. ### Security by Default diff --git a/content/docs/protocol/objectql/types.mdx b/content/docs/protocol/objectql/types.mdx index 25dfebddea..fff208d5a6 100644 --- a/content/docs/protocol/objectql/types.mdx +++ b/content/docs/protocol/objectql/types.mdx @@ -936,32 +936,23 @@ How types convert between databases: ## Sensitive Data: Masking & Encryption -There is no user-defined "custom type" registry. Instead, sensitive-data behavior -is configured directly on a field via the `maskingRule` and `encryptionConfig` -properties (and the `secret` type for reversible encrypted-at-rest values). +There is no user-defined "custom type" registry. For sensitive values, use the +dedicated `secret` field type, which encrypts on write and masks on read, and +restrict reader access with [field-level security](/docs/permissions/field-level-security) +(`readable: false`) or `hidden`. ```yaml -# PII masking — show only the last 4 digits to most roles -ssn: - type: text - label: Social Security Number - maskingRule: - field: ssn - strategy: partial - pattern: "\\d{3}-\\d{2}-(\\d{4})" - exemptRoles: [compliance_officer] - -# Field-level encryption (PCI/HIPAA/GDPR) -card_number: - type: text - label: Credit Card - encryptionConfig: - enabled: true - algorithm: aes-256-gcm +# Reversible secret (API keys, DB passwords) +api_key: + type: secret + label: API Key ``` -For reversible secrets (API keys, DB passwords) use the dedicated `secret` field -type, which encrypts on write and masks on read. +> The per-field `maskingRule` and `encryptionConfig` properties were **pruned +> from the Field schema in 2026-06** — they were declared surface with no +> runtime consumer. The `MaskingRuleSchema`/`EncryptionConfigSchema` shapes +> remain in the System namespace for implementers; see +> [Security & Access Control](/docs/protocol/objectql/security) for their status. ## Type Selection Guide diff --git a/content/docs/protocol/objectui/concept.mdx b/content/docs/protocol/objectui/concept.mdx index 02fc7dddc4..30443ae1e3 100644 --- a/content/docs/protocol/objectui/concept.mdx +++ b/content/docs/protocol/objectui/concept.mdx @@ -721,6 +721,6 @@ function renderField(field: FieldDefinition) { ### For Business Users -- [Page Builder](/docs/references/studio/page-builder) - Visual page & form designer +- [Page API](/docs/references/ui/page) - Page & form composition reference - [Object Designer](/docs/references/studio/object-designer) - Model objects and fields - [Flow Builder](/docs/references/studio/flow-builder) - Build automation flows diff --git a/content/docs/releases/implementation-status.mdx b/content/docs/releases/implementation-status.mdx index 91e41a9db4..119eefcbd4 100644 --- a/content/docs/releases/implementation-status.mdx +++ b/content/docs/releases/implementation-status.mdx @@ -515,22 +515,22 @@ Implementation spans every layer of the platform. Core infrastructure, data mode diff --git a/content/docs/ui/apps.mdx b/content/docs/ui/apps.mdx index 126c14b83f..32ee9cd3c8 100644 --- a/content/docs/ui/apps.mdx +++ b/content/docs/ui/apps.mdx @@ -50,8 +50,8 @@ const crmApp = { | `branding` | `AppBranding` | optional | Visual customization | | `requiredPermissions` | `string[]` | optional | Required permissions to access | | `homePageId` | `string` | optional | ID of the navigation item to serve as the landing page | -| `objects` | `string[]` | optional | Objects included in this app | -| `apis` | `string[]` | optional | API endpoints included | +| `objects` | `unknown[]` | optional | Objects belonging to this app | +| `apis` | `unknown[]` | optional | Custom APIs belonging to this app | | `mobileNavigation` | `object` | optional | Mobile-specific navigation | ## Navigation Items diff --git a/content/docs/ui/dashboards.mdx b/content/docs/ui/dashboards.mdx index c22c9c47e9..d2347cdace 100644 --- a/content/docs/ui/dashboards.mdx +++ b/content/docs/ui/dashboards.mdx @@ -100,7 +100,7 @@ selects `dimensions` (X / group / split) and `values` (the measures to plot): | `title` | `string` | optional | Widget display title | | `description` | `string` | optional | Text shown below the title | | `filter` | `FilterCondition` | optional | Presentation-scope filter (`runtimeFilter`) | -| `layout` | `object` | ✅ | Grid position and size | +| `layout` | `object` | optional | Grid position and size (auto-flowed into the grid when omitted) | | `chartConfig` | `object` | optional | Advanced chart configuration | | `colorVariant` | `enum` | optional | KPI/card accent color | | `compareTo` | `enum \| object` | optional | Period-over-period comparison window | diff --git a/content/docs/ui/pages.mdx b/content/docs/ui/pages.mdx index 832e68be21..45c0dd189d 100644 --- a/content/docs/ui/pages.mdx +++ b/content/docs/ui/pages.mdx @@ -62,7 +62,7 @@ const homePage = { | `type` | `enum` | optional | Page type (see below; default `'record'`) | | `object` | `string` | optional | Associated object (for `record` type) | | `template` | `string` | optional | Layout template name (default: `'default'`) | -| `regions` | `PageRegion[]` | ✅ | Layout regions with components | +| `regions` | `PageRegion[]` | optional | Layout regions with components (default `[]` — `list` pages render via `interfaceConfig`, and an empty `record`/`home`/`app` page falls back to the synthesized default layout) | | `variables` | `PageVariable[]` | optional | Local state variables | | `isDefault` | `boolean` | optional | Is default page for its type | | `assignedProfiles` | `string[]` | optional | Profiles that can access this page | @@ -77,7 +77,7 @@ const homePage = { | `utility` | Utility/helper panel | Tools, settings, wizards | | `list` | Record list/interface surface | Data-driven interface pages | -The schema also declares roadmap types (`dashboard`, `form`, `record_detail`, `record_review`, `overview`, `blank`) that currently fall back to the list renderer; only the five types above have dedicated renderers today. +Earlier roadmap types (`dashboard`, `form`, `record_detail`, `record_review`, `overview`, `blank`) were **removed from the schema** because they never shipped a renderer (ADR-0049 enforce-or-remove); only the five types above are valid. ## Regions @@ -132,7 +132,7 @@ Components are the building blocks placed inside regions. | `type` | `string` | ✅ | Component type (standard `PageComponentType` enum or custom string) | | `id` | `string` | optional | Unique component instance identifier | | `label` | `string` | optional | Display label | -| `properties` | `Record` | ✅ | Component-specific configuration | +| `properties` | `Record` | optional | Component-specific configuration (default `{}` — many components carry no props) | | `events` | `Record` | optional | Event handlers (action expressions) | | `style` | `object` | optional | CSS styles | | `className` | `string` | optional | CSS class names | @@ -146,7 +146,7 @@ The `type` field is a union of the standard `PageComponentType` enum and any cus - **Navigation:** `app:launcher`, `nav:menu`, `nav:breadcrumb` - **Utility:** `global:search`, `global:notifications`, `user:profile` - **AI:** `ai:chat_window`, `ai:suggestion` -- **Elements:** `element:text`, `element:number`, `element:image`, `element:divider`, `element:button`, `element:filter`, `element:form`, `element:record_picker` +- **Elements:** `element:text`, `element:number`, `element:image`, `element:divider`, `element:button`, `element:filter`, `element:form`, `element:record_picker`, `element:text_input` Components may also carry `dataSource` (per-element object binding for multi-object pages), `responsive`, and `aria` configuration. Custom string types are also accepted for project-specific widgets. @@ -188,7 +188,7 @@ const accountRecordPage = { width: 'full', components: [ { - type: 'record_header', + type: 'record:highlights', id: 'header', properties: { fields: ['name', 'type', 'industry', 'owner'], @@ -202,7 +202,7 @@ const accountRecordPage = { width: 'small', components: [ { - type: 'record_detail', + type: 'record:details', id: 'key_fields', label: 'Key Fields', properties: { @@ -210,7 +210,7 @@ const accountRecordPage = { }, }, { - type: 'activity_timeline', + type: 'record:activity', id: 'activities', label: 'Activity', }, @@ -221,13 +221,13 @@ const accountRecordPage = { width: 'large', components: [ { - type: 'related_list', + type: 'record:related_list', id: 'contacts', label: 'Contacts', properties: { object: 'contact', relationship: 'account' }, }, { - type: 'related_list', + type: 'record:related_list', id: 'opportunities', label: 'Opportunities', properties: { object: 'opportunity', relationship: 'account' }, diff --git a/content/docs/ui/views.mdx b/content/docs/ui/views.mdx index 71ea1e4c1c..0e65c54c59 100644 --- a/content/docs/ui/views.mdx +++ b/content/docs/ui/views.mdx @@ -45,6 +45,7 @@ const taskListView = { | `gantt` | Gantt chart with dependencies | Project management | | `map` | Geographic map pins | Location-based data | | `chart` | Aggregate chart visualization | Dashboards and summaries | +| `tree` | Self-referencing hierarchy (tree-grid) | Org charts, category trees | ### List View Properties @@ -52,8 +53,8 @@ const taskListView = { | :--- | :--- | :--- | :--- | | `name` | `string` | ✅ | Machine name (`snake_case`) | | `label` | `string` | ✅ | Display label | -| `type` | `enum` | ✅ | View type (see table above) | -| `data` | `ViewData` | ✅ | Data source configuration | +| `type` | `enum` | optional | View type (see table above; default `'grid'`) | +| `data` | `ViewData` | optional | Data source configuration (defaults to the `object` provider) | | `columns` | `ListColumn[]` | optional | Column definitions | | `filter` | `array` | optional | Filter criteria | | `sort` | `array` | optional | Sort configuration |