From 85a3c4b771ddad67b10bcb8b950b5f736ae482d1 Mon Sep 17 00:00:00 2001 From: srosset81 Date: Fri, 10 Jul 2026 18:35:35 +0200 Subject: [PATCH] Refactor auth and init README --- README.md | 110 ++++++++++++++ examples/antd-app/src/App.tsx | 75 ++++------ examples/antd-app/src/shapes/data-models.ts | 10 -- examples/antd-app/src/shapes/shapeTypes.ts | 8 + examples/antd-app/src/utils/initNg.ts | 17 --- src/auth-provider.ts | 154 +++++++++----------- src/data-provider.ts | 14 +- src/live-provider.ts | 8 +- src/types.ts | 17 ++- 9 files changed, 233 insertions(+), 180 deletions(-) create mode 100644 README.md delete mode 100644 examples/antd-app/src/shapes/data-models.ts create mode 100644 examples/antd-app/src/shapes/shapeTypes.ts delete mode 100644 examples/antd-app/src/utils/initNg.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..93accfb --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# Refine providers for NextGraph + +Data, auth and live providers that can be used in any Refine application + +## Usage + +> For a quick overview, have a look at our [example app](./examples/antd-app/). + +### 1. Define your shapes + +Refine relies on the concept of "resources", which are data types, to manage your application in a structured way. NextGraph identifies data types using ShEx shapes which are transformed into "shape types" using [its own tool](https://docs.nextgraph.org/en/reference/shex-orm/). + +Create a `/shapes` directory and, within it, create two subdirectories: `/shex` and `/orm` + +Within the `/shapes/shex` directory, create a ShEx file with the shape of the data you want to handle. + +Here's an example of a shape for an ActivityStreams Note: + +````shex +PREFIX as: +PREFIX xsd: + +as:Note { + a [as:Note] ; + as:title xsd:string ; + as:content xsd:string ? ; +} +```` + +### 2. Pass the shape types to the data provider + +Go to the `/shapes` directory and run the following command: + +```bash +npx rdf-orm build --input ./shex --output ./orm +``` + +This will generate three files in the `/orm` subdirectory: + +- `note.schema.ts` +- `note.shapeTypes.ts` +- `note.typings.ts` + +Now within the `shapes` directory, create a `shapeTypes.ts` like this: + +```ts +import { ShapeTypes } from "@ng-org/refine-providers"; +import { NoteShapeType } from "./orm/note.shapeTypes"; + +const shapeTypes : ShapeTypes = { + notes: NoteShapeType +} + +export default shapeTypes; +``` + +### 3. Add the providers to Refine + +```ts +import { authProvider, dataProvider, liveProvider } from "@ng-org/refine-providers"; +import shapeTypes from "./shapes/shapeTypes"; + +const ngAuthProvider = authProvider(); +const ngDataProvider = dataProvider({ shapeTypes, authProvider: ngAuthProvider }) +const ngLiveProvider = liveProvider({ shapeTypes }) + +const App = () => ( + + ... +``` + +### 4. Create the React pages + +You can now call the various hooks provided by Refine to create, read, update or delete resources. + +For `useForm`, `useTable`, `useShow`, you can pass the `Note` generated by NextGraph CLI. + +Here's an example of a simple list view: + +```ts +import { List, useTable } from "@refinedev/antd"; +import { Table } from "antd"; +import { Note } from "./shapes/orm/notes.typings"; + +export const PostList = () => { + const { tableProps } = useTable(); + return ( + + + + + +
+
+ ); +}; +``` + +## Commands + +``` +pnpm build +pnpm watch +``` \ No newline at end of file diff --git a/examples/antd-app/src/App.tsx b/examples/antd-app/src/App.tsx index 8a76434..1d938f8 100644 --- a/examples/antd-app/src/App.tsx +++ b/examples/antd-app/src/App.tsx @@ -1,56 +1,40 @@ -import { - Refine, - Authenticated, -} from "@refinedev/core"; -import { - useNotificationProvider, - ThemedLayout, - ErrorComponent, - RefineThemes, -} from "@refinedev/antd"; -import { - DashboardOutlined, -} from "@ant-design/icons"; - -import routerProvider, { - NavigateToResource, - CatchAllNavigate, - UnsavedChangesNotifier, - DocumentTitleHandler, -} from "@refinedev/react-router"; -import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router"; +import { Refine, Authenticated } from "@refinedev/core"; +import { useNotificationProvider, ThemedLayout, ErrorComponent, RefineThemes } from "@refinedev/antd"; +import routerProvider, { NavigateToResource, CatchAllNavigate, UnsavedChangesNotifier, DocumentTitleHandler } from "@refinedev/react-router"; +import { BrowserRouter, Routes, Route, Outlet } from "react-router"; +import { DashboardOutlined } from "@ant-design/icons"; import { App as AntdApp, ConfigProvider } from "antd"; import "@ant-design/v5-patch-for-react-19"; import "@refinedev/antd/dist/reset.css"; -import { authProvider, dataProvider, liveProvider } from "@ng-org/refine-providers"; +import { authProvider as ngAuthProvider, dataProvider, liveProvider } from "@ng-org/refine-providers"; -import dataModels from "./shapes/data-models"; +import shapeTypes from "./shapes/shapeTypes"; import AuthPage from "./components/AuthPage"; import { PostList, PostCreate, PostEdit, PostShow } from "../src/pages/posts"; import { DashboardPage } from "../src/pages/dashboard"; -import { getSession } from "./utils/initNg"; +const authProvider = ngAuthProvider(); const App: React.FC = () => ( , - // }, - // }, + { + name: "dashboard", + list: "/", + meta: { + label: "Dashboard", + icon: , + }, + }, { name: "posts", list: "/posts", @@ -73,18 +57,17 @@ const App: React.FC = () => ( } - // > + } + > - // + } > - } /> - {/* } /> */} + } /> } /> @@ -94,7 +77,7 @@ const App: React.FC = () => ( - {/* }> @@ -103,15 +86,15 @@ const App: React.FC = () => ( > } /> } /> - */} + + - // + } > } /> diff --git a/examples/antd-app/src/shapes/data-models.ts b/examples/antd-app/src/shapes/data-models.ts deleted file mode 100644 index beb3fef..0000000 --- a/examples/antd-app/src/shapes/data-models.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataModels } from "@ng-org/refine-providers"; -import { NoteShapeType } from "./orm/post.shapeTypes"; - -const dataModels : DataModels = { - posts: { - shapeType: NoteShapeType - } -} - -export default dataModels; diff --git a/examples/antd-app/src/shapes/shapeTypes.ts b/examples/antd-app/src/shapes/shapeTypes.ts new file mode 100644 index 0000000..f5e6bef --- /dev/null +++ b/examples/antd-app/src/shapes/shapeTypes.ts @@ -0,0 +1,8 @@ +import { ShapeTypes } from "@ng-org/refine-providers"; +import { NoteShapeType } from "./orm/post.shapeTypes"; + +const shapeTypes : ShapeTypes = { + posts: NoteShapeType +} + +export default shapeTypes; diff --git a/examples/antd-app/src/utils/initNg.ts b/examples/antd-app/src/utils/initNg.ts deleted file mode 100644 index 7d2b86b..0000000 --- a/examples/antd-app/src/utils/initNg.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ng, init, Session } from "@ng-org/web"; -import { initNg } from "@ng-org/orm"; - -export let session: Session | undefined; - -init( - async (event: any) => { - session = event.session; - // The ORM needs to have access to ng, - // the interface to the engine running in WASM. - initNg(ng, session!); - }, - true, - [], -); - -export const getSession = () => session; diff --git a/src/auth-provider.ts b/src/auth-provider.ts index 978a45c..59b50e2 100644 --- a/src/auth-provider.ts +++ b/src/auth-provider.ts @@ -1,96 +1,74 @@ -import { - type AuthProvider, -} from "@refinedev/core"; +import { ng, init, Session } from "@ng-org/web"; +import { initNg } from "@ng-org/orm"; +import { AuthProvider } from "./types"; -import { - ng, - init as initNgWeb, - type Session as NextGraphSession, -} from "@ng-org/web"; -import { initNg as initNgSignals } from "@ng-org/orm"; +const authProvider: () => AuthProvider = () => { + let session: Session | undefined; -export type NgAuthProvider = AuthProvider & { - getSession: () => NextGraphSession | undefined; -}; + // TODO load the session only when the login method is called + init( + async (event: any) => { + session = event.session; + // The ORM needs to have access to ng, + // the interface to the engine running in WASM. + initNg(ng, session!); + }, + true, + [], + ); -/** The session with the NextGraph engine or undefined if not loaded. */ -export let session: NextGraphSession | undefined; - -export let resolveSessionPromise: ( - value: NextGraphSession | PromiseLike, -) => void; -export let rejectSessionPromise: (reason?: any) => void; - -/** Resolves to the current NextGraph session. */ -export let sessionPromise: Promise = new Promise( - (resolve, reject) => { - resolveSessionPromise = resolve; - rejectSessionPromise = reject; - }, -); - -const authProvider: NgAuthProvider = { - login: async ({ brokerUrl }) => { - // try { - // await initNgWeb( - // async (event: any) => { - // session = event.session; - // session!.ng ??= ng; - // localStorage.setItem("logged", "true"); - // console.log('session', session); - // resolveSessionPromise(session!); - - - - // initNgSignals(ng, session!); - // }, - // true, - // [], - // ); - - - // } catch (error) { - // rejectSessionPromise(error); - // } - - return { - success: true - }; - }, - register: async ({ brokerUrl }) => { - console.log('brokerUrl', brokerUrl); - - return { - success: true - }; - }, - logout: async () => { - localStorage.removeItem("logged"); - return { - success: true, - redirectTo: "/login", - }; - }, - onError: async (error) => { - if (error.response?.status === 401) { + return ({ + login: async ({ brokerUrl }) => { + console.log('login', brokerUrl); return { - logout: true, + success: true }; - } + }, + register: async ({ brokerUrl }) => { + console.log('register', brokerUrl); - return { error }; - }, - check: async () => { - return ({ authenticated: localStorage.getItem("logged") === 'true' }); - }, - getPermissions: async (params) => params?.permissions, - getIdentity: async () => ({ - id: 1, - name: "Jane Doe", - avatar: - "https://unsplash.com/photos/IWLOvomUmWU/download?force=true&w=640", - }), - getSession : () => session -}; + return { + success: true + }; + }, + logout: async () => { + console.log('logout'); + return { + success: true, + redirectTo: "/login", + }; + }, + onError: async (error) => { + console.log('onError', error); + if (error.response?.status === 401) { + return { + logout: true, + }; + } + + return { error }; + }, + check: async () => { + console.log('check'); + return { + authenticated: true + }; + }, + getPermissions: async (params) => { + console.log('getPermissions', params); + return params?.permissions + }, + getIdentity: async () => { + console.log('getIdentity'); + return { + id: 1, + name: "Jane Doe", + avatar: + "https://unsplash.com/photos/IWLOvomUmWU/download?force=true&w=640", + }; + }, + getSession : () => session + }); +} export default authProvider; diff --git a/src/data-provider.ts b/src/data-provider.ts index 0b1e9a5..48e5bba 100644 --- a/src/data-provider.ts +++ b/src/data-provider.ts @@ -4,11 +4,11 @@ import { getObjects } from "@ng-org/orm"; import { addIdToObject, isGraphKey, getSignalObject } from "./utils"; import { DataProviderConfig } from "./types"; -const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProvider => ({ +const dataProvider = ({ shapeTypes, authProvider }: DataProviderConfig) : DataProvider => ({ getList: async ({ resource, pagination, sorters, filters, meta }) => { console.log('list', resource, pagination, sorters, filters, meta); - const objects = await getObjects(dataModels[resource].shapeType, ""); + const objects = await getObjects(shapeTypes[resource], ""); return { data: [...objects].map(addIdToObject) as any, @@ -18,7 +18,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv create: async ({ resource, variables, meta }) => { console.log("create", resource, variables, meta); - const session = getSession(); + const session = authProvider.getSession(); const docNuri = await ng.doc_create( session!.session_id, @@ -28,7 +28,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv undefined ); - const set = await getSignalObject(dataModels[resource].shapeType, [docNuri]); + const set = await getSignalObject(shapeTypes[resource], [docNuri]); set.add({ "@graph": docNuri, @@ -46,7 +46,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv update: async ({ resource, id, variables, meta }) => { console.log('update', resource, id, variables, meta); - const set = await getSignalObject(dataModels[resource].shapeType, [`${id}`]); + const set = await getSignalObject(shapeTypes[resource], [`${id}`]); const oldData = set.first()!; @@ -71,7 +71,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv deleteOne: async ({ resource, id, variables, meta }) => { console.log('deleteOne', resource, id, variables, meta); - const set = await getSignalObject(dataModels[resource].shapeType, [`${id}`]); + const set = await getSignalObject(shapeTypes[resource], [`${id}`]); const oldData = set.first()!; @@ -85,7 +85,7 @@ const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProv console.log('getOne', resource, id, meta); // The ID should be a graph URI, but if it's an object key, we can use the subjects filter to the the object - const objects = await getObjects(dataModels[resource].shapeType, isGraphKey(id) ? `${id}` : { subjects: [`${id}`] }); + const objects = await getObjects(shapeTypes[resource], isGraphKey(id) ? `${id}` : { subjects: [`${id}`] }); return { data: addIdToObject([...objects][0]) diff --git a/src/live-provider.ts b/src/live-provider.ts index 357285d..20b96eb 100644 --- a/src/live-provider.ts +++ b/src/live-provider.ts @@ -3,7 +3,7 @@ import { watch } from "@ng-org/alien-deepsignals" import { getSignalObject, parsePatches } from "./utils"; import { LiveProviderConfig, UnwatchFn } from "./types"; -const liveProvider = ({ dataModels }: LiveProviderConfig) : LiveProvider => ({ +const liveProvider = ({ shapeTypes }: LiveProviderConfig) : LiveProvider => ({ subscribe: async ({ channel, params, types, callback, meta }) : Promise => { console.log('subscribe', channel, params?.subscriptionType, params, types, meta); @@ -12,11 +12,11 @@ const liveProvider = ({ dataModels }: LiveProviderConfig) : LiveProvider => ({ if (channel.startsWith("resources/")) { const resource = channel.split('/')[1]; - if (dataModels[resource]) { + if (shapeTypes[resource]) { if (ids?.length > 0) { // We don't close the subscription because we want to keep listening for changes - const set = await getSignalObject(dataModels[resource].shapeType, ids, false); + const set = await getSignalObject(shapeTypes[resource], ids, false); const { stopListening } = watch(set, ({ newValue, patches }) => { console.log('object modified', newValue, patches); @@ -37,7 +37,7 @@ const liveProvider = ({ dataModels }: LiveProviderConfig) : LiveProvider => ({ } else { // We don't close the subscription because we want to keep listening for changes - const set = await getSignalObject(dataModels[resource].shapeType, undefined, false); + const set = await getSignalObject(shapeTypes[resource], undefined, false); const { stopListening } = watch(set, ({ newValue, patches }) => { console.log('objects list modified', newValue, patches); diff --git a/src/types.ts b/src/types.ts index f5fcf3e..9ffc5f7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,19 +1,20 @@ +import type { AuthProvider as CoreAuthProvider } from "@refinedev/core"; import type { ShapeType } from "@ng-org/shex-orm"; import type { Session } from "@ng-org/web"; -export type DataModel = { - shapeType: ShapeType; -} +export type ShapeTypes = Record>; -export type DataModels = Record; - -export type DataProviderConfig = { - dataModels: DataModels; +export type AuthProvider = CoreAuthProvider & { getSession: () => Session | undefined; }; +export type DataProviderConfig = { + shapeTypes: ShapeTypes; + authProvider: AuthProvider; +}; + export type LiveProviderConfig = { - dataModels: DataModels; + shapeTypes: ShapeTypes; }; export type UnwatchFn = () => void;