Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improves consumer-facing store types #9244

Merged
merged 7 commits into from
Mar 3, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/-ember-data/addon/store.ts
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ import BaseStore, { CacheHandler } from '@ember-data/store';
import type { Cache } from '@warp-drive/core-types/cache';
import type { CacheCapabilitiesManager } from '@ember-data/store/-types/q/cache-store-wrapper';
import type { ModelSchema } from '@ember-data/store/-types/q/ds-model';
import { TypeFromInstance } from '@warp-drive/core-types/record';

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

override modelFor<T>(type: TypeFromInstance<T>): ModelSchema<T>;
override modelFor(type: string): ModelSchema;
override modelFor(type: string): ModelSchema {
return modelFor.call(this, type) || super.modelFor(type);
}
22 changes: 12 additions & 10 deletions packages/core-types/src/identifier.ts
Original file line number Diff line number Diff line change
@@ -17,14 +17,14 @@ export interface Identifier {
clientId?: string;
}

export interface ExistingRecordIdentifier extends Identifier {
export interface ExistingRecordIdentifier<T extends string = string> extends Identifier {
id: string;
type: string;
type: T;
}

export interface NewRecordIdentifier extends Identifier {
export interface NewRecordIdentifier<T extends string = string> extends Identifier {
id: string | null;
type: string;
type: T;
}

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

/**
* Used when an Identifier is known to be the stable version
@@ -63,9 +63,9 @@ export interface StableIdentifier extends Identifier {
*
* @internal
*/
export interface StableExistingRecordIdentifier extends StableIdentifier {
export interface StableExistingRecordIdentifier<T extends string = string> extends StableIdentifier {
id: string;
type: string;
type: T;
[DEBUG_CLIENT_ORIGINATED]?: boolean;
[CACHE_OWNER]: number | undefined;
[DEBUG_STALE_CACHE_OWNER]?: number | undefined;
@@ -82,9 +82,9 @@ export interface StableExistingRecordIdentifier extends StableIdentifier {
*
* @internal
*/
export interface StableNewRecordIdentifier extends StableIdentifier {
export interface StableNewRecordIdentifier<T extends string = string> extends StableIdentifier {
id: string | null;
type: string;
type: T;
[DEBUG_CLIENT_ORIGINATED]?: boolean;
[CACHE_OWNER]: number | undefined;
[DEBUG_STALE_CACHE_OWNER]?: number | undefined;
@@ -120,4 +120,6 @@ export interface StableNewRecordIdentifier extends StableIdentifier {
* @property {string | null} id
* @public
*/
export type StableRecordIdentifier = StableExistingRecordIdentifier | StableNewRecordIdentifier;
export type StableRecordIdentifier<T extends string = string> =
| StableExistingRecordIdentifier<T>
| StableNewRecordIdentifier<T>;
51 changes: 51 additions & 0 deletions packages/core-types/src/record.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* @module @warp-drive/core-types
*/
import type { ResourceType } from './symbols';

/**
* Records may be anything, They don't even
* have to be objects.
*
* Whatever they are, if they have a ResourceType
* property, that property will be used by EmberData
* and WarpDrive to provide better type safety and
* intellisense.
*
* @class TypedRecordInstance
* @typedoc
*/
export interface TypedRecordInstance {
/**
* The type of the resource.
*
* This is an optional feature that can be used by
* record implementations to provide a typescript
* hint for the type of the resource.
*
* When used, EmberData and WarpDrive APIs can
* take advantage of this to provide better type
* safety and intellisense.
*
* @property {ResourceType} [ResourceType]
* @type {string}
* @typedoc
*/
[ResourceType]: string;
}

/**
* A type utility that extracts the ResourceType if available,
* otherwise it returns never.
*
* @typedoc
*/
export type TypeFromInstance<T> = T extends TypedRecordInstance ? T[typeof ResourceType] : never;

/**
* A type utility that extracts the ResourceType if available,
* otherwise it returns string
*
* @typedoc
*/
export type TypeFromInstanceOrString<T> = T extends TypedRecordInstance ? T[typeof ResourceType] : string;
29 changes: 16 additions & 13 deletions packages/core-types/src/spec/raw.ts
Original file line number Diff line number Diff line change
@@ -27,9 +27,9 @@ export interface PaginationLinks extends Links {
* [JSON:API Spec](https://jsonapi.org/format/#document-resource-identifier-objects)
* @internal
*/
export interface ExistingResourceIdentifierObject {
export interface ExistingResourceIdentifierObject<T extends string = string> {
id: string;
type: string;
type: T;

/**
* While not officially part of the `JSON:API` spec,
@@ -65,7 +65,7 @@ export interface ExistingResourceIdentifierObject {
*
* @internal
*/
export interface NewResourceIdentifierObject {
export interface NewResourceIdentifierObject<T extends string = string> {
/**
* Resources newly created on the client _may_
* not have an `id` available to them prior
@@ -76,7 +76,7 @@ export interface NewResourceIdentifierObject {
* @internal
*/
id: string | null;
type: string;
type: T;

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

export type ResourceIdentifierObject =
export type ResourceIdentifierObject<T extends string = string> =
| ResourceIdentifier
| ExistingResourceIdentifierObject
| NewResourceIdentifierObject;
| ExistingResourceIdentifierObject<T>
| NewResourceIdentifierObject<T>;

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

export interface SingleResourceDocument extends Document {
data: ExistingResourceObject;
export interface SingleResourceDocument<T extends string = string> extends Document {
data: ExistingResourceObject<T>;
}

export interface CollectionResourceDocument extends Document {
data: ExistingResourceObject[];
export interface CollectionResourceDocument<T extends string = string> extends Document {
data: ExistingResourceObject<T>[];
}

/**
@@ -148,4 +148,7 @@ export interface CollectionResourceDocument extends Document {
*
* @internal
*/
export type JsonApiDocument = EmptyResourceDocument | SingleResourceDocument | CollectionResourceDocument;
export type JsonApiDocument<T extends string = string> =
| EmptyResourceDocument
| SingleResourceDocument<T>
| CollectionResourceDocument<T>;
19 changes: 19 additions & 0 deletions packages/core-types/src/symbols.ts
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
/*
* @module @warp-drive/core-types
*/
export const RecordStore = Symbol('Store');

/**
* Symbol for the type of a resource.
*
* This is an optional feature that can be used by
* record implementations to provide a typescript
* hint for the type of the resource.
*
* When used, EmberData and WarpDrive APIs can
* take advantage of this to provide better type
* safety and intellisense.
*
* @type {Symbol}
* @typedoc
*/
export const ResourceType = Symbol('$type');
19 changes: 11 additions & 8 deletions packages/legacy-compat/src/legacy-network-handler/fetch-manager.ts
Original file line number Diff line number Diff line change
@@ -13,8 +13,9 @@ import type { InstanceCache } from '@ember-data/store/-private/caches/instance-c
import type RequestStateService from '@ember-data/store/-private/network/request-cache';
import type { FindRecordQuery, Request, SaveRecordMutation } from '@ember-data/store/-private/network/request-cache';
import type { ModelSchema } from '@ember-data/store/-types/q/ds-model';
import type { FindOptions } from '@ember-data/store/-types/q/store';
import type { FindRecordOptions } from '@ember-data/store/-types/q/store';
import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@warp-drive/core-types/identifier';
import type { TypeFromInstance } from '@warp-drive/core-types/record';
import type { CollectionResourceDocument, SingleResourceDocument } from '@warp-drive/core-types/spec/raw';

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

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

export type FetchMutationOptions = FindOptions & { [SaveOp]: 'createRecord' | 'deleteRecord' | 'updateRecord' };
export type FetchMutationOptions = FindRecordOptions & { [SaveOp]: 'createRecord' | 'deleteRecord' | 'updateRecord' };

interface PendingFetchItem {
identifier: StableExistingRecordIdentifier;
queryRequest: Request;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolver: Deferred<any>;
options: FindOptions;
options: FindRecordOptions;
trace?: unknown;
promise: Promise<StableExistingRecordIdentifier>;
}
@@ -68,7 +69,9 @@ export default class FetchManager {
this.isDestroyed = false;
}

createSnapshot(identifier: StableRecordIdentifier, options: FindOptions = {}): Snapshot {
createSnapshot<T>(identifier: StableRecordIdentifier<TypeFromInstance<T>>, options?: FindRecordOptions): Snapshot<T>;
createSnapshot(identifier: StableRecordIdentifier, options?: FindRecordOptions): Snapshot;
createSnapshot(identifier: StableRecordIdentifier, options: FindRecordOptions = {}): Snapshot {
return new Snapshot(options, identifier, this._store);
}

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

scheduleFetch(
identifier: StableExistingRecordIdentifier,
options: FindOptions,
options: FindRecordOptions,
request: StoreRequestInfo
): Promise<StableExistingRecordIdentifier> {
const query: FindRecordQuery = {
@@ -222,7 +225,7 @@ export default class FetchManager {
return promise;
}

getPendingFetch(identifier: StableExistingRecordIdentifier, options: FindOptions) {
getPendingFetch(identifier: StableExistingRecordIdentifier, options: FindRecordOptions) {
const pendingFetches = this._pendingFetch.get(identifier.type)?.get(identifier);

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

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

// this function helps resolve whether we have a pending request that we should use instead
function isSameRequest(options: FindOptions = {}, existingOptions: FindOptions = {}) {
function isSameRequest(options: FindRecordOptions = {}, existingOptions: FindRecordOptions = {}) {
return (
optionsSatisfies(options.adapterOptions, existingOptions.adapterOptions) &&
includesSatisfies(options.include, existingOptions.include)
Original file line number Diff line number Diff line change
@@ -5,15 +5,15 @@ import type Store from '@ember-data/store';
import { SOURCE } from '@ember-data/store/-private';
import type IdentifierArray from '@ember-data/store/-private/record-arrays/identifier-array';
import type { ModelSchema } from '@ember-data/store/-types/q/ds-model';
import type { FindOptions } from '@ember-data/store/-types/q/store';
import type { FindAllOptions } from '@ember-data/store/-types/q/store';
import type { StableRecordIdentifier } from '@warp-drive/core-types';

import { upgradeStore } from '../-private';
import type Snapshot from './snapshot';
/**
SnapshotRecordArray is not directly instantiable.
Instances are provided to consuming application's
adapters for certain requests.
adapters for certain `findAll` requests.

@class SnapshotRecordArray
@public
@@ -25,7 +25,7 @@ export default class SnapshotRecordArray {
declare __store: Store;

declare adapterOptions?: Record<string, unknown>;
declare include?: string;
declare include?: string | string[];

/**
SnapshotRecordArray is not directly instantiable.
@@ -39,7 +39,7 @@ export default class SnapshotRecordArray {
@param {string} type
@param options
*/
constructor(store: Store, type: string, options: FindOptions = {}) {
constructor(store: Store, type: string, options: FindAllOptions = {}) {
this.__store = store;
/**
An array of snapshots
Loading
Loading