# HG changeset patch
# User Frank Bessou <frank.bessou@logilab.fr>
# Date 1613138587 -3600
#      Fri Feb 12 15:03:07 2021 +0100
# Node ID ac514970ebfc66aef1b5ba701b38f254daeadae5
# Parent  e8e24874379a4cf093334b005a10476a89729964
feat: demo

diff --git a/packages/demo/App.js b/packages/demo/App.js
deleted file mode 100644
--- a/packages/demo/App.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import * as React from "react";
-import {
-  Admin,
-  Resource,
-  ListGuesser,
-  List,
-  Datagrid,
-  TextField,
-  DateField,
-  NumberField,
-  ShowGuesser,
-  EditGuesser,
-  Edit,
-  SimpleForm,
-  TextInput,
-  NumberInput,
-  DateInput,
-} from "react-admin";
-//import jsonServerProvider from 'ra-data-json-server';
-
-// import authProvider from './authProvider';
-// see https://marmelab.com/react-admin/Authentication.html
-const authProvider = {
-  // authentication
-  login: params => Promise.resolve(),
-  checkError: error => Promise.resolve(),
-  checkAuth: params => Promise.resolve(),
-  logout: () => Promise.resolve(),
-  getIdentity: () => Promise.resolve(),
-  // authorization
-  getPermissions: params => Promise.resolve(),
-};
-
-// const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
-// https://marmelab.com/react-admin/DataProviders.html > Request Format
-
-const dataProvider = {
-  getList: (resource, params) => {
-    return fetch(
-      "https://www.logilab.fr/view?rql=Any+C+LIMIT+10+WHERE+C+is+Card&vid=ejsonexport",
-    )
-      .then(resp => resp.json())
-      .then(json => {
-        return {
-          data: json.map(item => ({...item, id: item.eid})),
-          total: json.length,
-        };
-      });
-  },
-  getOne: (resource, params) => {
-    return fetch(
-      "https://www.logilab.fr/view?rql=Any+C+LIMIT+1+WHERE+C+is+Card&vid=ejsonexport",
-    )
-      .then(resp => resp.json())
-      .then(json => {
-        return {
-          data: json.map(item => ({...item, id: item.eid}))[0],
-        };
-      });
-  },
-  getMany: (resource, params) => ({}), // NotImplemented
-  getManyReference: (resource, params) => ({}), // NotImplemented
-  update: (resource, params) => ({}), // NotImplemented
-  updateMany: (resource, params) => ({}), // NotImplemented
-  create: (resource, params) => ({}), // NotImplemented
-  //delete: (resource, params) => ({}), // NotImplemented
-  deleteMany: (resource, params) => ({}), // NotImplemented
-};
-
-export const UserEdit = props => (
-  <Edit {...props}>
-    <SimpleForm>
-      <TextInput source="cw_etype" />
-      <NumberInput source="eid" />
-      <TextInput source="title" />
-      <TextInput source="synopsis" />
-      <TextInput source="content" />
-      <TextInput source="content_format" />
-      <TextInput source="wikiid" />
-      <DateInput source="creation_date" />
-      <TextInput source="cwuri" />
-      <DateInput source="modification_date" />
-      <TextInput source="id" />
-    </SimpleForm>
-  </Edit>
-);
-
-export const UserList = props => (
-  <List {...props}>
-    <Datagrid rowClick="edit">
-      <NumberField source="id" />
-      <TextField source="cw_etype" />
-      <TextField source="title" />
-      <TextField source="synopsis" />
-      <TextField source="content" />
-      <TextField source="content_format" />
-      <TextField source="wikiid" />
-      <DateField source="creation_date" />
-      <TextField source="cwuri" />
-      <DateField source="modification_date" />
-    </Datagrid>
-  </List>
-);
-
-const App = () => (
-  <Admin dataProvider={dataProvider}>
-    {
-      // authProvider={authProvider}
-      // use cwclientlibjs to get list of CWETypes
-      // and add one <Resource  per type
-    }
-    <Resource name="users" list={UserList} show={ShowGuesser} edit={UserEdit} />
-  </Admin>
-);
-
-export default App;
diff --git a/packages/demo/index.js b/packages/demo/index.js
deleted file mode 100644
--- a/packages/demo/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-
-ReactDOM.render(
-  <React.StrictMode>
-    <App />
-  </React.StrictMode>,
-  document.getElementById('root')
-);
diff --git a/packages/demo/package.json b/packages/demo/package.json
--- a/packages/demo/package.json
+++ b/packages/demo/package.json
@@ -13,8 +13,8 @@
     "react-dom": "^17.0.1"
   },
   "scripts": {
-    "build": "webpack",
-    "start": "webpack serve"
+    "build": "webpack --mode=production",
+    "start": "webpack serve --mode=development"
   },
   "devDependencies": {
     "ts-loader": "^8.0.14",
diff --git a/packages/demo/src/App.tsx b/packages/demo/src/App.tsx
--- a/packages/demo/src/App.tsx
+++ b/packages/demo/src/App.tsx
@@ -15,53 +15,56 @@
   DateInput,
   EditProps,
   ListProps,
+  DataProvider,
 } from "react-admin";
