Skip to content

Commit 0e41115

Browse files
committed
Revert to non-proxy implementation of model fields
This is technically more correct in case a model is instantiated outside of the store - making the magic field behaviour inherent in the class.
1 parent 679c59b commit 0e41115

File tree

3 files changed

+64
-24
lines changed

3 files changed

+64
-24
lines changed

src/model.ts

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { JsonApiResource, ModelForType, SchemaCollection } from './types';
2-
3-
type PartialJsonApiResource<T extends JsonApiResource> = {
4-
[P in keyof T]?: Partial<T[P]>;
5-
};
2+
import { Store } from './store.ts';
63

74
class ModelBase<Schema extends JsonApiResource = JsonApiResource> {
85
public type: Schema['type'];
@@ -12,7 +9,10 @@ class ModelBase<Schema extends JsonApiResource = JsonApiResource> {
129
public meta: Schema['meta'] = {};
1310
public links: Schema['links'] = {};
1411

15-
constructor(data: Schema) {
12+
constructor(
13+
data: Schema,
14+
protected store: Store,
15+
) {
1616
this.type = data.type;
1717
this.id = data.id;
1818

@@ -32,23 +32,73 @@ class ModelBase<Schema extends JsonApiResource = JsonApiResource> {
3232
/**
3333
* Merge new JSON:API resource data into the model.
3434
*/
35-
public merge(data: PartialJsonApiResource<Schema>): void {
35+
public merge(
36+
data: Omit<JsonApiResource<Schema['type']>, 'type' | 'id'>,
37+
): void {
3638
this.links = data.links ?? this.links;
3739
this.meta = data.meta ?? this.meta;
3840

3941
if (data.attributes) {
4042
Object.assign(this.attributes, data.attributes);
43+
44+
Object.keys(data.attributes).forEach((name) => {
45+
if (
46+
Object.getOwnPropertyDescriptor(
47+
Object.getPrototypeOf(this),
48+
name,
49+
) ||
50+
Object.getOwnPropertyDescriptor(this, name)
51+
) {
52+
return;
53+
}
54+
55+
Object.defineProperty(this, name, {
56+
get: () => this.attributes[name],
57+
configurable: true,
58+
enumerable: true,
59+
});
60+
});
4161
}
4262

4363
if (data.relationships) {
4464
Object.entries(data.relationships).forEach(
4565
([name, relationship]) => {
4666
this.relationships[name] = this.relationships[name] || {};
67+
4768
Object.assign(this.relationships[name], relationship);
69+
70+
if (
71+
Object.getOwnPropertyDescriptor(
72+
Object.getPrototypeOf(this),
73+
name,
74+
) ||
75+
Object.getOwnPropertyDescriptor(this, name)
76+
) {
77+
return;
78+
}
79+
80+
Object.defineProperty(this, name, {
81+
get: () => this.getRelationship(name),
82+
configurable: true,
83+
enumerable: true,
84+
});
4885
},
4986
);
5087
}
5188
}
89+
90+
private getRelationship(name: string) {
91+
const data = this.relationships[name].data;
92+
93+
// https://github.com/microsoft/TypeScript/issues/14107
94+
if (Array.isArray(data)) {
95+
return this.store.find(data);
96+
}
97+
98+
if (data) {
99+
return this.store.find(data);
100+
}
101+
}
52102
}
53103

54104
type ProxiedModel<
@@ -79,4 +129,5 @@ export const Model: new <
79129
Schemas extends SchemaCollection = SchemaCollection,
80130
>(
81131
data: JsonApiResource<Schema['type']>,
132+
store: Store<Schemas>,
82133
) => Model<Schema, Schemas> = ModelBase as any;

src/store.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,7 @@ export class Store<Schemas extends SchemaCollection = {}> {
9393
): ModelForType<Type, Schemas> {
9494
const ModelClass = this.models[data.type] || Model;
9595

96-
return new Proxy(new ModelClass(data), {
97-
get: (target, prop, receiver) => {
98-
if (typeof prop === 'string') {
99-
if (target.attributes?.[prop] !== undefined) {
100-
return target.attributes[prop];
101-
}
102-
const data = target.relationships?.[prop]?.data;
103-
if (data !== undefined) {
104-
return Array.isArray(data)
105-
? this.find(data)
106-
: this.find(data);
107-
}
108-
}
109-
return Reflect.get(target, prop, receiver);
110-
},
111-
}) as ModelForType<Type, Schemas>;
96+
return new ModelClass(data, this) as ModelForType<Type, Schemas>;
11297
}
11398

11499
public forget(data: JsonApiIdentifier): void {

src/types.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Model } from './model.ts';
2+
import { Store } from './store.ts';
23

34
export interface JsonApiDocument<Type extends string = string> {
45
data: JsonApiResource<Type> | JsonApiResource<Type>[] | null;
@@ -43,10 +44,13 @@ export type SchemaCollection = { [Type in string]: JsonApiResource<Type> };
4344
export type ModelMap<Schemas extends SchemaCollection = SchemaCollection> = {
4445
[Type in keyof Schemas & string]?: new (
4546
data: JsonApiResource<Type>,
47+
store: Store<Schemas>,
4648
) => Schemas[Type];
4749
};
4850

4951
export type ModelForType<
5052
Type extends string,
51-
Schemas,
52-
> = Type extends keyof Schemas ? Schemas[Type] : Model<JsonApiResource<Type>>;
53+
Schemas extends SchemaCollection,
54+
> = Type extends keyof Schemas
55+
? Model<Schemas[Type], Schemas>
56+
: Model<JsonApiResource<Type>, Schemas>;

0 commit comments

Comments
 (0)