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

Backport request utils and diagnostic to 4.12 #9156

Closed
wants to merge 12 commits into from
Closed
6 changes: 6 additions & 0 deletions ember-data-types/q/params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type SerializablePrimitive = string | number | boolean | null;
export type Serializable = SerializablePrimitive | SerializablePrimitive[];
export type QueryParamsSerializationOptions = {
arrayFormat?: 'bracket' | 'indices' | 'repeat' | 'comma';
};
export type QueryParamsSource = Record<string, Serializable> | URLSearchParams;
230 changes: 230 additions & 0 deletions ember-data-types/q/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// FIXME: Seems weird that this import specifies ember-data
import { ResourceIdentifierObject } from './ember-data-json-api';
import { StableRecordIdentifier } from './identifier';
import { QueryParamsSerializationOptions } from './params';

type Store = unknown;

export const SkipCache = Symbol.for('wd:skip-cache');
export const EnableHydration = Symbol.for('wd:enable-hydration');
export const IS_FUTURE = Symbol('IS_FUTURE');
export const STRUCTURED = Symbol('DOC');

export type HTTPMethod = 'GET' | 'OPTIONS' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';

export type CacheOptions = {
key?: string;
reload?: boolean;
backgroundReload?: boolean;
};
export type FindRecordRequestOptions = {
url: string;
method: 'GET';
headers: Headers;
cacheOptions: CacheOptions;
op: 'findRecord';
records: [ResourceIdentifierObject];
};

export type QueryRequestOptions = {
url: string;
method: 'GET';
headers: Headers;
cacheOptions: CacheOptions;
op: 'query';
};

export type PostQueryRequestOptions = {
url: string;
method: 'POST' | 'QUERY';
headers: Headers;
body: string;
cacheOptions: CacheOptions & { key: string };
op: 'query';
};

export type DeleteRequestOptions = {
url: string;
method: 'DELETE';
headers: Headers;
op: 'deleteRecord';
data: {
record: StableRecordIdentifier;
};
};

export type UpdateRequestOptions = {
url: string;
method: 'PATCH' | 'PUT';
headers: Headers;
op: 'updateRecord';
data: {
record: StableRecordIdentifier;
};
};

export type CreateRequestOptions = {
url: string;
method: 'POST';
headers: Headers;
op: 'createRecord';
data: {
record: StableRecordIdentifier;
};
};

export type RemotelyAccessibleIdentifier = {
id: string;
type: string;
lid?: string;
};

export type ConstrainedRequestOptions = {
reload?: boolean;
backgroundReload?: boolean;
host?: string;
namespace?: string;
resourcePath?: string;
urlParamsSettings?: QueryParamsSerializationOptions;
};

export type FindRecordOptions = ConstrainedRequestOptions & {
include?: string | string[];
};

export interface StructuredDataDocument<T> {
[STRUCTURED]?: true;
request: ImmutableRequestInfo;
response: Response | ResponseInfo | null;
content: T;
}
export interface StructuredErrorDocument<T = unknown> extends Error {
[STRUCTURED]?: true;
request: ImmutableRequestInfo;
response: Response | ResponseInfo | null;
error: string | object;
content?: T;
}
export type StructuredDocument<T> = StructuredDataDocument<T> | StructuredErrorDocument<T>;

interface Request {
controller?: AbortController;
/* Returns the cache mode associated with request, which is a string indicating how the request will interact with the browser's cache when fetching. */
cache?: RequestCache;
/* Returns the credentials mode associated with request, which is a string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. */
credentials?: RequestCredentials;
/* Returns the kind of resource requested by request, e.g., "document" or "script". */
destination?: RequestDestination;
/* Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header. */
headers?: Headers;
/* Returns request's subresource integrity metadata, which is a cryptographic hash of the resource being fetched. Its value consists of multiple hashes separated by whitespace. [SRI] */
integrity?: string;
/* Returns a boolean indicating whether or not request can outlive the global in which it was created. */
keepalive?: boolean;
/* Returns request's HTTP method, which is "GET" by default. */
method?: HTTPMethod;
/* Returns the mode associated with request, which is a string indicating whether the request will use CORS, or will be restricted to same-origin URLs. */
mode?: RequestMode;
/* Returns the redirect mode associated with request, which is a string indicating how redirects for the request will be handled during fetching. A request will follow redirects by default. */
redirect?: RequestRedirect;
/* Returns the referrer of request. Its value can be a same-origin URL if explicitly set in init, the empty string to indicate no referrer, and "about:client" when defaulting to the global's default. This is used during fetching to determine the value of the `Referer` header of the request being made. */
referrer?: string;
/* Returns the referrer policy associated with request. This is used during fetching to compute the value of the request's referrer. */
referrerPolicy?: ReferrerPolicy;
/* Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort event handler. */
signal?: AbortSignal;
/* Returns the URL of request as a string. */
url?: string;
body?: BodyInit | null;
}

export type ImmutableHeaders = Headers & { clone?(): Headers; toJSON(): [string, string][] };

export interface RequestInfo extends Request {
cacheOptions?: { key?: string; reload?: boolean; backgroundReload?: boolean; [SkipCache]?: true };
store?: Store;

op?: string;
records?: StableRecordIdentifier[];

disableTestWaiter?: boolean;
/*
* data that a handler should convert into
* the query (GET) or body (POST)
*/
data?: Record<string, unknown>;
/*
* options specifically intended for handlers
* to utilize to process the request
*/
options?: Record<string, unknown>;
}

