@dxos/app-solid 0.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/LICENSE +8 -0
- package/README.md +21 -0
- package/package.json +48 -0
- package/src/common.ts +16 -0
- package/src/index.ts +10 -0
- package/src/useCapabilities.test.tsx +78 -0
- package/src/useCapabilities.ts +34 -0
- package/src/useOperationResolver.test.tsx +63 -0
- package/src/useOperationResolver.ts +28 -0
- package/src/usePluginManager.test.tsx +48 -0
- package/src/usePluginManager.ts +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
Copyright (c) 2025 DXOS
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @dxos/app-solid
|
|
2
|
+
|
|
3
|
+
SolidJS integration for @dxos/app-framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm i @dxos/app-solid
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## DXOS Resources
|
|
12
|
+
|
|
13
|
+
- [Website](https://dxos.org)
|
|
14
|
+
- [Developer Documentation](https://docs.dxos.org)
|
|
15
|
+
- Talk to us on [Discord](https://dxos.org/discord)
|
|
16
|
+
|
|
17
|
+
## Contributions
|
|
18
|
+
|
|
19
|
+
Your ideas, issues, and code are most welcome. Please take a look at our [community code of conduct](https://github.com/dxos/dxos/blob/main/CODE_OF_CONDUCT.md), the [issue guide](https://github.com/dxos/dxos/blob/main/CONTRIBUTING.md#submitting-issues), and the [PR contribution guide](https://github.com/dxos/dxos/blob/main/CONTRIBUTING.md#submitting-prs).
|
|
20
|
+
|
|
21
|
+
License: [MIT](./LICENSE) Copyright 2025 © DXOS
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dxos/app-solid",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "SolidJS integration for @dxos/app-framework",
|
|
5
|
+
"homepage": "https://dxos.org",
|
|
6
|
+
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "DXOS.org",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"source": "./src/index.ts",
|
|
14
|
+
"types": "./dist/types/src/index.d.ts",
|
|
15
|
+
"browser": "./dist/lib/browser/index.mjs",
|
|
16
|
+
"node": "./dist/lib/node-esm/index.mjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"types": "dist/types/src/index.d.ts",
|
|
20
|
+
"typesVersions": {
|
|
21
|
+
"*": {}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@dxos/app-framework": "0.8.3",
|
|
29
|
+
"@dxos/effect-atom-solid": "0.0.0",
|
|
30
|
+
"@dxos/operation": "0.0.0",
|
|
31
|
+
"@dxos/web-context-solid": "0.0.0",
|
|
32
|
+
"@dxos/invariant": "0.8.3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@effect-atom/atom": "^0.4.10",
|
|
36
|
+
"@solidjs/testing-library": "^0.8.10",
|
|
37
|
+
"effect": "3.19.11",
|
|
38
|
+
"solid-js": "^1.9.9",
|
|
39
|
+
"vite-plugin-solid": "^2.11.10"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"effect": "3.19.11",
|
|
43
|
+
"solid-js": "^1.9.9"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/common.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Accessor } from 'solid-js';
|
|
6
|
+
|
|
7
|
+
import { Common } from '@dxos/app-framework';
|
|
8
|
+
|
|
9
|
+
import { useCapability } from './useCapabilities';
|
|
10
|
+
|
|
11
|
+
export const useOperationInvoker = (): Accessor<Common.Capability.OperationInvoker> =>
|
|
12
|
+
useCapability(Common.Capability.OperationInvoker);
|
|
13
|
+
|
|
14
|
+
export const useAppGraph = (): Accessor<Common.Capability.AppGraph> => useCapability(Common.Capability.AppGraph);
|
|
15
|
+
|
|
16
|
+
export const useLayout = () => useCapability(Common.Capability.Layout);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Atom from '@effect-atom/atom/Atom';
|
|
6
|
+
import { render, screen } from '@solidjs/testing-library';
|
|
7
|
+
import { describe, expect, test } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { Capability, type PluginManager, PluginManagerContext } from '@dxos/app-framework';
|
|
10
|
+
import { ContextProtocolProvider } from '@dxos/web-context-solid';
|
|
11
|
+
|
|
12
|
+
import { useCapabilities, useCapability } from './useCapabilities';
|
|
13
|
+
|
|
14
|
+
const TestCapability = Capability.make<{ value: string }>('test.capability');
|
|
15
|
+
|
|
16
|
+
const mockAtom = Atom.make([{ value: 'hello' }]);
|
|
17
|
+
|
|
18
|
+
const mockManager = {
|
|
19
|
+
capabilities: {
|
|
20
|
+
atom: () => mockAtom,
|
|
21
|
+
},
|
|
22
|
+
} as unknown as PluginManager.PluginManager;
|
|
23
|
+
|
|
24
|
+
describe('useCapabilities', () => {
|
|
25
|
+
test('returns capabilities from the plugin manager', () => {
|
|
26
|
+
const TestComponent = () => {
|
|
27
|
+
const capabilities = useCapabilities(TestCapability);
|
|
28
|
+
return <div data-testid='caps-length'>{capabilities().length}</div>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
render(() => (
|
|
32
|
+
<ContextProtocolProvider context={PluginManagerContext} value={mockManager}>
|
|
33
|
+
<TestComponent />
|
|
34
|
+
</ContextProtocolProvider>
|
|
35
|
+
));
|
|
36
|
+
|
|
37
|
+
expect(screen.getByTestId('caps-length').textContent).toBe('1');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('useCapability', () => {
|
|
42
|
+
test('returns a single capability', () => {
|
|
43
|
+
const TestComponent = () => {
|
|
44
|
+
const capability = useCapability(TestCapability);
|
|
45
|
+
return <div data-testid='cap-value'>{capability().value}</div>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
render(() => (
|
|
49
|
+
<ContextProtocolProvider context={PluginManagerContext} value={mockManager}>
|
|
50
|
+
<TestComponent />
|
|
51
|
+
</ContextProtocolProvider>
|
|
52
|
+
));
|
|
53
|
+
|
|
54
|
+
expect(screen.getByTestId('cap-value').textContent).toBe('hello');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('throws if no capability is found', () => {
|
|
58
|
+
const emptyAtom = Atom.make([]);
|
|
59
|
+
const emptyManager = {
|
|
60
|
+
capabilities: {
|
|
61
|
+
atom: () => emptyAtom,
|
|
62
|
+
},
|
|
63
|
+
} as unknown as PluginManager.PluginManager;
|
|
64
|
+
|
|
65
|
+
const TestComponent = () => {
|
|
66
|
+
const capability = useCapability(TestCapability);
|
|
67
|
+
return <div>{capability().value}</div>; // This read triggers the invariant
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
expect(() =>
|
|
71
|
+
render(() => (
|
|
72
|
+
<ContextProtocolProvider context={PluginManagerContext} value={emptyManager}>
|
|
73
|
+
<TestComponent />
|
|
74
|
+
</ContextProtocolProvider>
|
|
75
|
+
)),
|
|
76
|
+
).toThrow(/No capability found/);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Accessor } from 'solid-js';
|
|
6
|
+
|
|
7
|
+
import { type Capability } from '@dxos/app-framework';
|
|
8
|
+
import { useAtomValue } from '@dxos/effect-atom-solid';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
10
|
+
|
|
11
|
+
import { usePluginManager } from './usePluginManager';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Hook to request capabilities from the plugin context.
|
|
15
|
+
* @returns An array of capabilities.
|
|
16
|
+
*/
|
|
17
|
+
export const useCapabilities = <T>(interfaceDef: Capability.InterfaceDef<T>): Accessor<T[]> => {
|
|
18
|
+
const manager = usePluginManager();
|
|
19
|
+
return useAtomValue(manager.capabilities.atom(interfaceDef));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hook to request a capability from the plugin context.
|
|
24
|
+
* @returns The capability.
|
|
25
|
+
* @throws If no capability is found.
|
|
26
|
+
*/
|
|
27
|
+
export const useCapability = <T>(interfaceDef: Capability.InterfaceDef<T>): Accessor<T> => {
|
|
28
|
+
const capabilities = useCapabilities(interfaceDef);
|
|
29
|
+
return (() => {
|
|
30
|
+
const caps = capabilities();
|
|
31
|
+
invariant(caps.length > 0, `No capability found for ${interfaceDef.identifier}`);
|
|
32
|
+
return caps[0];
|
|
33
|
+
}) as Accessor<T>;
|
|
34
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { render } from '@solidjs/testing-library';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
9
|
+
|
|
10
|
+
import { type PluginManager, PluginManagerContext } from '@dxos/app-framework';
|
|
11
|
+
import { Operation, OperationResolver } from '@dxos/operation';
|
|
12
|
+
import { ContextProtocolProvider } from '@dxos/web-context-solid';
|
|
13
|
+
|
|
14
|
+
import { useOperationResolver } from './useOperationResolver';
|
|
15
|
+
|
|
16
|
+
const TestOperation = Operation.make({
|
|
17
|
+
meta: { key: 'test/operation', name: 'Test Operation' },
|
|
18
|
+
schema: {
|
|
19
|
+
input: Schema.Struct({ value: Schema.String }),
|
|
20
|
+
output: Schema.Struct({ result: Schema.String }),
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('useOperationResolver', () => {
|
|
25
|
+
test('registers and unregisters operation resolver', () => {
|
|
26
|
+
const contributeMock = vi.fn();
|
|
27
|
+
const removeMock = vi.fn();
|
|
28
|
+
|
|
29
|
+
const mockManager = {
|
|
30
|
+
capabilities: {
|
|
31
|
+
contribute: contributeMock,
|
|
32
|
+
remove: removeMock,
|
|
33
|
+
},
|
|
34
|
+
} as unknown as PluginManager.PluginManager;
|
|
35
|
+
|
|
36
|
+
const resolver = OperationResolver.make({
|
|
37
|
+
operation: TestOperation,
|
|
38
|
+
handler: ({ value }) => Effect.succeed({ result: value }),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const TestComponent = () => {
|
|
42
|
+
useOperationResolver('test-module', resolver);
|
|
43
|
+
return <div />;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const { unmount } = render(() => (
|
|
47
|
+
<ContextProtocolProvider context={PluginManagerContext} value={mockManager}>
|
|
48
|
+
<TestComponent />
|
|
49
|
+
</ContextProtocolProvider>
|
|
50
|
+
));
|
|
51
|
+
|
|
52
|
+
expect(contributeMock).toHaveBeenCalledWith(
|
|
53
|
+
expect.objectContaining({
|
|
54
|
+
module: 'test-module',
|
|
55
|
+
implementation: [resolver],
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
unmount();
|
|
60
|
+
|
|
61
|
+
expect(removeMock).toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createEffect, onCleanup } from 'solid-js';
|
|
6
|
+
|
|
7
|
+
import { Common } from '@dxos/app-framework';
|
|
8
|
+
import { type OperationResolver } from '@dxos/operation';
|
|
9
|
+
|
|
10
|
+
import { usePluginManager } from './usePluginManager';
|
|
11
|
+
|
|
12
|
+
export const useOperationResolver = (module: string, resolver: OperationResolver.OperationResolver) => {
|
|
13
|
+
const manager = usePluginManager();
|
|
14
|
+
|
|
15
|
+
createEffect(() => {
|
|
16
|
+
const implementation = [resolver];
|
|
17
|
+
|
|
18
|
+
manager.capabilities.contribute({
|
|
19
|
+
module,
|
|
20
|
+
interface: Common.Capability.OperationResolver,
|
|
21
|
+
implementation,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
onCleanup(() => {
|
|
25
|
+
manager.capabilities.remove(Common.Capability.OperationResolver, implementation);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { render, screen } from '@solidjs/testing-library';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { type PluginManager, PluginManagerContext } from '@dxos/app-framework';
|
|
9
|
+
import { ContextProtocolProvider } from '@dxos/web-context-solid';
|
|
10
|
+
|
|
11
|
+
import { usePluginManager } from './usePluginManager';
|
|
12
|
+
|
|
13
|
+
describe('usePluginManager', () => {
|
|
14
|
+
test('returns the plugin manager from context', () => {
|
|
15
|
+
const mockManager = {
|
|
16
|
+
context: {
|
|
17
|
+
capabilities: () => ({ pipe: () => {} }),
|
|
18
|
+
},
|
|
19
|
+
} as unknown as PluginManager.PluginManager;
|
|
20
|
+
|
|
21
|
+
const TestComponent = () => {
|
|
22
|
+
const manager = usePluginManager();
|
|
23
|
+
return <div data-testid='manager-check'>{manager === mockManager ? 'match' : 'mismatch'}</div>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
render(() => (
|
|
27
|
+
<ContextProtocolProvider context={PluginManagerContext} value={mockManager}>
|
|
28
|
+
<TestComponent />
|
|
29
|
+
</ContextProtocolProvider>
|
|
30
|
+
));
|
|
31
|
+
|
|
32
|
+
expect(screen.getByTestId('manager-check').textContent).toBe('match');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('throws if plugin manager is missing', () => {
|
|
36
|
+
const TestComponent = () => {
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
usePluginManager(); // This should throw
|
|
39
|
+
return <div />;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Use a custom error boundary or just try/catch mechanism if simple render throws.
|
|
43
|
+
// However, Solid's reactivity system might make catching errors tricky during render phase directly in test if not handled.
|
|
44
|
+
// Given the invariant check is inside the hook, it executes during component initialization.
|
|
45
|
+
|
|
46
|
+
expect(() => render(() => <TestComponent />)).toThrow('PluginManager not found');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type PluginManager, PluginManagerContext } from '@dxos/app-framework';
|
|
6
|
+
import { invariant } from '@dxos/invariant';
|
|
7
|
+
import { useWebComponentContext } from '@dxos/web-context-solid';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook to access the plugin manager.
|
|
11
|
+
* @returns The plugin manager.
|
|
12
|
+
*/
|
|
13
|
+
export const usePluginManager = (): PluginManager.PluginManager => {
|
|
14
|
+
const manager = useWebComponentContext(PluginManagerContext);
|
|
15
|
+
const value = manager();
|
|
16
|
+
invariant(value, 'PluginManager not found');
|
|
17
|
+
return value;
|
|
18
|
+
};
|