+import { createDataProvider } from "ra-cubicweb/dist";
 
-import { dataProvider } from "ra-cubicweb";
+import { schema } from "./schema";
 
-const UserEdit = (props: EditProps) => (
+const MuseumEdit = (props: EditProps) => (
   <Edit {...props}>
     <SimpleForm>
-      <TextInput source="cw_etype" />
-      <NumberInput source="eid" />
-      <TextInput source="title" />
-      <TextInput source="synopsis" />
-      <TextInput source="content" />
-      <TextInput source="content_format" />
-      <TextInput source="wikiid" />
-      <DateInput source="creation_date" />
-      <TextInput source="cwuri" />
-      <DateInput source="modification_date" />
-      <TextInput source="id" />
+      <NumberInput source="id" />
+      <TextInput source="name" />
+      <NumberInput source="latitude" />
+      <NumberInput source="longitude" />
+      <TextInput source="postal_address" />
     </SimpleForm>
   </Edit>
 );
 
-const UserList = (props: ListProps) => (
+const MuseumList = (props: ListProps) => (
   <List {...props}>
     <Datagrid rowClick="edit">
       <NumberField source="id" />
-      <TextField source="cw_etype" />
-      <TextField source="title" />
-      <TextField source="synopsis" />
-      <TextField source="content" />
-      <TextField source="content_format" />
-      <TextField source="wikiid" />
-      <DateField source="creation_date" />
-      <TextField source="cwuri" />
-      <DateField source="modification_date" />
+      <TextField source="name" />
+      <NumberField source="latitude" />
+      <NumberField source="longitude" />
+      <TextField source="postal_address" />
     </Datagrid>
   </List>
 );
 
+const dataProvider = createDataProvider("http://localhost:8080", schema);
+dataProvider.getList("toto", {
+  pagination: { page: 1, perPage: 10 },
+  sort: { field: "id", order: "ASC" },
+  filter: null,
+});
+
 const App = (): JSX.Element => (
-  <Admin dataProvider={dataProvider}>
+  <Admin dataProvider={dataProvider as DataProvider}>
     {
       // authProvider={authProvider}
       // use cwclientlibjs to get list of CWETypes
       // and add one <Resource  per type
     }
-    <Resource name="users" list={UserList} show={ShowGuesser} edit={UserEdit} />
+    <Resource
+      name="Museum"
+      list={MuseumList}
+      show={ShowGuesser}
+      edit={MuseumEdit}
+    />
   </Admin>
 );
 
diff --git a/packages/demo/src/schema.ts b/packages/demo/src/schema.ts
new file mode 100644
--- /dev/null
+++ b/packages/demo/src/schema.ts
@@ -0,0 +1,42 @@
+import { ETypes, Relationships, Schema, yams } from "ra-cubicweb/dist/Schema";
+
+function makeSchema<E extends ETypes, R extends Relationships<E>>(
+  etypes: E,
+  relationships: R
+): Schema<E, R> {
+  return {
+    etypes,
+    relationships,
+  };
+}
+
+export const schema = makeSchema(
+  {
+    Museum: {
+      name: yams.String(),
+      latitude: yams.Float(),
+      longitude: yams.Float(),
+      postal_address: yams.String(),
+    },
+    City: {
+      name: yams.String(),
+      zip_code: yams.Int(),
+    },
+    Person: {
+      name: yams.String(),
+      email: yams.String(),
+    },
+  },
+  {
+    is_in: {
+      subject: "Museum",
+      object: "City",
+      cardinality: "1*",
+    },
+    director: {
+      subject: "Museum",
+      object: "Person",
+      cardinality: "**",
+    },
+  }
+);
diff --git a/packages/demo/webpack.config.js b/packages/demo/webpack.config.js
--- a/packages/demo/webpack.config.js
+++ b/packages/demo/webpack.config.js
@@ -1,6 +1,9 @@
 const webpack = require("webpack");
 
 module.exports = {
+  entry: {
+    main: "./src/index.tsx",
+  },
   resolve: {
     extensions: [".js", ".ts", ".tsx"],
   },
@@ -17,4 +20,7 @@
       "process.env": {}, // react-admin bundles node code
     }),
   ],
