@treenity/core 1.0.48 → 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/chain.d.ts +20 -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 +78 -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 +42 -0
- package/dist/comp/index.d.ts.map +1 -0
- package/dist/comp/index.js +126 -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 +41 -0
- package/dist/core/component.d.ts.map +1 -0
- package/dist/core/component.js +105 -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 +37 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/registry.d.ts +16 -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/log.d.ts +8 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +31 -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 +18 -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 +18 -0
- package/dist/mod/loader.d.ts.map +1 -0
- package/dist/mod/loader.js +154 -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/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 +3 -0
- package/dist/mods/mcp/server.d.ts.map +1 -0
- package/dist/mods/mcp/server.js +3 -0
- package/dist/mods/mcp/server.js.map +1 -0
- package/dist/mods/mcp/service.d.ts +2 -0
- package/dist/mods/mcp/service.d.ts.map +1 -0
- package/dist/mods/mcp/service.js +17 -0
- package/dist/mods/mcp/service.js.map +1 -0
- package/dist/mods/mcp/types.d.ts +5 -0
- package/dist/mods/mcp/types.d.ts.map +1 -0
- package/dist/mods/mcp/types.js +7 -0
- package/dist/mods/mcp/types.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/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/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 +53 -0
- package/dist/mods/treenity/seed.js.map +1 -0
- package/dist/mods/treenity/server.d.ts +5 -0
- package/dist/mods/treenity/server.d.ts.map +1 -0
- package/dist/mods/treenity/server.js +5 -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 +94 -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/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/test-opaque.d.ts +3 -0
- package/dist/schema/test-opaque.d.ts.map +1 -0
- package/dist/schema/test-opaque.js +43 -0
- package/dist/schema/test-opaque.js.map +1 -0
- package/dist/schema/types.d.ts +37 -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 +335 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/client.d.ts +183 -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 +244 -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 +23 -0
- package/dist/server/factory.d.ts.map +1 -0
- package/dist/server/factory.js +70 -0
- package/dist/server/factory.js.map +1 -0
- package/dist/server/main.d.ts +6 -0
- package/dist/server/main.d.ts.map +1 -0
- package/dist/server/main.js +45 -0
- package/dist/server/main.js.map +1 -0
- package/dist/server/mcp.d.ts +5 -0
- package/dist/server/mcp.d.ts.map +1 -0
- package/dist/server/mcp.js +280 -0
- package/dist/server/mcp.js.map +1 -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 +4 -0
- package/dist/server/mods-mount.d.ts.map +1 -0
- package/dist/server/mods-mount.js +169 -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 +75 -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 +70 -0
- package/dist/server/prefab.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 +117 -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 +192 -0
- package/dist/server/trpc.d.ts.map +1 -0
- package/dist/server/trpc.js +319 -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 +274 -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 +182 -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 +18 -0
- package/dist/tree-chain.d.ts.map +1 -0
- package/dist/tree-chain.js +109 -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 +79 -0
- package/dist/uri.js.map +1 -0
- package/package.json +39 -40
- package/src/chain.test.ts +190 -0
- package/src/chain.ts +82 -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 +91 -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 +129 -0
- package/src/comp/index.ts +175 -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 +151 -0
- package/src/core/context.ts +14 -0
- package/src/core/index.test.ts +203 -0
- package/src/core/index.ts +9 -0
- package/src/core/path.ts +35 -0
- package/src/core/registry.ts +115 -0
- package/src/core.ts +1 -0
- package/src/log.test.ts +70 -0
- package/src/log.ts +28 -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 +100 -0
- package/src/mod/docs/08-services.md +33 -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 +65 -0
- package/src/mod/docs/13-example.md +132 -0
- package/src/mod/docs/14-mod-format.md +304 -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 +20 -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 +168 -0
- package/src/mod/loader.ts +174 -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/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/mcp/CLAUDE.md +6 -0
- package/src/mods/mcp/server.ts +2 -0
- package/src/mods/mcp/service.ts +19 -0
- package/src/mods/mcp/types.ts +7 -0
- package/src/mods/treenity/agent-port.ts +93 -0
- package/src/mods/treenity/groups.ts +19 -0
- package/src/mods/treenity/mod-type.ts +10 -0
- package/src/mods/treenity/seed.ts +56 -0
- package/src/mods/treenity/server.ts +4 -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 +110 -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/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/autostart.json +44 -0
- package/src/schema/generated/board.column.json +25 -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/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/groups.json +20 -0
- package/src/schema/generated/launcher.json +91 -0
- package/src/schema/generated/mcp.server.json +15 -0
- package/src/schema/generated/mindmap.map.json +22 -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.llm.json +20 -0
- package/src/schema/generated/t.mod.json +27 -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/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/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/test-opaque.ts +42 -0
- package/src/schema/types.ts +33 -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 +454 -0
- package/src/server/auth.test.ts +441 -0
- package/src/server/auth.ts +386 -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 +286 -0
- package/src/server/e2e.test.ts +966 -0
- package/src/server/errors.ts +11 -0
- package/src/server/factory.ts +99 -0
- package/src/server/main.ts +56 -0
- package/src/server/mcp.ts +326 -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 +179 -0
- package/src/server/mount-adapters.ts +82 -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 +497 -0
- package/src/server/mount.ts +208 -0
- package/src/server/prefab.test.ts +415 -0
- package/src/server/prefab.ts +104 -0
- package/src/server/seed/index.ts +24 -0
- package/src/server/server.ts +153 -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 +369 -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 +251 -0
- package/src/tree/index.test.ts +164 -0
- package/src/tree/index.ts +216 -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 +723 -0
- package/src/tree-chain.ts +144 -0
- package/src/tree.ts +1 -0
- package/src/uri.test.ts +86 -0
- package/src/uri.ts +82 -0
- package/CHANGELOG.md +0 -314
- package/README.md +0 -18
- 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.d.ts +0 -13
- package/dist/index.d.ts.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,323 @@
|
|
|
1
|
+
import { createNode, register } from '#core';
|
|
2
|
+
import { clearRegistry } from '#core/index.test';
|
|
3
|
+
import { withSubscriptions } from '#server/sub';
|
|
4
|
+
import { createMemoryTree, resolveRef } from '#tree';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { beforeEach, describe, it } from 'node:test';
|
|
7
|
+
import { type ServiceCtx, type ServiceHandle, startServices, type StoreEvent } from './index';
|
|
8
|
+
|
|
9
|
+
describe('resolveRef', () => {
|
|
10
|
+
it('returns node as-is when not a ref', async () => {
|
|
11
|
+
const store = createMemoryTree();
|
|
12
|
+
const node = createNode('/a', 'dir');
|
|
13
|
+
await store.set(node);
|
|
14
|
+
assert.deepEqual(await resolveRef(store, node), node);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('resolves ref to target node', async () => {
|
|
18
|
+
const store = createMemoryTree();
|
|
19
|
+
const target = createNode('/bot', 'bot');
|
|
20
|
+
await store.set(target);
|
|
21
|
+
const refNode = { $path: '/sys/autostart/bot', $type: 'ref', $ref: '/bot' } as any;
|
|
22
|
+
await store.set(refNode);
|
|
23
|
+
assert.deepEqual(await resolveRef(store, refNode), target);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('throws on broken ref', async () => {
|
|
27
|
+
const store = createMemoryTree();
|
|
28
|
+
const refNode = { $path: '/sys/autostart/x', $type: 'ref', $ref: '/missing' } as any;
|
|
29
|
+
await assert.rejects(() => resolveRef(store, refNode));
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('startServices', () => {
|
|
34
|
+
beforeEach(() => clearRegistry());
|
|
35
|
+
|
|
36
|
+
it('returns null when no autostart node', async () => {
|
|
37
|
+
const store = createMemoryTree();
|
|
38
|
+
assert.equal(await startServices(store, () => () => {}), null);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('starts autostart service', async () => {
|
|
42
|
+
let started = false,
|
|
43
|
+
stopped = false;
|
|
44
|
+
register('autostart', 'service', async () => {
|
|
45
|
+
started = true;
|
|
46
|
+
return {
|
|
47
|
+
stop: async () => {
|
|
48
|
+
stopped = true;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const store = createMemoryTree();
|
|
54
|
+
await store.set(createNode('/sys/autostart', 'autostart'));
|
|
55
|
+
const handle = await startServices(store, () => () => {});
|
|
56
|
+
|
|
57
|
+
assert.equal(started, true);
|
|
58
|
+
assert.ok(handle);
|
|
59
|
+
await handle.stop();
|
|
60
|
+
assert.equal(stopped, true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('autostart walks children and starts services', async () => {
|
|
64
|
+
const log: string[] = [];
|
|
65
|
+
|
|
66
|
+
register('autostart', 'service', async (node, ctx) => {
|
|
67
|
+
const { items } = await ctx.store.getChildren(node.$path);
|
|
68
|
+
const handles: ServiceHandle[] = [];
|
|
69
|
+
for (const child of items) {
|
|
70
|
+
const target = await resolveRef(ctx.store, child);
|
|
71
|
+
const { resolve } = await import('#core');
|
|
72
|
+
const handler = resolve(target.$type, 'service');
|
|
73
|
+
if (!handler) continue;
|
|
74
|
+
try {
|
|
75
|
+
handles.push(await handler(target, ctx));
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
stop: async () => {
|
|
80
|
+
for (const h of handles) await h.stop();
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
register('echo', 'service', async (node) => {
|
|
86
|
+
log.push(`start:${node.$path}`);
|
|
87
|
+
return {
|
|
88
|
+
stop: async () => {
|
|
89
|
+
log.push(`stop:${node.$path}`);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const store = createMemoryTree();
|
|
95
|
+
await store.set(createNode('/sys/autostart', 'autostart'));
|
|
96
|
+
await store.set(createNode('/sys/autostart/a', 'echo'));
|
|
97
|
+
const target = createNode('/srv/b', 'echo');
|
|
98
|
+
await store.set(target);
|
|
99
|
+
await store.set({ $path: '/sys/autostart/b', $type: 'ref', $ref: '/srv/b' } as any);
|
|
100
|
+
|
|
101
|
+
const handle = await startServices(store, () => () => {})!;
|
|
102
|
+
assert.deepEqual(log, ['start:/sys/autostart/a', 'start:/srv/b']);
|
|
103
|
+
|
|
104
|
+
await handle!.stop();
|
|
105
|
+
assert.deepEqual(log, [
|
|
106
|
+
'start:/sys/autostart/a',
|
|
107
|
+
'start:/srv/b',
|
|
108
|
+
'stop:/sys/autostart/a',
|
|
109
|
+
'stop:/srv/b',
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('continues when one service fails to start', async () => {
|
|
114
|
+
const log: string[] = [];
|
|
115
|
+
|
|
116
|
+
register('autostart', 'service', async (node, ctx) => {
|
|
117
|
+
const { items } = await ctx.store.getChildren(node.$path);
|
|
118
|
+
const handles: ServiceHandle[] = [];
|
|
119
|
+
for (const child of items) {
|
|
120
|
+
const target = await resolveRef(ctx.store, child);
|
|
121
|
+
const { resolve } = await import('#core');
|
|
122
|
+
const handler = resolve(target.$type, 'service');
|
|
123
|
+
if (!handler) continue;
|
|
124
|
+
try {
|
|
125
|
+
handles.push(await handler(target, ctx));
|
|
126
|
+
} catch (e) {
|
|
127
|
+
/* swallow */
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
stop: async () => {
|
|
132
|
+
for (const h of handles) await h.stop();
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
register('bad', 'service', async () => {
|
|
138
|
+
throw new Error('boom');
|
|
139
|
+
});
|
|
140
|
+
register('good', 'service', async (node) => {
|
|
141
|
+
log.push('started');
|
|
142
|
+
return {
|
|
143
|
+
stop: async () => {
|
|
144
|
+
log.push('stopped');
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const store = createMemoryTree();
|
|
150
|
+
await store.set(createNode('/sys/autostart', 'autostart'));
|
|
151
|
+
await store.set(createNode('/sys/autostart/a', 'bad'));
|
|
152
|
+
await store.set(createNode('/sys/autostart/b', 'good'));
|
|
153
|
+
|
|
154
|
+
const handle = await startServices(store, () => () => {});
|
|
155
|
+
assert.deepEqual(log, ['started']);
|
|
156
|
+
await handle!.stop();
|
|
157
|
+
assert.deepEqual(log, ['started', 'stopped']);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('ServiceCtx.subscribe', () => {
|
|
162
|
+
beforeEach(() => clearRegistry());
|
|
163
|
+
|
|
164
|
+
it('service receives subscribe in ctx', async () => {
|
|
165
|
+
let receivedSubscribe = false;
|
|
166
|
+
register('watcher', 'service', async (_node, ctx) => {
|
|
167
|
+
receivedSubscribe = typeof ctx.subscribe === 'function';
|
|
168
|
+
return { stop: async () => {} };
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const store = createMemoryTree();
|
|
172
|
+
await store.set(createNode('/sys/autostart', 'watcher'));
|
|
173
|
+
await startServices(store, () => () => {});
|
|
174
|
+
assert.equal(receivedSubscribe, true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('subscribe fires on set', async () => {
|
|
178
|
+
const events: StoreEvent[] = [];
|
|
179
|
+
const reactive = withSubscriptions(createMemoryTree());
|
|
180
|
+
const subscribe: ServiceCtx['subscribe'] = (path, cb) =>
|
|
181
|
+
reactive.subscribe(path, (e) => { if ('path' in e) cb(e as StoreEvent); });
|
|
182
|
+
|
|
183
|
+
const unsub = subscribe('/config/bot', (e) => events.push(e));
|
|
184
|
+
|
|
185
|
+
await reactive.set(createNode('/config/bot', 'dir'));
|
|
186
|
+
assert.equal(events.length, 1);
|
|
187
|
+
assert.equal(events[0].type, 'set');
|
|
188
|
+
assert.equal(events[0].path, '/config/bot');
|
|
189
|
+
|
|
190
|
+
unsub();
|
|
191
|
+
await reactive.set({ ...createNode('/config/bot', 'dir'), name: 'changed' });
|
|
192
|
+
assert.equal(events.length, 1, 'no events after unsub');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('subscribe fires on remove', async () => {
|
|
196
|
+
const events: StoreEvent[] = [];
|
|
197
|
+
const reactive = withSubscriptions(createMemoryTree());
|
|
198
|
+
const subscribe: ServiceCtx['subscribe'] = (path, cb) =>
|
|
199
|
+
reactive.subscribe(path, (e) => { if ('path' in e) cb(e as StoreEvent); });
|
|
200
|
+
|
|
201
|
+
await reactive.set(createNode('/config/bot', 'dir'));
|
|
202
|
+
subscribe('/config/bot', (e) => events.push(e));
|
|
203
|
+
|
|
204
|
+
await reactive.remove('/config/bot');
|
|
205
|
+
assert.equal(events.length, 1);
|
|
206
|
+
assert.equal(events[0].type, 'remove');
|
|
207
|
+
assert.equal(events[0].path, '/config/bot');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('subscribe fires patch on update', async () => {
|
|
211
|
+
const events: StoreEvent[] = [];
|
|
212
|
+
const reactive = withSubscriptions(createMemoryTree());
|
|
213
|
+
const subscribe: ServiceCtx['subscribe'] = (path, cb) =>
|
|
214
|
+
reactive.subscribe(path, (e) => { if ('path' in e) cb(e as StoreEvent); });
|
|
215
|
+
|
|
216
|
+
await reactive.set(createNode('/config/bot', 'dir'));
|
|
217
|
+
subscribe('/config/bot', (e) => events.push(e));
|
|
218
|
+
|
|
219
|
+
await reactive.set({ ...createNode('/config/bot', 'dir'), token: 'abc', $rev: 1 });
|
|
220
|
+
assert.equal(events.length, 1);
|
|
221
|
+
assert.equal(events[0].type, 'patch');
|
|
222
|
+
assert.equal(events[0].path, '/config/bot');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('prefix subscribe catches children', async () => {
|
|
226
|
+
const events: StoreEvent[] = [];
|
|
227
|
+
const reactive = withSubscriptions(createMemoryTree());
|
|
228
|
+
const subscribe: ServiceCtx['subscribe'] = (path, cb, opts) =>
|
|
229
|
+
reactive.subscribe(path, (e) => { if ('path' in e) cb(e as StoreEvent); }, opts);
|
|
230
|
+
|
|
231
|
+
subscribe('/config', (e) => events.push(e), { children: true });
|
|
232
|
+
|
|
233
|
+
await reactive.set(createNode('/config/bot', 'dir'));
|
|
234
|
+
await reactive.set(createNode('/config/db', 'dir'));
|
|
235
|
+
assert.equal(events.length, 2);
|
|
236
|
+
assert.equal(events[0].path, '/config/bot');
|
|
237
|
+
assert.equal(events[1].path, '/config/db');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('exact subscribe does NOT catch children', async () => {
|
|
241
|
+
const events: StoreEvent[] = [];
|
|
242
|
+
const reactive = withSubscriptions(createMemoryTree());
|
|
243
|
+
const subscribe: ServiceCtx['subscribe'] = (path, cb, opts) =>
|
|
244
|
+
reactive.subscribe(path, (e) => { if ('path' in e) cb(e as StoreEvent); }, opts);
|
|
245
|
+
|
|
246
|
+
subscribe('/config', (e) => events.push(e));
|
|
247
|
+
|
|
248
|
+
await reactive.set(createNode('/config/bot', 'dir'));
|
|
249
|
+
assert.equal(events.length, 0, 'exact subscribe should not catch children');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('does not fire for unrelated paths', async () => {
|
|
253
|
+
const events: StoreEvent[] = [];
|
|
254
|
+
const reactive = withSubscriptions(createMemoryTree());
|
|
255
|
+
const subscribe: ServiceCtx['subscribe'] = (path, cb) =>
|
|
256
|
+
reactive.subscribe(path, (e) => { if ('path' in e) cb(e as StoreEvent); });
|
|
257
|
+
|
|
258
|
+
subscribe('/config/bot', (e) => events.push(e));
|
|
259
|
+
|
|
260
|
+
await reactive.set(createNode('/other/thing', 'dir'));
|
|
261
|
+
assert.equal(events.length, 0);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('service hot-reload: watches own config, reacts to admin update', async () => {
|
|
265
|
+
const reactive = withSubscriptions(createMemoryTree());
|
|
266
|
+
const subscribe: ServiceCtx['subscribe'] = (path, cb, opts) =>
|
|
267
|
+
reactive.subscribe(path, (e) => { if ('path' in e) cb(e as StoreEvent); }, opts);
|
|
268
|
+
|
|
269
|
+
// Seed bot config
|
|
270
|
+
await reactive.set(createNode('/config/bot', 'bot-config', { token: 'old-token', lang: 'en' }));
|
|
271
|
+
|
|
272
|
+
// Service starts and subscribes to its own config
|
|
273
|
+
let reloadCount = 0;
|
|
274
|
+
let lastEvent: StoreEvent | null = null;
|
|
275
|
+
const unsub = subscribe('/config/bot', (e) => {
|
|
276
|
+
reloadCount++;
|
|
277
|
+
lastEvent = e;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Admin updates bot token
|
|
281
|
+
const node = (await reactive.get('/config/bot'))!;
|
|
282
|
+
await reactive.set({ ...node, token: 'new-token' });
|
|
283
|
+
assert.equal(reloadCount, 1);
|
|
284
|
+
assert.equal(lastEvent!.type, 'patch');
|
|
285
|
+
|
|
286
|
+
// Admin updates another field
|
|
287
|
+
const node2 = (await reactive.get('/config/bot'))!;
|
|
288
|
+
await reactive.set({ ...node2, lang: 'ru' });
|
|
289
|
+
assert.equal(reloadCount, 2);
|
|
290
|
+
|
|
291
|
+
// Service stops — unsubscribe
|
|
292
|
+
unsub();
|
|
293
|
+
const node3 = (await reactive.get('/config/bot'))!;
|
|
294
|
+
await reactive.set({ ...node3, token: 'post-stop' });
|
|
295
|
+
assert.equal(reloadCount, 2, 'no events after unsub');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('service watches session dir for new users', async () => {
|
|
299
|
+
const reactive = withSubscriptions(createMemoryTree());
|
|
300
|
+
const subscribe: ServiceCtx['subscribe'] = (path, cb, opts) =>
|
|
301
|
+
reactive.subscribe(path, (e) => { if ('path' in e) cb(e as StoreEvent); }, opts);
|
|
302
|
+
|
|
303
|
+
await reactive.set(createNode('/sessions', 'dir'));
|
|
304
|
+
|
|
305
|
+
// Service watches /sessions children for new logins
|
|
306
|
+
const newSessions: string[] = [];
|
|
307
|
+
const unsub = subscribe('/sessions', (e) => {
|
|
308
|
+
if (e.type === 'set') newSessions.push(e.path);
|
|
309
|
+
}, { children: true });
|
|
310
|
+
|
|
311
|
+
// Users log in
|
|
312
|
+
await reactive.set(createNode('/sessions/alice', 'session', { ts: 1 }));
|
|
313
|
+
await reactive.set(createNode('/sessions/bob', 'session', { ts: 2 }));
|
|
314
|
+
assert.deepEqual(newSessions, ['/sessions/alice', '/sessions/bob']);
|
|
315
|
+
|
|
316
|
+
// Exact update to existing session — also caught by children watch
|
|
317
|
+
const alice = (await reactive.get('/sessions/alice'))!;
|
|
318
|
+
await reactive.set({ ...alice, lastSeen: 99 });
|
|
319
|
+
assert.equal(newSessions.length, 2, 'patch events have type patch, not set');
|
|
320
|
+
|
|
321
|
+
unsub();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Treenity Service Context — Layer 2
|
|
2
|
+
// Service = register(type, "service", handler) → returns { stop() }
|
|
3
|
+
|
|
4
|
+
import { type NodeData, resolve as resolveCtx } from '#core';
|
|
5
|
+
import { type Tree } from '#tree';
|
|
6
|
+
|
|
7
|
+
// ── Types ──
|
|
8
|
+
|
|
9
|
+
export type ServiceHandle = { stop(): Promise<void> };
|
|
10
|
+
export type StoreEvent =
|
|
11
|
+
| { type: 'set'; path: string }
|
|
12
|
+
| { type: 'patch'; path: string }
|
|
13
|
+
| { type: 'remove'; path: string };
|
|
14
|
+
export type StoreListener = (event: StoreEvent) => void;
|
|
15
|
+
export type SubscribeOpts = { children?: boolean };
|
|
16
|
+
export type ServiceCtx = {
|
|
17
|
+
store: Tree;
|
|
18
|
+
subscribe: (path: string, cb: StoreListener, opts?: SubscribeOpts) => () => void;
|
|
19
|
+
};
|
|
20
|
+
export type ServiceHandler = (node: NodeData, ctx: ServiceCtx) => Promise<ServiceHandle>;
|
|
21
|
+
|
|
22
|
+
declare module '#core/context' {
|
|
23
|
+
interface ContextHandlers {
|
|
24
|
+
service: ServiceHandler;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── Bootstrap ──
|
|
29
|
+
|
|
30
|
+
export async function startServices(
|
|
31
|
+
store: Tree,
|
|
32
|
+
subscribe: ServiceCtx['subscribe'],
|
|
33
|
+
path = '/sys/autostart',
|
|
34
|
+
): Promise<ServiceHandle | null> {
|
|
35
|
+
const node = await store.get(path);
|
|
36
|
+
if (!node) return null;
|
|
37
|
+
const handler = resolveCtx(node.$type, 'service');
|
|
38
|
+
if (!handler) {
|
|
39
|
+
console.error(`[service] no handler for ${node.$type}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return await handler(node, { store, subscribe });
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './service/index';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Treenity Telegram Binding — Layer 2
|
|
2
|
+
// Connects Grammy bot to Treenity tree.
|
|
3
|
+
// Supports middleware pipelines, event routing, callback params.
|
|
4
|
+
|
|
5
|
+
import { type ComponentData, isComponent, type NodeData, resolve } from '#core';
|
|
6
|
+
import { basename } from '#core/path';
|
|
7
|
+
import { type Tree } from '#tree';
|
|
8
|
+
import { Bot, type Context } from 'grammy';
|
|
9
|
+
|
|
10
|
+
// ── Types ──
|
|
11
|
+
|
|
12
|
+
export type TgCtx = {
|
|
13
|
+
ctx: Context;
|
|
14
|
+
store: Tree;
|
|
15
|
+
node: NodeData;
|
|
16
|
+
params: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type TgHandler = (node: NodeData, tgCtx: TgCtx) => Promise<void>;
|
|
21
|
+
export type TgMiddleware = (tgCtx: TgCtx, next: () => Promise<void>) => Promise<void>;
|
|
22
|
+
|
|
23
|
+
declare module '#core/context' {
|
|
24
|
+
interface ContextHandlers {
|
|
25
|
+
telegram: TgHandler;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type OnComponent = ComponentData & { filter: string };
|
|
30
|
+
|
|
31
|
+
// ── Middleware runner ──
|
|
32
|
+
|
|
33
|
+
function runHandler(middlewares: TgMiddleware[], tgCtx: TgCtx, handler: TgHandler): Promise<void> {
|
|
34
|
+
let i = 0;
|
|
35
|
+
const next = (): Promise<void> => {
|
|
36
|
+
if (i < middlewares.length) return middlewares[i++](tgCtx, next);
|
|
37
|
+
return handler(tgCtx.node, tgCtx);
|
|
38
|
+
};
|
|
39
|
+
return next();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Start bot from tree ──
|
|
43
|
+
|
|
44
|
+
export async function startBot(store: Tree, botPath: string, middlewares: TgMiddleware[] = []) {
|
|
45
|
+
const botNode = await store.get(botPath);
|
|
46
|
+
if (!botNode) throw new Error(`Bot node not found: ${botPath}`);
|
|
47
|
+
|
|
48
|
+
const cfgVal = botNode['config'];
|
|
49
|
+
if (!isComponent(cfgVal)) throw new Error(`Bot node missing config component`);
|
|
50
|
+
const config = cfgVal as ComponentData & { token: string };
|
|
51
|
+
|
|
52
|
+
const bot = new Bot(config.token);
|
|
53
|
+
const commandsPath = botPath + '/commands';
|
|
54
|
+
const onPath = botPath + '/on';
|
|
55
|
+
|
|
56
|
+
// event handlers from /bot/on/ children
|
|
57
|
+
const { items: eventNodes } = await store.getChildren(onPath);
|
|
58
|
+
for (const eventNode of eventNodes) {
|
|
59
|
+
const onVal = eventNode['on'];
|
|
60
|
+
if (!isComponent(onVal)) throw new Error(`Event node ${eventNode.$path} missing "on" component`);
|
|
61
|
+
const on = onVal as OnComponent;
|
|
62
|
+
bot.on(on.filter as Parameters<typeof bot.on>[0], async (ctx) => {
|
|
63
|
+
const handler = resolve(eventNode.$type, 'telegram');
|
|
64
|
+
if (!handler) throw new Error(`No handler for event type "${eventNode.$type}"`);
|
|
65
|
+
const tgCtx: TgCtx = { ctx, store, node: eventNode, params: '' };
|
|
66
|
+
await runHandler(middlewares, tgCtx, handler);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// commands from /bot/commands/ children
|
|
71
|
+
const { items: commandNodes } = await store.getChildren(commandsPath);
|
|
72
|
+
for (const cmdNode of commandNodes) {
|
|
73
|
+
const cmd = basename(cmdNode.$path);
|
|
74
|
+
bot.command(cmd, async (ctx) => {
|
|
75
|
+
const handler = resolve(cmdNode.$type, 'telegram');
|
|
76
|
+
if (!handler) throw new Error(`No handler for command "${cmd}"`);
|
|
77
|
+
const tgCtx: TgCtx = { ctx, store, node: cmdNode, params: '' };
|
|
78
|
+
await runHandler(middlewares, tgCtx, handler);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// callback queries: "action" or "action:param1:param2"
|
|
83
|
+
bot.on('callback_query:data', async (ctx) => {
|
|
84
|
+
const raw = ctx.callbackQuery.data;
|
|
85
|
+
const sep = raw.indexOf(':');
|
|
86
|
+
const action = sep === -1 ? raw : raw.slice(0, sep);
|
|
87
|
+
const params = sep === -1 ? '' : raw.slice(sep + 1);
|
|
88
|
+
|
|
89
|
+
const targetNode = await store.get(`${commandsPath}/${action}`);
|
|
90
|
+
if (!targetNode) return ctx.answerCallbackQuery('Unknown action');
|
|
91
|
+
const handler = resolve(targetNode.$type, 'telegram');
|
|
92
|
+
if (!handler) return ctx.answerCallbackQuery('No handler');
|
|
93
|
+
const tgCtx: TgCtx = { ctx, store, node: targetNode, params };
|
|
94
|
+
await runHandler(middlewares, tgCtx, handler);
|
|
95
|
+
await ctx.answerCallbackQuery();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
bot.catch((err) => console.error('[bot]', err.message ?? err));
|
|
99
|
+
|
|
100
|
+
// text fallback
|
|
101
|
+
bot.on('message:text', async (ctx) => {
|
|
102
|
+
const startNode = await store.get(`${commandsPath}/start`);
|
|
103
|
+
if (startNode) {
|
|
104
|
+
const handler = resolve(startNode.$type, 'telegram');
|
|
105
|
+
if (handler) {
|
|
106
|
+
const tgCtx: TgCtx = { ctx, store, node: startNode, params: '' };
|
|
107
|
+
await runHandler(middlewares, tgCtx, handler);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
await ctx.reply('Try /start');
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return bot;
|
|
115
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resolve } from '#core';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { describe, it } from 'node:test';
|
|
4
|
+
import './index';
|
|
5
|
+
|
|
6
|
+
// Text binding registers on import, but clearRegistry would wipe them.
|
|
7
|
+
// So we test after import, without clearing.
|
|
8
|
+
|
|
9
|
+
describe('Text binding', () => {
|
|
10
|
+
it('string renders value', () => {
|
|
11
|
+
const h = resolve('string', 'text');
|
|
12
|
+
assert.ok(h);
|
|
13
|
+
assert.equal(h({ value: 'hello' }), 'hello');
|
|
14
|
+
assert.equal(h({ value: 42 }), '42');
|
|
15
|
+
assert.equal(h({}), '');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('number renders value', () => {
|
|
19
|
+
const h = resolve('number', 'text');
|
|
20
|
+
assert.ok(h);
|
|
21
|
+
assert.equal(h({ value: 3.14 }), '3.14');
|
|
22
|
+
assert.equal(h({}), '0');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('boolean renders yes/no', () => {
|
|
26
|
+
const h = resolve('boolean', 'text');
|
|
27
|
+
assert.ok(h);
|
|
28
|
+
assert.equal(h({ value: true }), 'yes');
|
|
29
|
+
assert.equal(h({ value: false }), 'no');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Text context — plain text rendering for CLI/server
|
|
2
|
+
// Layer 2 binding. Registers "text" context handlers for primitive types.
|
|
3
|
+
// Block-type text handlers belong in their mods
|
|
4
|
+
|
|
5
|
+
import { register } from '#core';
|
|
6
|
+
|
|
7
|
+
export type TextHandler = (data: Record<string, unknown>) => string;
|
|
8
|
+
|
|
9
|
+
declare module '#core/context' {
|
|
10
|
+
interface ContextHandlers {
|
|
11
|
+
text: TextHandler;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Primitives
|
|
16
|
+
register('string', 'text', (data) => String(data.value ?? ''));
|
|
17
|
+
register('number', 'text', (data) => String(data.value ?? 0));
|
|
18
|
+
register('boolean', 'text', (data) => data.value ? 'yes' : 'no');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './text/index';
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// ── ACL ──
|
|
2
|
+
|
|
3
|
+
export type GroupPerm = { g: string; p: number };
|
|
4
|
+
export const R = 1,
|
|
5
|
+
W = 2,
|
|
6
|
+
A = 4,
|
|
7
|
+
S = 8;
|
|
8
|
+
|
|
9
|
+
// TODO: K extends `$${infer N}` ? never : K
|
|
10
|
+
// TODO: fix ComponentData and NodeData types. it should be generic types of its contents
|
|
11
|
+
export type ComponentData<T = Record<string, unknown>> = T & {
|
|
12
|
+
$type: string;
|
|
13
|
+
$acl?: GroupPerm[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type NodeData<T = Record<string, unknown>> = ComponentData<T> & {
|
|
17
|
+
$path: string;
|
|
18
|
+
$owner?: string;
|
|
19
|
+
$rev?: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
23
|
+
export type Class<T = unknown> = new (...args: any[]) => T;
|
|
24
|
+
|
|
25
|
+
// Accept string, object with $type, or registered class (registerType stamps $type on constructor)
|
|
26
|
+
export type TypeId<T = unknown> = string | { $type: string } | Class<T>;
|
|
27
|
+
|
|
28
|
+
// ── Type normalization ──
|
|
29
|
+
// Dot-less types belong to treenity namespace: 'dir' → 't.dir', 'ref' → 't.ref'
|
|
30
|
+
// Types with dots are already namespaced and returned as-is
|
|
31
|
+
export function normalizeType(type: TypeId): string {
|
|
32
|
+
if (typeof type === 'string') return type.includes('.') ? type : `t.${type}`;
|
|
33
|
+
if ('$type' in type && typeof (type as any).$type === 'string') return normalizeType((type as any).$type);
|
|
34
|
+
throw new Error('TypeId: object has no $type property');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Utils ──
|
|
38
|
+
|
|
39
|
+
export function isComponent(value: unknown): value is ComponentData {
|
|
40
|
+
return typeof value === 'object' && value !== null && '$type' in value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getCompByKey(node: NodeData, key: string): ComponentData | undefined {
|
|
44
|
+
const v = key === '' ? node : node[key];
|
|
45
|
+
return isComponent(v) ? v : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const AnyType = { $type: 'any' };
|
|
49
|
+
|
|
50
|
+
export function isOfType<T>(value: unknown, type: TypeId): value is ComponentData<T> {
|
|
51
|
+
if (!isComponent(value)) return false;
|
|
52
|
+
const t = normalizeType(type);
|
|
53
|
+
return t === 't.any' || normalizeType(value.$type) === t;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Ref ──
|
|
57
|
+
export type Ref = { $type?: string; $ref: string; $map?: string };
|
|
58
|
+
|
|
59
|
+
export function ref(path: string): Ref {
|
|
60
|
+
return { $type: 'ref', $ref: path };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isRef(value: unknown): value is Ref {
|
|
64
|
+
if (!value || typeof value !== 'object') return false;
|
|
65
|
+
const v = value as Record<string, unknown>;
|
|
66
|
+
return typeof v.$ref === 'string' && (!v.$type || v.$type === 'ref' || v.$type === 't.ref');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Node ──
|
|
70
|
+
|
|
71
|
+
export function assertNonSystemName(name: string) {
|
|
72
|
+
if (name.startsWith('$')) throw new Error(`Component name cannot start with $: ${name}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createNode<T = any, C = Record<string, ComponentData<any>>>(
|
|
76
|
+
path: string,
|
|
77
|
+
type: TypeId,
|
|
78
|
+
data?: T,
|
|
79
|
+
components?: C): NodeData<T & C> {
|
|
80
|
+
|
|
81
|
+
const node: NodeData<T & C> = { $path: path, $type: normalizeType(type) } as NodeData<T & C>;
|
|
82
|
+
if (components) Object.keys(components).forEach(assertNonSystemName);
|
|
83
|
+
if (data) Object.keys(data).forEach(assertNonSystemName);
|
|
84
|
+
|
|
85
|
+
Object.assign(node, components, data);
|
|
86
|
+
|
|
87
|
+
return node;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getComponentField<T = unknown>(
|
|
91
|
+
node: NodeData,
|
|
92
|
+
type: TypeId<T>,
|
|
93
|
+
field?: string,
|
|
94
|
+
): [ComponentData<T>, string] | undefined {
|
|
95
|
+
if (field != null) {
|
|
96
|
+
const v = field === '' ? node : node[field];
|
|
97
|
+
if (isOfType<T>(v, type)) return [v, field];
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (isOfType<T>(node, type)) return [node, ''];
|
|
101
|
+
for (const [k, v] of Object.entries(node)) {
|
|
102
|
+
if (k.startsWith('$')) continue;
|
|
103
|
+
if (isOfType<T>(v, type)) return [v, k];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getComponent<T = unknown>(
|
|
108
|
+
node: NodeData,
|
|
109
|
+
type: TypeId<T>,
|
|
110
|
+
field?: string,
|
|
111
|
+
): ComponentData<T> | undefined {
|
|
112
|
+
return getComponentField(node, type, field)?.[0];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getComponents<T = unknown>(
|
|
116
|
+
node: NodeData,
|
|
117
|
+
type: TypeId<T>,
|
|
118
|
+
): [string, ComponentData<T>][] {
|
|
119
|
+
const result: [string, ComponentData<T>][] = [];
|
|
120
|
+
if (isOfType<T>(node, type)) result.push(['', node]);
|
|
121
|
+
for (const [k, v] of Object.entries(node)) {
|
|
122
|
+
if (k.startsWith('$')) continue;
|
|
123
|
+
if (isOfType<T>(v, type)) result.push([k, v]);
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── $ ↔ _ key mapping (Mongo/sift compat) ──
|
|
129
|
+
|
|
130
|
+
export function toStorageKeys(node: Record<string, unknown>): Record<string, unknown> {
|
|
131
|
+
const out: Record<string, unknown> = {};
|
|
132
|
+
for (const [k, v] of Object.entries(node))
|
|
133
|
+
out[k.startsWith('$') ? `_${k.slice(1)}` : k] = v;
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function fromStorageKeys(doc: Record<string, unknown>): Record<string, unknown> {
|
|
138
|
+
const out: Record<string, unknown> = {};
|
|
139
|
+
for (const [k, v] of Object.entries(doc)) {
|
|
140
|
+
if (k === '_id') continue;
|
|
141
|
+
out[k.startsWith('_') ? `$${k.slice(1)}` : k] = v;
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function removeComponent(node: NodeData, name: string): boolean {
|
|
147
|
+
assertNonSystemName(name);
|
|
148
|
+
if (!isComponent(node[name])) return false;
|
|
149
|
+
delete node[name];
|
|
150
|
+
return true;
|
|
151
|
+
}
|