Skip to content

Commit 3073e92

Browse files
authored
feat: improves consumer-facing store types (#9244)
* chore: begin work on consumer types * use symbol approach for all store methods * small fixes * more types cleanup * chore: more things with consumer types * more progress on consumer types * it breathes
1 parent 7b03579 commit 3073e92

File tree

45 files changed

+618
-430
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+618
-430
lines changed

packages/-ember-data/addon/store.ts

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import BaseStore, { CacheHandler } from '@ember-data/store';
2020
import type { Cache } from '@warp-drive/core-types/cache';
2121
import type { CacheCapabilitiesManager } from '@ember-data/store/-types/q/cache-store-wrapper';
2222
import type { ModelSchema } from '@ember-data/store/-types/q/ds-model';
23+
import { TypeFromInstance } from '@warp-drive/core-types/record';
2324

2425
function hasRequestManager(store: BaseStore): boolean {
2526
return 'requestManager' in store;
@@ -55,6 +56,8 @@ export default class Store extends BaseStore {
5556
teardownRecord.call(this, record);
5657
}
5758

59+
override modelFor<T>(type: TypeFromInstance<T>): ModelSchema<T>;
60+
override modelFor(type: string): ModelSchema;
5861
override modelFor(type: string): ModelSchema {
5962
return modelFor.call(this, type) || super.modelFor(type);
6063
}

packages/core-types/src/identifier.ts

+12-10
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ export interface Identifier {
1717
clientId?: string;
1818
}
1919

20-
export interface ExistingRecordIdentifier extends Identifier {
20+
export interface ExistingRecordIdentifier<T extends string = string> extends Identifier {
2121
id: string;
22-
type: string;
22+
type: T;
2323
}
2424

25-
export interface NewRecordIdentifier extends Identifier {
25+
export interface NewRecordIdentifier<T extends string = string> extends Identifier {
2626
id: string | null;
27-
type: string;
27+
type: T;
2828
}
2929

3030
export type StableDocumentIdentifier = {
@@ -42,7 +42,7 @@ export type StableDocumentIdentifier = {
4242
*
4343
* @internal
4444
*/
45-
export type RecordIdentifier = ExistingRecordIdentifier | NewRecordIdentifier;
45+
export type RecordIdentifier<T extends string = string> = ExistingRecordIdentifier<T> | NewRecordIdentifier<T>;
4646

4747
/**
4848
* Used when an Identifier is known to be the stable version
@@ -63,9 +63,9 @@ export interface StableIdentifier extends Identifier {
6363
*
6464
* @internal
6565
*/
66-
export interface StableExistingRecordIdentifier extends StableIdentifier {
66+
export interface StableExistingRecordIdentifier<T extends string = string> extends StableIdentifier {
6767
id: string;
68-
type: string;
68+
type: T;
6969
[DEBUG_CLIENT_ORIGINATED]?: boolean;
7070
[CACHE_OWNER]: number | undefined;
7171
[DEBUG_STALE_CACHE_OWNER]?: number | undefined;
@@ -82,9 +82,9 @@ export interface StableExistingRecordIdentifier extends StableIdentifier {
8282
*
8383
* @internal
8484
*/
85-
export interface StableNewRecordIdentifier extends StableIdentifier {
85+
export interface StableNewRecordIdentifier<T extends string = string> extends StableIdentifier {
8686
id: string | null;
87-
type: string;
87+
type: T;
8888
[DEBUG_CLIENT_ORIGINATED]?: boolean;
8989
[CACHE_OWNER]: number | undefined;
9090
[DEBUG_STALE_CACHE_OWNER]?: number | undefined;
@@ -120,4 +120,6 @@ export interface StableNewRecordIdentifier extends StableIdentifier {
120120
* @property {string | null} id
121121
* @public
122122
*/
123-
export type StableRecordIdentifier = StableExistingRecordIdentifier | StableNewRecordIdentifier;
123+
export type StableRecordIdentifier<T extends string = string> =
124+
| StableExistingRecordIdentifier<T>
125+
| StableNewRecordIdentifier<T>;

packages/core-types/src/record.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* @module @warp-drive/core-types
3+
*/
4+
import type { ResourceType } from './symbols';
5+
6+
/**
7+
* Records may be anything, They don't even
8+
* have to be objects.
9+
*
10+
* Whatever they are, if they have a ResourceType
11+
* property, that property will be used by EmberData
12+
* and WarpDrive to provide better type safety and
13+
* intellisense.
14+
*
15+
* @class TypedRecordInstance
16+
* @typedoc
17+
*/
18+
export interface TypedRecordInstance {
19+
/**
20+
* The type of the resource.
21+
*
22+
* This is an optional feature that can be used by
23+
* record implementations to provide a typescript
24+
* hint for the type of the resource.
25+
*
26+
* When used, EmberData and WarpDrive APIs can
27+
* take advantage of this to provide better type
28+
* safety and intellisense.
29+
*
30+
* @property {ResourceType} [ResourceType]
31+
* @type {string}
32+
* @typedoc
33+
*/
34+
[ResourceType]: string;
35+
}
36+
37+
/**
38+
* A type utility that extracts the ResourceType if available,
39+
* otherwise it returns never.
40+
*
41+
* @typedoc
42+
*/
43+
export type TypeFromInstance<T> = T extends TypedRecordInstance ? T[typeof ResourceType] : never;
44+
45+
/**
46+
* A type utility that extracts the ResourceType if available,
47+
* otherwise it returns string
48+
*
49+
* @typedoc
50+
*/
51+
export type TypeFromInstanceOrString<T> = T extends TypedRecordInstance ? T[typeof ResourceType] : string;

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

+16-13
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ export interface PaginationLinks extends Links {
2727
* [JSON:API Spec](https://jsonapi.org/format/#document-resource-identifier-objects)
2828
* @internal
2929
*/
30-
export interface ExistingResourceIdentifierObject {
30+
export interface ExistingResourceIdentifierObject<T extends string = string> {
3131
id: string;
32-
type: string;
32+
type: T;
3333

3434
/**
3535
* While not officially part of the `JSON:API` spec,
@@ -65,7 +65,7 @@ export interface ExistingResourceIdentifierObject {
6565
*
6666
* @internal
6767
*/
68-
export interface NewResourceIdentifierObject {
68+
export interface NewResourceIdentifierObject<T extends string = string> {
6969
/**
7070
* Resources newly created on the client _may_
7171
* not have an `id` available to them prior
@@ -76,7 +76,7 @@ export interface NewResourceIdentifierObject {
7676
* @internal
7777
*/
7878
id: string | null;
79-
type: string;
79+
type: T;
8080

8181
/**
8282
* Resources newly created on the client _will always_
@@ -90,10 +90,10 @@ export interface ResourceIdentifier {
9090
lid: string;
9191
}
9292

93-
export type ResourceIdentifierObject =
93+
export type ResourceIdentifierObject<T extends string = string> =
9494
| ResourceIdentifier
95-
| ExistingResourceIdentifierObject
96-
| NewResourceIdentifierObject;
95+
| ExistingResourceIdentifierObject<T>
96+
| NewResourceIdentifierObject<T>;
9797

9898
// TODO disallow NewResource, make narrowable
9999
export interface SingleResourceRelationship {
@@ -112,7 +112,7 @@ export interface CollectionResourceRelationship {
112112
* Contains the data for an existing resource in JSON:API format
113113
* @internal
114114
*/
115-
export interface ExistingResourceObject extends ExistingResourceIdentifierObject {
115+
export interface ExistingResourceObject<T extends string = string> extends ExistingResourceIdentifierObject<T> {
116116
meta?: Meta;
117117
attributes?: ObjectValue;
118118
relationships?: Record<string, SingleResourceRelationship | CollectionResourceRelationship>;
@@ -132,12 +132,12 @@ export interface EmptyResourceDocument extends Document {
132132
data: null;
133133
}
134134

135-
export interface SingleResourceDocument extends Document {
136-
data: ExistingResourceObject;
135+
export interface SingleResourceDocument<T extends string = string> extends Document {
136+
data: ExistingResourceObject<T>;
137137
}
138138

139-
export interface CollectionResourceDocument extends Document {
140-
data: ExistingResourceObject[];
139+
export interface CollectionResourceDocument<T extends string = string> extends Document {
140+
data: ExistingResourceObject<T>[];
141141
}
142142

143143
/**
@@ -148,4 +148,7 @@ export interface CollectionResourceDocument extends Document {
148148
*
149149
* @internal
150150
*/
151-
export type JsonApiDocument = EmptyResourceDocument | SingleResourceDocument | CollectionResourceDocument;
151+
export type JsonApiDocument<T extends string = string> =
152+
| EmptyResourceDocument
153+
| SingleResourceDocument<T>
154+
| CollectionResourceDocument<T>;

packages/core-types/src/symbols.ts

+19
Original file line numberDiff line numberDiff line change
@@ -1 +1,20 @@
1+
/*
2+
* @module @warp-drive/core-types
3+
*/
14
export const RecordStore = Symbol('Store');
5+
6+
/**
7+
* Symbol for the type of a resource.
8+
*
9+
* This is an optional feature that can be used by
10+
* record implementations to provide a typescript
11+
* hint for the type of the resource.
12+
*
13+
* When used, EmberData and WarpDrive APIs can
14+
* take advantage of this to provide better type
15+
* safety and intellisense.
16+
*
17+
* @type {Symbol}
18+
* @typedoc
19+
*/
20+
export const ResourceType = Symbol('$type');

packages/legacy-compat/src/legacy-network-handler/fetch-manager.ts

+11-8
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import type { InstanceCache } from '@ember-data/store/-private/caches/instance-c
1313
import type RequestStateService from '@ember-data/store/-private/network/request-cache';
1414
import type { FindRecordQuery, Request, SaveRecordMutation } from '@ember-data/store/-private/network/request-cache';
1515
import type { ModelSchema } from '@ember-data/store/-types/q/ds-model';
16-
import type { FindOptions } from '@ember-data/store/-types/q/store';
16+
import type { FindRecordOptions } from '@ember-data/store/-types/q/store';
1717
import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@warp-drive/core-types/identifier';
18+
import type { TypeFromInstance } from '@warp-drive/core-types/record';
1819
import type { CollectionResourceDocument, SingleResourceDocument } from '@warp-drive/core-types/spec/raw';
1920

2021
import { upgradeStore } from '../-private';
@@ -32,14 +33,14 @@ type SerializerWithParseErrors = MinimumSerializerInterface & {
3233

3334
export const SaveOp: unique symbol = Symbol('SaveOp');
3435

35-
export type FetchMutationOptions = FindOptions & { [SaveOp]: 'createRecord' | 'deleteRecord' | 'updateRecord' };
36+
export type FetchMutationOptions = FindRecordOptions & { [SaveOp]: 'createRecord' | 'deleteRecord' | 'updateRecord' };
3637

3738
interface PendingFetchItem {
3839
identifier: StableExistingRecordIdentifier;
3940
queryRequest: Request;
4041
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4142
resolver: Deferred<any>;
42-
options: FindOptions;
43+
options: FindRecordOptions;
4344
trace?: unknown;
4445
promise: Promise<StableExistingRecordIdentifier>;
4546
}
@@ -68,7 +69,9 @@ export default class FetchManager {
6869
this.isDestroyed = false;
6970
}
7071

71-
createSnapshot(identifier: StableRecordIdentifier, options: FindOptions = {}): Snapshot {
72+
createSnapshot<T>(identifier: StableRecordIdentifier<TypeFromInstance<T>>, options?: FindRecordOptions): Snapshot<T>;
73+
createSnapshot(identifier: StableRecordIdentifier, options?: FindRecordOptions): Snapshot;
74+
createSnapshot(identifier: StableRecordIdentifier, options: FindRecordOptions = {}): Snapshot {
7275
return new Snapshot(options, identifier, this._store);
7376
}
7477

@@ -112,7 +115,7 @@ export default class FetchManager {
112115

113116
scheduleFetch(
114117
identifier: StableExistingRecordIdentifier,
115-
options: FindOptions,
118+
options: FindRecordOptions,
116119
request: StoreRequestInfo
117120
): Promise<StableExistingRecordIdentifier> {
118121
const query: FindRecordQuery = {
@@ -222,7 +225,7 @@ export default class FetchManager {
222225
return promise;
223226
}
224227

225-
getPendingFetch(identifier: StableExistingRecordIdentifier, options: FindOptions) {
228+
getPendingFetch(identifier: StableExistingRecordIdentifier, options: FindRecordOptions) {
226229
const pendingFetches = this._pendingFetch.get(identifier.type)?.get(identifier);
227230

228231
// We already have a pending fetch for this
@@ -246,7 +249,7 @@ export default class FetchManager {
246249

247250
fetchDataIfNeededForIdentifier(
248251
identifier: StableExistingRecordIdentifier,
249-
options: FindOptions = {},
252+
options: FindRecordOptions = {},
250253
request: StoreRequestInfo
251254
): Promise<StableExistingRecordIdentifier> {
252255
// pre-loading will change the isEmpty value
@@ -338,7 +341,7 @@ function optionsSatisfies(current: object | undefined, existing: object | undefi
338341
}
339342

340343
// this function helps resolve whether we have a pending request that we should use instead
341-
function isSameRequest(options: FindOptions = {}, existingOptions: FindOptions = {}) {
344+
function isSameRequest(options: FindRecordOptions = {}, existingOptions: FindRecordOptions = {}) {
342345
return (
343346
optionsSatisfies(options.adapterOptions, existingOptions.adapterOptions) &&
344347
includesSatisfies(options.include, existingOptions.include)

packages/legacy-compat/src/legacy-network-handler/snapshot-record-array.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import type Store from '@ember-data/store';
55
import { SOURCE } from '@ember-data/store/-private';
66
import type IdentifierArray from '@ember-data/store/-private/record-arrays/identifier-array';
77
import type { ModelSchema } from '@ember-data/store/-types/q/ds-model';
8-
import type { FindOptions } from '@ember-data/store/-types/q/store';
8+
import type { FindAllOptions } from '@ember-data/store/-types/q/store';
99
import type { StableRecordIdentifier } from '@warp-drive/core-types';
1010

1111
import { upgradeStore } from '../-private';
1212
import type Snapshot from './snapshot';
1313
/**
1414
SnapshotRecordArray is not directly instantiable.
1515
Instances are provided to consuming application's
16-
adapters for certain requests.
16+
adapters for certain `findAll` requests.
1717
1818
@class SnapshotRecordArray
1919
@public
@@ -25,7 +25,7 @@ export default class SnapshotRecordArray {
2525
declare __store: Store;
2626

2727
declare adapterOptions?: Record<string, unknown>;
28-
declare include?: string;
28+
declare include?: string | string[];
2929

3030
/**
3131
SnapshotRecordArray is not directly instantiable.
@@ -39,7 +39,7 @@ export default class SnapshotRecordArray {
3939
@param {string} type
4040
@param options
4141
*/
42-
constructor(store: Store, type: string, options: FindOptions = {}) {
42+
constructor(store: Store, type: string, options: FindAllOptions = {}) {
4343
this.__store = store;
4444
/**
4545
An array of snapshots

0 commit comments

Comments
 (0)