Skip to content

Commit

Permalink
almost final
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired committed Nov 10, 2022
1 parent 4d65483 commit 697a12f
Showing 1 changed file with 308 additions and 9 deletions.
317 changes: 308 additions & 9 deletions text/0854-ember-data-cache-v2.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -1005,8 +1005,8 @@ The design for caching documents focuses on solving three key constraints.
3) The cache should have access to serializable information about the request and response
that may be required for proper cache storage, management or desired for later access.

> Insert how store.push changes
> Insert how RequestCache changes
> TODO Insert how store.push changes
> TODO Insert how RequestCache changes

<table>
<thead>
Expand All @@ -1019,6 +1019,8 @@ The design for caching documents focuses on solving three key constraints.
```ts
class Cache {
/**
* Cache the response to a request
*
* Unlike `store.push` which has UPSERT
* semantics, `put` has `replace` semantics similar to
* the `http` method `PUT`
Expand All @@ -1027,18 +1029,61 @@ class Cache {
* should upsert, but the document data surrounding it should
* fully replace any existing information
*
* @method put
* @param {StructuredDocument} doc
* @returns {ResourceDocument}
* @public
*/
put(doc: StructuredDocument): ResourceDocument;
/**
* Update the "remote" or "canonical" (persisted) state of the Cache
* by merging new information into the existing state.
*
* @method patch
* @param {Operation} op
* @returns {void}
* @public
*/
patch(
op: Operation
): void;
/**
* Update the "local" or "current" (unpersisted) state of the Cache
*
* @method mutate
* @param {Mutation} mutation
* @returns {void}
* @public
*/
mutate(
mutation: Mutation
): void;
/**
* Peek the Cache for the existing data associated with
* a StructuredDocument
*
* @method peek
* @param {StableDocumentIdentifier}
* @returns {ResourceDocument | null}
* @public
*/
peek(
identifier: StableDocumentIdentifier
): StructuredDocument | null;
): ResourceDocument | null;
/**
* Peek the Cache for the existing request data associated with
* a cacheable request
*
* @method peekRequest
* @param {StableDocumentIdentifier}
* @returns {StableDocumentIdentifier | null}
* @public
*/
peekRequest(identifier: StableDocumentIdentifier): StructuredDocument | null;
}
```

