First commit
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
type AuthProvider,
|
||||
} from "@refinedev/core";
|
||||
|
||||
import {
|
||||
ng,
|
||||
init as initNgWeb,
|
||||
type Session as NextGraphSession,
|
||||
} from "@ng-org/web";
|
||||
import { initNg as initNgSignals } from "@ng-org/orm";
|
||||
|
||||
export type NgAuthProvider = AuthProvider & {
|
||||
getSession: () => NextGraphSession | undefined;
|
||||
};
|
||||
|
||||
/** The session with the NextGraph engine or undefined if not loaded. */
|
||||
export let session: NextGraphSession | undefined;
|
||||
|
||||
export let resolveSessionPromise: (
|
||||
value: NextGraphSession | PromiseLike<NextGraphSession>,
|
||||
) => void;
|
||||
export let rejectSessionPromise: (reason?: any) => void;
|
||||
|
||||
/** Resolves to the current NextGraph session. */
|
||||
export let sessionPromise: Promise<NextGraphSession> = 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 {
|
||||
logout: true,
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
export default authProvider;
|
||||
@@ -0,0 +1,104 @@
|
||||
import { DataProvider } from "@refinedev/core";
|
||||
import { ng } from "@ng-org/web";
|
||||
import { getObjects } from "@ng-org/orm";
|
||||
import { addIdToObject, isGraphKey, getSignalObject } from "./utils";
|
||||
import { DataProviderConfig } from "./types";
|
||||
|
||||
const dataProvider = ({ dataModels, getSession }: DataProviderConfig) : DataProvider => ({
|
||||
getList: async ({ resource, pagination, sorters, filters, meta }) => {
|
||||
console.log('list', resource, pagination, sorters, filters, meta);
|
||||
|
||||
const objects = await getObjects(dataModels[resource].shapeType, "");
|
||||
|
||||
return {
|
||||
data: [...objects].map(addIdToObject) as any,
|
||||
total: 1,
|
||||
};
|
||||
},
|
||||
create: async ({ resource, variables, meta }) => {
|
||||
console.log("create", resource, variables, meta);
|
||||
|
||||
const session = getSession();
|
||||
console.log('session', session);
|
||||
|
||||
const docNuri = await ng.doc_create(
|
||||
session!.session_id,
|
||||
"Graph",
|
||||
"data:graph",
|
||||
"store", // Private store
|
||||
undefined
|
||||
);
|
||||
|
||||
const set = await getSignalObject(dataModels[resource].shapeType, [docNuri]);
|
||||
|
||||
set.add({
|
||||
"@graph": docNuri,
|
||||
"@type": "https://www.w3.org/ns/activitystreams#Note",
|
||||
"@id": "",
|
||||
...variables
|
||||
});
|
||||
|
||||
const newData = set.first();
|
||||
|
||||
return {
|
||||
data: addIdToObject({ ...newData }),
|
||||
};
|
||||
},
|
||||
update: async ({ resource, id, variables, meta }) => {
|
||||
console.log('update', resource, id, variables, meta);
|
||||
|
||||
const set = await getSignalObject(dataModels[resource].shapeType, [`${id}`]);
|
||||
|
||||
const oldData = set.first()!;
|
||||
|
||||
const objectId = oldData["@id"];
|
||||
|
||||
const newData = {
|
||||
"@graph": id,
|
||||
"@type": "https://www.w3.org/ns/activitystreams#Note",
|
||||
"@id": objectId,
|
||||
...variables
|
||||
};
|
||||
|
||||
// Delete old data and add new data to the set
|
||||
// Hopefully the ORM will handle this intelligently
|
||||
set.delete(oldData);
|
||||
set.add(newData);
|
||||
|
||||
return {
|
||||
data: addIdToObject(newData)
|
||||
};
|
||||
},
|
||||
deleteOne: async ({ resource, id, variables, meta }) => {
|
||||
console.log('deleteOne', resource, id, variables, meta);
|
||||
|
||||
const set = await getSignalObject(dataModels[resource].shapeType, [`${id}`]);
|
||||
|
||||
const oldData = set.first()!;
|
||||
|
||||
set.delete(oldData);
|
||||
|
||||
return {
|
||||
data: addIdToObject({ ...oldData })
|
||||
};
|
||||
},
|
||||
getOne: async ({ resource, id, meta }) => {
|
||||
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}`] });
|
||||
|
||||
return {
|
||||
data: addIdToObject([...objects][0])
|
||||
}
|
||||
},
|
||||
getApiUrl: () => "",
|
||||
// optional methods
|
||||
// getMany: ({ resource, ids, meta }) => Promise,
|
||||
// createMany: ({ resource, variables, meta }) => Promise,
|
||||
// deleteMany: ({ resource, ids, variables, meta }) => Promise,
|
||||
// updateMany: ({ resource, ids, variables, meta }) => Promise,
|
||||
// custom: ({ url, method, filters, sorters, payload, query, headers, meta }) => Promise,
|
||||
});
|
||||
|
||||
export default dataProvider;
|
||||
@@ -0,0 +1,8 @@
|
||||
export { default as authProvider } from "./auth-provider";
|
||||
export * from "./auth-provider";
|
||||
|
||||
export { default as dataProvider } from "./data-provider";
|
||||
export { default as liveProvider } from "./live-provider";
|
||||
|
||||
export * from "./types";
|
||||
export * from "./utils";
|
||||
@@ -0,0 +1,76 @@
|
||||
import { LiveProvider } from "@refinedev/core";
|
||||
import { watch } from "@ng-org/alien-deepsignals"
|
||||
import { getSignalObject } from "./utils";
|
||||
import { parsePatches } from "./utils";
|
||||
import { LiveProviderConfig, UnwatchFn } from "./types";
|
||||
|
||||
const liveProvider = ({ dataModels }: LiveProviderConfig) : LiveProvider => ({
|
||||
subscribe: async ({ channel, params, types, callback, meta }) : Promise<UnwatchFn> => {
|
||||
console.log('subscribe', channel, params?.subscriptionType, params, types, meta);
|
||||
|
||||
const ids = params?.ids as string[];
|
||||
|
||||
if (channel.startsWith("resources/")) {
|
||||
const resource = channel.split('/')[1];
|
||||
|
||||
if (dataModels[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 { stopListening } = watch(set, ({ newValue, patches }) => {
|
||||
console.log('object modified', newValue, patches);
|
||||
|
||||
const modifiedIds = parsePatches(patches);
|
||||
|
||||
for( const [graphId, eventType] of Object.entries(modifiedIds)) {
|
||||
callback({
|
||||
channel,
|
||||
type: eventType,
|
||||
payload: { ids: [graphId] },
|
||||
date: new Date(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return stopListening;
|
||||
|
||||
} else {
|
||||
// We don't close the subscription because we want to keep listening for changes
|
||||
const set = await getSignalObject(dataModels[resource].shapeType, undefined, false);
|
||||
|
||||
console.log('set listening list', set);
|
||||
|
||||
const { stopListening } = watch(set, ({ newValue, patches }) => {
|
||||
console.log('objects list modified', newValue, patches);
|
||||
|
||||
const modifiedIds = parsePatches(patches);
|
||||
|
||||
for( const [graphId, eventType] of Object.entries(modifiedIds)) {
|
||||
callback({
|
||||
channel,
|
||||
type: eventType,
|
||||
payload: { ids: [graphId] },
|
||||
date: new Date(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return stopListening;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return () => undefined;
|
||||
},
|
||||
unsubscribe: async (unwatchFn: UnwatchFn | Promise<UnwatchFn>) => {
|
||||
console.log('unsubscribe', typeof unwatchFn, unwatchFn);
|
||||
const fn = await Promise.resolve(unwatchFn);
|
||||
if (typeof fn === "function") {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default liveProvider;
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ShapeType } from "@ng-org/shex-orm";
|
||||
import type { Session } from "@ng-org/web";
|
||||
|
||||
export type DataModel = {
|
||||
shapeType: ShapeType<any>;
|
||||
}
|
||||
|
||||
export type DataModels = Record<string, DataModel>;
|
||||
|
||||
export type DataProviderConfig = {
|
||||
dataModels: DataModels;
|
||||
getSession: () => Session | undefined;
|
||||
};
|
||||
|
||||
export type LiveProviderConfig = {
|
||||
dataModels: DataModels;
|
||||
};
|
||||
|
||||
export type UnwatchFn = () => void;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { BaseKey } from "@refinedev/core";
|
||||
import { OrmSubscription } from "@ng-org/orm";
|
||||
import type { ShapeType } from "@ng-org/shex-orm";
|
||||
import { DeepSignalSet, DeepPatch } from "@ng-org/alien-deepsignals"
|
||||
|
||||
export const getSignalObject = async (shapeType: ShapeType<any>, ids?: string[], closeSubscription: boolean = true) : Promise<DeepSignalSet<any>> => {
|
||||
const subscription = OrmSubscription.getOrCreate(shapeType, { graphs: ids || [''] });
|
||||
await subscription.readyPromise;
|
||||
const { signalObject } = subscription;
|
||||
if (closeSubscription) subscription.close();
|
||||
return signalObject;
|
||||
}
|
||||
|
||||
export const isGraphKey = (key: BaseKey): boolean => {
|
||||
return key !== null && typeof key === "string" && key.includes(":v:");
|
||||
}
|
||||
|
||||
export const addIdToObject = (obj: any) => {
|
||||
if (!obj["@graph"]) {
|
||||
throw new Error("Object is missing @graph property");
|
||||
}
|
||||
return { id: obj["@graph"], ...obj };
|
||||
};
|
||||
|
||||
export const parsePatches = (patches: DeepPatch[]) => {
|
||||
let modifiedIds : Record<string, 'created' | 'updated' | 'deleted'> = {};
|
||||
|
||||
for( const patch of patches) {
|
||||
// The path property include the @id property first, then the modified field
|
||||
const [graphId] = (patch.path[0] as string).split('|');
|
||||
const modifiedField = patch.path[1] as string;
|
||||
|
||||
// If a remove operation includes only the ID, this is a deletion
|
||||
if (patch.op === "remove" && !modifiedField) {
|
||||
modifiedIds[graphId] = 'deleted';
|
||||
}
|
||||
|
||||
// If an add operation involves an @id, this is a creation
|
||||
if (patch.op === "add" && modifiedField === "@id") {
|
||||
if (modifiedIds[graphId] === 'deleted') {
|
||||
// If the object was previously marked as deleted, consider this an update instead
|
||||
modifiedIds[graphId] = 'updated';
|
||||
} else {
|
||||
modifiedIds[graphId] = 'created';
|
||||
}
|
||||
}
|
||||
|
||||
if (modifiedIds[graphId] !== 'created' && modifiedIds[graphId] !== 'deleted') {
|
||||
modifiedIds[graphId] = 'updated';
|
||||
}
|
||||
};
|
||||
|
||||
return modifiedIds;
|
||||
}
|
||||
Reference in New Issue
Block a user