Skip to content

Commit

Permalink
feat(schema-record): handle nested objects for schema-object and sche…
Browse files Browse the repository at this point in the history
…ma-array (#9496)

* feat(schema-record): handle nested objects for schema-object and schema-array

* Add write tests

* fix lint
  • Loading branch information
richgt authored Aug 22, 2024
1 parent 4cf0ce1 commit 6b256b1
Show file tree
Hide file tree
Showing 9 changed files with 851 additions and 85 deletions.
76 changes: 62 additions & 14 deletions packages/schema-record/src/-private/compute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import type {
import type { Link, Links } from '@warp-drive/core-types/spec/json-api-raw';
import { RecordStore } from '@warp-drive/core-types/symbols';

import type { SchemaRecord } from '../record';
import { SchemaRecord } from '../record';
import type { SchemaService } from '../schema';
import { Identifier, Parent } from '../symbols';
import { Editable, Identifier, Legacy, Parent } from '../symbols';
import { ManagedArray } from './managed-array';
import { ManagedObject } from './managed-object';

Expand All @@ -33,7 +33,7 @@ export const ManagedArrayMap = getOrSetGlobal(
);
export const ManagedObjectMap = getOrSetGlobal(
'ManagedObjectMap',
new Map<SchemaRecord, Map<FieldSchema, ManagedObject>>()
new Map<SchemaRecord, Map<FieldSchema, ManagedObject | SchemaRecord>>()
);

export function computeLocal(record: typeof Proxy<SchemaRecord>, field: LocalField, prop: string): unknown {
Expand All @@ -54,7 +54,12 @@ export function peekManagedArray(record: SchemaRecord, field: FieldSchema): Mana
}
}

export function peekManagedObject(record: SchemaRecord, field: FieldSchema): ManagedObject | undefined {
export function peekManagedObject(record: SchemaRecord, field: ObjectField): ManagedObject | undefined;
export function peekManagedObject(record: SchemaRecord, field: SchemaObjectField): SchemaRecord | undefined;
export function peekManagedObject(
record: SchemaRecord,
field: ObjectField | SchemaObjectField
): ManagedObject | SchemaRecord | undefined {
const managedObjectMapForRecord = ManagedObjectMap.get(record);
if (managedObjectMapForRecord) {
return managedObjectMapForRecord.get(field);
Expand Down Expand Up @@ -85,7 +90,7 @@ export function computeArray(
identifier: StableRecordIdentifier,
field: ArrayField | SchemaArrayField,
path: string[],
isSchemaArray = false
isSchemaArray: boolean
) {
// the thing we hand out needs to know its owner and path in a private manner
// its "address" is the parent identifier (identifier) + field name (field.name)
Expand Down Expand Up @@ -122,8 +127,7 @@ export function computeObject(
record: SchemaRecord,
identifier: StableRecordIdentifier,
field: ObjectField | SchemaObjectField,
prop: string,
isSchemaObject = false
path: string[]
) {
const managedObjectMapForRecord = ManagedObjectMap.get(record);
let managedObject;
Expand All @@ -133,7 +137,7 @@ export function computeObject(
if (managedObject) {
return managedObject;
} else {
let rawValue = cache.getAttr(identifier, prop) as object;
let rawValue = cache.getAttr(identifier, path) as object;
if (!rawValue) {
return null;
}
Expand All @@ -142,17 +146,61 @@ export function computeObject(
const transform = schema.transformation(field);
rawValue = transform.hydrate(rawValue as ObjectValue, field.options ?? null, record) as object;
}
}
managedObject = new ManagedObject(store, schema, cache, field, rawValue, identifier, prop, record, isSchemaObject);
if (!managedObjectMapForRecord) {
ManagedObjectMap.set(record, new Map([[field, managedObject]]));
} else {
managedObjectMapForRecord.set(field, managedObject);
// for schema-object, this should likely be an embedded SchemaRecord now
managedObject = new ManagedObject(store, schema, cache, field, rawValue, identifier, path, record, false);
if (!managedObjectMapForRecord) {
ManagedObjectMap.set(record, new Map([[field, managedObject]]));
} else {
managedObjectMapForRecord.set(field, managedObject);
}
}
}
return managedObject;
}

export function computeSchemaObject(
store: Store,
cache: Cache,
record: SchemaRecord,
identifier: StableRecordIdentifier,
field: ObjectField | SchemaObjectField,
path: string[],
legacy: boolean,
editable: boolean
) {
const schemaObjectMapForRecord = ManagedObjectMap.get(record);
let schemaObject;
if (schemaObjectMapForRecord) {
schemaObject = schemaObjectMapForRecord.get(field);
}
if (schemaObject) {
return schemaObject;
} else {
const rawValue = cache.getAttr(identifier, path) as object;
if (!rawValue) {
return null;
}
const embeddedPath = path.slice();
schemaObject = new SchemaRecord(
store,
identifier,
{
[Editable]: editable,
[Legacy]: legacy,
},
true,
field.type,
embeddedPath
);
}
if (!schemaObjectMapForRecord) {
ManagedObjectMap.set(record, new Map([[field, schemaObject]]));
} else {
schemaObjectMapForRecord.set(field, schemaObject);
}
return schemaObject;
}

export function computeAttribute(cache: Cache, identifier: StableRecordIdentifier, prop: string): unknown {
return cache.getAttr(identifier, prop);
}
Expand Down
28 changes: 15 additions & 13 deletions packages/schema-record/src/-private/managed-array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { ArrayField, HashField, SchemaArrayField } from '@warp-drive/core-t

import { SchemaRecord } from '../record';
import type { SchemaService } from '../schema';
import { ARRAY_SIGNAL, Editable, Identifier, Legacy, MUTATE, SOURCE } from '../symbols';
import { ARRAY_SIGNAL, Editable, Identifier, Legacy, MUTATE, Parent, SOURCE } from '../symbols';

export function notifyArray(arr: ManagedArray) {
addToTransaction(arr[ARRAY_SIGNAL]);
Expand Down Expand Up @@ -105,7 +105,7 @@ export interface ManagedArray extends Omit<Array<unknown>, '[]'> {

export class ManagedArray {
[SOURCE]: unknown[];
declare address: StableRecordIdentifier;
declare identifier: StableRecordIdentifier;
declare path: string[];
declare owner: SchemaRecord;
declare [ARRAY_SIGNAL]: Signal;
Expand All @@ -116,7 +116,7 @@ export class ManagedArray {
cache: Cache,
field: ArrayField | SchemaArrayField,
data: unknown[],
address: StableRecordIdentifier,
identifier: StableRecordIdentifier,
path: string[],
owner: SchemaRecord,
isSchemaArray: boolean
Expand All @@ -127,7 +127,7 @@ export class ManagedArray {
this[ARRAY_SIGNAL] = createSignal(this, 'length');
const _SIGNAL = this[ARRAY_SIGNAL];
const boundFns = new Map<KeyType, ProxiedMethod>();
this.address = address;
this.identifier = identifier;
this.path = path;
this.owner = owner;
let transaction = false;
Expand All @@ -149,8 +149,8 @@ export class ManagedArray {
if (prop === ARRAY_SIGNAL) {
return _SIGNAL;
}
if (prop === 'address') {
return self.address;
if (prop === 'identifier') {
return self.identifier;
}
if (prop === 'owner') {
return self.owner;
Expand All @@ -160,7 +160,7 @@ export class ManagedArray {
if (_SIGNAL.shouldReset && (index !== null || SYNC_PROPS.has(prop) || isArrayGetter(prop))) {
_SIGNAL.t = false;
_SIGNAL.shouldReset = false;
const newData = cache.getAttr(address, path);
const newData = cache.getAttr(identifier, path);
if (newData && newData !== self[SOURCE]) {
self[SOURCE].length = 0;
self[SOURCE].push(...(newData as ArrayValue));
Expand Down Expand Up @@ -216,9 +216,11 @@ export class ManagedArray {
// same object reference from cache should result in same SchemaRecord
// embedded object.
recordPath.push(index as unknown as string);
const recordIdentifier = self.owner[Identifier] || self.owner[Parent];

record = new SchemaRecord(
store,
self.owner[Identifier],
recordIdentifier,
{ [Editable]: self.owner[Editable], [Legacy]: self.owner[Legacy] },
true,
field.type,
Expand Down Expand Up @@ -281,9 +283,9 @@ export class ManagedArray {
return Reflect.get(target, prop, receiver);
},
set(target, prop: KeyType, value, receiver) {
if (prop === 'address') {
if (prop === 'identifier') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
self.address = value;
self.identifier = value;
return true;
}
if (prop === 'owner') {
Expand All @@ -295,7 +297,7 @@ export class ManagedArray {

if (reflect) {
if (!field.type) {
cache.setAttr(address, path, self[SOURCE] as Value);
cache.setAttr(identifier, path, self[SOURCE] as Value);
_SIGNAL.shouldReset = true;
return true;
}
Expand All @@ -304,13 +306,13 @@ export class ManagedArray {
if (!isSchemaArray) {
const transform = schema.transformation(field);
if (!transform) {
throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`);
throw new Error(`No '${field.type}' transform defined for use by ${identifier.type}.${String(prop)}`);
}
rawValue = (self[SOURCE] as ArrayValue).map((item) =>
transform.serialize(item, field.options ?? null, self.owner)
);
}
cache.setAttr(address, path, rawValue as Value);
cache.setAttr(identifier, path, rawValue as Value);
_SIGNAL.shouldReset = true;
}
return reflect;
Expand Down
44 changes: 20 additions & 24 deletions packages/schema-record/src/-private/managed-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { addToTransaction, createSignal, subscribe } from '@ember-data/tracking/
import type { StableRecordIdentifier } from '@warp-drive/core-types';
import type { Cache } from '@warp-drive/core-types/cache';
import type { ObjectValue, Value } from '@warp-drive/core-types/json/raw';
import { STRUCTURED } from '@warp-drive/core-types/request';
import type { ObjectField, SchemaObjectField } from '@warp-drive/core-types/schema/fields';

import type { SchemaRecord } from '../record';
Expand All @@ -15,7 +16,7 @@ export function notifyObject(obj: ManagedObject) {
}

type KeyType = string | symbol | number;
const ignoredGlobalFields = new Set<string>(['constructor', 'setInterval', 'nodeType', 'length']);
const ignoredGlobalFields = new Set<string>(['setInterval', 'nodeType', 'nodeName', 'length', 'document', STRUCTURED]);
export interface ManagedObject {
[MUTATE]?(
target: unknown[],
Expand All @@ -28,8 +29,8 @@ export interface ManagedObject {

export class ManagedObject {
[SOURCE]: object;
declare address: StableRecordIdentifier;
declare key: string;
declare identifier: StableRecordIdentifier;
declare path: string[];
declare owner: SchemaRecord;
declare [OBJECT_SIGNAL]: Signal;

Expand All @@ -39,8 +40,8 @@ export class ManagedObject {
cache: Cache,
field: ObjectField | SchemaObjectField,
data: object,
address: StableRecordIdentifier,
key: string,
identifier: StableRecordIdentifier,
path: string[],
owner: SchemaRecord,
isSchemaObject: boolean
) {
Expand All @@ -50,8 +51,8 @@ export class ManagedObject {
this[OBJECT_SIGNAL] = createSignal(this, 'length');
const _SIGNAL = this[OBJECT_SIGNAL];
// const boundFns = new Map<KeyType, ProxiedMethod>();
this.address = address;
this.key = key;
this.identifier = identifier;
this.path = path;
this.owner = owner;
const transaction = false;

Expand Down Expand Up @@ -117,22 +118,22 @@ export class ManagedObject {
if (prop === OBJECT_SIGNAL) {
return _SIGNAL;
}
if (prop === 'address') {
return self.address;
}
if (prop === 'key') {
return self.key;
if (prop === 'identifier') {
return self.identifier;
}
if (prop === 'owner') {
return self.owner;
}
if (prop === Symbol.toStringTag) {
return `ManagedObject<${address.type}:${address.id} (${address.lid})>`;
return `ManagedObject<${identifier.type}:${identifier.id} (${identifier.lid})>`;
}
if (prop === 'constructor') {
return Object;
}

if (prop === 'toString') {
return function () {
return `ManagedObject<${address.type}:${address.id} (${address.lid})>`;
return `ManagedObject<${identifier.type}:${identifier.id} (${identifier.lid})>`;
};
}

Expand All @@ -144,7 +145,7 @@ export class ManagedObject {
if (_SIGNAL.shouldReset) {
_SIGNAL.t = false;
_SIGNAL.shouldReset = false;
let newData = cache.getAttr(self.address, self.key);
let newData = cache.getAttr(self.identifier, self.path);
if (newData && newData !== self[SOURCE]) {
if (!isSchemaObject && field.type) {
const transform = schema.transformation(field);
Expand Down Expand Up @@ -173,14 +174,9 @@ export class ManagedObject {
},

set(target, prop: KeyType, value, receiver) {
if (prop === 'address') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
self.address = value;
return true;
}
if (prop === 'key') {
if (prop === 'identifier') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
self.key = value;
self.identifier = value;
return true;
}
if (prop === 'owner') {
Expand All @@ -198,14 +194,14 @@ export class ManagedObject {

if (reflect) {
if (isSchemaObject || !field.type) {
cache.setAttr(self.address, self.key, self[SOURCE] as Value);
cache.setAttr(self.identifier, self.path, self[SOURCE] as Value);
_SIGNAL.shouldReset = true;
return true;
}

const transform = schema.transformation(field);
const val = transform.serialize(self[SOURCE], field.options ?? null, self.owner);
cache.setAttr(self.address, self.key, val);
cache.setAttr(self.identifier, self.path, val);
_SIGNAL.shouldReset = true;
}
return reflect;
Expand Down
Loading

0 comments on commit 6b256b1

Please sign in to comment.