+  devServer: {
+    publicPath: "/dist/",
+  },
 };
diff --git a/packages/ra-cubicweb/package.json b/packages/ra-cubicweb/package.json
--- a/packages/ra-cubicweb/package.json
+++ b/packages/ra-cubicweb/package.json
@@ -5,6 +5,7 @@
   "types": "dist/index.d.ts",
   "license": "MIT",
   "peerDependencies": {
+    "@logilab/cwclientlibjs": "^1.1.0",
     "ra-core": "^3.12.0"
   },
   "scripts": {
@@ -14,5 +15,8 @@
     "@types/node": "^14.14.22",
     "@types/react": "^17.0.0",
     "ra-core": "^3.12.0"
+  },
+  "dependencies": {
+    "@logilab/cwclientlibjs": "^1.1.0"
   }
 }
diff --git a/packages/ra-cubicweb/src/Schema.ts b/packages/ra-cubicweb/src/Schema.ts
new file mode 100644
--- /dev/null
+++ b/packages/ra-cubicweb/src/Schema.ts
@@ -0,0 +1,35 @@
+export const yams = {
+  String: () => ({ type: "String" }),
+  Date: () => ({ type: "Date" }),
+  Float: () => ({ type: "Float" }),
+  Int: () => ({ type: "Int" }),
+} as const;
+
+export type BuildObj = ReturnType<typeof yams[keyof typeof yams]>;
+
+export type Cardinality = "*" | "1" | "?" | "+";
+
+export type CardinalityPair = `${Cardinality}${Cardinality}`;
+
+export type Relationships<E> = Record<
+  string,
+  {
+    cardinality: CardinalityPair;
+    subject: keyof E;
+    object: keyof E;
+  }
+>;
+
+export type ETypes = {
+  [etypeName: string]: {
+    [attributeName: string]: BuildObj;
+  };
+};
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type Schema<E extends ETypes = any, R extends Relationships<E> = any> = {
+  etypes: E;
+  relationships: R;
+};
+
+export type ETypesNames<S extends Schema> = keyof S["etypes"];
diff --git a/packages/ra-cubicweb/src/index.ts b/packages/ra-cubicweb/src/index.ts
--- a/packages/ra-cubicweb/src/index.ts
+++ b/packages/ra-cubicweb/src/index.ts
@@ -1,4 +1,6 @@
+import { client } from "@logilab/cwclientlibjs";
 import { AuthProvider, DataProvider } from "ra-core";
+import { Schema, ETypesNames } from "./Schema";
 
 // import authProvider from './authProvider';
 // see https://marmelab.com/react-admin/Authentication.html
@@ -17,38 +19,67 @@
   eid: string;
 }
 
