Skip to content
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

Improve communication of serialised Realm objects between device and plugin #123

Merged
merged 21 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions documentation/LiveUpdating.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ We check if the object being deleted is currently in view, then we remove it fro
const addedObject = convertObjects(
[clone],
state.currentSchema,
downloadData
)[0];
newObjects.splice(index, 0, addedObject);
const newLastObject = newObjects[newObjects.length - 1];
Expand Down Expand Up @@ -178,7 +177,6 @@ We check if the object being deleted is currently in view, then we remove it fro
const addedObject = convertObjects(
[clone],
state.currentSchema,
downloadData
)[0];
newObjects.splice(index, 1, addedObject);
const newLastObject = newObjects[newObjects.length - 1];
Expand Down
15 changes: 15 additions & 0 deletions flipper-plugin-realm/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## v1.1.0

This version brings a number of major changes to plugin's functionality, improving the ability to display more complex types of Realm objects. Please make sure to update your `realm-flipper-plugin-device` to the new version as well to ensure compatibility.

### Enhancements
* Referenced objects are now lazy-loaded. By default, they will display their object key and type (just for internal reference) and their actual values can be seen when they are inspected. This brings performance improvements when loading many objects with references as well as better support for circular and deeply nested references.
* Embedded object support.

### Fixed
* Objects with circular references displaying as undefined. ([#96](https://github.com/realm/realm-flipper-plugin/issues/96)).
* Clicking "Cancel" when modifying objects still changing the objects' local display state in the desktop plugin.

### Compatibility
* `realm` >= v11
* `realm-flipper-plugin-device` >= v1.1.0
2 changes: 1 addition & 1 deletion flipper-plugin-realm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "realm-flipper-plugin",
"id": "realm",
"version": "1.0.14",
"version": "1.1.0",
"pluginType": "client",
"description": "A Flipper Plugin to debug Realm applications.",
"main": "dist/bundle.js",
Expand Down
167 changes: 63 additions & 104 deletions flipper-plugin-realm/src/CommonTypes.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
// A helper interface which extends Realm.Object with
// a string index signature and object key field for reference.
export interface IndexableRealmObject extends Realm.Object {
[key: string]: unknown;
// Contains the object key that was sent by the device.
_pluginObjectKey: string;
import { AddLiveObjectRequest, AddObjectRequest, DownloadDataRequest, DeleteLiveObjectRequest, EditLiveObjectRequest, ModifyObjectRequest, GetObjectRequest, GetObjectsRequest, GetObjectsResponse, GetRealmsResponse, GetSchemasRequest, GetSchemasResponse, PlainRealmObject, ReceivedCurrentQueryRequest, RemoveObjectRequest, SerializedRealmObject, RealmObjectReference} from "./SharedTypes";


/**
* A helper interface which wraps Realm.Object with
* information about its object type and key for reference.
* @see SerializedRealmObject
**/
export interface DeserializedRealmObject extends RealmObjectReference {
// A plain representation of the Realm object
realmObject: PlainRealmObject;
}

export interface DeserializedRealmData {
length: number;
info: [string, string, string];
}

// A Realm.CanonicalObjectSchema interface with a sorting order field.
export interface DeserializedRealmDecimal128 {
$numberDecimal: string
}

/** A Realm.CanonicalObjectSchema interface with a sorting order field. */
export interface SortedObjectSchema extends Realm.CanonicalObjectSchema {
order: string[];
}
Expand All @@ -17,11 +31,13 @@ export interface CanonicalObjectSchemaPropertyRow
primaryKey: boolean;
}

export type DownloadDataFunction = (schema: string, objectKey: string, propertyName: string) => Promise<Uint8Array>;

export type RealmPluginState = {
deviceSerial: string;
realms: string[];
selectedRealm: string;
objects: IndexableRealmObject[];
objects: DeserializedRealmObject[];
schemas: SortedObjectSchema[];
currentSchema: SortedObjectSchema | null;
schemaHistory: SortedObjectSchema[];
Expand All @@ -37,113 +53,29 @@ export type RealmPluginState = {
};

export type Events = {
getObjects: ObjectsMessage;
getSchemas: SchemaMessage;
getObjects: GetObjectsResponse;
getSchemas: GetSchemasResponse;
liveObjectAdded: AddLiveObjectRequest;
liveObjectDeleted: DeleteLiveObjectRequest;
liveObjectEdited: EditLiveObjectRequest;
getCurrentQuery: undefined;
getRealms: RealmsMessage;
getRealms: GetRealmsResponse;
executeQuery: QueryResult;
};

export type Methods = {
executeQuery: (query: QueryObject) => Promise<Realm.Object[]>;
getObjects: (data: getForwardsObjectsRequest) => Promise<ObjectsMessage>;
getSchemas: (data: RealmRequest) => Promise<SchemaMessage>;
getRealms: () => Promise<RealmsMessage>;
addObject: (object: AddObject) => Promise<Realm.Object>;
modifyObject: (newObject: EditObject) => Promise<Realm.Object>;
removeObject: (object: RemoveObject) => Promise<void>;
getObjects: (data: GetObjectsRequest) => Promise<GetObjectsResponse>;
getObject: (data: GetObjectRequest) => Promise<SerializedRealmObject>;
getSchemas: (data: GetSchemasRequest) => Promise<GetSchemasResponse>;
getRealms: () => Promise<GetRealmsResponse>;
addObject: (object: AddObjectRequest) => Promise<PlainRealmObject>;
modifyObject: (newObject: ModifyObjectRequest) => Promise<PlainRealmObject>;
removeObject: (object: RemoveObjectRequest) => Promise<void>;
receivedCurrentQuery: (request: ReceivedCurrentQueryRequest) => Promise<void>;
downloadData: (data: DataDownloadRequest) => Promise<Uint8Array>;
};

type ReceivedCurrentQueryRequest = {
schemaName: string | null;
realm: string;
sortingDirection: 'ascend' | 'descend' | null;
sortingColumn: string | null;
}

type DataDownloadRequest = {
schemaName: string;
realm: string;
objectKey: string;
propertyName: string;
};

export type EditObject = {
schemaName?: string;
realm?: string;
object: Realm.Object;
propsChanged?: string[];
objectKey: string;
};

export type RemoveObject = {
schemaName?: string;
realm?: string;
object: Realm.Object;
objectKey: string;
};

export type AddObject = {
schemaName?: string;
realm?: string;
object: Realm.Object;
propsChanged?: string[];
};
export type RealmsMessage = {
realms: string[];
objects: Record<string, unknown>[];
total: number;
};
export type ObjectsMessage = {
objects: IndexableRealmObject[];
total: number;
nextCursor: string;
prev_cursor: { [sortingField: string]: number };
hasMore: boolean;
};
export type ObjectMessage = {
object: Realm.Object;
};
export type SchemaMessage = {
schemas: Array<Realm.CanonicalObjectSchema>;
};
type RealmRequest = {
realm: string;
};
type getForwardsObjectsRequest = {
schemaName: string;
realm: string;
cursor: string | null;
sortingColumn: string | null;
sortingDirection: 'ascend' | 'descend' | null;
query: string;
downloadData: (data: DownloadDataRequest) => Promise<Uint8Array>;
};

export type ObjectRequest = {
schemaName: string;
realm: string;
primaryKey: string;
};
export type AddLiveObjectRequest = {
newObject: IndexableRealmObject;
index: number;
schemaName: string;
newObjectKey: string;
};
export type DeleteLiveObjectRequest = {
index: number;
schemaName: string;
};
export type EditLiveObjectRequest = {
newObject: IndexableRealmObject;
index: number;
schemaName: string;
newObjectKey: string;
};
type QueryObject = {
schemaName: string;
query: string;
Expand All @@ -152,3 +84,30 @@ type QueryObject = {
export type QueryResult = {
result: Array<Realm.Object> | string;
};


export type MenuItem = {
key: number;
text: string;
onClick: () => void;
};

export type MenuItemGenerator = (
row: DeserializedRealmObject,
schemaProperty: Realm.CanonicalObjectSchemaProperty,
schema: Realm.ObjectSchema,
) => Array<MenuItem>;

export type DropdownPropertyType = {
record: DeserializedRealmObject | null;
schemaProperty: Realm.CanonicalObjectSchemaProperty | null;
currentSchema: Realm.ObjectSchema;
visible: boolean;
pointerX: number;
pointerY: number;
scrollX: number;
scrollY: number;
generateMenuItems: MenuItemGenerator;
};
export { AddLiveObjectRequest, AddObjectRequest, DownloadDataRequest, DeleteLiveObjectRequest, EditLiveObjectRequest, ModifyObjectRequest, GetObjectRequest, GetObjectsRequest, GetObjectsResponse, GetRealmsResponse, GetSchemasRequest, GetSchemasResponse, PlainRealmObject, ReceivedCurrentQueryRequest, RemoveObjectRequest, SerializedRealmObject, RealmObjectReference };

117 changes: 117 additions & 0 deletions flipper-plugin-realm/src/SharedTypes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/** Types shared across the desktop plugin and the device library */
export type PlainRealmObject = Record<string, unknown>
/**
* An interface containing refereence information about a Realm object sent
* from the device plugin.
*/
export interface RealmObjectReference {
// The object key of the stored Realm object
objectKey: string;
objectType?: string;
}

/**
* An interface for receiving and sending Realm Objects between
* the desktop plugin and the device.
* @see DeserializedRealmObject
**/
export interface SerializedRealmObject extends RealmObjectReference {
// Result of serializaing a Realm object from flatted.toJSON(realmObject.toJSON())
realmObject: any;
}

export type ReceivedCurrentQueryRequest = {
schemaName: string | null;
realm: string;
sortingDirection: 'ascend' | 'descend' | null;
sortingColumn: string | null;
}

export type DownloadDataRequest = {
schemaName: string;
realm: string;
objectKey: string;
propertyName: string;
};

export type ModifyObjectRequest = {
schemaName?: string;
realm?: string;
object: PlainRealmObject;
propsChanged?: string[];
objectKey: string;
};

export type RemoveObjectRequest = {
schemaName?: string;
realm?: string;
object: PlainRealmObject;
objectKey: string;
};

export type AddObjectRequest = {
schemaName?: string;
realm?: string;
object: PlainRealmObject;
propsChanged?: string[];
};

export type GetRealmsResponse = {
realms: string[];
objects: Record<string, unknown>[];
total: number;
};

export type ObjectMessage = {
object: Realm.Object;
};

export type GetSchemasRequest = {
realm: string;
};

export type GetSchemasResponse = {
schemas: Array<Realm.CanonicalObjectSchema>;
};

export type GetObjectRequest = {
schemaName: string;
realm: string;
objectKey: string;
};

export type GetObjectsRequest = {
schemaName: string;
realm: string;
cursor: string | null;
sortingColumn: string | null;
sortingDirection: 'ascend' | 'descend' | null;
query: string;
};

export type GetObjectsResponse = {
objects: SerializedRealmObject[];
total: number;
nextCursor: string;
prev_cursor: { [sortingField: string]: number };
hasMore: boolean;
};

export type AddLiveObjectRequest = {
newObject: SerializedRealmObject;
index: number;
schemaName: string;
newObjectKey: string;
};

export type DeleteLiveObjectRequest = {
index: number;
schemaName: string;
};

export type EditLiveObjectRequest = {
newObject: SerializedRealmObject;
index: number;
schemaName: string;
newObjectKey: string;
};
26 changes: 1 addition & 25 deletions flipper-plugin-realm/src/components/CustomDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,6 @@
import React, { useState } from 'react';
import { theme } from 'flipper-plugin';
import { IndexableRealmObject } from '../CommonTypes';

export type DropdownPropertyType = {
record: IndexableRealmObject | null;
schemaProperty: Realm.CanonicalObjectSchemaProperty | null;
currentSchema: Realm.ObjectSchema;
visible: boolean;
pointerX: number;
pointerY: number;
scrollX: number;
scrollY: number;
generateMenuItems: MenuItemGenerator;
};

type MenuItem = {
key: number;
text: string;
onClick: () => void;
};

export type MenuItemGenerator = (
row: IndexableRealmObject,
schemaProperty: Realm.CanonicalObjectSchemaProperty,
schema: Realm.ObjectSchema,
) => Array<MenuItem>;
import { DropdownPropertyType, MenuItem } from '../CommonTypes';

const listItem = (menuItem: MenuItem) => {
const [hover, setHover] = useState(false);
Expand Down
Loading