export interface ImmutableRequestInfo {
readonly cacheOptions?: {
key?: string;
reload?: boolean;
backgroundReload?: boolean;
[SkipCache]?: true;
};
readonly store?: Store;

readonly op?: string;
readonly records?: StableRecordIdentifier[];

readonly disableTestWaiter?: boolean;
/* Returns the cache mode associated with request, which is a string indicating how the request will interact with the browser's cache when fetching. */
readonly cache?: RequestCache;
/* Returns the credentials mode associated with request, which is a string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. */
readonly credentials?: RequestCredentials;
/* Returns the kind of resource requested by request, e.g., "document" or "script". */
readonly destination?: RequestDestination;
/* Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header. */
readonly headers?: Headers & { clone?(): Headers };
/* Returns request's subresource integrity metadata, which is a cryptographic hash of the resource being fetched. Its value consists of multiple hashes separated by whitespace. [SRI] */
readonly integrity?: string;
/* Returns a boolean indicating whether or not request can outlive the global in which it was created. */
readonly keepalive?: boolean;
/* Returns request's HTTP method, which is "GET" by default. */
readonly method?: HTTPMethod;
/* Returns the mode associated with request, which is a string indicating whether the request will use CORS, or will be restricted to same-origin URLs. */
readonly mode?: RequestMode;
/* Returns the redirect mode associated with request, which is a string indicating how redirects for the request will be handled during fetching. A request will follow redirects by default. */
readonly redirect?: RequestRedirect;
/* Returns the referrer of request. Its value can be a same-origin URL if explicitly set in init, the empty string to indicate no referrer, and "about:client" when defaulting to the global's default. This is used during fetching to determine the value of the `Referer` header of the request being made. */
readonly referrer?: string;
/* Returns the referrer policy associated with request. This is used during fetching to compute the value of the request's referrer. */
readonly referrerPolicy?: ReferrerPolicy;
/* Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort event handler. */
readonly signal?: AbortSignal;
/* Returns the URL of request as a string. */
readonly url?: string;
/*
* data that a handler should convert into
* the query (GET) or body (POST)
*/
readonly data?: Record<string, unknown>;
/*
* options specifically intended for handlers
* to utilize to process the request
*/
readonly options?: Record<string, unknown>;
}

export interface ResponseInfo {
readonly headers: ImmutableHeaders; // to do, maybe not this?
readonly ok: boolean;
readonly redirected: boolean;
readonly status: number;
readonly statusText: string;
readonly type: string;
readonly url: string;
}

export interface RequestContext {
request: ImmutableRequestInfo;
id: number;

setStream(stream: ReadableStream): void;
setResponse(response: Response | ResponseInfo): void;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,4 @@
"ember-cli-htmlbars": "^6.2.0"
}
}
}
}
11 changes: 11 additions & 0 deletions packages/active-record/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
The MIT License (MIT)

Copyright (C) 2017-2023 Ember.js contributors
Portions Copyright (C) 2011-2017 Tilde, Inc. and contributors.
Portions Copyright (C) 2011 LivingSocial Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
69 changes: 69 additions & 0 deletions packages/active-record/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<p align="center">
<img
class="project-logo"
src="./ember-data-logo-dark.svg#gh-dark-mode-only"
alt="EmberData ActiveRecord"
width="240px"
title="EmberData ActiveRecord"
/>
<img
class="project-logo"
src="./ember-data-logo-light.svg#gh-light-mode-only"
alt="EmberData ActiveRecord"
width="240px"
title="EmberData ActiveRecord"
/>
</p>

<p align="center">Elegantly composable. Made for <strong>Active</strong>Record</p>

This package provides utilities for working with **Active**Record APIs with [*Ember***Data**](https://github.com/emberjs/data/).

## Installation

Install using your javascript package manager of choice. For instance with [pnpm](https://pnpm.io/)

```no-highlight
pnpm add @ember-data/active-record
```

## Getting Started

If this package is how you are first learning about EmberData, we recommend starting with learning about the [Store](https://github.com/emberjs/data/blob/main/packages/store/README.md) and [Requests](https://github.com/emberjs/data/blob/main/packages/request/README.md)

## Request Builders

Request builders are functions that produce [Fetch Options](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). They take a few contextual inputs about the request you want to make, abstracting away the gnarlier details.

For instance, to fetch a resource from your API

```ts
import { findRecord } from '@ember-data/active-record/request';

const options = findRecord('ember-developer', '1', { include: ['pets', 'friends'] });

/*
=> {
url: 'https://api.example.com/v1/ember_developers/1?include=friends,pets',
method: 'GET',
headers: <Headers>, // 'Content-Type': 'application/json;charset=utf-8'
op: 'findRecord';
records: [{ type: 'ember-developer', id: '1' }]
}
*/
```

Request builder output may be used with either `requestManager.request` or `store.request`.

URLs are stable. The same query will produce the same URL every time, even if the order of keys in
the query or values in an array changes.

URLs follow the most common ActiveRecord format (underscored pluralized resource types).

### Available Builders

- [createRecord]()
- [deleteRecord]()
- [findRecord]()
- [query]()
- [updateRecord]()
19 changes: 19 additions & 0 deletions packages/active-record/addon-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
name: require('./package.json').name,

treeForVendor() {
return;
},
treeForPublic() {
return;
},
treeForStyles() {
return;
},
treeForAddonStyles() {
return;
},
treeForApp() {
return;
},
};
8 changes: 8 additions & 0 deletions packages/active-record/babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"plugins": [
"@babel/plugin-transform-runtime",
["@babel/plugin-transform-typescript", { "allowDeclareFields": true }],
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-transform-class-properties"
]
}
Loading