Skip to content

Commit

Permalink
- Added full object/record construction
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidArayan committed Dec 4, 2023
1 parent d7c63e9 commit 8335fc7
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 25 deletions.
69 changes: 67 additions & 2 deletions sdk-core/src/core/core-object.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GlobalObjectPool } from "./global-object-pool";
import { CoreObjectRelations } from "./relations/core-object-relations";

/**
Expand Down Expand Up @@ -26,7 +27,7 @@ export interface FetchData {
readonly attributes: any;
readonly relationships: any;
};
readonly includes: Array<any>;
readonly includes: Map<string, any>;
readonly cache: Map<string, CoreObject<CoreObjectAttributes>>;
}

Expand Down Expand Up @@ -134,18 +135,82 @@ export abstract class CoreObject<Attributes extends CoreObjectAttributes> {
throw new Error(`CoreObject.setFromAPI() - type mismatch, cannot set ${this.type} from data type ${data.object.type}`);
}

// assign the ID
// clear all previous cache as new object is getting constructed
this.relationships.cache.clear();

// assign the ID from the record
this._id = data.object.id;

// delete all previous keys from our object instance
Object.keys(this._attributes).forEach(key => delete (<any>(this._attributes))[key]);

// assign new keys to our attributes
// NOTE: this could probably be optimized by using attributes directly instead of deep-copy
for (const [key, value] of Object.entries(data.object.attributes)) {
(<any>(this._attributes))[key] = value;
}

// we need to build the relationships of this object from the records section
// which includes all the records from any include query
for (const [_key, value] of Object.entries(data.object.relationships)) {
const relationRecord: any = (<any>value).data;

// check if the object exists in the includes section - the value
// can either be a single object or an array
// this only contains id or type but not the full record
if (Array.isArray(relationRecord)) {
const arrayRecord: Array<any> = relationRecord;

arrayRecord.forEach((record: any) => {
this._CreateRecord(data, record);
});
}
else {
this._CreateRecord(data, relationRecord);
}
}
}

/**
* internal use function by setFromAPI that constructs a new record
*/
private _CreateRecord(data: FetchData, record: any): void {
const includedRecord: any = data.includes.get(record.id);

// quick exit - we don't need to do anything if record doesn't exist
// and doesn't want to be constructed
if (!includedRecord) {
return;
}

// check the cache to see if this record was previously constructed
// if so, we use that and quick exit
const cachedRecord: CoreObject<CoreObjectAttributes> | undefined = data.cache.get(record.id);

if (cachedRecord) {
this.relationships.cache.append(cachedRecord);

return;
}

// otherwise, create a new record and add it as a relation
const newObject: CoreObject<CoreObjectAttributes> | null = GlobalObjectPool.newInstance(record.type);

if (!newObject) {
throw new Error(`record constructor is unable to create a new record of type ${record.type}`);
}

// add the new object into the cache
data.cache.set(record.id, newObject);

// recursively construct the new object
newObject.setFromAPI({
object: record,
includes: data.includes,
cache: data.cache
});

// add as a relationship to the current object
this.relationships.cache.append(newObject);
}
}
21 changes: 15 additions & 6 deletions sdk-core/src/core/query/core-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,17 @@ export abstract class CoreQuery<T extends CoreObject<U>, U extends CoreObjectAtt
return results;
}

// map includes query into a map structure
const includes: Array<any> = json.included || new Array<any>();
const includesMap: Map<string, any> = new Map<string, any>();

// fill in the includes map for faster Lookup when creating object hierarchies
includes.forEach((includesRecord: any) => {
if (includesRecord.id) {
includesMap.set(includesRecord.id, includesRecord);
}
});

// begin parsing the json, which should be the details of the current
// object type - this could also be an array so we'll need extra object instances
// if Array - we are dealing with multiple records, otherwise its a single record
Expand All @@ -283,7 +294,6 @@ export abstract class CoreQuery<T extends CoreObject<U>, U extends CoreObjectAtt
// consecutive results will be created dynamically
// we create a global LUT cache to keep track of recursions
const cache: Map<string, CoreObject<CoreObjectAttributes>> = new Map<string, CoreObject<CoreObjectAttributes>>();
const includes: Array<any> = json.included || new Array<any>();
const object: any = listRecords[0];

// add the first object to the instance
Expand All @@ -292,7 +302,7 @@ export abstract class CoreQuery<T extends CoreObject<U>, U extends CoreObjectAtt
// construct the first object
instance.setFromAPI({
object: object,
includes: includes,
includes: includesMap,
cache: cache
});

