@treenity/react 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AclEditor.d.ts +11 -0
- package/dist/AclEditor.d.ts.map +1 -0
- package/dist/AclEditor.js +152 -0
- package/dist/AclEditor.js.map +1 -0
- package/dist/App.d.ts +2 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/App.js +521 -0
- package/dist/App.js.map +1 -0
- package/dist/Inspector.d.ts +12 -0
- package/dist/Inspector.d.ts.map +1 -0
- package/dist/Inspector.js +360 -0
- package/dist/Inspector.js.map +1 -0
- package/dist/Tree.d.ts +16 -0
- package/dist/Tree.d.ts.map +1 -0
- package/dist/Tree.js +100 -0
- package/dist/Tree.js.map +1 -0
- package/dist/ViewPage.d.ts +5 -0
- package/dist/ViewPage.d.ts.map +1 -0
- package/dist/ViewPage.js +13 -0
- package/dist/ViewPage.js.map +1 -0
- package/dist/bind/computed.d.ts +9 -0
- package/dist/bind/computed.d.ts.map +1 -0
- package/dist/bind/computed.js +61 -0
- package/dist/bind/computed.js.map +1 -0
- package/dist/bind/engine.d.ts +3 -0
- package/dist/bind/engine.d.ts.map +1 -0
- package/dist/bind/engine.js +184 -0
- package/dist/bind/engine.js.map +1 -0
- package/dist/bind/eval.d.ts +13 -0
- package/dist/bind/eval.d.ts.map +1 -0
- package/dist/bind/eval.js +97 -0
- package/dist/bind/eval.js.map +1 -0
- package/dist/bind/hook.d.ts +8 -0
- package/dist/bind/hook.d.ts.map +1 -0
- package/dist/bind/hook.js +99 -0
- package/dist/bind/hook.js.map +1 -0
- package/dist/bind/parse.d.ts +19 -0
- package/dist/bind/parse.d.ts.map +1 -0
- package/dist/bind/parse.js +86 -0
- package/dist/bind/parse.js.map +1 -0
- package/dist/bind/pipes.d.ts +4 -0
- package/dist/bind/pipes.d.ts.map +1 -0
- package/dist/bind/pipes.js +43 -0
- package/dist/bind/pipes.js.map +1 -0
- package/dist/cache.d.ts +27 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +236 -0
- package/dist/cache.js.map +1 -0
- package/dist/client-tree.d.ts +9 -0
- package/dist/client-tree.d.ts.map +1 -0
- package/dist/client-tree.js +14 -0
- package/dist/client-tree.js.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +10 -0
- package/dist/client.js.map +1 -0
- package/dist/components/ui/accordion.d.ts +8 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +18 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/badge.d.ts +10 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +19 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/button.d.ts +11 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +31 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +7 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +18 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +37 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +14 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/drawer.js +35 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/input.d.ts +4 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +7 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +8 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/popover.d.ts +11 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +26 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/progress.d.ts +5 -0
- package/dist/components/ui/progress.d.ts.map +1 -0
- package/dist/components/ui/progress.js +9 -0
- package/dist/components/ui/progress.js.map +1 -0
- package/dist/components/ui/select.d.ts +16 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +39 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/slider.d.ts +5 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/slider.js +15 -0
- package/dist/components/ui/slider.js.map +1 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/sonner.js +21 -0
- package/dist/components/ui/sonner.js.map +1 -0
- package/dist/components/ui/switch.d.ts +7 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +9 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +4 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +7 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +8 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +18 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/context/index.d.ts +31 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +98 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/hooks.d.ts +21 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +156 -0
- package/dist/hooks.js.map +1 -0
- package/dist/idb.d.ts +13 -0
- package/dist/idb.d.ts.map +1 -0
- package/dist/idb.js +67 -0
- package/dist/idb.js.map +1 -0
- package/dist/lib/minimd.d.ts +3 -0
- package/dist/lib/minimd.d.ts.map +1 -0
- package/dist/lib/minimd.js +97 -0
- package/dist/lib/minimd.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/load-client.d.ts +2 -0
- package/dist/load-client.d.ts.map +1 -0
- package/dist/load-client.js +6 -0
- package/dist/load-client.js.map +1 -0
- package/dist/main.d.ts +4 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +16 -0
- package/dist/main.js.map +1 -0
- package/dist/mods/editor-ui/client.d.ts +6 -0
- package/dist/mods/editor-ui/client.d.ts.map +1 -0
- package/dist/mods/editor-ui/client.js +8 -0
- package/dist/mods/editor-ui/client.js.map +1 -0
- package/dist/mods/editor-ui/default-view.d.ts +2 -0
- package/dist/mods/editor-ui/default-view.d.ts.map +1 -0
- package/dist/mods/editor-ui/default-view.js +71 -0
- package/dist/mods/editor-ui/default-view.js.map +1 -0
- package/dist/mods/editor-ui/dir-view.d.ts +2 -0
- package/dist/mods/editor-ui/dir-view.d.ts.map +1 -0
- package/dist/mods/editor-ui/dir-view.js +42 -0
- package/dist/mods/editor-ui/dir-view.js.map +1 -0
- package/dist/mods/editor-ui/form-fields.d.ts +6 -0
- package/dist/mods/editor-ui/form-fields.d.ts.map +1 -0
- package/dist/mods/editor-ui/form-fields.js +401 -0
- package/dist/mods/editor-ui/form-fields.js.map +1 -0
- package/dist/mods/editor-ui/layout-view.d.ts +2 -0
- package/dist/mods/editor-ui/layout-view.d.ts.map +1 -0
- package/dist/mods/editor-ui/layout-view.js +22 -0
- package/dist/mods/editor-ui/layout-view.js.map +1 -0
- package/dist/mods/editor-ui/list-items.d.ts +2 -0
- package/dist/mods/editor-ui/list-items.d.ts.map +1 -0
- package/dist/mods/editor-ui/list-items.js +38 -0
- package/dist/mods/editor-ui/list-items.js.map +1 -0
- package/dist/mods/editor-ui/node-utils.d.ts +10 -0
- package/dist/mods/editor-ui/node-utils.d.ts.map +1 -0
- package/dist/mods/editor-ui/node-utils.js +76 -0
- package/dist/mods/editor-ui/node-utils.js.map +1 -0
- package/dist/mods/editor-ui/user-view.d.ts +2 -0
- package/dist/mods/editor-ui/user-view.d.ts.map +1 -0
- package/dist/mods/editor-ui/user-view.js +47 -0
- package/dist/mods/editor-ui/user-view.js.map +1 -0
- package/dist/mods/treenity/client.d.ts +4 -0
- package/dist/mods/treenity/client.d.ts.map +1 -0
- package/dist/mods/treenity/client.js +6 -0
- package/dist/mods/treenity/client.js.map +1 -0
- package/dist/mods/treenity/groups/index.d.ts +2 -0
- package/dist/mods/treenity/groups/index.d.ts.map +1 -0
- package/dist/mods/treenity/groups/index.js +27 -0
- package/dist/mods/treenity/groups/index.js.map +1 -0
- package/dist/mods/treenity/preview.d.ts +6 -0
- package/dist/mods/treenity/preview.d.ts.map +1 -0
- package/dist/mods/treenity/preview.js +95 -0
- package/dist/mods/treenity/preview.js.map +1 -0
- package/dist/mods/treenity/ref-view.d.ts +2 -0
- package/dist/mods/treenity/ref-view.d.ts.map +1 -0
- package/dist/mods/treenity/ref-view.js +29 -0
- package/dist/mods/treenity/ref-view.js.map +1 -0
- package/dist/mods/treenity/schema-form.d.ts +2 -0
- package/dist/mods/treenity/schema-form.d.ts.map +1 -0
- package/dist/mods/treenity/schema-form.js +38 -0
- package/dist/mods/treenity/schema-form.js.map +1 -0
- package/dist/mods/treenity/seed.d.ts +2 -0
- package/dist/mods/treenity/seed.d.ts.map +1 -0
- package/dist/mods/treenity/seed.js +53 -0
- package/dist/mods/treenity/seed.js.map +1 -0
- package/dist/mods/treenity/server.d.ts +2 -0
- package/dist/mods/treenity/server.d.ts.map +1 -0
- package/dist/mods/treenity/server.js +2 -0
- package/dist/mods/treenity/server.js.map +1 -0
- package/dist/mods/treenity/type-view.d.ts +2 -0
- package/dist/mods/treenity/type-view.d.ts.map +1 -0
- package/dist/mods/treenity/type-view.js +36 -0
- package/dist/mods/treenity/type-view.js.map +1 -0
- package/dist/remote-tree.d.ts +6 -0
- package/dist/remote-tree.d.ts.map +1 -0
- package/dist/remote-tree.js +18 -0
- package/dist/remote-tree.js.map +1 -0
- package/dist/schema-loader.d.ts +19 -0
- package/dist/schema-loader.d.ts.map +1 -0
- package/dist/schema-loader.js +63 -0
- package/dist/schema-loader.js.map +1 -0
- package/dist/trpc.d.ts +187 -0
- package/dist/trpc.d.ts.map +1 -0
- package/dist/trpc.js +21 -0
- package/dist/trpc.js.map +1 -0
- package/package.json +88 -0
- package/src/AclEditor.tsx +330 -0
- package/src/App.tsx +775 -0
- package/src/CLAUDE.md +16 -0
- package/src/Inspector.tsx +857 -0
- package/src/Tree.tsx +237 -0
- package/src/ViewPage.tsx +45 -0
- package/src/bind/bind.test.ts +316 -0
- package/src/bind/computed.ts +64 -0
- package/src/bind/engine.ts +198 -0
- package/src/bind/eval.ts +108 -0
- package/src/bind/hook.ts +112 -0
- package/src/bind/parse.ts +104 -0
- package/src/bind/pipes.ts +71 -0
- package/src/cache.test.ts +139 -0
- package/src/cache.ts +244 -0
- package/src/client-tree.test.ts +116 -0
- package/src/client-tree.ts +24 -0
- package/src/client.ts +11 -0
- package/src/components/ui/accordion.tsx +63 -0
- package/src/components/ui/badge.tsx +27 -0
- package/src/components/ui/button.tsx +44 -0
- package/src/components/ui/checkbox.tsx +19 -0
- package/src/components/ui/dialog.tsx +156 -0
- package/src/components/ui/drawer.tsx +132 -0
- package/src/components/ui/input.tsx +19 -0
- package/src/components/ui/label.tsx +21 -0
- package/src/components/ui/popover.tsx +86 -0
- package/src/components/ui/progress.tsx +30 -0
- package/src/components/ui/select.tsx +189 -0
- package/src/components/ui/slider.tsx +62 -0
- package/src/components/ui/sonner.tsx +32 -0
- package/src/components/ui/switch.tsx +34 -0
- package/src/components/ui/textarea.tsx +17 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/context/index.tsx +131 -0
- package/src/context.ts +1 -0
- package/src/hooks.ts +208 -0
- package/src/idb.ts +80 -0
- package/src/index.html +14 -0
- package/src/lib/minimd.css +28 -0
- package/src/lib/minimd.ts +95 -0
- package/src/lib/utils.ts +6 -0
- package/src/load-client.ts +5 -0
- package/src/main.tsx +22 -0
- package/src/mods/editor-ui/CLAUDE.md +3 -0
- package/src/mods/editor-ui/client.ts +8 -0
- package/src/mods/editor-ui/default-view.tsx +148 -0
- package/src/mods/editor-ui/dir-view.tsx +91 -0
- package/src/mods/editor-ui/form-fields.tsx +861 -0
- package/src/mods/editor-ui/layout-view.tsx +62 -0
- package/src/mods/editor-ui/list-items.tsx +63 -0
- package/src/mods/editor-ui/node-utils.ts +84 -0
- package/src/mods/editor-ui/user-view.tsx +101 -0
- package/src/mods/treenity/CLAUDE.md +7 -0
- package/src/mods/treenity/client.ts +6 -0
- package/src/mods/treenity/groups/index.tsx +65 -0
- package/src/mods/treenity/preview.tsx +133 -0
- package/src/mods/treenity/ref-view.tsx +87 -0
- package/src/mods/treenity/schema-form.tsx +65 -0
- package/src/mods/treenity/seed.ts +56 -0
- package/src/mods/treenity/server.ts +1 -0
- package/src/mods/treenity/type-view.tsx +116 -0
- package/src/remote-tree.test.ts +142 -0
- package/src/remote-tree.ts +25 -0
- package/src/schema-loader.ts +84 -0
- package/src/style.css +1269 -0
- package/src/trpc.ts +27 -0
- package/src/vite-env.d.ts +3 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type NodeData, R, S } from '@treenity/core/core';
|
|
2
|
+
import { registerPrefab } from '@treenity/core/mod';
|
|
3
|
+
|
|
4
|
+
registerPrefab('core', 'seed', [
|
|
5
|
+
{ $path: 'sys', $type: 'treenity.system' },
|
|
6
|
+
{ $path: 'auth', $type: 'dir' },
|
|
7
|
+
{ $path: 'auth/users', $type: 'mount-point',
|
|
8
|
+
connection: { $type: 'connection', db: 'treenity', collection: 'users' },
|
|
9
|
+
mount: { $type: 't.mount.mongo' },
|
|
10
|
+
$acl: [{ g: 'authenticated', p: R | S }, { g: 'public', p: 0 }],
|
|
11
|
+
},
|
|
12
|
+
{ $path: 'sys/types', $type: 'mount-point',
|
|
13
|
+
mount: { $type: 't.mount.types' },
|
|
14
|
+
$acl: [{ g: 'public', p: R }],
|
|
15
|
+
},
|
|
16
|
+
{ $path: 'sys/mods', $type: 'mount-point',
|
|
17
|
+
mount: { $type: 't.mount.mods' },
|
|
18
|
+
$acl: [{ g: 'public', p: R }],
|
|
19
|
+
},
|
|
20
|
+
{ $path: 'auth/sessions', $type: 'mount-point',
|
|
21
|
+
connection: { $type: 'connection', db: 'treenity', collection: 'sessions' },
|
|
22
|
+
mount: { $type: 't.mount.mongo' },
|
|
23
|
+
},
|
|
24
|
+
{ $path: 'mnt', $type: 'dir' },
|
|
25
|
+
{ $path: 'mnt/orders', $type: 't.mount.mongo',
|
|
26
|
+
connection: { $type: 'connection', db: 'treenity', collection: 'orders' },
|
|
27
|
+
},
|
|
28
|
+
{ $path: 'entities', $type: 'dir' },
|
|
29
|
+
{ $path: 'demo', $type: 'dir',
|
|
30
|
+
metadata: { $type: 'metadata', title: 'Demo Node', description: 'Try calling actions' },
|
|
31
|
+
status: { $type: 'status', value: 'draft' },
|
|
32
|
+
counter: { $type: 'counter', count: 0 },
|
|
33
|
+
},
|
|
34
|
+
{ $path: 'llm', $type: 't.llm' },
|
|
35
|
+
{ $path: 'sys/mcp', $type: 'mcp.server', port: 0 },
|
|
36
|
+
{ $path: 'demo/sensors', $type: 'examples.demo.sensor',
|
|
37
|
+
mount: { $type: 't.mount.memory' },
|
|
38
|
+
},
|
|
39
|
+
{ $path: 'sys/claude-search', $type: 'claude-search' },
|
|
40
|
+
{ $path: 'proc', $type: 'mount-point',
|
|
41
|
+
mount: { $type: 't.mount.memory' },
|
|
42
|
+
$acl: [{ g: 'public', p: R }],
|
|
43
|
+
},
|
|
44
|
+
{ $path: 'sys/autostart', $type: 'autostart' },
|
|
45
|
+
{ $path: '/sys/autostart/mcp', $type: 'ref', $ref: '/sys/mcp' },
|
|
46
|
+
{ $path: '/sys/autostart/claude-search', $type: 'ref', $ref: '/sys/claude-search' },
|
|
47
|
+
{ $path: '/sys/autostart/sensors', $type: 'ref', $ref: '/demo/sensors' },
|
|
48
|
+
] as NodeData[], (nodes) => {
|
|
49
|
+
// MCP port from env
|
|
50
|
+
const mcpPort = Number(process.env.MCP_PORT) || 3212;
|
|
51
|
+
let result = nodes.map(n =>
|
|
52
|
+
n.$path === 'sys/mcp' ? { ...n, port: mcpPort } : n,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}, { tier: 'core' });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './seed';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Type node views — react + react:list + live preview
|
|
2
|
+
|
|
3
|
+
import { getContextsForType, type NodeData, register } from '@treenity/core/core';
|
|
4
|
+
import { TypePreview } from './preview';
|
|
5
|
+
|
|
6
|
+
// ── Helpers ──
|
|
7
|
+
|
|
8
|
+
function typeName(path: string) {
|
|
9
|
+
return path.replace(/^\/sys\/types\//, '').replace(/\//g, '.');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function SectionLabel({ children }: { children: React.ReactNode }) {
|
|
13
|
+
return <div className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider mb-2">{children}</div>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ── react (full view with live preview) ──
|
|
17
|
+
|
|
18
|
+
function TypeNodeView({ value }: { value: NodeData }) {
|
|
19
|
+
const name = typeName(value.$path);
|
|
20
|
+
const schema = value.schema as Record<string, unknown> | undefined;
|
|
21
|
+
const title = schema?.title ? String(schema.title) : null;
|
|
22
|
+
const props = (schema?.properties ?? {}) as Record<string, Record<string, unknown>>;
|
|
23
|
+
const contexts = getContextsForType(name);
|
|
24
|
+
const components = Object.keys(value).filter(k => !k.startsWith('$') && k !== 'schema');
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="max-w-xl space-y-4">
|
|
28
|
+
{title && <div className="text-lg font-semibold">{title}</div>}
|
|
29
|
+
|
|
30
|
+
{Object.keys(props).length > 0 && (
|
|
31
|
+
<div>
|
|
32
|
+
<SectionLabel>Properties</SectionLabel>
|
|
33
|
+
<div className="flex flex-col gap-1">
|
|
34
|
+
{Object.entries(props).map(([k, v]) => (
|
|
35
|
+
<div key={k} className="flex gap-2 text-sm px-2 py-1 bg-muted rounded">
|
|
36
|
+
<span className="font-mono text-primary">{k}</span>
|
|
37
|
+
<span className="text-muted-foreground">{String(v.type ?? 'string')}</span>
|
|
38
|
+
{typeof v.label === 'string' && <span className="text-muted-foreground ml-auto">{v.label}</span>}
|
|
39
|
+
</div>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
|
|
45
|
+
{components.length > 0 && (
|
|
46
|
+
<div>
|
|
47
|
+
<SectionLabel>Components</SectionLabel>
|
|
48
|
+
<div className="flex flex-wrap gap-1.5">
|
|
49
|
+
{components.map(c => (
|
|
50
|
+
<span key={c} className="px-2.5 py-0.5 rounded-full text-xs font-mono bg-muted border border-border text-muted-foreground">{c}</span>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
{contexts.length > 0 && (
|
|
57
|
+
<div>
|
|
58
|
+
<SectionLabel>Contexts</SectionLabel>
|
|
59
|
+
<div className="flex flex-wrap gap-1.5">
|
|
60
|
+
{contexts.map(c => (
|
|
61
|
+
<span key={c} className="px-2.5 py-0.5 rounded-full text-xs font-mono bg-muted border border-border text-muted-foreground">{c}</span>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
<div>
|
|
68
|
+
<SectionLabel>Preview</SectionLabel>
|
|
69
|
+
<TypePreview key={name} typeName={name} properties={props} />
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── react:list (compact) ──
|
|
76
|
+
|
|
77
|
+
function TypeListItem({ value }: { value: NodeData }) {
|
|
78
|
+
const name = typeName(value.$path);
|
|
79
|
+
const schema = value.schema as Record<string, unknown> | undefined;
|
|
80
|
+
const title = schema?.title ? String(schema.title) : null;
|
|
81
|
+
const description = schema?.description ? String(schema.description) : null;
|
|
82
|
+
const props = Object.keys((schema?.properties ?? {}) as object);
|
|
83
|
+
const methods = Object.keys((schema?.methods ?? {}) as object);
|
|
84
|
+
const hasClass = !!value.class;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="flex-1 py-1">
|
|
88
|
+
<div className="flex items-center gap-2">
|
|
89
|
+
<span className="font-semibold text-sm">{name}</span>
|
|
90
|
+
{hasClass && (
|
|
91
|
+
<span className="text-[10px] px-1.5 py-px rounded bg-primary/10 text-primary">class</span>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
{(title || description) && (
|
|
95
|
+
<div className="text-xs text-muted-foreground mt-0.5">
|
|
96
|
+
{title}{title && description ? ' — ' : ''}{description}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
{(props.length > 0 || methods.length > 0) && (
|
|
100
|
+
<div className="flex gap-1 mt-1 flex-wrap">
|
|
101
|
+
{props.map(p => (
|
|
102
|
+
<span key={p} className="text-[11px] px-1.5 py-px rounded bg-muted text-muted-foreground font-mono">{p}</span>
|
|
103
|
+
))}
|
|
104
|
+
{methods.map(m => (
|
|
105
|
+
<span key={m} className="text-[11px] px-1.5 py-px rounded bg-muted text-primary font-mono">{m}()</span>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Registration ──
|
|
114
|
+
|
|
115
|
+
register('type', 'react', TypeNodeView as any);
|
|
116
|
+
register('type', 'react:list', TypeListItem as any);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { NodeData } from '@treenity/core/core';
|
|
2
|
+
import { withCache } from '@treenity/core/tree/cache';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { describe, it } from 'node:test';
|
|
5
|
+
import { createRemoteTree } from './remote-tree';
|
|
6
|
+
|
|
7
|
+
// ── Mock tRPC client ──
|
|
8
|
+
|
|
9
|
+
function createMockTrpc(backing: Map<string, NodeData>) {
|
|
10
|
+
let getCalls = 0;
|
|
11
|
+
|
|
12
|
+
const mock = {
|
|
13
|
+
get getCalls() { return getCalls; },
|
|
14
|
+
resetCalls() { getCalls = 0; },
|
|
15
|
+
|
|
16
|
+
get: {
|
|
17
|
+
query: async ({ path }: { path: string }) => {
|
|
18
|
+
getCalls++;
|
|
19
|
+
return backing.get(path);
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
getChildren: {
|
|
23
|
+
query: async ({ path, limit, offset }: { path: string; limit?: number; offset?: number }) => {
|
|
24
|
+
getCalls++;
|
|
25
|
+
const prefix = path === '/' ? '/' : path + '/';
|
|
26
|
+
const items = [...backing.values()].filter(
|
|
27
|
+
n => n.$path.startsWith(prefix) && n.$path !== path
|
|
28
|
+
&& n.$path.slice(prefix.length).indexOf('/') === -1,
|
|
29
|
+
);
|
|
30
|
+
const start = offset ?? 0;
|
|
31
|
+
const sliced = limit ? items.slice(start, start + limit) : items.slice(start);
|
|
32
|
+
return { items: sliced, total: items.length };
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
set: {
|
|
36
|
+
mutate: async ({ node }: { node: Record<string, unknown> }) => {
|
|
37
|
+
backing.set(node.$path as string, node as NodeData);
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
remove: {
|
|
41
|
+
mutate: async ({ path }: { path: string }) => {
|
|
42
|
+
backing.delete(path);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return mock;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Tests ──
|
|
51
|
+
|
|
52
|
+
describe('createRemoteTree — method mapping', () => {
|
|
53
|
+
it('get delegates to trpc.get.query', async () => {
|
|
54
|
+
const data = new Map<string, NodeData>();
|
|
55
|
+
data.set('/a', { $path: '/a', $type: 'test', v: 1 } as NodeData);
|
|
56
|
+
const mock = createMockTrpc(data);
|
|
57
|
+
const store = createRemoteTree(mock as any);
|
|
58
|
+
|
|
59
|
+
const node = await store.get('/a');
|
|
60
|
+
assert.equal(node?.$path, '/a');
|
|
61
|
+
assert.equal((node as any).v, 1);
|
|
62
|
+
assert.equal(mock.getCalls, 1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('get returns undefined for missing path', async () => {
|
|
66
|
+
const store = createRemoteTree(createMockTrpc(new Map()) as any);
|
|
67
|
+
const node = await store.get('/missing');
|
|
68
|
+
assert.equal(node, undefined);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('getChildren delegates to trpc.getChildren.query', async () => {
|
|
72
|
+
const data = new Map<string, NodeData>();
|
|
73
|
+
data.set('/p', { $path: '/p', $type: 'dir' } as NodeData);
|
|
74
|
+
data.set('/p/a', { $path: '/p/a', $type: 'test' } as NodeData);
|
|
75
|
+
data.set('/p/b', { $path: '/p/b', $type: 'test' } as NodeData);
|
|
76
|
+
const store = createRemoteTree(createMockTrpc(data) as any);
|
|
77
|
+
|
|
78
|
+
const result = await store.getChildren('/p');
|
|
79
|
+
assert.equal(result.items.length, 2);
|
|
80
|
+
assert.equal(result.total, 2);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('set delegates to trpc.set.mutate', async () => {
|
|
84
|
+
const data = new Map<string, NodeData>();
|
|
85
|
+
const store = createRemoteTree(createMockTrpc(data) as any);
|
|
86
|
+
|
|
87
|
+
await store.set({ $path: '/x', $type: 'test', v: 42 } as NodeData);
|
|
88
|
+
assert.ok(data.has('/x'));
|
|
89
|
+
assert.equal((data.get('/x') as any).v, 42);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('remove delegates to trpc.remove.mutate', async () => {
|
|
93
|
+
const data = new Map<string, NodeData>();
|
|
94
|
+
data.set('/x', { $path: '/x', $type: 'test' } as NodeData);
|
|
95
|
+
const store = createRemoteTree(createMockTrpc(data) as any);
|
|
96
|
+
|
|
97
|
+
const result = await store.remove('/x');
|
|
98
|
+
assert.equal(result, true);
|
|
99
|
+
assert.ok(!data.has('/x'));
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('withCache(remoteStore) — client pipeline', () => {
|
|
104
|
+
it('caches get results — second call skips tRPC', async () => {
|
|
105
|
+
const data = new Map<string, NodeData>();
|
|
106
|
+
data.set('/a', { $path: '/a', $type: 'test' } as NodeData);
|
|
107
|
+
const mock = createMockTrpc(data);
|
|
108
|
+
const store = withCache(createRemoteTree(mock as any));
|
|
109
|
+
|
|
110
|
+
await store.get('/a');
|
|
111
|
+
mock.resetCalls();
|
|
112
|
+
await store.get('/a'); // should hit cache
|
|
113
|
+
assert.equal(mock.getCalls, 0);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('write-populate: set warms cache for next get', async () => {
|
|
117
|
+
const data = new Map<string, NodeData>();
|
|
118
|
+
const mock = createMockTrpc(data);
|
|
119
|
+
const store = withCache(createRemoteTree(mock as any));
|
|
120
|
+
|
|
121
|
+
await store.set({ $path: '/a', $type: 'test', v: 1 } as NodeData);
|
|
122
|
+
mock.resetCalls();
|
|
123
|
+
|
|
124
|
+
const node = await store.get('/a'); // should hit cache (write-populated)
|
|
125
|
+
assert.equal(mock.getCalls, 0);
|
|
126
|
+
assert.equal((node as any).v, 1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('inflight dedup: concurrent gets produce single tRPC call', async () => {
|
|
130
|
+
const data = new Map<string, NodeData>();
|
|
131
|
+
data.set('/a', { $path: '/a', $type: 'test', v: 99 } as NodeData);
|
|
132
|
+
const mock = createMockTrpc(data);
|
|
133
|
+
const store = withCache(createRemoteTree(mock as any));
|
|
134
|
+
|
|
135
|
+
const results = await Promise.all(
|
|
136
|
+
Array.from({ length: 5 }, () => store.get('/a')),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
assert.equal(mock.getCalls, 1);
|
|
140
|
+
for (const r of results) assert.equal((r as any).v, 99);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Remote Tree — Tree adapter over tRPC client.
|
|
2
|
+
// Maps the 4 Tree methods to tRPC query/mutation calls.
|
|
3
|
+
// Enables client to use the same combinators as server:
|
|
4
|
+
// withSubscriptions(withCache(createRemoteTree(trpc)))
|
|
5
|
+
|
|
6
|
+
import type { NodeData } from '@treenity/core/core';
|
|
7
|
+
import type { Tree } from '@treenity/core/tree';
|
|
8
|
+
import { defaultPatch } from '@treenity/core/tree/patch';
|
|
9
|
+
import type { trpc } from './trpc';
|
|
10
|
+
|
|
11
|
+
type TrpcClient = typeof trpc;
|
|
12
|
+
|
|
13
|
+
export function createRemoteTree(client: TrpcClient): Tree {
|
|
14
|
+
const get = (path: string) => client.get.query({ path }) as Promise<NodeData | undefined>;
|
|
15
|
+
const set = (node: NodeData) => client.set.mutate({ node: node as Record<string, unknown> });
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
get,
|
|
19
|
+
getChildren: (path, opts) => client.getChildren.query({ path, ...opts }),
|
|
20
|
+
set,
|
|
21
|
+
remove: (path) => client.remove.mutate({ path }).then(() => true),
|
|
22
|
+
// TODO: add tRPC patch endpoint for single-RPC atomic patch
|
|
23
|
+
patch: (path, ops) => defaultPatch(get, set, path, ops),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Lazy registry loader — fetches type nodes from /sys/types on demand
|
|
2
|
+
import { type ContextHandlers, register, resolve } from '@treenity/core/core';
|
|
3
|
+
import type { TypeSchema } from '@treenity/core/schema/types';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import { trpc } from './trpc';
|
|
6
|
+
|
|
7
|
+
// ── Fetcher ──────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const fetched = new Set<string>();
|
|
10
|
+
const inflight = new Map<string, Promise<void>>();
|
|
11
|
+
|
|
12
|
+
/** Fetch /sys/types/{type} and register its contexts into the core registry. */
|
|
13
|
+
export async function ensureType(type: string): Promise<void> {
|
|
14
|
+
if (fetched.has(type)) return;
|
|
15
|
+
if (inflight.has(type)) return inflight.get(type);
|
|
16
|
+
|
|
17
|
+
const promise = trpc.get
|
|
18
|
+
.query({ path: `/sys/types/${type.replace(/\./g, '/')}` })
|
|
19
|
+
.then((node: any) => {
|
|
20
|
+
const schema = node?.schema;
|
|
21
|
+
if (schema?.$id && !resolve(schema.$id, 'schema')) {
|
|
22
|
+
register(schema.$id, 'schema', () => schema);
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.catch(() => {}) // type may have no schema — not an error
|
|
26
|
+
.finally(() => {
|
|
27
|
+
fetched.add(type);
|
|
28
|
+
inflight.delete(type);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
inflight.set(type, promise);
|
|
32
|
+
return promise;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Hook ─────────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Lazy registry hook — returns the handler itself, not its result.
|
|
39
|
+
* undefined = loading, null = not found, Handler = ready.
|
|
40
|
+
*
|
|
41
|
+
* Calling convention is context-specific:
|
|
42
|
+
* 'react' — handler IS the component: useReg(type, 'react') → FC
|
|
43
|
+
* 'schema' — handler is a thunk: useReg(type, 'schema')?.() → TypeSchema
|
|
44
|
+
*
|
|
45
|
+
* Use useSchema() for the ergonomic schema shortcut.
|
|
46
|
+
*/
|
|
47
|
+
export function useReg<K extends keyof ContextHandlers>(
|
|
48
|
+
type: string | null | undefined,
|
|
49
|
+
context: K,
|
|
50
|
+
): ContextHandlers[K] | null | undefined;
|
|
51
|
+
export function useReg<T extends (...args: any[]) => any>(
|
|
52
|
+
type: string | null | undefined,
|
|
53
|
+
context: string,
|
|
54
|
+
): T | null | undefined;
|
|
55
|
+
export function useReg(type: string | null | undefined, context: string) {
|
|
56
|
+
const get = () => {
|
|
57
|
+
if (!type) return null;
|
|
58
|
+
return resolve(type, context) ?? undefined;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const [handler, setHandler] = useState(get);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!type) { setHandler(null); return; }
|
|
65
|
+
const h = resolve(type, context);
|
|
66
|
+
if (h) { setHandler(() => h); return; }
|
|
67
|
+
setHandler(undefined);
|
|
68
|
+
ensureType(type).then(() => {
|
|
69
|
+
const h2 = resolve(type, context);
|
|
70
|
+
setHandler(h2 ? () => h2 : null);
|
|
71
|
+
});
|
|
72
|
+
}, [type, context]);
|
|
73
|
+
|
|
74
|
+
return handler;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Schema convenience ────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/** undefined = loading, null = no schema, TypeSchema = ready */
|
|
80
|
+
export function useSchema(type: string | null | undefined): TypeSchema | null | undefined {
|
|
81
|
+
const getter = useReg(type, 'schema');
|
|
82
|
+
if (getter === undefined) return undefined;
|
|
83
|
+
return (getter as (() => TypeSchema) | null)?.() ?? null;
|
|
84
|
+
}
|