-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate off backbone.js, jQuery and underscore.js #4286
Comments
What I am dreaming of:
then, inside the component, it can call a hook like useResourceField or useResource, that would in turn resolve a single field value from the object, or entire object - and as a result, re-render on that field change, or any change *so the above can be implemented even with backbone remaining in place (in fact, we have a useResource that kind of does that already), but there are other issues with backbone, so bandaids like this are not the best long-term solution *on further thought, probably all references should be table name + front-end only GUID, so that if new resource becomes saved, the previous reference still holds true At the same time, I would ideally like to have a separate typescript type for saved resource (or reference to a resource), new resource, or a type that accepts both - this would add type safety and would clearly show whether react component expects to receive a saved resource, or if it doesn't care either way Additional benefit of central store for all objects is the fact that we can load the same object again in the code, and not worry about caching as load would get the object from the central store So now just to find a library that can do the above, or as close as we can, or else implement our own |
Some suggested libraries after playing with ChatGPT with AutoExpert plugin: For state management:
For ORM aspects:
TODOs:
|
Exploring ref as a stringA ref should probably be just front-end only GUID, nothing else. this way it's a string, thus preventing a bug where a new instance of an object is created on each render (as could happen with a ref like Then, the following types could be used for the GUID (and from now on, I will refer to GUID as Ref): export type NewResourceRef<TABLE extends keyof Tables> = Nominal<string, `NewResourceRef_${TABLE}`>;
export type SavedResourceRef<TABLE extends keyof Tables> = Nominal<string, `SavedResourceRef_${TABLE}`>;
export type ResourceRef<TABLE extends keyof Tables> = NewResourceRef<TABLE> | SavedResourceRef<TABLE>;
// now, in the code can use these types as argument types in functions/components or in return types:
NewResourceRef<'Accession'> // ref to new Accession resource
SavedRecordRef<'CollectionObject' | 'Accession'> // ref to saved CollectionObject or Accession resource
ResourceRef<keyof Tables & `${string}Attachment`> // ref to a saved or new resource, from any table whose name ends with Attachment
ResourceRef<keyof Tables> // a ref to any resource from any table
// see more about nominal types - https://zackoverflow.dev/writing/nominal-and-refinement-types-typescript
const Brand = Symbol();
export type Nominal<Type, Name extends string> = Type & {
readonly [Brand]: [Name];
}; The above Then, the following utils would be available:
(using Resource rather than shorter Record as Record conflicts with TypeScript's native Record type) That all is cool, but: Exploring Object refsWe could have refs be just {} - simple and memory efficient. Also, "guid" may be confused with the "guid" database field, so from now on, I would call it uuid instead. type NewResourceRef<TABLE_NAME extends keyof Tables> = {
readonly table: Tables[TABLE_NAME];
readonly uuid: number;
readonly id: undefined;
};
type SavedResourceRef<TABLE_NAME extends keyof Tables> = {
readonly table: Tables[TABLE_NAME];
readonly uuid: number;
readonly id: number;
};
type ResourceRef<TABLE_NAME extends keyof Tables> = NewResourceRef<TABLE_NAME> | SavedResourceRef<TABLE_NAME>;
type NewResource<TABLE_NAME extends keyof Tables> = NewResourceRef<TABLE_NAME> & {
data: Record<string, unknown>;
readonly reference: NewResourceRef<TABLE_NAME>;
};
type SavedResource<TABLE_NAME extends keyof Tables> = SavedResourceRef<TABLE_NAME> & {
data: Record<string, unknown>;
readonly reference: SavedResourceRef<TABLE_NAME>;
};
type Resource<TABLE_NAME extends keyof Tables> = NewResource<TABLE_NAME> | SavedResource<TABLE_NAME>;
const store = new WeakMap<ResourceRef<keyof Tables>, Resource<keyof Tables>>();
const tables = {} as { readonly [TABLE_NAME in keyof Tables]: Tables[TABLE_NAME]; };
const genericTables = {} as Readonly<Record<keyof Tables,Tables[keyof Tables]>>;
const makeUuid = ()=>Math.random();
async function fetchResource<TABLE_NAME extends keyof Tables>(tableName:TABLE_NAME, id:number): Promise<Resource<TABLE_NAME>> {
const reference: SavedResourceRef<TABLE_NAME> = { table: tables[tableName], id, uuid:makeUuid() };
// simplified:
const resourceData = await fetch('').then((response)=>response.json());
const resource = {...reference, reference, data: resourceData as Record<string,unknown> };
store.set(reference, resource);
return resource;
} still need to figure out how "new resource" gets converted into "saved resource" when it's saved (how to do so type safely and bug-free) other questions:
here I am realizing something: given that a WeakMap key must be an object, and we want to ensure there is only one instance of a given object at a time, then there is no way to resolve a tableName+id to an existing WeakMap entry, because any mapping between tableName+id to resource or resource ref would prevent garbage collection.
The reason I am a bit afraid of option 1 is that in cases of query results, or attachments viewer, thousands of objects can be created - no garbage collection could mean big memory leaks TODOs:
|
I have just remembered that JavaScript recently gained a new feature - WeakRef, which is exactly what we need - a reference to the object, that does not prevent garbage-collection. Then, for an object ref, I decided to go with this shape: { tableName: keyof Tables, id: number }
{ tableName: keyof Tables, id: string } where id is number for saved resource, but a string UUID for an unsaved resource. benefits:
you can check if resource is saved using Experimental implementation on the See #4339 for work in progress code TODO: in tests, save the value of the store in beforeAll hook, and restore the value in afterAll hook (basically, global context objects should be preserved, but all others should not be saved to the context). or maybe simply disable the global context when in tests? |
From #5139: |
We talked about this a lot, but didn't have an explicit issue for this.
Backbone.js is used for representing database resources on the front-end. That library also uses jQuery and underscore.js. That's the only place where jQuery and underscore.js are still used.
Backbone.js is not very compatible with the way react works (it's not reactive at all - making relatively simple things very complicated due to need for manually setting up listeners in useEffect - see #4264 for an example), is not very type-safe (may return null or undefined, rather than always undefined)
Closely related to #2621 (and see #2621 (comment))
This is going to be a somewhat large project because:
We could write our own library for representing resources on the front-end, but it would be best to do at least some research into available options before doing so (surely some new nice thing has became available in the last 15 years since backbone.js)
The text was updated successfully, but these errors were encountered: