diff --git a/packages/connected-nextgraph/README.md b/packages/connected-nextgraph/README.md
index 94c9ada..9b0383f 100644
--- a/packages/connected-nextgraph/README.md
+++ b/packages/connected-nextgraph/README.md
@@ -1,204 +1,117 @@
-# @ldo/solid
+# `@ldo/connected-nextgraph`
-@ldo/solid is a client that implements the Solid specification with the use of Linked Data Objects.
+The `@ldo/connected-nextgraph` library allows you to integrate [NextGraph](https://nextgraph.org) with the [LDO](https://ldo.js.org) ecosystem. It provides a `ConnectedLdoDataset` that manages RDF data across decentralized NextGraph resources with real-time synchronization and read/write capabilities.
## Installation
-Navigate into your project's root folder and run the following command:
-```
-cd my_project/
-npx run @ldo/cli init
+First, install the required libraries:
+
+```bash
+npm install nextgraph @ldo/connected-nextgraph
```
-Now install the @ldo/solid library
+## Usage:
+
+### 1. Setup: Create a ConnectedLdoDataset
+
+```ts
+import { createNextGraphLdoDataset } from "@ldo/connected-nextgraph";
+// Create the dataset
+const ldoDataset = createNextGraphLdoDataset();
```
-npm i @ldo/solid
+
+### 2. Connect to a NextGraph Wallet Session
+
+Before you can create or access resources, you need an active session:
+
+```ts
+import ng from "nextgraph";
+
+// Open your nextgraph wallet
+const openedWallet = await ng.wallet_open_with_mnemonic_words(
+ wallet.wallet,
+ mnemonic,
+ [1, 2, 1, 2]
+);
+
+// Start a session
+const session = await ng.session_in_memory_start(
+ openedWallet.V0.wallet_id,
+ openedWallet.V0.personal_site
+);
```
-
-
-Manual Installation
-
+---
-If you already have generated ShapeTypes, you may install the `@ldo/ldo` and `@ldo/solid` libraries independently.
+### 3. Link Your Dataset to the NextGraph Session
+```ts
+ldoDataset.setContext("nextgraph", {
+ sessionId: session.session_id
+});
```
-npm i @ldo/ldo @ldo/solid
+
+### 4. Create a Resource
+
+To create a new resource in your store:
+
+```ts
+const resource = await ldoDataset.createResource("nextgraph");
+if (!resource.isError) {
+ console.log("Created resource:", resource.uri);
+}
```
-
-
-## Simple Examples
-
-Below is a simple example of @ldo/solid. Assume that a ShapeType was previously generated and placed at `./.ldo/foafProfile.shapeTypes`. Also assume we have a shape type for social media at `./.ldo/socialMediaPost.shapeTypes`
-
-```typescript
-import { changeData, commitData, createSolidLdoDataset } from "@ldo/solid";
-import { fetch, getDefaultSession } from "@inrupt/solid-client-authn-browser";
-import { FoafProfileShapeType } from "./.ldo/foafProfile.shapeTypes";
-import { SocialMediaPostShapeType } from "./.ldo/socialMediaPost.shapeTypes";
-
-async function main() {
- /**
- * ===========================================================================
- * READING DATA FROM A POD
- * ===========================================================================
- */
-
- // Before we begin using @ldo/solid. Let's get the WebId of the current user
- const webIdUri = getDefaultSession().info.webId;
- if (!webIdUri) throw new Error("User is not logged in");
-
- // Now let's proceed with @ldo/solid. Our first step is setting up a
- // SolidLdoDataset. You can think of this dataset as a local store for all the
- // information in the Solidverse. Don't forget to pass the authenticated fetch
- // function to do your queries!
- const solidLdoDataset = createSolidLdoDataset({ fetch });
-
- // We'll start with getting a representation of our WebId's resource
- const webIdResource = solidLdoDataset.getResource(webIdUri);
-
- // This resource is currently unfetched
- console.log(webIdResource.isUnfetched()); // Logs true
-
- // So let's fetch it! Running the `read` command will make a request to get
- // the WebId.
- const readResult = await webIdResource.read();
-
- // @ldo/solid will never throw an error. Instead, it will return errors. This
- // design decision was made to force you to handle any errors. It may seem a
- // bit annoying at first, but it will result in more resiliant code. You can
- // easily follow intellisense tooltips to see what kinds of errors each action
- // can throw.
- if (readResult.isError) {
- switch (readResult.type) {
- case "serverError":
- console.error("The solid server had an error:", readResult.message);
- return;
- case "noncompliantPodError":
- console.error("The Pod responded in a way not compliant with the spec");
- return;
- default:
- console.error("Some other error was detected:", readResult.message);
- }
- }
-
- // When fetching a data resource, read triples will automatically be added to
- // the solidLdoDataset. You can access them using Linked Data Objects. In
- // the following example we're using a Profile Linked Data Object that was
- // generated with the init step.
- const profile = solidLdoDataset
- .usingType(FoafProfileShapeType)
- .fromSubject(webIdUri);
-
- // Now you can read "profile" like any JSON.
- console.log(profile.name);
-
- /**
- * ===========================================================================
- * MODIFYING DATA
- * ===========================================================================
- */
-
- // When we want to modify data the first step is to use the `changeData`
- // function. We pass in an object that we want to change (in this case,
- // "profile") as well an a list of any resources to which we want those
- // changes to be applied (in this case, just the webIdResource). This gives
- // us a new variable (conventionally named with a c for "changed") that we can
- // write changes to.
- const cProfile = changeData(profile, webIdResource);
-
- // We can make changes just like it's regular JSON
- cProfile.name = "Captain Cool Dude";
-
- // Committing data is as easy as running the "commitData" function.
- const commitResult = await commitData(cProfile);
-
- // Remember to check for and handle errors! We'll keep it short this time.
- if (commitResult.isError) throw commitResult;
-
- /**
- * ===========================================================================
- * CREATING NEW RESOURCES
- * ===========================================================================
- */
-
- // Let's create some social media posts to be stored on the Solid Pod!
- // Our first step is going to be finding where to place these posts. In the
- // future, there will be advanced ways to determine the location of resources
- // but for now, let's throw it in the root folder.
-
- // But, first, let's find out where the root folder is. We can take our WebId
- // resource and call `getRootContainer`. Let's assume the root container has
- // a URI "https://example.com/"
- const rootContainer = await webIdResource.getRootContainer();
- if (rootContainer.isError) throw rootContainer;
-
- // Now, let's create a container for our posts
- const createPostContainerResult =
- await rootContainer.createChildIfAbsent("social-posts/");
- if (createPostContainerResult.isError) throw createPostContainerResult;
-
- // Most results store the affected resource in the "resource" field. This
- // container has the URI "https://example.com/social-posts/"
- const postContainer = createPostContainerResult.resource;
-
- // Now that we have our container, let's make a Post resource! This is a data
- // resource, which means we can put raw Solid Data (RDF) into it.
- const postResourceResult =
- await postContainer.createChildAndOverwrite("post1.ttl");
- if (postResourceResult.isError) throw postResourceResult;
- const postResource = postResourceResult.resource;
-
- // We can also create binary resources with things like images
- const imageResourceResult = await postContainer.uploadChildAndOverwrite(
- // name of the binary
- "image1.svg",
- // A blob for the binary
- new Blob([``]),
- // mime type of the binary
- "image/svg+xml",
- );
- if (imageResourceResult.isError) throw imageResourceResult;
- const imageResource = imageResourceResult.resource;
-
- /**
- * ===========================================================================
- * CREATING NEW DATA
- * ===========================================================================
- */
-
- // We create data in a similar way to the way we modify data. We can use the
- // "createData" method.
- const cPost = solidLdoDataset.createData(
- // An LDO ShapeType saying that this is a social media psot
- SocialMediaPostShapeType,
- // The URI of the post (in this case we'll make it the same as the resource)
- postResource.uri,
- // The resource we should write it to
- postResource,
- );
-
- // We can add new data
- cPost.text = "Check out this bad svg:";
- cPost.image = { "@id": imageResource.uri };
-
- // And now we commit data
- const newDataResult = await commitData(cPost);
- if (newDataResult.isError) throw newDataResult;
-
- /**
- * ===========================================================================
- * DELETING RESOURCES
- * ===========================================================================
- */
-
- // Deleting resources can be done with a single method call. In this case,
- // the container will be deleted along with all its contained resources
- const deleteResult = await postContainer.delete();
- if (deleteResult.isError) throw deleteResult;
+
+### 5. Read and Monitor a Resource**
+
+#### Read Existing Resource
+
+```ts
+const resource = ldoDataset.getResource(existingUri);
+const readResult = await resource.read();
+
+if (!readResult.isError) {
+ console.log("Resource loaded!", readResult.type);
}
-main();
+```
+
+#### Read Only If Unfetched
+
+Avoid redundant fetches:
+
+```ts
+const readResult = await resource.readIfUnfetched();
+```
+
+#### Subscribe to Notifications
+
+```ts
+const unsubscribeId = await resource.subscribeToNotifications();
+await resource.unsubscribeFromNotification(unsubscribeId);
+await resource.unsubscribeFromAllNotifications();
+```
+
+---
+
+### 6. Write Data to a Resource
+
+You can write RDF data to a resource using `update()`:
+
+```ts
+import { parseRdf } from "@ldo/ldo";
+
+const ttlData = `
+@prefix foaf: .
+<#spiderman> a foaf:Person ; foaf:name "Spiderman" .
+`;
+
+const triples = await parseRdf(ttlData);
+
+await resource.update({
+ added: triples,
+ removed: undefined
+});
```
## API Details
diff --git a/packages/connected-nextgraph/src/resources/NextGraphResource.ts b/packages/connected-nextgraph/src/resources/NextGraphResource.ts
index a25dfda..9b33fcd 100644
--- a/packages/connected-nextgraph/src/resources/NextGraphResource.ts
+++ b/packages/connected-nextgraph/src/resources/NextGraphResource.ts
@@ -21,7 +21,7 @@ import { namedNode, quad as createQuad } from "@rdfjs/data-model";
import { NextGraphReadSuccess } from "../results/NextGraphReadSuccess";
import { NextGraphNotificationSubscription } from "../notifications/NextGraphNotificationSubscription";
import { parseRdf } from "@ldo/ldo";
-import type { LdoDataset } from "packages/ldo/dist/LdoDataset";
+import type { LdoDataset } from "@ldo/ldo";
import { createDataset } from "@ldo/dataset";
export class NextGraphResource
diff --git a/packages/connected-solid/src/requester/results/success/SolidReadSuccess.ts b/packages/connected-solid/src/requester/results/success/SolidReadSuccess.ts
index 7340792..042ce6b 100644
--- a/packages/connected-solid/src/requester/results/success/SolidReadSuccess.ts
+++ b/packages/connected-solid/src/requester/results/success/SolidReadSuccess.ts
@@ -1,7 +1,7 @@
+import type { ConnectedResult } from "@ldo/connected";
import { ReadSuccess, type Resource } from "@ldo/connected";
import type { SolidLeaf } from "../../../resources/SolidLeaf";
import type { SolidContainer } from "../../../resources/SolidContainer";
-import type { ResourceResult } from "packages/connected/dist/results/ResourceResult";
/**
* Indicates that the read request was successful and that the resource
@@ -66,7 +66,7 @@ export class ContainerReadSuccess extends ReadSuccess {
* @returns true if the result is a ReadSuccessResult result
*/
export function isReadSuccess(
- result: ResourceResult,
+ result: ConnectedResult,
): result is ReadSuccess {
return (
result.type === "binaryReadSuccess" ||
diff --git a/packages/connected-solid/typedoc.json b/packages/connected-solid/typedoc.json
index c0e7b5d..bbd6bbf 100644
--- a/packages/connected-solid/typedoc.json
+++ b/packages/connected-solid/typedoc.json
@@ -4,4 +4,5 @@
"allReflectionsHaveOwnDocument": true,
"hideInPageTOC": true,
"hideBreadcrumbs": true,
-}
\ No newline at end of file
+ "excludeExternals": true,
+}
diff --git a/packages/connected/README.md b/packages/connected/README.md
index 94c9ada..086eb20 100644
--- a/packages/connected/README.md
+++ b/packages/connected/README.md
@@ -1,6 +1,6 @@
-# @ldo/solid
+# @ldo/connected
-@ldo/solid is a client that implements the Solid specification with the use of Linked Data Objects.
+@ldo/connected provides tools for LDO to connect to a remote datasource. It requires plugins for that datasource.
## Installation
@@ -13,7 +13,13 @@ npx run @ldo/cli init
Now install the @ldo/solid library
```
-npm i @ldo/solid
+npm i @ldo/connected
+```
+
+You may also install a connected plugin, for example `@ldo/connected-solid` and `@ldo/connected-nextgraph`.
+
+```
+npm i @ldo/connected-nextgraph
```
@@ -33,53 +39,84 @@ npm i @ldo/ldo @ldo/solid
Below is a simple example of @ldo/solid. Assume that a ShapeType was previously generated and placed at `./.ldo/foafProfile.shapeTypes`. Also assume we have a shape type for social media at `./.ldo/socialMediaPost.shapeTypes`
```typescript
-import { changeData, commitData, createSolidLdoDataset } from "@ldo/solid";
-import { fetch, getDefaultSession } from "@inrupt/solid-client-authn-browser";
+import {
+ changeData,
+ commitData,
+ createConnectedLdoDataset
+} from "@ldo/connected";
+import { solidConnectedPlugin } from "@ldo/connected-solid";
+import { nextGraphConnectedPlugin } from "@ldo/connected-nextgraph";
+
+
+// Shape Types
import { FoafProfileShapeType } from "./.ldo/foafProfile.shapeTypes";
import { SocialMediaPostShapeType } from "./.ldo/socialMediaPost.shapeTypes";
+// These are tools for Solid and NextGraph outside of the LDO ecosystem
+import { fetch, getDefaultSession } from "@inrupt/solid-client-authn-browser";
+import ng from "nextgraph";
+
async function main() {
/**
* ===========================================================================
- * READING DATA FROM A POD
+ * SETTING UP A CONNECTED LDO DATASTORE WITH 2 PLUGINS
* ===========================================================================
*/
+ const connectedLdoDataset = createConnectedLdoDataset([
+ solidConncetedPlugin,
+ nextGraphConnectedPlugin
+ ]);
+ // Set context to be able to make authenticated requests
+ connectedLdoDataset.setContext("solid", { fetch });
+ const session = await ng.session_in_memory_start(
+ openedWallet.V0.wallet_id,
+ openedWallet.V0.personal_site
+ );
+ connectedLdoDataset.setContext("nextGraph", { sessionId: session.sessionId });
- // Before we begin using @ldo/solid. Let's get the WebId of the current user
- const webIdUri = getDefaultSession().info.webId;
- if (!webIdUri) throw new Error("User is not logged in");
+ /**
+ * ===========================================================================
+ * READING DATA FROM REMOTE
+ * ===========================================================================
+ */
- // Now let's proceed with @ldo/solid. Our first step is setting up a
- // SolidLdoDataset. You can think of this dataset as a local store for all the
- // information in the Solidverse. Don't forget to pass the authenticated fetch
- // function to do your queries!
- const solidLdoDataset = createSolidLdoDataset({ fetch });
+ // We can get a Solid resource by including a Solid-Compatible URL
+ const solidResource = solidLdoDataset.getResource(
+ "https://pod.example.com/profile.ttl"
+ );
+ // Similarly, we can get a NextGraph resource by including a
+ // NextGraph-Compatible URL
+ const nextGraphResource = solidLdoDataset.getResource(
+ "did:ng:o:W6GCQRfQkNTLtSS_2-QhKPJPkhEtLVh-B5lzpWMjGNEA:v:h8ViqyhCYMS2I6IKwPrY6UZi4ougUm1gpM4QnxlmNMQA"
+ );
+ // Optionally, you can provide the name of the specific plugin you want to use
+ const anotherSolidResource = solidLdoDataset.getResource("", "solid");
- // We'll start with getting a representation of our WebId's resource
- const webIdResource = solidLdoDataset.getResource(webIdUri);
// This resource is currently unfetched
- console.log(webIdResource.isUnfetched()); // Logs true
+ console.log(solidResource.isUnfetched()); // Logs true
+ console.log(nextGraphResource.isUnfetched()); // Logs true
// So let's fetch it! Running the `read` command will make a request to get
// the WebId.
- const readResult = await webIdResource.read();
-
- // @ldo/solid will never throw an error. Instead, it will return errors. This
- // design decision was made to force you to handle any errors. It may seem a
- // bit annoying at first, but it will result in more resiliant code. You can
- // easily follow intellisense tooltips to see what kinds of errors each action
- // can throw.
- if (readResult.isError) {
- switch (readResult.type) {
+ const solidReadResult = await solidResource.read();
+ const ngReadResult = await nextGraphREsource.read();
+
+ // @ldo/connected will never throw an error. Instead, it will return errors.
+ // This design decision was made to force you to handle any errors. It may
+ // seem a bit annoying at first, but it will result in more resiliant code.
+ // You can easily follow intellisense tooltips to see what kinds of errors
+ // each action can throw.
+ if (solidReadResult.isError) {
+ switch (solidReadResult.type) {
case "serverError":
- console.error("The solid server had an error:", readResult.message);
+ console.error("The solid server had an error:", solidReadResult.message);
return;
case "noncompliantPodError":
console.error("The Pod responded in a way not compliant with the spec");
return;
default:
- console.error("Some other error was detected:", readResult.message);
+ console.error("Some other error was detected:", solidReadResult.message);
}
}
@@ -87,9 +124,9 @@ async function main() {
// the solidLdoDataset. You can access them using Linked Data Objects. In
// the following example we're using a Profile Linked Data Object that was
// generated with the init step.
- const profile = solidLdoDataset
+ const profile = connectedLdoDataset
.usingType(FoafProfileShapeType)
- .fromSubject(webIdUri);
+ .fromSubject("https://pod.example.com/profile#me");
// Now you can read "profile" like any JSON.
console.log(profile.name);
@@ -106,7 +143,7 @@ async function main() {
// changes to be applied (in this case, just the webIdResource). This gives
// us a new variable (conventionally named with a c for "changed") that we can
// write changes to.
- const cProfile = changeData(profile, webIdResource);
+ const cProfile = changeData(profile, solidResource);
// We can make changes just like it's regular JSON
cProfile.name = "Captain Cool Dude";
@@ -123,28 +160,17 @@ async function main() {
* ===========================================================================
*/
- // Let's create some social media posts to be stored on the Solid Pod!
- // Our first step is going to be finding where to place these posts. In the
- // future, there will be advanced ways to determine the location of resources
- // but for now, let's throw it in the root folder.
-
- // But, first, let's find out where the root folder is. We can take our WebId
- // resource and call `getRootContainer`. Let's assume the root container has
- // a URI "https://example.com/"
- const rootContainer = await webIdResource.getRootContainer();
- if (rootContainer.isError) throw rootContainer;
+ // Let's create some social media posts to be stored on the Solid Pod and in
+ // NextGraph! We can create new resources using the "createResource" method.
+ const newSolidResource = await connectedLdoDataset.createResource("solid");
+ const newNgResource = await connectedLdoDataset.createResource("nextGraph");
- // Now, let's create a container for our posts
+ // For Solid, you can also create resources at a predefined location
+ const postContainer = connectedLdoDataset
+ .getResource("https://pod.example.com/socialPosts/");
const createPostContainerResult =
- await rootContainer.createChildIfAbsent("social-posts/");
+ await solidSocialPostsContainer.createIfAbsent();
if (createPostContainerResult.isError) throw createPostContainerResult;
-
- // Most results store the affected resource in the "resource" field. This
- // container has the URI "https://example.com/social-posts/"
- const postContainer = createPostContainerResult.resource;
-
- // Now that we have our container, let's make a Post resource! This is a data
- // resource, which means we can put raw Solid Data (RDF) into it.
const postResourceResult =
await postContainer.createChildAndOverwrite("post1.ttl");
if (postResourceResult.isError) throw postResourceResult;
@@ -203,30 +229,47 @@ main();
## API Details
-SolidLdoDataset
+ConnectedLdoDataset
- - [createSolidLdoDataset](https://ldo.js.org/latest/api/solid/functions/createSolidLdoDataset/)
- - [SolidLdoDataset](https://ldo.js.org/latest/api/solid/classes/SolidLdoDataset/)
+- [createConnectedLdoDataset](https://ldo.js.org/latest/api/connected/functions/createConnectedLdoDataset.md)
+- [ConnectedLdoDataset](https://ldo.js.org/latest/api/connected/classes/ConnectedLdoDataset.md)
+- [ConnectedLdoTransactionDataset](https://ldo.js.org/latest/api/connected/classes/ConnectedLdoTransactionDataset.md)
+- [IConnectedLdoDataset](https://ldo.js.org/latest/api/connected/interfaces/IConnectedLdoDataset.md)
-Resources (Manage batching requests)
+ConnectedPlugins
- - [LeafUri](https://ldo.js.org/latest/api/solid/types/LeafUri/)
- - [ContainerUri](https://ldo.js.org/latest/api/solid/types/ContainerUri/)
- - [Leaf](https://ldo.js.org/latest/api/solid/classes/Leaf/)
- - [Container](https://ldo.js.org/latest/api/solid/classes/Container/)
+- [ConnectedPlugin](https://ldo.js.org/latest/api/connected/interfaces/ConnectedPlugin.md)
-Standalone Functions
+Resources (Manage batching requests)
- - [checkRootContainter](https://ldo.js.org/latest/api/solid/functions/checkRootContainer/)
- - [createDataResource](https://ldo.js.org/latest/api/solid/functions/createDataResource/)
- - [deleteResource](https://ldo.js.org/latest/api/solid/functions/deleteResource/)
- - [readResource](https://ldo.js.org/latest/api/solid/functions/readResource/)
- - [updateResource](https://ldo.js.org/latest/api/solid/functions/updateResource/)
- - [uploadResource](https://ldo.js.org/latest/api/solid/functions/uploadResource/)
+- [Resource](https://ldo.js.org/latest/api/connected/interfaces/Resource.md)
Data Functions
- - [changeData](https://ldo.js.org/latest/api/solid/functions/changeData/)
- - [commitData](https://ldo.js.org/latest/api/solid/functions/commitData/)
+
+- [changeData](https://ldo.js.org/latest/api/connected/functions/changeData/)
+- [commitData](https://ldo.js.org/latest/api/connected/functions/commitData/)
+
+SuccessResult
+
+- [SuccessResult](https://ldo.js.org/latest/api/connected/classes/SuccessResult.md)
+- [AbsentReadSuccess](https://ldo.js.org/latest/api/connected/classes/AbsentReadSuccess.md)
+- [AggregateSuccess](https://ldo.js.org/latest/api/connected/classes/AggregateSuccess.md)
+- [IgnoredInvalidUpdateSuccess](https://ldo.js.org/latest/api/connected/classes/IgnoredInvalidUpdateSuccess.md)
+- [ReadSuccess](https://ldo.js.org/latest/api/connected/classes/ReadSuccess.md)
+- [ResourceSuccess](https://ldo.js.org/latest/api/connected/classes/ResourceSuccess.md)
+- [Unfetched](https://ldo.js.org/latest/api/connected/classes/Unfetched.md)
+- [UpdateDefaultGraphSuccess](https://ldo.js.org/latest/api/connected/classes/UpdateDefaultGraphSuccess.md)
+- [UpdateSuccess](https://ldo.js.org/latest/api/connected/classes/UpdateSuccess.md)å
+
+ErrorResult
+
+- [ErrorResult](https://ldo.js.org/latest/api/connected/classes/ErrorResult.md)
+- [AggregateError](https://ldo.js.org/latest/api/connected/classes/AggregateError.md)
+- [DisconnectedAttemptingReconnectError](https://ldo.js.org/latest/api/connected/classes/DisconnectedAttemptingReconnectError.md)
+- [InvalidUriError](https://ldo.js.org/latest/api/connected/classes/InvalidUriError.md)
+- [ResourceError](https://ldo.js.org/latest/api/connected/classes/ResourceError.md)
+- [UnexpectedResourceError](https://ldo.js.org/latest/api/connected/classes/UnexpectedResourceError.md)
+- [UnsupportedNotificationError](https://ldo.js.org/latest/api/connected/classes/UnsupportedNotificationError.md)
## Sponsorship
This project was made possible by a grant from NGI Zero Entrust via nlnet. Learn more on the [NLnet project page](https://nlnet.nl/project/SolidUsableApps/).
diff --git a/packages/connected/typedoc.json b/packages/connected/typedoc.json
index c0e7b5d..31ad5e8 100644
--- a/packages/connected/typedoc.json
+++ b/packages/connected/typedoc.json
@@ -1,5 +1,6 @@
{
"entryPoints": ["src/index.ts"],
+ "tsconfig": "tsconfig.build.json",
"out": "docs",
"allReflectionsHaveOwnDocument": true,
"hideInPageTOC": true,