@treenity/core 1.0.48 → 3.0.1
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/README.md +72 -12
- package/dist/chain.d.ts +19 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +59 -0
- package/dist/chain.js.map +1 -0
- package/dist/client/handle.d.ts +13 -0
- package/dist/client/handle.d.ts.map +1 -0
- package/dist/client/handle.js +46 -0
- package/dist/client/handle.js.map +1 -0
- package/dist/client/index.d.ts +21 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/trpc.d.ts +13 -0
- package/dist/client/trpc.d.ts.map +1 -0
- package/dist/client/trpc.js +79 -0
- package/dist/client/trpc.js.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/comp/handle.d.ts +14 -0
- package/dist/comp/handle.d.ts.map +1 -0
- package/dist/comp/handle.js +21 -0
- package/dist/comp/handle.js.map +1 -0
- package/dist/comp/index.d.ts +41 -0
- package/dist/comp/index.d.ts.map +1 -0
- package/dist/comp/index.js +127 -0
- package/dist/comp/index.js.map +1 -0
- package/dist/comp/needs.d.ts +26 -0
- package/dist/comp/needs.d.ts.map +1 -0
- package/dist/comp/needs.js +101 -0
- package/dist/comp/needs.js.map +1 -0
- package/dist/comp/validate.d.ts +13 -0
- package/dist/comp/validate.d.ts.map +1 -0
- package/dist/comp/validate.js +117 -0
- package/dist/comp/validate.js.map +1 -0
- package/dist/comp.d.ts +2 -0
- package/dist/comp.d.ts.map +1 -0
- package/dist/comp.js +2 -0
- package/dist/comp.js.map +1 -0
- package/dist/contexts/schema/index.d.ts +7 -0
- package/dist/contexts/schema/index.d.ts.map +1 -0
- package/dist/contexts/schema/index.js +2 -0
- package/dist/contexts/schema/index.js.map +1 -0
- package/dist/contexts/schema.d.ts +2 -0
- package/dist/contexts/schema.d.ts.map +1 -0
- package/dist/contexts/schema.js +2 -0
- package/dist/contexts/schema.js.map +1 -0
- package/dist/contexts/service/index.d.ts +31 -0
- package/dist/contexts/service/index.d.ts.map +1 -0
- package/dist/contexts/service/index.js +16 -0
- package/dist/contexts/service/index.js.map +1 -0
- package/dist/contexts/service.d.ts +1 -14
- package/dist/contexts/service.d.ts.map +1 -1
- package/dist/contexts/service.js +2 -0
- package/dist/contexts/service.js.map +1 -0
- package/dist/contexts/telegram/index.d.ts +19 -0
- package/dist/contexts/telegram/index.d.ts.map +1 -0
- package/dist/contexts/telegram/index.js +89 -0
- package/dist/contexts/telegram/index.js.map +1 -0
- package/dist/contexts/text/index.d.ts +7 -0
- package/dist/contexts/text/index.d.ts.map +1 -0
- package/dist/contexts/text/index.js +9 -0
- package/dist/contexts/text/index.js.map +1 -0
- package/dist/contexts/text.d.ts +2 -0
- package/dist/contexts/text.d.ts.map +1 -0
- package/dist/contexts/text.js +2 -0
- package/dist/contexts/text.js.map +1 -0
- package/dist/core/component.d.ts +43 -0
- package/dist/core/component.d.ts.map +1 -0
- package/dist/core/component.js +101 -0
- package/dist/core/component.js.map +1 -0
- package/dist/core/context.d.ts +5 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +3 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/path.d.ts +7 -0
- package/dist/core/path.d.ts.map +1 -0
- package/dist/core/path.js +41 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/registry.d.ts +17 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +100 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +2 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +1 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +38 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +150 -0
- package/dist/log.js.map +1 -0
- package/dist/mod/discover.d.ts +4 -0
- package/dist/mod/discover.d.ts.map +1 -0
- package/dist/mod/discover.js +103 -0
- package/dist/mod/discover.js.map +1 -0
- package/dist/mod/examples/ticker/seed.d.ts +3 -0
- package/dist/mod/examples/ticker/seed.d.ts.map +1 -0
- package/dist/mod/examples/ticker/seed.js +16 -0
- package/dist/mod/examples/ticker/seed.js.map +1 -0
- package/dist/mod/examples/ticker/service.d.ts +2 -0
- package/dist/mod/examples/ticker/service.d.ts.map +1 -0
- package/dist/mod/examples/ticker/service.js +17 -0
- package/dist/mod/examples/ticker/service.js.map +1 -0
- package/dist/mod/examples/ticker/types.d.ts +16 -0
- package/dist/mod/examples/ticker/types.d.ts.map +1 -0
- package/dist/mod/examples/ticker/types.js +20 -0
- package/dist/mod/examples/ticker/types.js.map +1 -0
- package/dist/mod/examples/ticker/view.d.ts +2 -0
- package/dist/mod/examples/ticker/view.d.ts.map +1 -0
- package/dist/mod/examples/ticker/view.js +10 -0
- package/dist/mod/examples/ticker/view.js.map +1 -0
- package/dist/mod/index.d.ts +11 -0
- package/dist/mod/index.d.ts.map +1 -0
- package/dist/mod/index.js +8 -0
- package/dist/mod/index.js.map +1 -0
- package/dist/mod/loader.d.ts +19 -0
- package/dist/mod/loader.d.ts.map +1 -0
- package/dist/mod/loader.js +180 -0
- package/dist/mod/loader.js.map +1 -0
- package/dist/mod/optimistic.d.ts +34 -0
- package/dist/mod/optimistic.d.ts.map +1 -0
- package/dist/mod/optimistic.js +176 -0
- package/dist/mod/optimistic.js.map +1 -0
- package/dist/mod/prefab.d.ts +18 -0
- package/dist/mod/prefab.d.ts.map +1 -0
- package/dist/mod/prefab.js +42 -0
- package/dist/mod/prefab.js.map +1 -0
- package/dist/mod/tracking.d.ts +8 -0
- package/dist/mod/tracking.d.ts.map +1 -0
- package/dist/mod/tracking.js +44 -0
- package/dist/mod/tracking.js.map +1 -0
- package/dist/mod/types.d.ts +31 -0
- package/dist/mod/types.d.ts.map +1 -0
- package/dist/mod/types.js +5 -0
- package/dist/mod/types.js.map +1 -0
- package/dist/mod.d.ts +2 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +2 -0
- package/dist/mod.js.map +1 -0
- package/dist/mods/autostart/server.d.ts +2 -0
- package/dist/mods/autostart/server.d.ts.map +1 -0
- package/dist/mods/autostart/server.js +2 -0
- package/dist/mods/autostart/server.js.map +1 -0
- package/dist/mods/autostart/service.d.ts +14 -0
- package/dist/mods/autostart/service.d.ts.map +1 -0
- package/dist/mods/autostart/service.js +88 -0
- package/dist/mods/autostart/service.js.map +1 -0
- package/dist/mods/clients.d.ts +2 -0
- package/dist/mods/clients.d.ts.map +1 -0
- package/dist/mods/clients.js +3 -0
- package/dist/mods/clients.js.map +1 -0
- package/dist/mods/llm/index.d.ts +13 -0
- package/dist/mods/llm/index.d.ts.map +1 -0
- package/dist/mods/llm/index.js +43 -0
- package/dist/mods/llm/index.js.map +1 -0
- package/dist/mods/llm/server.d.ts +2 -0
- package/dist/mods/llm/server.d.ts.map +1 -0
- package/dist/mods/llm/server.js +2 -0
- package/dist/mods/llm/server.js.map +1 -0
- package/dist/mods/mcp/server.d.ts +2 -0
- package/dist/mods/mcp/server.js +2 -0
- package/dist/mods/mcp/service.d.ts +1 -0
- package/dist/mods/mcp/service.js +16 -0
- package/dist/mods/mcp/types.d.ts +4 -0
- package/dist/mods/mcp/types.js +6 -0
- package/dist/mods/servers.d.ts +4 -0
- package/dist/mods/servers.d.ts.map +1 -0
- package/dist/mods/servers.js +5 -0
- package/dist/mods/servers.js.map +1 -0
- package/dist/mods/treenity/agent-port.d.ts +2 -0
- package/dist/mods/treenity/agent-port.d.ts.map +1 -0
- package/dist/mods/treenity/agent-port.js +76 -0
- package/dist/mods/treenity/agent-port.js.map +1 -0
- package/dist/mods/treenity/builtins.d.ts +2 -0
- package/dist/mods/treenity/builtins.d.ts.map +1 -0
- package/dist/mods/treenity/builtins.js +18 -0
- package/dist/mods/treenity/builtins.js.map +1 -0
- package/dist/mods/treenity/groups.d.ts +2 -0
- package/dist/mods/treenity/groups.d.ts.map +1 -0
- package/dist/mods/treenity/groups.js +18 -0
- package/dist/mods/treenity/groups.js.map +1 -0
- package/dist/mods/treenity/logs.d.ts +18 -0
- package/dist/mods/treenity/logs.d.ts.map +1 -0
- package/dist/mods/treenity/logs.js +17 -0
- package/dist/mods/treenity/logs.js.map +1 -0
- package/dist/mods/treenity/mod-type.d.ts +2 -0
- package/dist/mods/treenity/mod-type.d.ts.map +1 -0
- package/dist/mods/treenity/mod-type.js +9 -0
- package/dist/mods/treenity/mod-type.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 +55 -0
- package/dist/mods/treenity/seed.js.map +1 -0
- package/dist/mods/treenity/server.d.ts +7 -0
- package/dist/mods/treenity/server.d.ts.map +1 -0
- package/dist/mods/treenity/server.js +7 -0
- package/dist/mods/treenity/server.js.map +1 -0
- package/dist/mods/treenity/system.d.ts +36 -0
- package/dist/mods/treenity/system.d.ts.map +1 -0
- package/dist/mods/treenity/system.js +61 -0
- package/dist/mods/treenity/system.js.map +1 -0
- package/dist/mods/uix/client.d.ts +3 -0
- package/dist/mods/uix/client.d.ts.map +1 -0
- package/dist/mods/uix/client.js +96 -0
- package/dist/mods/uix/client.js.map +1 -0
- package/dist/mods/uix/compile.d.ts +7 -0
- package/dist/mods/uix/compile.d.ts.map +1 -0
- package/dist/mods/uix/compile.js +96 -0
- package/dist/mods/uix/compile.js.map +1 -0
- package/dist/mods/uix/jsx-parser.d.ts +2 -0
- package/dist/mods/uix/jsx-parser.d.ts.map +1 -0
- package/dist/mods/uix/jsx-parser.js +565 -0
- package/dist/mods/uix/jsx-parser.js.map +1 -0
- package/dist/mods/uix/verify.d.ts +7 -0
- package/dist/mods/uix/verify.d.ts.map +1 -0
- package/dist/mods/uix/verify.js +59 -0
- package/dist/mods/uix/verify.js.map +1 -0
- package/dist/schema/_test-fixture.d.ts +11 -0
- package/dist/schema/_test-fixture.d.ts.map +1 -0
- package/dist/schema/_test-fixture.js +8 -0
- package/dist/schema/_test-fixture.js.map +1 -0
- package/dist/schema/catalog.d.ts +23 -0
- package/dist/schema/catalog.d.ts.map +1 -0
- package/dist/schema/catalog.js +82 -0
- package/dist/schema/catalog.js.map +1 -0
- package/dist/schema/extract-schemas.d.ts +5 -0
- package/dist/schema/extract-schemas.d.ts.map +1 -0
- package/dist/schema/extract-schemas.js +444 -0
- package/dist/schema/extract-schemas.js.map +1 -0
- package/dist/schema/load.d.ts +2 -0
- package/dist/schema/load.d.ts.map +1 -0
- package/dist/schema/load.js +23 -0
- package/dist/schema/load.js.map +1 -0
- package/dist/schema/test-fixture.types.d.ts +2 -0
- package/dist/schema/test-fixture.types.d.ts.map +1 -0
- package/dist/schema/test-fixture.types.js +19 -0
- package/dist/schema/test-fixture.types.js.map +1 -0
- package/dist/schema/types.d.ts +38 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +3 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/server/actions.d.ts +35 -0
- package/dist/server/actions.d.ts.map +1 -0
- package/dist/server/actions.js +170 -0
- package/dist/server/actions.js.map +1 -0
- package/dist/server/agent.d.ts +6 -0
- package/dist/server/agent.d.ts.map +1 -0
- package/dist/server/agent.js +15 -0
- package/dist/server/agent.js.map +1 -0
- package/dist/server/auth.d.ts +39 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +336 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/client.d.ts +190 -0
- package/dist/server/client.d.ts.map +1 -0
- package/dist/server/client.js +22 -0
- package/dist/server/client.js.map +1 -0
- package/dist/server/cookies.d.ts +5 -0
- package/dist/server/cookies.d.ts.map +1 -0
- package/dist/server/cookies.js +24 -0
- package/dist/server/cookies.js.map +1 -0
- package/dist/server/doc-index.d.ts +38 -0
- package/dist/server/doc-index.d.ts.map +1 -0
- package/dist/server/doc-index.js +245 -0
- package/dist/server/doc-index.js.map +1 -0
- package/dist/server/errors.d.ts +7 -0
- package/dist/server/errors.d.ts.map +1 -0
- package/dist/server/errors.js +11 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/factory.d.ts +29 -0
- package/dist/server/factory.d.ts.map +1 -0
- package/dist/server/factory.js +89 -0
- package/dist/server/factory.js.map +1 -0
- package/dist/server/main.d.ts +2 -0
- package/dist/server/main.d.ts.map +1 -0
- package/dist/server/main.js +19 -0
- package/dist/server/main.js.map +1 -0
- package/dist/server/mcp.d.ts +4 -0
- package/dist/server/mcp.js +279 -0
- package/dist/server/migrate.d.ts +3 -0
- package/dist/server/migrate.d.ts.map +1 -0
- package/dist/server/migrate.js +56 -0
- package/dist/server/migrate.js.map +1 -0
- package/dist/server/mod-catalog.d.ts +14 -0
- package/dist/server/mod-catalog.d.ts.map +1 -0
- package/dist/server/mod-catalog.js +40 -0
- package/dist/server/mod-catalog.js.map +1 -0
- package/dist/server/mods-mount.d.ts +3 -0
- package/dist/server/mods-mount.d.ts.map +1 -0
- package/dist/server/mods-mount.js +168 -0
- package/dist/server/mods-mount.js.map +1 -0
- package/dist/server/mount-adapters.d.ts +9 -0
- package/dist/server/mount-adapters.d.ts.map +1 -0
- package/dist/server/mount-adapters.js +80 -0
- package/dist/server/mount-adapters.js.map +1 -0
- package/dist/server/mount.d.ts +3 -0
- package/dist/server/mount.d.ts.map +1 -0
- package/dist/server/mount.js +195 -0
- package/dist/server/mount.js.map +1 -0
- package/dist/server/prefab.d.ts +18 -0
- package/dist/server/prefab.d.ts.map +1 -0
- package/dist/server/prefab.js +72 -0
- package/dist/server/prefab.js.map +1 -0
- package/dist/server/refs.d.ts +3 -0
- package/dist/server/refs.d.ts.map +1 -0
- package/dist/server/refs.js +59 -0
- package/dist/server/refs.js.map +1 -0
- package/dist/server/seed/index.d.ts +6 -0
- package/dist/server/seed/index.d.ts.map +1 -0
- package/dist/server/seed/index.js +16 -0
- package/dist/server/seed/index.js.map +1 -0
- package/dist/server/server.d.ts +28 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +127 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/sub.d.ts +34 -0
- package/dist/server/sub.d.ts.map +1 -0
- package/dist/server/sub.js +174 -0
- package/dist/server/sub.js.map +1 -0
- package/dist/server/trpc.d.ts +199 -0
- package/dist/server/trpc.d.ts.map +1 -0
- package/dist/server/trpc.js +323 -0
- package/dist/server/trpc.js.map +1 -0
- package/dist/server/types-mount.d.ts +3 -0
- package/dist/server/types-mount.d.ts.map +1 -0
- package/dist/server/types-mount.js +144 -0
- package/dist/server/types-mount.js.map +1 -0
- package/dist/server/validate.d.ts +3 -0
- package/dist/server/validate.d.ts.map +1 -0
- package/dist/server/validate.js +20 -0
- package/dist/server/validate.js.map +1 -0
- package/dist/server/volatile.d.ts +11 -0
- package/dist/server/volatile.d.ts.map +1 -0
- package/dist/server/volatile.js +26 -0
- package/dist/server/volatile.js.map +1 -0
- package/dist/server/watch.d.ts +23 -0
- package/dist/server/watch.d.ts.map +1 -0
- package/dist/server/watch.js +178 -0
- package/dist/server/watch.js.map +1 -0
- package/dist/tree/cache.d.ts +3 -0
- package/dist/tree/cache.d.ts.map +1 -0
- package/dist/tree/cache.js +48 -0
- package/dist/tree/cache.js.map +1 -0
- package/dist/tree/fs.d.ts +3 -0
- package/dist/tree/fs.d.ts.map +1 -0
- package/dist/tree/fs.js +276 -0
- package/dist/tree/fs.js.map +1 -0
- package/dist/tree/index.d.ts +38 -0
- package/dist/tree/index.d.ts.map +1 -0
- package/dist/tree/index.js +183 -0
- package/dist/tree/index.js.map +1 -0
- package/dist/tree/inflight.d.ts +2 -0
- package/dist/tree/inflight.d.ts.map +1 -0
- package/dist/tree/inflight.js +15 -0
- package/dist/tree/inflight.js.map +1 -0
- package/dist/tree/json-codec.d.ts +2 -0
- package/dist/tree/json-codec.d.ts.map +1 -0
- package/dist/tree/json-codec.js +13 -0
- package/dist/tree/json-codec.js.map +1 -0
- package/dist/tree/mimefs.d.ts +13 -0
- package/dist/tree/mimefs.d.ts.map +1 -0
- package/dist/tree/mimefs.js +124 -0
- package/dist/tree/mimefs.js.map +1 -0
- package/dist/tree/mongo.d.ts +5 -0
- package/dist/tree/mongo.d.ts.map +1 -0
- package/dist/tree/mongo.js +110 -0
- package/dist/tree/mongo.js.map +1 -0
- package/dist/tree/patch.d.ts +30 -0
- package/dist/tree/patch.d.ts.map +1 -0
- package/dist/tree/patch.js +112 -0
- package/dist/tree/patch.js.map +1 -0
- package/dist/tree/query.d.ts +12 -0
- package/dist/tree/query.d.ts.map +1 -0
- package/dist/tree/query.js +61 -0
- package/dist/tree/query.js.map +1 -0
- package/dist/tree/repath.d.ts +3 -0
- package/dist/tree/repath.d.ts.map +1 -0
- package/dist/tree/repath.js +38 -0
- package/dist/tree/repath.js.map +1 -0
- package/dist/tree-chain.d.ts +19 -0
- package/dist/tree-chain.d.ts.map +1 -0
- package/dist/tree-chain.js +111 -0
- package/dist/tree-chain.js.map +1 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +2 -0
- package/dist/tree.js.map +1 -0
- package/dist/uri.d.ts +11 -0
- package/dist/uri.d.ts.map +1 -0
- package/dist/uri.js +84 -0
- package/dist/uri.js.map +1 -0
- package/package.json +78 -37
- package/src/chain.test.ts +190 -0
- package/src/chain.ts +84 -0
- package/src/client/client.test.ts +192 -0
- package/src/client/handle.ts +53 -0
- package/src/client/index.ts +18 -0
- package/src/client/trpc.ts +92 -0
- package/src/client.ts +1 -0
- package/src/comp/CLAUDE.md +14 -0
- package/src/comp/handle.ts +36 -0
- package/src/comp/index.test.ts +165 -0
- package/src/comp/index.ts +186 -0
- package/src/comp/needs.test.ts +499 -0
- package/src/comp/needs.ts +113 -0
- package/src/comp/validate.test.ts +304 -0
- package/src/comp/validate.ts +125 -0
- package/src/comp.ts +1 -0
- package/src/contexts/schema/index.ts +7 -0
- package/src/contexts/schema.ts +1 -0
- package/src/contexts/service/index.test.ts +323 -0
- package/src/contexts/service/index.ts +43 -0
- package/src/contexts/service.ts +1 -0
- package/src/contexts/telegram/index.ts +115 -0
- package/src/contexts/text/index.test.ts +31 -0
- package/src/contexts/text/index.ts +18 -0
- package/src/contexts/text.ts +1 -0
- package/src/core/component.ts +153 -0
- package/src/core/context.ts +14 -0
- package/src/core/index.test.ts +224 -0
- package/src/core/index.ts +9 -0
- package/src/core/path.ts +38 -0
- package/src/core/registry.ts +112 -0
- package/src/core.ts +1 -0
- package/src/index.ts +1 -0
- package/src/log.test.ts +70 -0
- package/src/log.ts +199 -0
- package/src/mod/discover.test.ts +133 -0
- package/src/mod/discover.ts +100 -0
- package/src/mod/docs/00-index.md +19 -0
- package/src/mod/docs/01-primitives.md +38 -0
- package/src/mod/docs/02-core-api.md +68 -0
- package/src/mod/docs/03-registry.md +30 -0
- package/src/mod/docs/04-store.md +62 -0
- package/src/mod/docs/05-comp.md +111 -0
- package/src/mod/docs/06-actions.md +193 -0
- package/src/mod/docs/07-realtime.md +108 -0
- package/src/mod/docs/08-services.md +35 -0
- package/src/mod/docs/09-mounts.md +43 -0
- package/src/mod/docs/10-acl.md +60 -0
- package/src/mod/docs/11-server.md +62 -0
- package/src/mod/docs/12-conventions.md +108 -0
- package/src/mod/docs/13-example.md +142 -0
- package/src/mod/docs/14-mod-format.md +377 -0
- package/src/mod/docs/15-documenting-types.md +156 -0
- package/src/mod/examples/ticker/seed.ts +19 -0
- package/src/mod/examples/ticker/service.ts +19 -0
- package/src/mod/examples/ticker/ticker.test.ts +18 -0
- package/src/mod/examples/ticker/types.ts +22 -0
- package/src/mod/examples/ticker/view.tsx +19 -0
- package/src/mod/index.ts +12 -0
- package/src/mod/loader.test.ts +220 -0
- package/src/mod/loader.ts +207 -0
- package/src/mod/optimistic.test.ts +446 -0
- package/src/mod/optimistic.ts +210 -0
- package/src/mod/prefab.ts +62 -0
- package/src/mod/tracking.test.ts +59 -0
- package/src/mod/tracking.ts +51 -0
- package/src/mod/types.ts +40 -0
- package/src/mod.ts +1 -0
- package/src/mods/autostart/CLAUDE.md +6 -0
- package/src/mods/autostart/autostart.test.ts +85 -0
- package/src/mods/autostart/server.ts +1 -0
- package/src/mods/autostart/service.ts +98 -0
- package/src/mods/clients.ts +2 -0
- package/src/mods/llm/CLAUDE.md +6 -0
- package/src/mods/llm/index.ts +57 -0
- package/src/mods/llm/llm.test.ts +109 -0
- package/src/mods/llm/server.ts +1 -0
- package/src/mods/servers.ts +4 -0
- package/src/mods/treenity/agent-port.ts +93 -0
- package/src/mods/treenity/builtins.ts +21 -0
- package/src/mods/treenity/groups.ts +19 -0
- package/src/mods/treenity/logs.ts +26 -0
- package/src/mods/treenity/mod-type.ts +10 -0
- package/src/mods/treenity/seed.ts +58 -0
- package/src/mods/treenity/server.ts +6 -0
- package/src/mods/treenity/system.ts +70 -0
- package/src/mods/uix/CLAUDE.md +7 -0
- package/src/mods/uix/client.ts +117 -0
- package/src/mods/uix/compile.test.ts +228 -0
- package/src/mods/uix/compile.ts +112 -0
- package/src/mods/uix/jsx-parser.test.ts +554 -0
- package/src/mods/uix/jsx-parser.ts +489 -0
- package/src/mods/uix/lazy-load.test.ts +261 -0
- package/src/mods/uix/uix-repomix.md +3509 -0
- package/src/mods/uix/verify.ts +68 -0
- package/src/schema/CLAUDE.md +13 -0
- package/src/schema/_test-fixture.ts +12 -0
- package/src/schema/catalog.ts +101 -0
- package/src/schema/extract-schemas.test.ts +84 -0
- package/src/schema/extract-schemas.ts +462 -0
- package/src/schema/generated/ai.agent.json +133 -0
- package/src/schema/generated/ai.approval.json +105 -0
- package/src/schema/generated/ai.approvals.json +24 -0
- package/src/schema/generated/ai.assignment.json +28 -0
- package/src/schema/generated/ai.plan.json +84 -0
- package/src/schema/generated/ai.policy.json +105 -0
- package/src/schema/generated/ai.pool.json +37 -0
- package/src/schema/generated/ai.thread.json +64 -0
- package/src/schema/generated/autostart.json +44 -0
- package/src/schema/generated/board.column.json +25 -0
- package/src/schema/generated/board.kanban.json +7 -0
- package/src/schema/generated/board.task.json +147 -0
- package/src/schema/generated/brahman.action.back.json +12 -0
- package/src/schema/generated/brahman.action.broadcast.json +31 -0
- package/src/schema/generated/brahman.action.call.json +57 -0
- package/src/schema/generated/brahman.action.emittext.json +23 -0
- package/src/schema/generated/brahman.action.eval.json +23 -0
- package/src/schema/generated/brahman.action.file.json +28 -0
- package/src/schema/generated/brahman.action.forward.json +29 -0
- package/src/schema/generated/brahman.action.getvalue.json +28 -0
- package/src/schema/generated/brahman.action.ifelse.json +42 -0
- package/src/schema/generated/brahman.action.keywordselect.json +46 -0
- package/src/schema/generated/brahman.action.message.json +127 -0
- package/src/schema/generated/brahman.action.onerror.json +29 -0
- package/src/schema/generated/brahman.action.page.json +22 -0
- package/src/schema/generated/brahman.action.params.json +36 -0
- package/src/schema/generated/brahman.action.question.json +43 -0
- package/src/schema/generated/brahman.action.remove.json +12 -0
- package/src/schema/generated/brahman.action.resethistory.json +12 -0
- package/src/schema/generated/brahman.action.resetsession.json +12 -0
- package/src/schema/generated/brahman.action.selectlang.json +20 -0
- package/src/schema/generated/brahman.action.setvalue.json +27 -0
- package/src/schema/generated/brahman.action.tag.json +27 -0
- package/src/schema/generated/brahman.bot.json +68 -0
- package/src/schema/generated/brahman.page.json +25 -0
- package/src/schema/generated/brahman.session.json +29 -0
- package/src/schema/generated/brahman.user.json +58 -0
- package/src/schema/generated/cafe.contact.json +56 -0
- package/src/schema/generated/cafe.mail.json +29 -0
- package/src/schema/generated/canary.item.json +40 -0
- package/src/schema/generated/claude-search.json +20 -0
- package/src/schema/generated/craft.product.json +47 -0
- package/src/schema/generated/craft.shop.json +94 -0
- package/src/schema/generated/craft.subscription.json +27 -0
- package/src/schema/generated/default.json +15 -0
- package/src/schema/generated/doc.page.json +23 -0
- package/src/schema/generated/examples.demo.generator.json +16 -0
- package/src/schema/generated/examples.demo.sensor.json +35 -0
- package/src/schema/generated/examples.demo.sensor.reading.json +25 -0
- package/src/schema/generated/flow.node.action.json +61 -0
- package/src/schema/generated/flow.node.code.json +43 -0
- package/src/schema/generated/flow.node.condition.json +37 -0
- package/src/schema/generated/flow.node.end.json +35 -0
- package/src/schema/generated/flow.node.http.json +65 -0
- package/src/schema/generated/flow.node.llm.json +67 -0
- package/src/schema/generated/flow.node.loop.json +49 -0
- package/src/schema/generated/flow.node.start.json +39 -0
- package/src/schema/generated/flow.scenario.json +118 -0
- package/src/schema/generated/groups.json +20 -0
- package/src/schema/generated/grove.attempt.json +199 -0
- package/src/schema/generated/grove.path.json +93 -0
- package/src/schema/generated/grove.review.json +27 -0
- package/src/schema/generated/grove.task.json +164 -0
- package/src/schema/generated/intel.scenario.json +58 -0
- package/src/schema/generated/intel.signal.json +113 -0
- package/src/schema/generated/intel.world.json +15 -0
- package/src/schema/generated/landing.block.json +201 -0
- package/src/schema/generated/landing.page.json +84 -0
- package/src/schema/generated/launcher.json +91 -0
- package/src/schema/generated/mcp.server.json +15 -0
- package/src/schema/generated/metatron.config.json +119 -0
- package/src/schema/generated/metatron.permission.json +36 -0
- package/src/schema/generated/metatron.skill.json +36 -0
- package/src/schema/generated/metatron.task.json +114 -0
- package/src/schema/generated/metatron.template.json +26 -0
- package/src/schema/generated/metatron.workspace.json +60 -0
- package/src/schema/generated/mindmap.map.json +22 -0
- package/src/schema/generated/order.status.json +21 -0
- package/src/schema/generated/polyhope.backtest.json +161 -0
- package/src/schema/generated/polyhope.feed.json +33 -0
- package/src/schema/generated/polyhope.run.json +94 -0
- package/src/schema/generated/polyhope.strategy.json +152 -0
- package/src/schema/generated/polymax.activity.json +65 -0
- package/src/schema/generated/polymax.aggr-feed.json +28 -0
- package/src/schema/generated/polymax.aggr.json +20 -0
- package/src/schema/generated/polymax.alert.json +56 -0
- package/src/schema/generated/polymax.bot-config.json +14 -0
- package/src/schema/generated/polymax.bot-status.json +35 -0
- package/src/schema/generated/polymax.classification.json +55 -0
- package/src/schema/generated/polymax.deposits.json +45 -0
- package/src/schema/generated/polymax.holding.json +55 -0
- package/src/schema/generated/polymax.identity.json +71 -0
- package/src/schema/generated/polymax.lb-entry.json +75 -0
- package/src/schema/generated/polymax.leaderboard.json +37 -0
- package/src/schema/generated/polymax.market-ref.json +25 -0
- package/src/schema/generated/polymax.performance.json +65 -0
- package/src/schema/generated/polymax.profile.json +16 -0
- package/src/schema/generated/polymax.scan-result.json +50 -0
- package/src/schema/generated/polymax.status.json +40 -0
- package/src/schema/generated/polymax.tags.json +53 -0
- package/src/schema/generated/polymax.trader.json +16 -0
- package/src/schema/generated/polymax.wallet-market.json +50 -0
- package/src/schema/generated/polymax.wallet-pnl.json +35 -0
- package/src/schema/generated/pult.concept.json +53 -0
- package/src/schema/generated/pult.config.json +227 -0
- package/src/schema/generated/pult.connector.json +72 -0
- package/src/schema/generated/pult.market.json +113 -0
- package/src/schema/generated/pult.order.json +113 -0
- package/src/schema/generated/pult.rete.json +68 -0
- package/src/schema/generated/pult.sensor.json +74 -0
- package/src/schema/generated/pult.signal.json +54 -0
- package/src/schema/generated/pult.synapse.json +93 -0
- package/src/schema/generated/pult.trade.json +93 -0
- package/src/schema/generated/resim.config.json +34 -0
- package/src/schema/generated/resim.function.json +62 -0
- package/src/schema/generated/resim.goal.json +22 -0
- package/src/schema/generated/resim.resource.json +40 -0
- package/src/schema/generated/resim.state.json +48 -0
- package/src/schema/generated/resim.world.json +40 -0
- package/src/schema/generated/saveme.action.save.json +29 -0
- package/src/schema/generated/saveme.message.json +36 -0
- package/src/schema/generated/saveme.router.json +31 -0
- package/src/schema/generated/sim.agent.json +24 -0
- package/src/schema/generated/sim.ai.json +24 -0
- package/src/schema/generated/sim.config.json +38 -0
- package/src/schema/generated/sim.descriptive.json +26 -0
- package/src/schema/generated/sim.events.json +47 -0
- package/src/schema/generated/sim.item.json +20 -0
- package/src/schema/generated/sim.memory.json +17 -0
- package/src/schema/generated/sim.nearby.json +17 -0
- package/src/schema/generated/sim.position.json +25 -0
- package/src/schema/generated/sim.round.json +64 -0
- package/src/schema/generated/sim.world.json +32 -0
- package/src/schema/generated/t.agent.port.json +74 -0
- package/src/schema/generated/t.coolify.json +50 -0
- package/src/schema/generated/t.event.json +46 -0
- package/src/schema/generated/t.llm.json +20 -0
- package/src/schema/generated/t.logs.json +155 -0
- package/src/schema/generated/t.mod.json +27 -0
- package/src/schema/generated/t.note.json +31 -0
- package/src/schema/generated/t.person.json +36 -0
- package/src/schema/generated/t.tenant.json +57 -0
- package/src/schema/generated/t.tenant.status.json +42 -0
- package/src/schema/generated/t3d.animator.json +46 -0
- package/src/schema/generated/t3d.audio.json +58 -0
- package/src/schema/generated/t3d.camera.json +50 -0
- package/src/schema/generated/t3d.collider.json +84 -0
- package/src/schema/generated/t3d.light.json +90 -0
- package/src/schema/generated/t3d.line.json +47 -0
- package/src/schema/generated/t3d.lod.json +28 -0
- package/src/schema/generated/t3d.material.json +131 -0
- package/src/schema/generated/t3d.mesh.json +65 -0
- package/src/schema/generated/t3d.object.json +64 -0
- package/src/schema/generated/t3d.particles.json +109 -0
- package/src/schema/generated/t3d.rigidbody.json +81 -0
- package/src/schema/generated/t3d.scene.json +7 -0
- package/src/schema/generated/t3d.script.json +23 -0
- package/src/schema/generated/t3d.text.json +86 -0
- package/src/schema/generated/t3d.trail.json +45 -0
- package/src/schema/generated/tagger.config.json +115 -0
- package/src/schema/generated/tagger.result.json +57 -0
- package/src/schema/generated/tagger.tree.json +36 -0
- package/src/schema/generated/task.json +96 -0
- package/src/schema/generated/test.fixture.json +43 -0
- package/src/schema/generated/ticker.config.json +43 -0
- package/src/schema/generated/ticker.price.json +20 -0
- package/src/schema/generated/todo.item.json +25 -0
- package/src/schema/generated/todo.list.json +33 -0
- package/src/schema/generated/treenity.system.json +259 -0
- package/src/schema/generated/ui.table.json +46 -0
- package/src/schema/generated/whisper.audio.json +25 -0
- package/src/schema/generated/whisper.checklist.json +17 -0
- package/src/schema/generated/whisper.config.json +30 -0
- package/src/schema/generated/whisper.inbox.json +24 -0
- package/src/schema/generated/whisper.meta.json +35 -0
- package/src/schema/generated/whisper.text.json +16 -0
- package/src/schema/load.ts +24 -0
- package/src/schema/schema.test.ts +86 -0
- package/src/schema/test-fixture.types.ts +21 -0
- package/src/schema/types.ts +34 -0
- package/src/server/CLAUDE.md +26 -0
- package/src/server/actions.test.ts +272 -0
- package/src/server/actions.ts +274 -0
- package/src/server/agent-sub.test.ts +90 -0
- package/src/server/agent.test.ts +305 -0
- package/src/server/agent.ts +17 -0
- package/src/server/api.test.ts +463 -0
- package/src/server/auth.test.ts +441 -0
- package/src/server/auth.ts +387 -0
- package/src/server/client.ts +24 -0
- package/src/server/conditions.test.ts +128 -0
- package/src/server/cookies.ts +25 -0
- package/src/server/coverage.test.ts +827 -0
- package/src/server/doc-index.ts +287 -0
- package/src/server/e2e.test.ts +967 -0
- package/src/server/errors.ts +11 -0
- package/src/server/factory.ts +121 -0
- package/src/server/main.ts +24 -0
- package/src/server/migrate.test.ts +123 -0
- package/src/server/migrate.ts +62 -0
- package/src/server/mod-catalog.ts +59 -0
- package/src/server/mods-mount.ts +177 -0
- package/src/server/mount-adapters.ts +87 -0
- package/src/server/mount.parametrized.test.ts +52 -0
- package/src/server/mount.query.test.ts +127 -0
- package/src/server/mount.test.ts +565 -0
- package/src/server/mount.ts +208 -0
- package/src/server/prefab.test.ts +415 -0
- package/src/server/prefab.ts +105 -0
- package/src/server/refs.test.ts +82 -0
- package/src/server/refs.ts +64 -0
- package/src/server/seed/index.ts +24 -0
- package/src/server/server.ts +164 -0
- package/src/server/stress.test.ts +844 -0
- package/src/server/sub.test.ts +55 -0
- package/src/server/sub.ts +224 -0
- package/src/server/trpc.ts +375 -0
- package/src/server/types-mount.ts +142 -0
- package/src/server/validate.test.ts +91 -0
- package/src/server/validate.ts +22 -0
- package/src/server/volatile.test.ts +140 -0
- package/src/server/volatile.ts +32 -0
- package/src/server/watch.test.ts +594 -0
- package/src/server/watch.ts +202 -0
- package/src/server/workflow.test.ts +82 -0
- package/src/tree/CLAUDE.md +13 -0
- package/src/tree/cache.test.ts +213 -0
- package/src/tree/cache.ts +51 -0
- package/src/tree/fs.test.ts +247 -0
- package/src/tree/fs.ts +257 -0
- package/src/tree/index.test.ts +190 -0
- package/src/tree/index.ts +217 -0
- package/src/tree/inflight.ts +15 -0
- package/src/tree/json-codec.ts +16 -0
- package/src/tree/mimefs.test.ts +289 -0
- package/src/tree/mimefs.ts +142 -0
- package/src/tree/mongo.ts +125 -0
- package/src/tree/patch.test.ts +192 -0
- package/src/tree/patch.ts +133 -0
- package/src/tree/query.test.ts +110 -0
- package/src/tree/query.ts +70 -0
- package/src/tree/repath.test.ts +86 -0
- package/src/tree/repath.ts +53 -0
- package/src/tree-chain.test.ts +716 -0
- package/src/tree-chain.ts +150 -0
- package/src/tree.ts +1 -0
- package/src/uri.test.ts +113 -0
- package/src/uri.ts +84 -0
- package/CHANGELOG.md +0 -314
- package/dist/context.d.ts +0 -41
- package/dist/context.d.ts.map +0 -1
- package/dist/context.mjs +0 -81
- package/dist/context.mjs.map +0 -1
- package/dist/contexts/node-engine.d.ts +0 -12
- package/dist/contexts/node-engine.d.ts.map +0 -1
- package/dist/contexts/node-engine.mjs +0 -7
- package/dist/contexts/node-engine.mjs.map +0 -1
- package/dist/contexts/noflo/types.d.ts +0 -20
- package/dist/contexts/noflo/types.d.ts.map +0 -1
- package/dist/contexts/object.d.ts +0 -11
- package/dist/contexts/object.d.ts.map +0 -1
- package/dist/contexts/object.mjs +0 -15
- package/dist/contexts/object.mjs.map +0 -1
- package/dist/contexts/proto.d.ts +0 -11
- package/dist/contexts/proto.d.ts.map +0 -1
- package/dist/contexts/proto.mjs +0 -7
- package/dist/contexts/proto.mjs.map +0 -1
- package/dist/contexts/react-context.d.ts +0 -21
- package/dist/contexts/react-context.d.ts.map +0 -1
- package/dist/contexts/react-context.mjs +0 -24
- package/dist/contexts/react-context.mjs.map +0 -1
- package/dist/contexts/service.mjs +0 -7
- package/dist/contexts/service.mjs.map +0 -1
- package/dist/get-type-cache.d.ts +0 -2
- package/dist/get-type-cache.d.ts.map +0 -1
- package/dist/get-type-cache.mjs +0 -7
- package/dist/get-type-cache.mjs.map +0 -1
- package/dist/index.mjs +0 -10
- package/dist/index.mjs.map +0 -1
- package/dist/link/link.d.ts +0 -25
- package/dist/link/link.d.ts.map +0 -1
- package/dist/link/link.mjs +0 -72
- package/dist/link/link.mjs.map +0 -1
- package/dist/link/link.test.d.ts +0 -2
- package/dist/link/link.test.d.ts.map +0 -1
- package/dist/loading.d.ts +0 -9
- package/dist/loading.d.ts.map +0 -1
- package/dist/meta-type.d.ts +0 -58
- package/dist/meta-type.d.ts.map +0 -1
- package/dist/meta-type.mjs +0 -104
- package/dist/meta-type.mjs.map +0 -1
- package/dist/meta.d.ts +0 -20
- package/dist/meta.d.ts.map +0 -1
- package/dist/meta.mjs +0 -16
- package/dist/meta.mjs.map +0 -1
- package/dist/node/index.d.ts +0 -2
- package/dist/node/index.d.ts.map +0 -1
- package/dist/node/types.d.ts +0 -37
- package/dist/node/types.d.ts.map +0 -1
- package/dist/test/context.test.d.ts +0 -2
- package/dist/test/context.test.d.ts.map +0 -1
- package/dist/types.d.ts +0 -14
- package/dist/types.d.ts.map +0 -1
- package/dist/types.mjs +0 -16
- package/dist/types.mjs.map +0 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { createNode } from '#core';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, rm, stat } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { afterEach, describe, it } from 'node:test';
|
|
7
|
+
import { createFsTree } from './fs';
|
|
8
|
+
import { mapSiftQuery } from './query';
|
|
9
|
+
|
|
10
|
+
describe('FsStore', () => {
|
|
11
|
+
let dir: string;
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
if (dir) await rm(dir, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
async function setup() {
|
|
18
|
+
dir = await mkdtemp(join(tmpdir(), 'treenity-fs-test-'));
|
|
19
|
+
return createFsTree(dir);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function exists(path: string) {
|
|
23
|
+
return stat(join(dir, path)).then(() => true, () => false);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
it('set and get', async () => {
|
|
27
|
+
const store = await setup();
|
|
28
|
+
const node = createNode('/users/alice', 'user');
|
|
29
|
+
await store.set(node);
|
|
30
|
+
assert.deepEqual(await store.get('/users/alice'), node);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('get returns undefined for missing', async () => {
|
|
34
|
+
const store = await setup();
|
|
35
|
+
assert.equal(await store.get('/nope'), undefined);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('getChildren direct', async () => {
|
|
39
|
+
const store = await setup();
|
|
40
|
+
await store.set(createNode('/app', 'root'));
|
|
41
|
+
await store.set(createNode('/app/a', 'item'));
|
|
42
|
+
await store.set(createNode('/app/b', 'item'));
|
|
43
|
+
await store.set(createNode('/app/a/deep', 'item'));
|
|
44
|
+
|
|
45
|
+
const children = await store.getChildren('/app');
|
|
46
|
+
assert.equal(children.items.length, 2);
|
|
47
|
+
assert.deepEqual(children.items.map((n) => n.$path).sort(), ['/app/a', '/app/b']);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('getChildren with depth', async () => {
|
|
51
|
+
const store = await setup();
|
|
52
|
+
await store.set(createNode('/x', 'root'));
|
|
53
|
+
await store.set(createNode('/x/a', 'item'));
|
|
54
|
+
await store.set(createNode('/x/a/b', 'item'));
|
|
55
|
+
await store.set(createNode('/x/a/b/c', 'item'));
|
|
56
|
+
|
|
57
|
+
const d2 = await store.getChildren('/x', { depth: 2 });
|
|
58
|
+
assert.equal(d2.items.length, 2);
|
|
59
|
+
assert.deepEqual(d2.items.map((n) => n.$path).sort(), ['/x/a', '/x/a/b']);
|
|
60
|
+
|
|
61
|
+
const all = await store.getChildren('/x', { depth: Infinity });
|
|
62
|
+
assert.equal(all.items.length, 3);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('remove', async () => {
|
|
66
|
+
const store = await setup();
|
|
67
|
+
await store.set(createNode('/z', 'item'));
|
|
68
|
+
assert.equal(await store.remove('/z'), true);
|
|
69
|
+
assert.equal(await store.get('/z'), undefined);
|
|
70
|
+
assert.equal(await store.remove('/z'), false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('handles nested paths', async () => {
|
|
74
|
+
const store = await setup();
|
|
75
|
+
const node = createNode('/a/b/c/d/e', 'deep');
|
|
76
|
+
await store.set(node);
|
|
77
|
+
assert.deepEqual(await store.get('/a/b/c/d/e'), node);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ── Storage format tests ──
|
|
81
|
+
|
|
82
|
+
it('leaf node stored as name.json', async () => {
|
|
83
|
+
const store = await setup();
|
|
84
|
+
await store.set(createNode('/leaf', 'item'));
|
|
85
|
+
|
|
86
|
+
assert.ok(await exists('leaf.json'), 'leaf.json should exist');
|
|
87
|
+
assert.ok(!(await exists('leaf/$.json')), 'leaf/$.json should NOT exist');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('root always stored as $/$.json (dir form)', async () => {
|
|
91
|
+
const store = await setup();
|
|
92
|
+
await store.set(createNode('/', 'root'));
|
|
93
|
+
|
|
94
|
+
assert.ok(await exists('$.json'), 'root $.json should exist');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('auto-promotes leaf to dir when child is created', async () => {
|
|
98
|
+
const store = await setup();
|
|
99
|
+
await store.set(createNode('/parent', 'dir'));
|
|
100
|
+
|
|
101
|
+
// Initially leaf form
|
|
102
|
+
assert.ok(await exists('parent.json'), 'should be leaf form');
|
|
103
|
+
assert.ok(!(await exists('parent/$.json')), 'should NOT be dir form yet');
|
|
104
|
+
|
|
105
|
+
// Add child — parent must be promoted to dir form
|
|
106
|
+
await store.set(createNode('/parent/child', 'item'));
|
|
107
|
+
|
|
108
|
+
assert.ok(await exists('parent/$.json'), 'parent should now be dir form');
|
|
109
|
+
assert.ok(!(await exists('parent.json')), 'parent.json should be gone');
|
|
110
|
+
assert.ok(await exists('parent/child.json'), 'child should be leaf form');
|
|
111
|
+
|
|
112
|
+
// Both nodes still readable
|
|
113
|
+
const parent = await store.get('/parent');
|
|
114
|
+
assert.equal(parent?.$type, 't.dir');
|
|
115
|
+
const child = await store.get('/parent/child');
|
|
116
|
+
assert.equal(child?.$type, 't.item');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('auto-demotes dir to leaf when last child is removed', async () => {
|
|
120
|
+
const store = await setup();
|
|
121
|
+
await store.set(createNode('/parent', 'dir'));
|
|
122
|
+
await store.set(createNode('/parent/child', 'item'));
|
|
123
|
+
|
|
124
|
+
// Parent in dir form
|
|
125
|
+
assert.ok(await exists('parent/$.json'));
|
|
126
|
+
|
|
127
|
+
// Remove the child
|
|
128
|
+
await store.remove('/parent/child');
|
|
129
|
+
|
|
130
|
+
// Parent should be demoted to leaf form
|
|
131
|
+
assert.ok(await exists('parent.json'), 'parent should be demoted to leaf form');
|
|
132
|
+
assert.ok(!(await exists('parent/$.json')), 'parent dir form should be gone');
|
|
133
|
+
|
|
134
|
+
// Parent still readable
|
|
135
|
+
const parent = await store.get('/parent');
|
|
136
|
+
assert.equal(parent?.$type, 't.dir');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('node with children stays in dir form on re-save', async () => {
|
|
140
|
+
const store = await setup();
|
|
141
|
+
await store.set(createNode('/p', 'dir'));
|
|
142
|
+
await store.set(createNode('/p/a', 'item'));
|
|
143
|
+
|
|
144
|
+
// Re-save parent — should stay dir form because it has children
|
|
145
|
+
const p = await store.get('/p');
|
|
146
|
+
await store.set({ ...p!, $rev: p!.$rev });
|
|
147
|
+
|
|
148
|
+
assert.ok(await exists('p/$.json'), 'should remain dir form');
|
|
149
|
+
assert.ok(!(await exists('p.json')), 'should not have leaf form');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('deep promotion chain', async () => {
|
|
153
|
+
const store = await setup();
|
|
154
|
+
await store.set(createNode('/a', 'n'));
|
|
155
|
+
await store.set(createNode('/a/b', 'n'));
|
|
156
|
+
|
|
157
|
+
// Both start as leaves where possible
|
|
158
|
+
assert.ok(await exists('a/$.json'), 'a has child b → dir form');
|
|
159
|
+
assert.ok(await exists('a/b.json'), 'b is leaf');
|
|
160
|
+
|
|
161
|
+
// Create deep child — b must promote
|
|
162
|
+
await store.set(createNode('/a/b/c', 'n'));
|
|
163
|
+
|
|
164
|
+
assert.ok(await exists('a/b/$.json'), 'b promoted to dir form');
|
|
165
|
+
assert.ok(!(await exists('a/b.json')), 'b.json gone');
|
|
166
|
+
assert.ok(await exists('a/b/c.json'), 'c is leaf');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('deep demotion chain', async () => {
|
|
170
|
+
const store = await setup();
|
|
171
|
+
await store.set(createNode('/a', 'n'));
|
|
172
|
+
await store.set(createNode('/a/b', 'n'));
|
|
173
|
+
await store.set(createNode('/a/b/c', 'n'));
|
|
174
|
+
|
|
175
|
+
// Remove c — b should demote, a should demote too (b.json is still a child of a)
|
|
176
|
+
await store.remove('/a/b/c');
|
|
177
|
+
|
|
178
|
+
assert.ok(await exists('a/b.json'), 'b demoted to leaf');
|
|
179
|
+
assert.ok(await exists('a/$.json'), 'a still dir form (has child b.json)');
|
|
180
|
+
|
|
181
|
+
// Remove b — a should demote
|
|
182
|
+
await store.remove('/a/b');
|
|
183
|
+
|
|
184
|
+
assert.ok(await exists('a.json'), 'a demoted to leaf');
|
|
185
|
+
assert.ok(!(await exists('a/$.json')), 'a dir form gone');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('data integrity through promotion/demotion', async () => {
|
|
189
|
+
const store = await setup();
|
|
190
|
+
const parent = createNode('/p', 'dir', { label: 'parent' });
|
|
191
|
+
await store.set(parent);
|
|
192
|
+
|
|
193
|
+
const child = createNode('/p/c', 'item', { label: 'child' });
|
|
194
|
+
await store.set(child);
|
|
195
|
+
|
|
196
|
+
// Verify data survived promotion
|
|
197
|
+
const p = await store.get('/p');
|
|
198
|
+
assert.equal((p as any).label, 'parent');
|
|
199
|
+
|
|
200
|
+
await store.remove('/p/c');
|
|
201
|
+
|
|
202
|
+
// Verify data survived demotion
|
|
203
|
+
const p2 = await store.get('/p');
|
|
204
|
+
assert.equal((p2 as any).label, 'parent');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('OCC still works across storage forms', async () => {
|
|
208
|
+
const store = await setup();
|
|
209
|
+
await store.set(createNode('/n', 'item'));
|
|
210
|
+
const n = await store.get('/n');
|
|
211
|
+
|
|
212
|
+
// Promote by adding child
|
|
213
|
+
await store.set(createNode('/n/child', 'item'));
|
|
214
|
+
|
|
215
|
+
// OCC should still work on promoted node
|
|
216
|
+
await assert.rejects(() => store.set({ ...n!, $rev: 999 }));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('getChildren applies sift query filter', async () => {
|
|
220
|
+
const store = await setup();
|
|
221
|
+
await store.set(createNode('/tasks', 'dir'));
|
|
222
|
+
await store.set(createNode('/tasks/t1', 'task', { status: 'pending' }));
|
|
223
|
+
await store.set(createNode('/tasks/t2', 'task', { status: 'done' }));
|
|
224
|
+
await store.set(createNode('/tasks/t3', 'task', { status: 'pending' }));
|
|
225
|
+
|
|
226
|
+
const pending = await store.getChildren('/tasks', {
|
|
227
|
+
query: mapSiftQuery({ $type: 't.task', status: 'pending' }) as Record<string, unknown>,
|
|
228
|
+
});
|
|
229
|
+
assert.equal(pending.items.length, 2);
|
|
230
|
+
assert.deepEqual(pending.items.map(n => n.$path).sort(), ['/tasks/t1', '/tasks/t3']);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('getChildren sift filter excludes non-matching types', async () => {
|
|
234
|
+
const store = await setup();
|
|
235
|
+
await store.set(createNode('/parent', 'config'));
|
|
236
|
+
await store.set(createNode('/parent/mount-a', 'mount-point'));
|
|
237
|
+
await store.set(createNode('/parent/mount-b', 'mount-point'));
|
|
238
|
+
await store.set(createNode('/parent/t1', 'task', { status: 'pending' }));
|
|
239
|
+
await store.set(createNode('/parent/t2', 'task', { status: 'done' }));
|
|
240
|
+
|
|
241
|
+
const tasks = await store.getChildren('/parent', {
|
|
242
|
+
query: mapSiftQuery({ $type: 't.task' }) as Record<string, unknown>,
|
|
243
|
+
});
|
|
244
|
+
assert.equal(tasks.items.length, 2);
|
|
245
|
+
assert.ok(tasks.items.every(n => n.$type === 't.task'));
|
|
246
|
+
});
|
|
247
|
+
});
|
package/src/tree/fs.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Treenity FS Tree — Layer 1
|
|
2
|
+
// Stores nodes as JSON files on disk.
|
|
3
|
+
// Leaf nodes → name.json, directory nodes (with children) → name/$.json
|
|
4
|
+
// Auto-promotes leaf→dir when children appear, demotes dir→leaf when last child removed.
|
|
5
|
+
|
|
6
|
+
import type { NodeData } from '#core';
|
|
7
|
+
import { dirname as treeDirname } from '#core/path';
|
|
8
|
+
import { mkdir, readdir, readFile, rmdir, unlink, writeFile } from 'node:fs/promises';
|
|
9
|
+
import { dirname, join, resolve } from 'node:path';
|
|
10
|
+
import sift from 'sift';
|
|
11
|
+
import type { Tree } from './index';
|
|
12
|
+
import { paginate } from './index';
|
|
13
|
+
import { defaultPatch } from './patch';
|
|
14
|
+
import { mapNodeForSift } from './query';
|
|
15
|
+
|
|
16
|
+
function securityCheck(root: string, file: string) {
|
|
17
|
+
if (!file.startsWith(resolve(root))) throw new Error(`Path traversal blocked`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function createFsTree(rootDir: string): Promise<Tree> {
|
|
21
|
+
rootDir = resolve(rootDir);
|
|
22
|
+
await mkdir(rootDir, { recursive: true });
|
|
23
|
+
|
|
24
|
+
// Per-path write queue — serializes concurrent writes to the same file
|
|
25
|
+
const writeQueues = new Map<string, Promise<void>>();
|
|
26
|
+
function enqueue(path: string, fn: () => Promise<void>): Promise<void> {
|
|
27
|
+
const prev = writeQueues.get(path) ?? Promise.resolve();
|
|
28
|
+
const next = prev.then(fn, fn);
|
|
29
|
+
const cleanup = () => { if (writeQueues.get(path) === next) writeQueues.delete(path); };
|
|
30
|
+
next.then(cleanup, cleanup);
|
|
31
|
+
writeQueues.set(path, next);
|
|
32
|
+
return next;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Read node from whichever form exists: dir (path/$.json) or leaf (path.json)
|
|
36
|
+
async function readNode(path: string): Promise<NodeData | undefined> {
|
|
37
|
+
const dirFile = resolve(join(rootDir, path, '$.json'));
|
|
38
|
+
securityCheck(rootDir, dirFile);
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(await readFile(dirFile, 'utf-8'));
|
|
41
|
+
} catch (e: any) {
|
|
42
|
+
if (e.code === 'ENOENT') { /* fall through to leaf form */ }
|
|
43
|
+
else throw e; // SyntaxError = corrupted JSON, propagate loudly
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (path !== '/') {
|
|
47
|
+
const leafFile = resolve(join(rootDir, path + '.json'));
|
|
48
|
+
securityCheck(rootDir, leafFile);
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(await readFile(leafFile, 'utf-8'));
|
|
51
|
+
} catch (e: any) {
|
|
52
|
+
if (e.code === 'ENOENT') { /* not found */ }
|
|
53
|
+
else throw e;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Promote a node from leaf form (path.json) to dir form (path/$.json)
|
|
61
|
+
async function promoteIfNeeded(path: string): Promise<void> {
|
|
62
|
+
if (path === '/') return;
|
|
63
|
+
const leafFile = resolve(join(rootDir, path + '.json'));
|
|
64
|
+
try {
|
|
65
|
+
const data = await readFile(leafFile, 'utf-8');
|
|
66
|
+
const dir = resolve(join(rootDir, path));
|
|
67
|
+
await mkdir(dir, { recursive: true });
|
|
68
|
+
await writeFile(join(dir, '$.json'), data);
|
|
69
|
+
await unlink(leafFile);
|
|
70
|
+
} catch (e: any) {
|
|
71
|
+
if (e.code !== 'ENOENT') throw e;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Promote all ancestors that might be in leaf form
|
|
76
|
+
async function promoteAncestors(path: string): Promise<void> {
|
|
77
|
+
const parts = path === '/' ? [] : path.slice(1).split('/');
|
|
78
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
79
|
+
await promoteIfNeeded('/' + parts.slice(0, i + 1).join('/'));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if a node's directory has children (entries beyond $.json)
|
|
84
|
+
async function hasChildren(path: string): Promise<boolean> {
|
|
85
|
+
const dir = resolve(join(rootDir, path));
|
|
86
|
+
try {
|
|
87
|
+
const entries = await readdir(dir);
|
|
88
|
+
return entries.some(e => e !== '$.json');
|
|
89
|
+
} catch (e: any) {
|
|
90
|
+
if (e.code === 'ENOENT') return false;
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// After removing a node, clean up empty dirs and demote childless parents
|
|
96
|
+
async function cleanupAfterRemove(removedPath: string): Promise<void> {
|
|
97
|
+
// Remove the node's now-empty directory if it exists
|
|
98
|
+
try {
|
|
99
|
+
const nodeDir = resolve(join(rootDir, removedPath));
|
|
100
|
+
const entries = await readdir(nodeDir);
|
|
101
|
+
if (entries.length === 0) await rmdir(nodeDir);
|
|
102
|
+
} catch (e: any) {
|
|
103
|
+
if (e.code !== 'ENOENT') console.error(`[fs] cleanup error at ${removedPath}:`, e);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Walk up and demote parents that lost their last child
|
|
107
|
+
let current = treeDirname(removedPath);
|
|
108
|
+
while (current && current !== '/') {
|
|
109
|
+
const dir = resolve(join(rootDir, current));
|
|
110
|
+
try {
|
|
111
|
+
const entries = await readdir(dir);
|
|
112
|
+
if (entries.length === 1 && entries[0] === '$.json') {
|
|
113
|
+
// Only $.json remains — demote to leaf form
|
|
114
|
+
const data = await readFile(join(dir, '$.json'), 'utf-8');
|
|
115
|
+
await unlink(join(dir, '$.json'));
|
|
116
|
+
await rmdir(dir);
|
|
117
|
+
await writeFile(resolve(join(rootDir, current + '.json')), data);
|
|
118
|
+
} else if (entries.length === 0) {
|
|
119
|
+
await rmdir(dir);
|
|
120
|
+
} else {
|
|
121
|
+
break; // still has children
|
|
122
|
+
}
|
|
123
|
+
} catch (e: any) {
|
|
124
|
+
if (e.code !== 'ENOENT') console.error(`[fs] demotion walk error at ${current}:`, e);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
current = treeDirname(current);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Collect children of a tree path up to given depth by walking only the relevant FS subtree
|
|
132
|
+
async function collectChildren(parent: string, depth: number): Promise<NodeData[]> {
|
|
133
|
+
const results: NodeData[] = [];
|
|
134
|
+
const fsDir = resolve(join(rootDir, parent));
|
|
135
|
+
|
|
136
|
+
async function walk(dir: string, currentDepth: number) {
|
|
137
|
+
let entries;
|
|
138
|
+
try { entries = await readdir(dir, { withFileTypes: true }); } catch (e: any) {
|
|
139
|
+
if (e.code === 'ENOENT') return;
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const e of entries) {
|
|
144
|
+
if (e.name === '$.json') continue; // parent's own data, not a child
|
|
145
|
+
const full = join(dir, e.name);
|
|
146
|
+
|
|
147
|
+
if (e.isDirectory()) {
|
|
148
|
+
// Directory child — read its $.json if exists
|
|
149
|
+
const childPath = full.slice(rootDir.length) || '/';
|
|
150
|
+
const node = await readNode(childPath);
|
|
151
|
+
if (node) results.push(node);
|
|
152
|
+
if (currentDepth < depth) await walk(full, currentDepth + 1);
|
|
153
|
+
} else if (e.name.endsWith('.json')) {
|
|
154
|
+
// Leaf child — name.json
|
|
155
|
+
const node: NodeData = JSON.parse(await readFile(full, 'utf-8'));
|
|
156
|
+
results.push(node);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await walk(fsDir, 1);
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const tree: Tree = {
|
|
166
|
+
async get(path) {
|
|
167
|
+
return readNode(path);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async getChildren(parent, opts) {
|
|
171
|
+
const depth = opts?.depth ?? 1;
|
|
172
|
+
let filtered = await collectChildren(parent, depth);
|
|
173
|
+
if (opts?.query) {
|
|
174
|
+
const test = sift(opts.query);
|
|
175
|
+
filtered = filtered.filter(n => test(mapNodeForSift(n)));
|
|
176
|
+
}
|
|
177
|
+
return paginate(filtered, opts);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
async set(node) {
|
|
181
|
+
return enqueue(node.$path, async () => {
|
|
182
|
+
const path = node.$path;
|
|
183
|
+
|
|
184
|
+
await promoteAncestors(path);
|
|
185
|
+
|
|
186
|
+
// OCC check
|
|
187
|
+
if (node.$rev != null) {
|
|
188
|
+
const existing = await readNode(path);
|
|
189
|
+
if (!existing) {
|
|
190
|
+
throw new Error(`OptimisticConcurrencyError: node ${path} does not exist but $rev was provided`);
|
|
191
|
+
}
|
|
192
|
+
if (existing.$rev !== node.$rev) {
|
|
193
|
+
throw new Error(`OptimisticConcurrencyError: node ${path} modified by another transaction. Expected $rev ${existing.$rev}, got ${node.$rev}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
node.$rev = (node.$rev ?? 0) + 1;
|
|
198
|
+
const data = JSON.stringify(node, null, 2) + '\n';
|
|
199
|
+
|
|
200
|
+
if (path === '/' || await hasChildren(path)) {
|
|
201
|
+
// Dir form: has children
|
|
202
|
+
const dirFile = resolve(join(rootDir, path, '$.json'));
|
|
203
|
+
securityCheck(rootDir, dirFile);
|
|
204
|
+
await mkdir(resolve(join(rootDir, path)), { recursive: true });
|
|
205
|
+
await writeFile(dirFile, data);
|
|
206
|
+
// Clean up stale leaf form
|
|
207
|
+
if (path !== '/') {
|
|
208
|
+
try { await unlink(resolve(join(rootDir, path + '.json'))); } catch (e: any) { if (e.code !== 'ENOENT') throw e; }
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
// Leaf form: no children
|
|
212
|
+
const leafFile = resolve(join(rootDir, path + '.json'));
|
|
213
|
+
securityCheck(rootDir, leafFile);
|
|
214
|
+
await mkdir(dirname(leafFile), { recursive: true });
|
|
215
|
+
await writeFile(leafFile, data);
|
|
216
|
+
// Clean up stale dir form + empty dir
|
|
217
|
+
try { await unlink(resolve(join(rootDir, path, '$.json'))); } catch (e: any) { if (e.code !== 'ENOENT') throw e; }
|
|
218
|
+
try { await rmdir(resolve(join(rootDir, path))); } catch (e: any) { if (e.code !== 'ENOENT' && e.code !== 'ENOTEMPTY') throw e; }
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
async remove(path) {
|
|
224
|
+
// Try dir form first
|
|
225
|
+
const dirFile = resolve(join(rootDir, path, '$.json'));
|
|
226
|
+
securityCheck(rootDir, dirFile);
|
|
227
|
+
try {
|
|
228
|
+
await unlink(dirFile);
|
|
229
|
+
await cleanupAfterRemove(path);
|
|
230
|
+
return true;
|
|
231
|
+
} catch (e: any) {
|
|
232
|
+
if (e.code !== 'ENOENT') throw e;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Try leaf form
|
|
236
|
+
if (path !== '/') {
|
|
237
|
+
const leafFile = resolve(join(rootDir, path + '.json'));
|
|
238
|
+
securityCheck(rootDir, leafFile);
|
|
239
|
+
try {
|
|
240
|
+
await unlink(leafFile);
|
|
241
|
+
await cleanupAfterRemove(path);
|
|
242
|
+
return true;
|
|
243
|
+
} catch (e: any) {
|
|
244
|
+
if (e.code !== 'ENOENT') throw e;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return false;
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
async patch(path, ops, ctx) {
|
|
252
|
+
return defaultPatch(readNode, (n) => tree.set(n, ctx), path, ops, ctx);
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
return tree;
|
|
257
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createNode } from '#core';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { describe, it } from 'node:test';
|
|
4
|
+
import { createMemoryTree, createOverlayTree } from './index';
|
|
5
|
+
|
|
6
|
+
describe('MemoryStore', () => {
|
|
7
|
+
it('set and get', async () => {
|
|
8
|
+
const store = createMemoryTree();
|
|
9
|
+
const node = createNode('/tasks/1', 'task');
|
|
10
|
+
await store.set(node);
|
|
11
|
+
assert.deepEqual(await store.get('/tasks/1'), node);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('get returns undefined for missing', async () => {
|
|
15
|
+
const store = createMemoryTree();
|
|
16
|
+
assert.equal(await store.get('/nope'), undefined);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('getChildren returns direct children only', async () => {
|
|
20
|
+
const store = createMemoryTree();
|
|
21
|
+
await store.set(createNode('/bot', 'bot'));
|
|
22
|
+
await store.set(createNode('/bot/commands', 'dir'));
|
|
23
|
+
await store.set(createNode('/bot/commands/start', 'page'));
|
|
24
|
+
await store.set(createNode('/bot/commands/help', 'page'));
|
|
25
|
+
await store.set(createNode('/bot/config', 'config'));
|
|
26
|
+
|
|
27
|
+
const { items: botChildren } = await store.getChildren('/bot');
|
|
28
|
+
assert.equal(botChildren.length, 2);
|
|
29
|
+
assert.deepEqual(botChildren.map((n) => n.$path).sort(), ['/bot/commands', '/bot/config']);
|
|
30
|
+
|
|
31
|
+
const { items: commands } = await store.getChildren('/bot/commands');
|
|
32
|
+
assert.equal(commands.length, 2);
|
|
33
|
+
assert.deepEqual(commands.map((n) => n.$path).sort(), [
|
|
34
|
+
'/bot/commands/help',
|
|
35
|
+
'/bot/commands/start',
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('getChildren with depth=2 returns two levels', async () => {
|
|
40
|
+
const store = createMemoryTree();
|
|
41
|
+
await store.set(createNode('/a', 'dir'));
|
|
42
|
+
await store.set(createNode('/a/b', 'dir'));
|
|
43
|
+
await store.set(createNode('/a/b/c', 'item'));
|
|
44
|
+
await store.set(createNode('/a/b/c/d', 'item'));
|
|
45
|
+
await store.set(createNode('/a/x', 'item'));
|
|
46
|
+
|
|
47
|
+
const { items } = await store.getChildren('/a', { depth: 2 });
|
|
48
|
+
assert.equal(items.length, 3);
|
|
49
|
+
assert.deepEqual(items.map((n) => n.$path).sort(), ['/a/b', '/a/b/c', '/a/x']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('getChildren with depth=Infinity returns all descendants', async () => {
|
|
53
|
+
const store = createMemoryTree();
|
|
54
|
+
await store.set(createNode('/r', 'root'));
|
|
55
|
+
await store.set(createNode('/r/a', 'dir'));
|
|
56
|
+
await store.set(createNode('/r/a/b', 'dir'));
|
|
57
|
+
await store.set(createNode('/r/a/b/c', 'item'));
|
|
58
|
+
|
|
59
|
+
const result = await store.getChildren('/r', { depth: Infinity });
|
|
60
|
+
assert.equal(result.items.length, 3);
|
|
61
|
+
assert.deepEqual(result.items.map((n) => n.$path).sort(), ['/r/a', '/r/a/b', '/r/a/b/c']);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('remove', async () => {
|
|
65
|
+
const store = createMemoryTree();
|
|
66
|
+
await store.set(createNode('/x', 'x'));
|
|
67
|
+
assert.equal(await store.remove('/x'), true);
|
|
68
|
+
assert.equal(await store.get('/x'), undefined);
|
|
69
|
+
assert.equal(await store.remove('/x'), false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('getChildren with limit', async () => {
|
|
73
|
+
const store = createMemoryTree();
|
|
74
|
+
await store.set(createNode('/p', 'dir'));
|
|
75
|
+
await store.set(createNode('/p/a', 'item'));
|
|
76
|
+
await store.set(createNode('/p/b', 'item'));
|
|
77
|
+
await store.set(createNode('/p/c', 'item'));
|
|
78
|
+
|
|
79
|
+
const result = await store.getChildren('/p', { limit: 2 });
|
|
80
|
+
assert.equal(result.items.length, 2);
|
|
81
|
+
assert.equal(result.total, 3);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('remove does not cascade to children', async () => {
|
|
85
|
+
const store = createMemoryTree();
|
|
86
|
+
await store.set(createNode('/a', 'dir'));
|
|
87
|
+
await store.set(createNode('/a/b', 'item'));
|
|
88
|
+
await store.set(createNode('/a/b/c', 'item'));
|
|
89
|
+
assert.equal(await store.remove('/a'), true);
|
|
90
|
+
assert.equal(await store.get('/a'), undefined);
|
|
91
|
+
assert.equal((await store.get('/a/b'))?.$type, 't.item');
|
|
92
|
+
assert.equal((await store.get('/a/b/c'))?.$type, 't.item');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('getChildren with limit and offset', async () => {
|
|
96
|
+
const store = createMemoryTree();
|
|
97
|
+
await store.set(createNode('/p', 'dir'));
|
|
98
|
+
await store.set(createNode('/p/a', 'item'));
|
|
99
|
+
await store.set(createNode('/p/b', 'item'));
|
|
100
|
+
await store.set(createNode('/p/c', 'item'));
|
|
101
|
+
|
|
102
|
+
const all = await store.getChildren('/p');
|
|
103
|
+
const result = await store.getChildren('/p', { limit: 2, offset: 1 });
|
|
104
|
+
assert.equal(result.items.length, 2);
|
|
105
|
+
assert.equal(result.total, 3);
|
|
106
|
+
assert.deepEqual(result.items, all.items.slice(1, 3));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('get returns isolated copy — mutating result does not affect stored node', async () => {
|
|
110
|
+
const store = createMemoryTree();
|
|
111
|
+
await store.set(createNode('/x', 'item', { tags: ['a', 'b'] }));
|
|
112
|
+
|
|
113
|
+
const copy = await store.get('/x') as any;
|
|
114
|
+
copy.tags.push('mutated');
|
|
115
|
+
copy.extra = 'injected';
|
|
116
|
+
|
|
117
|
+
const stored = await store.get('/x') as any;
|
|
118
|
+
assert.deepEqual(stored.tags, ['a', 'b']);
|
|
119
|
+
assert.equal(stored.extra, undefined);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('set stores isolated copy — mutating original after set does not affect stored node', async () => {
|
|
123
|
+
const store = createMemoryTree();
|
|
124
|
+
const node = createNode('/y', 'item', { value: 1 }) as any;
|
|
125
|
+
await store.set(node);
|
|
126
|
+
|
|
127
|
+
node.value = 999;
|
|
128
|
+
node.injected = true;
|
|
129
|
+
|
|
130
|
+
const stored = await store.get('/y') as any;
|
|
131
|
+
assert.equal(stored.value, 1);
|
|
132
|
+
assert.equal(stored.injected, undefined);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('OverlayStore', () => {
|
|
138
|
+
it('reads from upper first', async () => {
|
|
139
|
+
const upper = createMemoryTree();
|
|
140
|
+
const lower = createMemoryTree();
|
|
141
|
+
await upper.set(createNode('/a', 'upper'));
|
|
142
|
+
await lower.set(createNode('/a', 'lower'));
|
|
143
|
+
const store = createOverlayTree(upper, lower);
|
|
144
|
+
const node = await store.get('/a');
|
|
145
|
+
assert.equal(node?.$type, 't.upper');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('falls back to lower on miss', async () => {
|
|
149
|
+
const upper = createMemoryTree();
|
|
150
|
+
const lower = createMemoryTree();
|
|
151
|
+
await lower.set(createNode('/b', 'lower'));
|
|
152
|
+
const store = createOverlayTree(upper, lower);
|
|
153
|
+
assert.equal((await store.get('/b'))?.$type, 't.lower');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('writes go to upper only', async () => {
|
|
157
|
+
const upper = createMemoryTree();
|
|
158
|
+
const lower = createMemoryTree();
|
|
159
|
+
const store = createOverlayTree(upper, lower);
|
|
160
|
+
await store.set(createNode('/c', 'new'));
|
|
161
|
+
assert.equal((await upper.get('/c'))?.$type, 't.new');
|
|
162
|
+
assert.equal(await lower.get('/c'), undefined);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('getChildren merges, upper wins', async () => {
|
|
166
|
+
const upper = createMemoryTree();
|
|
167
|
+
const lower = createMemoryTree();
|
|
168
|
+
await lower.set(createNode('/p/a', 'lower'));
|
|
169
|
+
await lower.set(createNode('/p/b', 'lower'));
|
|
170
|
+
await upper.set(createNode('/p/b', 'upper'));
|
|
171
|
+
await upper.set(createNode('/p/c', 'upper'));
|
|
172
|
+
const store = createOverlayTree(upper, lower);
|
|
173
|
+
const { items } = await store.getChildren('/p');
|
|
174
|
+
assert.equal(items.length, 3);
|
|
175
|
+
const map = Object.fromEntries(items.map((n) => [n.$path, n.$type]));
|
|
176
|
+
assert.equal(map['/p/a'], 't.lower');
|
|
177
|
+
assert.equal(map['/p/b'], 't.upper');
|
|
178
|
+
assert.equal(map['/p/c'], 't.upper');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('remove only affects upper', async () => {
|
|
182
|
+
const upper = createMemoryTree();
|
|
183
|
+
const lower = createMemoryTree();
|
|
184
|
+
await upper.set(createNode('/x', 'upper'));
|
|
185
|
+
await lower.set(createNode('/y', 'lower'));
|
|
186
|
+
const store = createOverlayTree(upper, lower);
|
|
187
|
+
assert.equal(await store.remove('/x'), true);
|
|
188
|
+
assert.equal(await store.remove('/y'), false);
|
|
189
|
+
});
|
|
190
|
+
});
|