-export const dataProvider: DataProvider = {
-  getList: (_resource, _params) => {
-    return fetch(
-      "https://www.logilab.fr/view?rql=Any+C+LIMIT+10+WHERE+C+is+Card&vid=ejsonexport"
-    )
-      .then((resp) => resp.json())
-      .then((json) => {
-        return {
-          data: json.map((item: Entity) => ({ ...item, id: item.eid })),
-          total: json.length,
-        };
+export function createDataProvider<S extends Schema>(
+  endpoint: string,
+  schema: S
+): DataProvider {
+  const httpClient = new client.CwSimpleHttpClient(endpoint, true);
+  const rqlClient = new client.CwRqlClient(httpClient);
+  return {
+    getList: async (resource: ETypesNames<S>, { pagination }) => {
+      const attributesNames = ["eid", ...Object.keys(schema.etypes[resource])];
+      const selection: string[] = [];
+      const restrictions: string[] = [];
+      attributesNames.forEach((key, idx) => {
+        const variable = `X${idx}`;
+        selection.push(variable);
+        restrictions.push(`X ${key} ${variable}`);
       });
-  },
-  getOne: (_resource, _params) => {
-    return fetch(
-      "https://www.logilab.fr/view?rql=Any+C+LIMIT+1+WHERE+C+is+Card&vid=ejsonexport"
-    )
-      .then((resp) => resp.json())
-      .then((json) => {
-        return {
-          data: json.map((entity: Entity) => ({
-            ...entity,
-            id: entity.eid,
-          }))[0],
-        };
-      });
-  },
-  getMany: (_resource, _params) => Promise.reject("Not implemented"),
-  getManyReference: (_resource, _params) => Promise.reject("Not implemented"),
-  update: (_resource, _params) => Promise.reject("Not implemented"),
-  updateMany: (_resource, _params) => Promise.reject("Not implemented"),
-  create: (_resource, _params) => Promise.reject("Not implemented"),
-  delete: (_resource, _params) => Promise.reject("Not implemented"),
-  deleteMany: (_resource, _params) => Promise.reject("Not implemented"),
-};
+      const total = await rqlClient
+        .queryRows(`Any Count(${selection[0]}) WHERE ${restrictions.join(",")}`)
+        .then((rows) => rows[0][0]);
+      return rqlClient
+        .queryRows(
+          `Any ${selection.join(", ")} LIMIT ${pagination.perPage} OFFSET ${
+            pagination.page * pagination.perPage
+          } WHERE ${restrictions.join(", ")}`,
+          {}
+        )
+        .then((rows) => {
+          return {
+            data: rows.map((row) =>
+              row.reduce(
+                (agg, attributeValue, idx) => ({
+                  [attributesNames[idx]]: attributeValue,
+                  ...agg,
+                }),
+                { id: row[0] }
+              )
+            ),
+            total,
+          };
+        });
+    },
+    getOne: (resource: keyof S["etypes"], _params) => {
+      return fetch(
+        `${endpoint}/view?rql=Any+C+LIMIT+1+WHERE+C+is+${resource}+,+C+eid+${_params.id}&vid=ejsonexport`
+      )
+        .then((resp) => resp.json())
+        .then((json) => {
+          return {
+            data: json.map((entity: Entity) => ({
+              ...entity,
+              id: entity.eid,
+            }))[0],
+          };
+        });
+    },
+    getMany: (_resource, _params) => Promise.reject("Not implemented"),
+    getManyReference: (_resource, _params) => Promise.reject("Not implemented"),
+    update: (_resource, _params) => Promise.reject("Not implemented"),
+    updateMany: (_resource, _params) => Promise.reject("Not implemented"),
+    create: (_resource, _params) => Promise.reject("Not implemented"),
+    delete: (_resource, _params) => Promise.reject("Not implemented"),
+    deleteMany: (_resource, _params) => Promise.reject("Not implemented"),
+  };
+}
diff --git a/yarn.lock b/yarn.lock
--- a/yarn.lock
+++ b/yarn.lock
@@ -75,6 +75,14 @@
     "@types/yargs" "^15.0.0"
     chalk "^4.0.0"
 
+"@logilab/cwclientlibjs@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@logilab/cwclientlibjs/-/cwclientlibjs-1.1.0.tgz#4b64f8b590f1f754e3322f822b9d3ed0a391d889"
+  integrity sha512-RWznAmHVO8+TEYnAL82YUtXiwEmcGgAh/Zpsl1qDw+uXBa5EhQeUiFwrsNMDqDprm7opMwCsjMXnQcXERcAMCA==
+  dependencies:
+    crypto-js "^3.1.9-1"
+    isomorphic-fetch "^2.2.1"
+
 "@material-ui/core@^4.11.2":
   version "4.11.3"
   resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.3.tgz#f22e41775b0bd075e36a7a093d43951bf7f63850"
@@ -1170,6 +1178,11 @@
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+crypto-js@^3.1.9-1:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b"
+  integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==
+
 css-mediaquery@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0"
@@ -1403,6 +1416,13 @@
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
   integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
 
+encoding@^0.1.11:
+  version "0.1.13"
+  resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
+  integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
+  dependencies:
+    iconv-lite "^0.6.2"
+
 end-of-stream@^1.1.0:
   version "1.4.4"
   resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@@ -2216,6 +2236,13 @@
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
+iconv-lite@^0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
+  integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3.0.0"
+
 ignore@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
@@ -2497,7 +2524,7 @@
     call-bind "^1.0.2"
     has-symbols "^1.0.1"
 
-is-stream@^1.1.0:
+is-stream@^1.0.1, is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
   integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@@ -2551,6 +2578,14 @@
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
   integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
 
+isomorphic-fetch@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
+  integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
+  dependencies:
+    node-fetch "^1.0.1"
+    whatwg-fetch ">=0.10.0"
+
 jest-worker@^26.6.2:
   version "26.6.2"
   resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
@@ -2998,6 +3033,14 @@
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
   integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 
+node-fetch@^1.0.1:
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
+  integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
+  dependencies:
+    encoding "^0.1.11"
+    is-stream "^1.0.1"
+
 node-forge@^0.10.0:
   version "0.10.0"
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
@@ -3852,7 +3895,7 @@
   dependencies:
     ret "~0.1.10"
 
-"safer-buffer@>= 2.1.2 < 3":
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -4723,6 +4766,11 @@
   resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
   integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
 
+whatwg-fetch@>=0.10.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868"
+  integrity sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==
+
 which-module@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"