Skip to content

Commit 7d4f65c

Browse files
Baltazorerunspired
andauthoredSep 17, 2023
feat: docs, tests and fixes for create/update/deleteRecord builders (#8849)
* WIP adding create-record builder docs * Add more documentation for rest of builders handling save record * Try to setup store in test app for builders * Add tests, need some help here * Add working test setup * Had to comment out this assert - new record allowed to not have ID * Fix lint and lock file * Add missing docs classitems * Add other tests, comment out saveRecord asserts * add integration test for createRecord builder and fix CacheHandler * add deleteRecord integration test, fix it to work * add update-record test, expand delete-record test * fix lint * fixup new rollback * cleanup asserts * small fixes to docs --------- Co-authored-by: Chris Thoburn <runspired@gmail.com>
1 parent 21c86ab commit 7d4f65c

File tree

22 files changed

+1999
-47
lines changed

22 files changed

+1999
-47
lines changed
 

‎packages/active-record/src/-private/builders/find-record.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type FindRecordOptions = ConstrainedRequestOptions & {
2020

2121
/**
2222
* Builds request options to fetch a single resource by a known id or identifier
23-
* configured for the url and header expectations of most JSON:API APIs.
23+
* configured for the url and header expectations of most ActiveRecord APIs.
2424
*
2525
* **Basic Usage**
2626
*

‎packages/active-record/src/-private/builders/save-record.ts

+140-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,58 @@ function isExisting(identifier: StableRecordIdentifier): identifier is StableExi
2424
return 'id' in identifier && identifier.id !== null && 'type' in identifier && identifier.type !== null;
2525
}
2626

27+
/**
28+
* Builds request options to delete record for resources,
29+
* configured for the url, method and header expectations of ActiveRecord APIs.
30+
*
31+
* **Basic Usage**
32+
*
33+
* ```ts
34+
* import { deleteRecord } from '@ember-data/active-record/request';
35+
*
36+
* const person = this.store.peekRecord('person', '1');
37+
*
38+
* // mark record as deleted
39+
* store.deleteRecord(person);
40+
*
41+
* // persist deletion
42+
* const data = await store.request(deleteRecord(person));
43+
* ```
44+
*
45+
* **Supplying Options to Modify the Request Behavior**
46+
*
47+
* The following options are supported:
48+
*
49+
* - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.
50+
* - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.
51+
* - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type
52+
* - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this
53+
* option will delegate to the store's lifetimes service, defaulting to `false` if none is configured.
54+
* - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the
55+
* promise with the cached value, not supplying this option will delegate to the store's lifetimes service,
56+
* defaulting to `false` if none is configured.
57+
* - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)
58+
*
59+
* ```ts
60+
* import { deleteRecord } from '@ember-data/active-record/request';
61+
*
62+
* const person = this.store.peekRecord('person', '1');
63+
*
64+
* // mark record as deleted
65+
* store.deleteRecord(person);
66+
*
67+
* // persist deletion
68+
* const options = deleteRecord(person, { namespace: 'api/v1' });
69+
* const data = await store.request(options);
70+
* ```
71+
*
72+
* @method deleteRecord
73+
* @public
74+
* @static
75+
* @for @ember-data/active-record/request
76+
* @param record
77+
* @param options
78+
*/
2779
export function deleteRecord(record: unknown, options: ConstrainedRequestOptions = {}): DeleteRequestOptions {
2880
const identifier = recordIdentifierFor(record);
2981
assert(`Expected to be given a record instance`, identifier);
@@ -52,10 +104,51 @@ export function deleteRecord(record: unknown, options: ConstrainedRequestOptions
52104
};
53105
}
54106

107+
/**
108+
* Builds request options to create new record for resources,
109+
* configured for the url, method and header expectations of most ActiveRecord APIs.
110+
*
111+
* **Basic Usage**
112+
*
113+
* ```ts
114+
* import { createRecord } from '@ember-data/active-record/request';
115+
*
116+
* const person = this.store.createRecord('person', { name: 'Ted' });
117+
* const data = await store.request(createRecord(person));
118+
* ```
119+
*
120+
* **Supplying Options to Modify the Request Behavior**
121+
*
122+
* The following options are supported:
123+
*
124+
* - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.
125+
* - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.
126+
* - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type
127+
* - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this
128+
* option will delegate to the store's lifetimes service, defaulting to `false` if none is configured.
129+
* - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the
130+
* promise with the cached value, not supplying this option will delegate to the store's lifetimes service,
131+
* defaulting to `false` if none is configured.
132+
* - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)
133+
*
134+
* ```ts
135+
* import { createRecord } from '@ember-data/active-record/request';
136+
*
137+
* const person = this.store.createRecord('person', { name: 'Ted' });
138+
* const options = createRecord(person, { namespace: 'api/v1' });
139+
* const data = await store.request(options);
140+
* ```
141+
*
142+
* @method createRecord
143+
* @public
144+
* @static
145+
* @for @ember-data/active-record/request
146+
* @param record
147+
* @param options
148+
*/
55149
export function createRecord(record: unknown, options: ConstrainedRequestOptions = {}): CreateRequestOptions {
56150
const identifier = recordIdentifierFor(record);
57151
assert(`Expected to be given a record instance`, identifier);
58-
assert(`Cannot delete a record that does not have an associated type and id.`, isExisting(identifier));
59152

60153
const urlOptions: CreateRecordUrlOptions = {
61154
identifier: identifier,
@@ -80,13 +173,58 @@ export function createRecord(record: unknown, options: ConstrainedRequestOptions
80173
};
81174
}
82175

176+
/**
177+
* Builds request options to update existing record for resources,
178+
* configured for the url, method and header expectations of most ActiveRecord APIs.
179+
*
180+
* **Basic Usage**
181+
*
182+
* ```ts
183+
* import { updateRecord } from '@ember-data/active-record/request';
184+
*
185+
* const person = this.store.peekRecord('person', '1');
186+
* person.name = 'Chris';
187+
* const data = await store.request(updateRecord(person));
188+
* ```
189+
*
190+
* **Supplying Options to Modify the Request Behavior**
191+
*
192+
* The following options are supported:
193+
*
194+
* - `patch` - Allows caller to specify whether to use a PATCH request instead of a PUT request, defaults to `false`.
195+
* - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.
196+
* - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.
197+
* - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type
198+
* - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this
199+
* option will delegate to the store's lifetimes service, defaulting to `false` if none is configured.
200+
* - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the
201+
* promise with the cached value, not supplying this option will delegate to the store's lifetimes service,
202+
* defaulting to `false` if none is configured.
203+
* - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)
204+
*
205+
* ```ts
206+
* import { updateRecord } from '@ember-data/active-record/request';
207+
*
208+
* const person = this.store.peekRecord('person', '1');
209+
* person.name = 'Chris';
210+
* const options = updateRecord(person, { patch: true });
211+
* const data = await store.request(options);
212+
* ```
213+
*
214+
* @method updateRecord
215+
* @public
216+
* @static
217+
* @for @ember-data/active-record/request
218+
* @param record
219+
* @param options
220+
*/
83221
export function updateRecord(
84222
record: unknown,
85223
options: ConstrainedRequestOptions & { patch?: boolean } = {}
86224
): UpdateRequestOptions {
87225
const identifier = recordIdentifierFor(record);
88226
assert(`Expected to be given a record instance`, identifier);
89-
assert(`Cannot delete a record that does not have an associated type and id.`, isExisting(identifier));
227+
assert(`Cannot update a record that does not have an associated type and id.`, isExisting(identifier));
90228

91229
const urlOptions: UpdateRecordUrlOptions = {
92230
identifier: identifier,

‎packages/json-api/src/-private/builders/save-record.ts

+140-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,58 @@ function isExisting(identifier: StableRecordIdentifier): identifier is StableExi
2323
return 'id' in identifier && identifier.id !== null && 'type' in identifier && identifier.type !== null;
2424
}
2525

26+
/**
27+
* Builds request options to delete record for resources,
28+
* configured for the url, method and header expectations of most JSON:API APIs.
29+
*
30+
* **Basic Usage**
31+
*
32+
* ```ts
33+
* import { deleteRecord } from '@ember-data/json-api/request';
34+
*
35+
* const person = this.store.peekRecord('person', '1');
36+
*
37+
* // mark record as deleted
38+
* store.deleteRecord(person);
39+
*
40+
* // persist deletion
41+
* const data = await store.request(deleteRecord(person));
42+
* ```
43+
*
44+
* **Supplying Options to Modify the Request Behavior**
45+
*
46+
* The following options are supported:
47+
*
48+
* - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.
49+
* - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.
50+
* - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type
51+
* - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this
52+
* option will delegate to the store's lifetimes service, defaulting to `false` if none is configured.
53+
* - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the
54+
* promise with the cached value, not supplying this option will delegate to the store's lifetimes service,
55+
* defaulting to `false` if none is configured.
56+
* - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)
57+
*
58+
* ```ts
59+
* import { deleteRecord } from '@ember-data/json-api/request';
60+
*
61+
* const person = this.store.peekRecord('person', '1');
62+
*
63+
* // mark record as deleted
64+
* store.deleteRecord(person);
65+
*
66+
* // persist deletion
67+
* const options = deleteRecord(person, { namespace: 'api/v1' });
68+
* const data = await store.request(options);
69+
* ```
70+
*
71+
* @method deleteRecord
72+
* @public
73+
* @static
74+
* @for @ember-data/json-api/request
75+
* @param record
76+
* @param options
77+
*/
2678
export function deleteRecord(record: unknown, options: ConstrainedRequestOptions = {}): DeleteRequestOptions {
2779
const identifier = recordIdentifierFor(record);
2880
assert(`Expected to be given a record instance`, identifier);
@@ -52,10 +104,51 @@ export function deleteRecord(record: unknown, options: ConstrainedRequestOptions
52104
};
53105
}
54106

107+
/**
108+
* Builds request options to create new record for resources,
109+
* configured for the url, method and header expectations of most JSON:API APIs.
110+
*
111+
* **Basic Usage**
112+
*
113+
* ```ts
114+
* import { createRecord } from '@ember-data/json-api/request';
115+
*
116+
* const person = this.store.createRecord('person', { name: 'Ted' });
117+
* const data = await store.request(createRecord(person));
118+
* ```
119+
*
120+
* **Supplying Options to Modify the Request Behavior**
121+
*
122+
* The following options are supported:
123+
*
124+
* - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.
125+
* - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.
126+
* - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type
127+
* - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this
128+
* option will delegate to the store's lifetimes service, defaulting to `false` if none is configured.
129+
* - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the
130+
* promise with the cached value, not supplying this option will delegate to the store's lifetimes service,
131+
* defaulting to `false` if none is configured.
132+
* - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)
133+
*
134+
* ```ts
135+
* import { createRecord } from '@ember-data/json-api/request';
136+
*
137+
* const person = this.store.createRecord('person', { name: 'Ted' });
138+
* const options = createRecord(person, { namespace: 'api/v1' });
139+
* const data = await store.request(options);
140+
* ```
141+
*
142+
* @method createRecord
143+
* @public
144+
* @static
145+
* @for @ember-data/json-api/request
146+
* @param record
147+
* @param options
148+
*/
55149
export function createRecord(record: unknown, options: ConstrainedRequestOptions = {}): CreateRequestOptions {
56150
const identifier = recordIdentifierFor(record);
57151
assert(`Expected to be given a record instance`, identifier);
58-
assert(`Cannot delete a record that does not have an associated type and id.`, isExisting(identifier));
59152

60153
const urlOptions: CreateRecordUrlOptions = {
61154
identifier: identifier,
@@ -81,13 +174,58 @@ export function createRecord(record: unknown, options: ConstrainedRequestOptions
81174
};
82175
}
83176

177+
/**
178+
* Builds request options to update existing record for resources,
179+
* configured for the url, method and header expectations of most JSON:API APIs.
180+
*
181+
* **Basic Usage**
182+
*
183+
* ```ts
184+
* import { updateRecord } from '@ember-data/json-api/request';
185+
*
186+
* const person = this.store.peekRecord('person', '1');
187+
* person.name = 'Chris';
188+
* const data = await store.request(updateRecord(person));
189+
* ```
190+
*
191+
* **Supplying Options to Modify the Request Behavior**
192+
*
193+
* The following options are supported:
194+
*
195+
* - `patch` - Allows caller to specify whether to use a PATCH request instead of a PUT request, defaults to `false`.
196+
* - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.
197+
* - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.
198+
* - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type
199+
* - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this
200+
* option will delegate to the store's lifetimes service, defaulting to `false` if none is configured.
201+
* - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the
202+
* promise with the cached value, not supplying this option will delegate to the store's lifetimes service,
203+
* defaulting to `false` if none is configured.
204+
* - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)
205+
*
206+
* ```ts
207+
* import { updateRecord } from '@ember-data/json-api/request';
208+
*
209+
* const person = this.store.peekRecord('person', '1');
210+
* person.name = 'Chris';
211+
* const options = updateRecord(person, { patch: true });
212+
* const data = await store.request(options);
213+
* ```
214+
*
215+
* @method updateRecord
216+
* @public
217+
* @static
218+
* @for @ember-data/json-api/request
219+
* @param record
220+
* @param options
221+
*/
84222
export function updateRecord(
85223
record: unknown,
86224
options: ConstrainedRequestOptions & { patch?: boolean } = {}
87225
): UpdateRequestOptions {
88226
const identifier = recordIdentifierFor(record);
89227
assert(`Expected to be given a record instance`, identifier);
90-
assert(`Cannot delete a record that does not have an associated type and id.`, isExisting(identifier));
228+
assert(`Cannot update a record that does not have an associated type and id.`, isExisting(identifier));
91229

92230
const urlOptions: UpdateRecordUrlOptions = {
93231
identifier: identifier,

‎packages/json-api/src/-private/cache.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,7 @@ export default class JSONAPICache implements Cache {
11171117

11181118
if (cached.isNew) {
11191119
// > Note: Graph removal handled by unloadRecord
1120+
cached.isDeletionCommitted = true;
11201121
cached.isDeleted = true;
11211122
cached.isNew = false;
11221123
}

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

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type BelongsToReference from './references/belongs-to';
1010
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
1111
import type { LegacySupport } from './legacy-relationships-support';
1212
import type { Cache } from '@ember-data/types/q/cache';
13+
import type RecordState from './record-state';
1314

1415
export type ModelCreateArgs = {
1516
_createProps: Record<string, unknown>;
@@ -26,6 +27,8 @@ export type ModelCreateArgs = {
2627
class Model extends EmberObject {
2728
store: Store;
2829
errors: Errors;
30+
currentState: RecordState;
31+
adapterError?: Error;
2932
toString(): string;
3033
save(): Promise<this>;
3134
hasMany(key: string): HasManyReference;

0 commit comments

Comments
 (0)