Expand Down Expand Up @@ -1084,15 +1129,59 @@ interface ResourceDocument {

### 4. Introduction of Streaming Cache API

Cache implementations should implement two methods to support SSR
and AOT Hydration.
Cache implementations should implement two methods to support streaming
SSR and AOT Hydration.

`cache.dump` should return a stream of the cache's contents that can be
provided to the same cache's `hydrate` method. The only requirement is
that a `Cache` should output a stream from `cache.dump` that it can also import
via `cache.hydrate`. The opaque nature of this contract allows cache implementations
the flexibility to experiment with the best formats for fastest restore.

`cache.dump` returns a promise resolving to this stream to allow the cache the
opportunity to handle any necessary async operations before beginning the stream.

`cache.hydrate` should accept a stream of content to add to the cache, and return
a `Promise` the resolves when reading the stream has completed or rejects if for
some reason `hydrate` has failed. Currently there is no defined behavior for
recovery when `hydrate` fails, and caches may handle a failure however they see fit.

A key consideration implementations of `cache.hydrate` must make is that `hydrate`
should expect that it may be called in two different modes: both during initial
hydration and to hydrate additional state into an already booted application.



```ts
class Cache {
// chunked stream out
/**
* Serialize the entire contents of the Cache into a Stream
* which may be fed back into a new instance of the same Cache
* via `cache.hydrate`.
*
* @method dump
* @returns {Promise<ReadableStream>}
* @public
*/
async dump(): Promise<ReadableStream<unknown>>;
// chunked stream in
// allowable to stream in post-boot
/**
* hydrate a Cache from a Stream with content previously serialized
* from another instance of the same Cache, resolving when hydration
* is complete.
*
* This method should expect to be called both in the context of restoring
* the Cache during application rehydration after SSR **AND** at unknown
* times during the lifetime of an already booted application when it is
* desired to bulk-load additional information into the cache. This latter
* behavior supports optimizing pre/fetching of data for route transitions
* via data-only SSR modes.
*
* @method hydrate
* @param {ReadableStream} stream
* @returns {Promise<void>}
* @public
*/
async hydrate(stream: ReadableStream<unknown>): void;
}
```
Expand All @@ -1101,18 +1190,228 @@ class Cache {
### 5. Introduction of Cache Forking

Cache implementations should implement three methods to support
store forking.
store forking. While the mechanics of how a Cache chooses to
fork are left to it, forks should expect to live-up to the following
constraints.

1. A parent should never retain a reference to a child.
2. A child should never mutate/update the state of a parent.

```ts
class Store {
/**
* Create a fork of the Store starting
* from the current state.
*
* @method fork
* @public
* @returns Promise<Store>
*/
async fork(): Promise<Store>;
/**
* Merge a fork back into a parent Store
*
* @method merge
* @param {Store} store
* @public
* @returns Promise<void>
*/
async merge(store: Store): void;
}
```

```ts
class Cache {
/**
* Create a fork of the cache from the current state.
*
* Applications should typically not call this method themselves,
* preferring instead to fork at the Store level, which will
* utilize this method to fork the cache.
*
* @method fork
* @public
* @returns Promise<Cache>
*/
async fork(): Promise<Cache>;
/**
* Merge a fork back into a parent Cache.
*
* Applications should typically not call this method themselves,
* preferring instead to merge at the Store level, which will
* utilize this method to merge the caches.
*
* @method merge
* @param {Cache} cache
* @public
* @returns Promise<void>
*/
async merge(cache: Cache): void;
/**
* Generate the list of changes applied to all
* record in the store.
*
* Each individual resource or document that has
* been mutated should be described as an individual
* `Change` entry in the returned array.
*
* A `Change` is described by an object containing up to
* three properties: (1) the `identifier` of the entity that
* changed; (2) the `op` code of that change being one of
* `upsert` or `remove`, and if the op is `upsert` a `patch`
* containing the data to merge into the cache for the given
* entity.
*
* This `patch` is opaque to the Store but should be understood
* by the Cache and may expect to be utilized by an Adapter
* when generating data during a `save` operation.
*
* It is generally recommended that the `patch` contain only
* the updated state, ignoring fields that are unchanged
*
* ```ts
* interface Change {
* identifier: StableRecordIdentifier | StableDocumentIdentifier;
* op: 'upsert' | 'remove';
* patch?: unknown;
* }
* ```
*
*/
async diff(): Promise<Change[]>;
}
```

## The complete v2.1 Cache Interface

```ts
interface Cache {
version: '2';
// Cache Management
// ================
put(doc: StructuredDocument): ResourceDocument;
patch(op: Operation): void;
mutate(mutation: Mutation): void;
peek(identifier: StableRecordIdentifier): ResourceBlob | null;
peek(identifier: StableDocumentIdentifier): ResourceDocument | null;
peekRequest(identifier: StableDocumentIdentifier): StructuredDocument | null;
upsert(
identifier: StableRecordIdentifier,
data: ResourceBlob,
hasRecord?: boolean
): void | string[];
// Cache Forking Support
// =====================
async fork(): Promise<Cache>;
async merge(cache: Cache): void;
async diff(): Promise<object[]>;
// SSR Support
// ===========
async dump(): Promise<ReadableStream<unknown>>;
async hydrate(stream: ReadableStream<unknown>): void;
// Resource Support
// ================
willCommit(
identifier: StableRecordIdentifier
): void;
didCommit(
identifier: StableRecordIdentifier,
data: ResourceBlob | null
): void;
commitWasRejected(
identifier: StableRecordIdentifier,
errors?: ValidationError[]
): void;
getErrors(
identifier: StableRecordIdentifier
): ValidationError[];
getAttr(
identifier: StableRecordIdentifier,
field: string
): unknown;
setAttr(
identifier: StableRecordIdentifier,
field: string,
value: unknown
): void;
changedAttrs(
identifier: StableRecordIdentifier
): Record<string, [unknown, unknown]>;
hasChangedAttrs(
identifier: StableRecordIdentifier
): boolean;
rollbackAttrs(
identifier: StableRecordIdentifier
): string[];
getRelationship(
identifier: StableRecordIdentifier,
field: string
): Relationship;
unloadRecord(
identifier: StableRecordIdentifier
): void;
isEmpty(
identifier: StableRecordIdentifier
): boolean;
clientDidCreate(
identifier: StableRecordIdentifier,
createArgs?: Record<string, unknown>
): Record<string, unknown>;
isNew(
identifier: StableRecordIdentifier
): boolean;
setIsDeleted(
identifier: StableRecordIdentifier,
isDeleted: boolean
): void;
isDeleted(
identifier: StableRecordIdentifier
): boolean;
isDeletionCommitted(
identifier: StableRecordIdentifier
): boolean;
}
```

## Typescript Support

All implementable interfaces involved in this RFC will be made available via a new package `@ember-data/experimental-types`. These types should be considered unstable. When we no
longer consider these types experimental we will mark their stability by migrating them
to `@ember-data/types`.

The specific reason for this instability is the need to flesh out and implement an official
pattern for *registries* for record types and their fields. For instance, we expect to change from `type: string` to the more narrowable and restricted `keyof ModelRegistry & string` when that occurs.

## How we teach this

- updated learning URLs
Expand Down

0 comments on commit 697a12f

Please sign in to comment.