Skip to content

Commit b19f93f

Browse files
committed
more typing
1 parent 90c81c7 commit b19f93f

File tree

7 files changed

+244
-151
lines changed

7 files changed

+244
-151
lines changed

packages/core-types/src/spec/raw.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -96,26 +96,31 @@ export type ResourceIdentifierObject<T extends string = string> =
9696
| NewResourceIdentifierObject<T>;
9797

9898
// TODO disallow NewResource, make narrowable
99-
export interface SingleResourceRelationship {
100-
data?: ExistingResourceIdentifierObject | NewResourceIdentifierObject | null;
99+
export interface SingleResourceRelationship<T = ExistingResourceIdentifierObject | NewResourceIdentifierObject> {
100+
data?: T | null;
101101
meta?: Meta;
102102
links?: Links;
103103
}
104104

105-
export interface CollectionResourceRelationship {
106-
data?: Array<ExistingResourceIdentifierObject | NewResourceIdentifierObject>;
105+
export interface CollectionResourceRelationship<T = ExistingResourceIdentifierObject | NewResourceIdentifierObject> {
106+
data?: T[];
107107
meta?: Meta;
108108
links?: PaginationLinks;
109109
}
110110

111+
export type ResourceRelationshipsObject<T = ExistingResourceIdentifierObject | NewResourceIdentifierObject> = Record<
112+
string,
113+
SingleResourceRelationship<T> | CollectionResourceRelationship<T>
114+
>;
115+
111116
/**
112117
* Contains the data for an existing resource in JSON:API format
113118
* @internal
114119
*/
115120
export interface ExistingResourceObject<T extends string = string> extends ExistingResourceIdentifierObject<T> {
116121
meta?: Meta;
117122
attributes?: ObjectValue;
118-
relationships?: Record<string, SingleResourceRelationship | CollectionResourceRelationship>;
123+
relationships?: ResourceRelationshipsObject<ExistingResourceIdentifierObject>;
119124
links?: Links;
120125
}
121126

packages/legacy-compat/src/legacy-network-handler/legacy-data-fetch.js packages/legacy-compat/src/legacy-network-handler/legacy-data-fetch.ts

+123-65
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,76 @@
11
import { assert } from '@ember/debug';
22

33
import { DEBUG } from '@ember-data/env';
4+
import type Store from '@ember-data/store';
5+
import type { BaseFinderOptions } from '@ember-data/store/-types/q/store';
6+
import type { StableRecordIdentifier } from '@warp-drive/core-types';
7+
import type { RelationshipSchema } from '@warp-drive/core-types/schema';
8+
import type { ExistingResourceObject, JsonApiDocument } from '@warp-drive/core-types/spec/raw';
49

10+
import { upgradeStore } from '../-private';
511
import { iterateData, payloadIsNotBlank } from './legacy-data-utils';
12+
import type { MinimumAdapterInterface } from './minimum-adapter-interface';
613
import { normalizeResponseHelper } from './serializer-response';
714

8-
export function _findHasMany(adapter, store, identifier, link, relationship, options) {
9-
let promise = Promise.resolve().then(() => {
15+
export function _findHasMany(
16+
adapter: MinimumAdapterInterface,
17+
store: Store,
18+
identifier: StableRecordIdentifier,
19+
link: string | null | { href: string },
20+
relationship: RelationshipSchema,
21+
options: BaseFinderOptions
22+
) {
23+
upgradeStore(store);
24+
const promise = Promise.resolve().then(() => {
1025
const snapshot = store._fetchManager.createSnapshot(identifier, options);
1126
const useLink = !link || typeof link === 'string';
1227
const relatedLink = useLink ? link : link.href;
28+
assert(
29+
`Attempted to load a hasMany relationship from a specified 'link' in the original payload, but the specified link is empty. You must provide a valid 'link' in the original payload to use 'findHasMany'`,
30+
relatedLink
31+
);
32+
assert(
33+
`Expected the adapter to implement 'findHasMany' but it does not`,
34+
typeof adapter.findHasMany === 'function'
35+
);
1336
return adapter.findHasMany(store, snapshot, relatedLink, relationship);
1437
});
1538

16-
promise = promise.then(
17-
(adapterPayload) => {
18-
assert(
19-
`You made a 'findHasMany' request for a ${identifier.type}'s '${relationship.name}' relationship, using link '${link}' , but the adapter's response did not have any data`,
20-
payloadIsNotBlank(adapterPayload)
21-
);
22-
const modelClass = store.modelFor(relationship.type);
23-
24-
const serializer = store.serializerFor(relationship.type);
25-
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findHasMany');
39+
return promise.then((adapterPayload) => {
40+
assert(
41+
`You made a 'findHasMany' request for a ${identifier.type}'s '${
42+
relationship.name
43+
}' relationship, using link '${JSON.stringify(link)}' , but the adapter's response did not have any data`,
44+
payloadIsNotBlank(adapterPayload)
45+
);
46+
const modelClass = store.modelFor(relationship.type);
2647

27-
assert(
28-
`fetched the hasMany relationship '${relationship.name}' for ${identifier.type}:${identifier.id} with link '${link}', but no data member is present in the response. If no data exists, the response should set { data: [] }`,
29-
'data' in payload && Array.isArray(payload.data)
30-
);
48+
const serializer = store.serializerFor(relationship.type);
49+
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findHasMany');
3150

32-
payload = syncRelationshipDataFromLink(store, payload, identifier, relationship);
33-
return store._push(payload, true);
34-
},
35-
null,
36-
`DS: Extract payload of '${identifier.type}' : hasMany '${relationship.type}'`
37-
);
51+
assert(
52+
`fetched the hasMany relationship '${relationship.name}' for ${identifier.type}:${
53+
identifier.id
54+
} with link '${JSON.stringify(
55+
link
56+
)}', but no data member is present in the response. If no data exists, the response should set { data: [] }`,
57+
'data' in payload && Array.isArray(payload.data)
58+
);
3859

39-
return promise;
60+
payload = syncRelationshipDataFromLink(store, payload, identifier as ResourceIdentity, relationship);
61+
return store._push(payload, true);
62+
}, null);
4063
}
4164

42-
export function _findBelongsTo(store, identifier, link, relationship, options) {
43-
let promise = Promise.resolve().then(() => {
65+
export function _findBelongsTo(
66+
store: Store,
67+
identifier: StableRecordIdentifier,
68+
link: string | null | { href: string },
69+
relationship: RelationshipSchema,
70+
options: BaseFinderOptions
71+
) {
72+
upgradeStore(store);
73+
const promise = Promise.resolve().then(() => {
4474
const adapter = store.adapterFor(identifier.type);
4575
assert(`You tried to load a belongsTo relationship but you have no adapter (for ${identifier.type})`, adapter);
4676
assert(
@@ -50,34 +80,35 @@ export function _findBelongsTo(store, identifier, link, relationship, options) {
5080
const snapshot = store._fetchManager.createSnapshot(identifier, options);
5181
const useLink = !link || typeof link === 'string';
5282
const relatedLink = useLink ? link : link.href;
83+
assert(
84+
`Attempted to load a belongsTo relationship from a specified 'link' in the original payload, but the specified link is empty. You must provide a valid 'link' in the original payload to use 'findBelongsTo'`,
85+
relatedLink
86+
);
5387
return adapter.findBelongsTo(store, snapshot, relatedLink, relationship);
5488
});
5589

56-
promise = promise.then(
57-
(adapterPayload) => {
58-
const modelClass = store.modelFor(relationship.type);
59-
const serializer = store.serializerFor(relationship.type);
60-
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findBelongsTo');
61-
62-
assert(
63-
`fetched the belongsTo relationship '${relationship.name}' for ${identifier.type}:${identifier.id} with link '${link}', but no data member is present in the response. If no data exists, the response should set { data: null }`,
64-
'data' in payload &&
65-
(payload.data === null || (typeof payload.data === 'object' && !Array.isArray(payload.data)))
66-
);
90+
return promise.then((adapterPayload) => {
91+
const modelClass = store.modelFor(relationship.type);
92+
const serializer = store.serializerFor(relationship.type);
93+
let payload = normalizeResponseHelper(serializer, store, modelClass, adapterPayload, null, 'findBelongsTo');
6794

68-
if (!payload.data && !payload.links && !payload.meta) {
69-
return null;
70-
}
95+
assert(
96+
`fetched the belongsTo relationship '${relationship.name}' for ${identifier.type}:${
97+
identifier.id
98+
} with link '${JSON.stringify(
99+
link
100+
)}', but no data member is present in the response. If no data exists, the response should set { data: null }`,
101+
'data' in payload && (payload.data === null || (typeof payload.data === 'object' && !Array.isArray(payload.data)))
102+
);
71103

72-
payload = syncRelationshipDataFromLink(store, payload, identifier, relationship);
104+
if (!payload.data && !payload.links && !payload.meta) {
105+
return null;
106+
}
73107

74-
return store._push(payload, true);
75-
},
76-
null,
77-
`DS: Extract payload of ${identifier.type} : ${relationship.type}`
78-
);
108+
payload = syncRelationshipDataFromLink(store, payload, identifier as ResourceIdentity, relationship);
79109

80-
return promise;
110+
return store._push(payload, true);
111+
}, null);
81112
}
82113

83114
// sync
@@ -86,7 +117,12 @@ export function _findBelongsTo(store, identifier, link, relationship, options) {
86117
// assert that record.relationships[inverse] is either undefined (so we can fix it)
87118
// or provide a data: {id, type} that matches the record that requested it
88119
// return the relationship data for the parent
89-
function syncRelationshipDataFromLink(store, payload, parentIdentifier, relationship) {
120+
function syncRelationshipDataFromLink(
121+
store: Store,
122+
payload: JsonApiDocument,
123+
parentIdentifier: ResourceIdentity,
124+
relationship: RelationshipSchema
125+
) {
90126
// ensure the right hand side (incoming payload) points to the parent record that
91127
// requested this relationship
92128
const relationshipData = payload.data
@@ -97,7 +133,7 @@ function syncRelationshipDataFromLink(store, payload, parentIdentifier, relation
97133
})
98134
: null;
99135

100-
const relatedDataHash = {};
136+
const relatedDataHash = {} as JsonApiDocument;
101137

102138
if ('meta' in payload) {
103139
relatedDataHash.meta = payload.meta;
@@ -127,7 +163,16 @@ function syncRelationshipDataFromLink(store, payload, parentIdentifier, relation
127163
return payload;
128164
}
129165

130-
function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, parentRelationship, index) {
166+
type ResourceIdentity = { id: string; type: string };
167+
type RelationshipData = ResourceIdentity | ResourceIdentity[] | null;
168+
169+
function ensureRelationshipIsSetToParent(
170+
payload: ExistingResourceObject,
171+
parentIdentifier: ResourceIdentity,
172+
store: Store,
173+
parentRelationship: RelationshipSchema,
174+
index: number
175+
) {
131176
const { id, type } = payload;
132177

133178
if (!payload.relationships) {
@@ -139,14 +184,14 @@ function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, paren
139184
if (inverse) {
140185
const { inverseKey, kind } = inverse;
141186

142-
const relationshipData = relationships[inverseKey] && relationships[inverseKey].data;
187+
const relationshipData = relationships[inverseKey]?.data as RelationshipData | undefined;
143188

144189
if (DEBUG) {
145190
if (
146191
typeof relationshipData !== 'undefined' &&
147192
!relationshipDataPointsToParent(relationshipData, parentIdentifier)
148193
) {
149-
const inspect = function inspect(thing) {
194+
const inspect = function inspect(thing: unknown) {
150195
return `'${JSON.stringify(thing)}'`;
151196
};
152197
const quotedType = inspect(type);
@@ -159,7 +204,8 @@ function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, paren
159204
const got = inspect(relationshipData);
160205
const prefix = typeof index === 'number' ? `data[${index}]` : `data`;
161206
const path = `${prefix}.relationships.${inverseKey}.data`;
162-
const other = relationshipData ? `<${relationshipData.type}:${relationshipData.id}>` : null;
207+
const data = Array.isArray(relationshipData) ? relationshipData[0] : relationshipData;
208+
const other = data ? `<${data.type}:${data.id}>` : null;
163209
const relationshipFetched = `${expectedModel}.${parentRelationship.kind}("${parentRelationship.name}")`;
164210
const includedRecord = `<${type}:${id}>`;
165211
const message = [
@@ -176,12 +222,12 @@ function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, paren
176222

177223
if (kind !== 'hasMany' || typeof relationshipData !== 'undefined') {
178224
relationships[inverseKey] = relationships[inverseKey] || {};
179-
relationships[inverseKey].data = fixRelationshipData(relationshipData, kind, parentIdentifier);
225+
relationships[inverseKey].data = fixRelationshipData(relationshipData ?? null, kind, parentIdentifier);
180226
}
181227
}
182228
}
183229

184-
function inverseForRelationship(store, identifier, key) {
230+
function inverseForRelationship(store: Store, identifier: { type: string; id?: string }, key: string) {
185231
const definition = store.getSchemaDefinitionService().relationshipsDefinitionFor(identifier)[key];
186232
if (!definition) {
187233
return null;
@@ -195,7 +241,12 @@ function inverseForRelationship(store, identifier, key) {
195241
return definition.options.inverse;
196242
}
197243

198-
function getInverse(store, parentIdentifier, parentRelationship, type) {
244+
function getInverse(
245+
store: Store,
246+
parentIdentifier: ResourceIdentity,
247+
parentRelationship: RelationshipSchema,
248+
type: string
249+
) {
199250
const { name: lhs_relationshipName } = parentRelationship;
200251
const { type: parentType } = parentIdentifier;
201252
const inverseKey = inverseForRelationship(store, { type: parentType }, lhs_relationshipName);
@@ -210,7 +261,7 @@ function getInverse(store, parentIdentifier, parentRelationship, type) {
210261
}
211262
}
212263

213-
function relationshipDataPointsToParent(relationshipData, identifier) {
264+
function relationshipDataPointsToParent(relationshipData: RelationshipData, identifier: ResourceIdentity): boolean {
214265
if (relationshipData === null) {
215266
return false;
216267
}
@@ -232,37 +283,44 @@ function relationshipDataPointsToParent(relationshipData, identifier) {
232283
return false;
233284
}
234285

235-
function fixRelationshipData(relationshipData, relationshipKind, { id, type }) {
286+
function fixRelationshipData(
287+
relationshipData: RelationshipData,
288+
relationshipKind: 'hasMany' | 'belongsTo',
289+
{ id, type }: ResourceIdentity
290+
) {
236291
const parentRelationshipData = {
237292
id,
238293
type,
239294
};
240295

241-
let payload;
296+
let payload: { type: string; id: string } | { type: string; id: string }[] | null = null;
242297

243298
if (relationshipKind === 'hasMany') {
244-
payload = relationshipData || [];
299+
const relData = (relationshipData as { type: string; id: string }[]) || [];
245300
if (relationshipData) {
301+
assert('expected the relationship data to be an array', Array.isArray(relationshipData));
246302
// these arrays could be massive so this is better than filter
247303
// Note: this is potentially problematic if type/id are not in the
248304
// same state of normalization.
249305
const found = relationshipData.find((v) => {
250306
return v.type === parentRelationshipData.type && v.id === parentRelationshipData.id;
251307
});
252308
if (!found) {
253-
payload.push(parentRelationshipData);
309+
relData.push(parentRelationshipData);
254310
}
255311
} else {
256-
payload.push(parentRelationshipData);
312+
relData.push(parentRelationshipData);
257313
}
314+
payload = relData;
258315
} else {
259-
payload = relationshipData || {};
260-
Object.assign(payload, parentRelationshipData);
316+
const relData = (relationshipData as { type: string; id: string }) || {};
317+
Object.assign(relData, parentRelationshipData);
318+
payload = relData;
261319
}
262320

263321
return payload;
264322
}
265323

266-
function validateRelationshipEntry({ id }, { id: parentModelID }) {
267-
return id && id.toString() === parentModelID;
324+
function validateRelationshipEntry({ id }: ResourceIdentity, { id: parentModelID }: ResourceIdentity): boolean {
325+
return !!id && id.toString() === parentModelID;
268326
}

packages/legacy-compat/src/legacy-network-handler/legacy-data-utils.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import type { AdapterPayload } from './minimum-adapter-interface';
22

3-
export function iterateData<T>(data: T[] | T, fn: (o: T, index?: number) => T) {
3+
type IteratorCB<T> = ((o: T, index: number) => T) | ((o: T) => T);
4+
5+
export function iterateData<T>(data: T[] | T, fn: IteratorCB<T>) {
46
if (Array.isArray(data)) {
57
return data.map(fn);
68
} else {
7-
return fn(data);
9+
return fn(data, 0);
810
}
911
}
1012

packages/model/src/-private/model.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ class Model extends EmberObject {
7474
callback: (this: ModelSchema<this>, key: K, type: string) => void,
7575
binding?: T
7676
): void;
77+
static determineRelationshipType(
78+
knownSide: RelationshipSchema,
79+
store: Store
80+
): 'oneToOne' | 'manyToOne' | 'oneToMany' | 'manyToMany' | 'oneToNone' | 'manyToNone';
7781

7882
static toString(): string;
7983
static isModel: true;

0 commit comments

Comments
 (0)