Expand All @@ -301,7 +311,7 @@ export abstract class CoreQuery<T extends CoreObject<U>, U extends CoreObjectAtt
// begin construction of every other instance
for (let i = 1; i < listRecords.length; i++) {
const record = listRecords[i];
const objectInstance: CoreObject<CoreObjectAttributes> | null = GlobalObjectPool.newInstance(record.type);
const objectInstance: CoreObject<CoreObjectAttributes> | null = cache.get(object.id) || GlobalObjectPool.newInstance(record.type);

if (!objectInstance) {
CoreError.init({
Expand All @@ -319,7 +329,7 @@ export abstract class CoreQuery<T extends CoreObject<U>, U extends CoreObjectAtt

objectInstance.setFromAPI({
object: listRecords[i],
includes: includes,
includes: includesMap,
cache: cache
});

Expand All @@ -339,15 +349,14 @@ export abstract class CoreQuery<T extends CoreObject<U>, U extends CoreObjectAtt
// consecutive results will be created dynamically
// we create a global LUT cache to keep track of recursions
const cache: Map<string, CoreObject<CoreObjectAttributes>> = new Map<string, CoreObject<CoreObjectAttributes>>();
const includes: Array<any> = json.included || new Array<any>();

// add the first object to the instance
cache.set(record.id, instance);

// construct the first object
instance.setFromAPI({
object: record,
includes: includes,
includes: includesMap,
cache: cache
});

Expand Down
41 changes: 24 additions & 17 deletions sdk-core/src/core/relations/core-object-relations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,14 @@ import { Service } from "../service";

export type QuerySearch<T extends CoreObject<CoreObjectAttributes>> = (object: T) => boolean;

interface RelationCacheState {
fetched: boolean;
objects: Array<CoreObject<CoreObjectAttributes>> | null;
}

export class RelationCache {
private readonly _cache: Map<string, RelationCacheState> = new Map<string, RelationCacheState>();
private readonly _cache: Map<string, Array<CoreObject<CoreObjectAttributes>>> = new Map<string, Array<CoreObject<CoreObjectAttributes>>>();

/**
* Checks the current status of the provided object type's cache
*/
public checkStatus(objectType: typeof CoreObject): boolean {
const state: RelationCacheState | undefined = this._cache.get(objectType.type);

return state ? state.fetched : false;
return this._cache.has(objectType.type);
}

/**
Expand All @@ -41,24 +34,38 @@ export class RelationCache {
/**
* Adds a new object of type as a relation into the cache
*/
public put(objectType: typeof CoreObject, objects: Array<CoreObject<CoreObjectAttributes>>): void {
this._cache.set(objectType.type, { fetched: true, objects: objects });
public put(objectType: string, objects: Array<CoreObject<CoreObjectAttributes>>): void {
this._cache.set(objectType, objects);
}

/**
* Appends a new record into an existing relationship list
*/
public append(object: CoreObject<CoreObjectAttributes>): void {
const previousRecords: Array<CoreObject<CoreObjectAttributes>> | undefined = this._cache.get(object.type);

if (previousRecords) {
previousRecords.push(object);
return;
}

// otherwise create a new record
this.put(object.type, [object]);
}

/**
* Returns all objects related to this instance that matches the provided query
*/
public get<T extends CoreObject<CoreObjectAttributes>>(objectType: typeof CoreObject, search?: QuerySearch<T> | null, optArr?: Array<T> | null): Array<T> {
const results: Array<T> = optArr || new Array<T>();
const state: Array<CoreObject<CoreObjectAttributes>> | undefined = this._cache.get(objectType.type);

const state: RelationCacheState | undefined = this._cache.get(objectType.type);

if (!state || !state.objects || state.objects.length <= 0) {
if (!state || state.length <= 0) {
return results;
}

if (search) {
state.objects.forEach((object) => {
state.forEach((object) => {
try {
if (search(<T>object)) {
results.push(<T>object);
Expand All @@ -68,7 +75,7 @@ export class RelationCache {
});
}
else {
results.push(...<Array<T>>state.objects);
results.push(...<Array<T>>state);
}

return results;
Expand Down Expand Up @@ -114,7 +121,7 @@ export class CoreObjectRelations {
const results: Array<T> = await CoreQuery.fetch(connection, objectType.newInstance(), `${connection.url}/${this._instance.type}/${this._instance.id}/${objectType.type}`, 'GET');

// add the results into the cache
this.cache.put(objectType, results);
this.cache.put(objectType.type, results);

// return the final results from the cache
return this.cache.get(objectType, search);
Expand Down

0 comments on commit 8335fc7

Please sign in to comment.