Skip to content

Commit

Permalink
stash work
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired committed Nov 19, 2022
1 parent d45902d commit 9c02c15
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 96 deletions.
File renamed without changes.
15 changes: 0 additions & 15 deletions packages/request/addon-main.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,6 @@
const path = require('path');

const Funnel = require('broccoli-funnel');

module.exports = {
name: require('./package.json').name,

options: {
babel: {
plugins: [require.resolve('./lib/transform-ext.js')],
},
},

treeForAddon() {
const assetDir = path.join(__dirname, './dist');
return this._super.treeForAddon.call(this, new Funnel(assetDir, { include: ['**/*.js'] }));
},

treeForVendor() {
return;
},
Expand Down
3 changes: 2 additions & 1 deletion packages/request/babel.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"@babel/plugin-transform-runtime",
["@babel/plugin-transform-typescript", { "allowDeclareFields": true }],
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-proposal-class-properties"
"@babel/plugin-proposal-class-properties",
"@ember-data/private-build-infra/src/transforms/babel-plugin-transform-ext"
]
}
13 changes: 6 additions & 7 deletions packages/request/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "@ember-data/request",
"description": "Network Primitive for coordinating and configuring `fetch` usage",
"description": "⚡️ A simple, small and fast framework-agnostic library to make `fetch` happen",
"version": "4.9.0-alpha.14",
"private": false,
"license": "MIT",
"author": "Chris Thoburn <[email protected]>",
"repository": {
"type": "git",
"url": "git+ssh://[email protected]:emberjs/data.git",
"directory": "addons/tracking"
"directory": "packages/request"
},
"homepage": "https://github.com/emberjs/data",
"bugs": "https://github.com/emberjs/data/issues",
Expand All @@ -20,18 +20,16 @@
"extends": "../../package.json"
},
"dependencies": {
"broccoli-funnel": "^3.0.8",
"ember-cli-babel": "^7.26.11"
},
"exports": {
".": "./dist/index.js",
"./*": "./dist/*",
".": "./addon/index.js",
"./*": "./addon/*",
"./addon-main.js": "./addon-main.js"
},
"files": [
"lib",
"addon-main.js",
"dist"
"addon"
],
"scripts": {
"build": "rollup --config",
Expand All @@ -47,6 +45,7 @@
"devDependencies": {
"@embroider/addon-dev": "^2.0.0",
"@embroider/macros": "^1.9.0",
"@ember-data/private-build-infra": "workspace:4.9.0-alpha.14",
"rollup": "^3.2.3",
"@babel/core": "^7.19.6",
"@babel/plugin-proposal-class-properties": "^7.18.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/request/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { nodeResolve } from '@rollup/plugin-node-resolve';

const addon = new Addon({
srcDir: 'src',
destDir: 'dist',
destDir: 'addon',
});

export default {
Expand Down
50 changes: 0 additions & 50 deletions packages/request/src/-private.ts

This file was deleted.

31 changes: 31 additions & 0 deletions packages/request/src/-private/future.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @module @ember-data/request
*/
export interface StructuredDocument<T> {
data?: T;
}

/**
* @class Future
*/
export interface Future<T> extends Promise<StructuredDocument<T>> {
/**
* Cancel this request by firing the AbortController's signal.
*
* @method abort
* @public
* @returns {void}
*/
abort(): void;

/**
* Get the response stream, if any, once made available.
*
* @method getStream
* @public
* @returns {Promise<ReadableStream | null>}
*/
getStream(): Promise<ReadableStream | null>;
}

export function createFuture<T>(promise: Promise<T>): Future<T> {}
26 changes: 26 additions & 0 deletions packages/request/src/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @module @ember-data/request/fetch
*/
import type { RequestContext } from './index';

/**
* A basic handler which onverts a request into a
* `fetch` call presuming the response to be `json`.
*
* ```ts
* import Fetch from '@ember-data/request/fetch';
*
* manager.use([Fetch]);
* ```
*
* @class Fetch
* @public
*/
export default {
async request(context: RequestContext) {
const response = await fetch(context.request.url, context.request);
context.setResponse(response);

return response.json();
},
};
125 changes: 103 additions & 22 deletions packages/request/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,93 @@
import { isDevelopingApp, isTesting, macroCondition } from '@embroider/macros';

interface Request {}
import { createFuture, Future } from './-private/future';

interface NextFn {
(request: Request): Promise<unknown>;
interface Request {
/** 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: Record<string, string>;
/** 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: string;
/** 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;
}

interface RequestResponse<T> {
result: unknown;
interface RequestInfo extends Request {
/**
* 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 ResponseInfo {}
export interface RequestContext {
request: RequestInfo;

setStream(stream: ReadableStream): void;
setResponse(response: Response | ResponseInfo): void;
}

type NextFn<P = unknown> = (req: RequestInfo) => Future<P>;
export interface Handler {
request<T = unknown>(context: RequestContext, next: NextFn): Promise<T>;
}

interface Middleware {
request(request: Request, next: NextFn): Promise<unknown>;
interface RequestResponse<T> {
result: T;
}

type GenericCreateArgs = Record<string | symbol, unknown>;

export default class RequestManager {
#wares: Middleware[] = [];
#handlers: Handler[] = [];

constructor(options?: GenericCreateArgs) {
Object.assign(this, options);
}

use(newWares: Middleware[]) {
const wares = this.#wares;
use(newHandlers: Handler[]) {
const handlers = this.#handlers;
if (macroCondition(isDevelopingApp())) {
if (Object.isFrozen(wares)) {
if (Object.isFrozen(handlers)) {
throw new Error(`Cannot add a Middleware to a RequestManager after a request has been made`);
}
}
wares.push(...newWares);
handlers.push(...newHandlers);
}

async request<T>(request: Request): Promise<RequestResponse<T>> {
const wares = this.#wares;
request<T = unknown>(request: Request): Future<T> {
const handlers = this.#handlers;
if (macroCondition(isDevelopingApp())) {
if (!Object.isFrozen(wares)) {
Object.freeze(wares);
if (!Object.isFrozen(handlers)) {
Object.freeze(handlers);
}
}
let promise = Promise.resolve(perform(wares, request));
let promise = perform<T>(handlers, request);
if (macroCondition(isTesting())) {
// const { waitForPromise } = importSync('ember-test-waiters');
// promise = waitForPromise(promise);
Expand All @@ -53,15 +100,49 @@ export default class RequestManager {
}
}

async function perform<T>(wares: Readonly<Middleware[]>, request: Request, i: number = 0): Promise<RequestResponse<T>> {
function createContext(request: RequestInfo): RequestContext {
const context = {
request,
setStream() {},
setResponse() {},
};
return context;
}

function isFuture<T>(maybe: Future<T> | Promise<T>): maybe is Future<T> {
return false;
}

async function perform<T>(wares: Readonly<Handler[]>, request: RequestInfo, i: number = 0): Future<T> {
if (macroCondition(isDevelopingApp())) {
if (i === wares.length) {
throw new Error(`No middleware was able to handle this request.`);
throw new Error(`No handler was able to handle this request.`);
}
}
function next(r: Request): Promise<RequestResponse<T>> {

let nextCalled = 0;
function next(r: RequestInfo): Future<T> {
nextCalled++;
return perform(wares, r, i + 1);
}
const result = await wares[i].request(request, next);
return { result };
const context = createContext(request);
const maybeFuture = wares[i].request<T>(context, next);

// if we immediately receive a Future, we curry in full
if (isFuture<T>(maybeFuture) && nextCalled === 1) {
// curry the future
// TODO don't curry request,
// return a new future
return maybeFuture;
}

// create a new future
const future = createFuture<T>(maybeFuture);

const result = await maybeFuture;

if (nextCalled === 1) {
}

return future;
}

0 comments on commit 9c02c15

Please sign in to comment.