Add implementation for fetch method in tests.refactor/new-arch
@@ -1,7 +1,9 @@ | |||||
import {App as BaseApp, AppOperations, BaseAppState, Endpoint, Response} from '../common'; | import {App as BaseApp, AppOperations, BaseAppState, Endpoint, Response} from '../common'; | ||||
import {DataSource} from './data-source'; | |||||
interface BackendParams<App extends BaseApp> { | interface BackendParams<App extends BaseApp> { | ||||
app: App; | app: App; | ||||
dataSource?: DataSource; | |||||
} | } | ||||
export interface ImplementationContext { | export interface ImplementationContext { | ||||
@@ -14,6 +16,7 @@ type ImplementationFunction = (params: ImplementationContext) => Promise<Respons | |||||
export interface Backend<App extends BaseApp = BaseApp> { | export interface Backend<App extends BaseApp = BaseApp> { | ||||
app: App; | app: App; | ||||
dataSource?: DataSource; | |||||
implementations: Map<string, ImplementationFunction>; | implementations: Map<string, ImplementationFunction>; | ||||
implementOperation<Operation extends AppOperations<App>>( | implementOperation<Operation extends AppOperations<App>>( | ||||
operation: Operation, implementation: ImplementationFunction): this; | operation: Operation, implementation: ImplementationFunction): this; | ||||
@@ -21,10 +24,12 @@ export interface Backend<App extends BaseApp = BaseApp> { | |||||
class BackendInstance<App extends BaseApp> implements Backend<App> { | class BackendInstance<App extends BaseApp> implements Backend<App> { | ||||
readonly app: App; | readonly app: App; | ||||
readonly dataSource?: DataSource; | |||||
readonly implementations: Map<string, ImplementationFunction>; | readonly implementations: Map<string, ImplementationFunction>; | ||||
constructor(params: BackendParams<App>) { | constructor(params: BackendParams<App>) { | ||||
this.app = params.app; | this.app = params.app; | ||||
this.dataSource = params.dataSource; | |||||
this.implementations = new Map<string, ImplementationFunction>(); | this.implementations = new Map<string, ImplementationFunction>(); | ||||
} | } | ||||
@@ -0,0 +1,37 @@ | |||||
import {QueryAndGrouping, validation as v} from '../common'; | |||||
export interface EmplaceDetails { | |||||
isCreated: boolean; | |||||
} | |||||
export type DataSourceQuery = QueryAndGrouping; | |||||
export interface DataSource< | |||||
ItemData extends object = object, | |||||
ID extends unknown = unknown, | |||||
Query extends DataSourceQuery = DataSourceQuery, | |||||
Emplace extends EmplaceDetails = EmplaceDetails, | |||||
DeleteResult = unknown, | |||||
> { | |||||
initialize(): Promise<unknown>; | |||||
getTotalCount?(query?: Query): Promise<number>; | |||||
getMultiple(query?: Query): Promise<ItemData[]>; | |||||
getById(id: ID): Promise<ItemData | null>; | |||||
getSingle?(query?: Query): Promise<ItemData | null>; | |||||
create(data: ItemData): Promise<ItemData>; | |||||
delete(id: ID): Promise<DeleteResult>; | |||||
emplace(id: ID, data: ItemData): Promise<[ItemData, Emplace]>; | |||||
patch(id: ID, data: Partial<ItemData>): Promise<ItemData | null>; | |||||
newId(): Promise<ID>; | |||||
} | |||||
export interface ResourceIdConfig<IdSchema extends v.BaseSchema> { | |||||
generationStrategy: GenerationStrategy; | |||||
serialize: (id: unknown) => string; | |||||
deserialize: (id: string) => v.Output<IdSchema>; | |||||
schema: IdSchema; | |||||
} | |||||
export interface GenerationStrategy { | |||||
(dataSource: DataSource, ...args: unknown[]): Promise<string | number | unknown>; | |||||
} |
@@ -1,2 +1,3 @@ | |||||
export * from './common'; | export * from './common'; | ||||
export * from './data-source'; | |||||
export * from './server'; | export * from './server'; |
@@ -1,2 +0,0 @@ | |||||
export interface DataSource {} |
@@ -1,4 +1,4 @@ | |||||
import {DataSource} from './data-source'; | |||||
import {DataSource} from '../backend/data-source'; | |||||
import {validation as v} from '.'; | import {validation as v} from '.'; | ||||
export type EndpointQueue = [Endpoint, Record<string, unknown> | undefined][]; | export type EndpointQueue = [Endpoint, Record<string, unknown> | undefined][]; | ||||
@@ -87,6 +87,7 @@ export interface Endpoint< | |||||
State extends BaseEndpointState = BaseEndpointState | State extends BaseEndpointState = BaseEndpointState | ||||
> { | > { | ||||
name: Name; | name: Name; | ||||
dataSource?: DataSource; | |||||
schema: Schema; | schema: Schema; | ||||
params: Set<string>; | params: Set<string>; | ||||
operations: Set<string>; | operations: Set<string>; | ||||
@@ -126,6 +127,7 @@ class EndpointInstance< | |||||
State extends BaseEndpointState | State extends BaseEndpointState | ||||
> implements Endpoint<Params['name'], Params['schema'], State> { | > implements Endpoint<Params['name'], Params['schema'], State> { | ||||
readonly name: Params['name']; | readonly name: Params['name']; | ||||
readonly dataSource: Params['dataSource']; | |||||
readonly operations: Set<string>; | readonly operations: Set<string>; | ||||
readonly params: Set<string>; | readonly params: Set<string>; | ||||
readonly schema: Params['schema']; | readonly schema: Params['schema']; | ||||
@@ -135,6 +137,7 @@ class EndpointInstance< | |||||
this.schema = params.schema; | this.schema = params.schema; | ||||
this.operations = new Set<string>(); | this.operations = new Set<string>(); | ||||
this.params = new Set<string>(); | this.params = new Set<string>(); | ||||
this.dataSource = params.dataSource; | |||||
} | } | ||||
can<OpName extends string = string, OpValue extends OpValueType = OpValueType>( | can<OpName extends string = string, OpValue extends OpValueType = OpValueType>( | ||||
@@ -1,10 +1,10 @@ | |||||
export * from './app'; | export * from './app'; | ||||
export * from './charset'; | export * from './charset'; | ||||
export * from './data-source'; | |||||
export * from './endpoint'; | export * from './endpoint'; | ||||
export * from './language'; | export * from './language'; | ||||
export * from './media-type'; | export * from './media-type'; | ||||
export * from './operation'; | export * from './operation'; | ||||
export * from './queries'; | |||||
export * from './response'; | export * from './response'; | ||||
export * from './service'; | export * from './service'; | ||||
export * as statusCodes from './status-codes'; | export * as statusCodes from './status-codes'; | ||||
@@ -0,0 +1,53 @@ | |||||
import {MediaType} from '../media-type'; | |||||
const OPERATORS = [ | |||||
'=', | |||||
'!=', | |||||
'>=', | |||||
'<=', | |||||
'>', | |||||
'<', | |||||
'LIKE', | |||||
'ILIKE', | |||||
'REGEXP', | |||||
] as const; | |||||
type QueryOperator = typeof OPERATORS[number]; | |||||
type QueryExpressionValue = string | number | boolean; | |||||
interface QueryOperatorExpression { | |||||
lhs: string; | |||||
operator: QueryOperator; | |||||
rhs: QueryExpressionValue | RegExp; | |||||
} | |||||
interface QueryFunctionExpression { | |||||
name: string; | |||||
args: QueryExpressionValue[]; | |||||
} | |||||
export type QueryAnyExpression = QueryOperatorExpression | QueryFunctionExpression; | |||||
export interface QueryOrGrouping { | |||||
type: 'or'; | |||||
expressions: QueryAnyExpression[]; | |||||
} | |||||
export interface QueryAndGrouping { | |||||
type: 'and'; | |||||
expressions: QueryOrGrouping[]; | |||||
} | |||||
export type Query = QueryAndGrouping; | |||||
export interface QueryMediaType< | |||||
Name extends string = string, | |||||
SerializeOptions extends {} = {}, | |||||
DeserializeOptions extends {} = {} | |||||
> extends MediaType< | |||||
Name, | |||||
QueryAndGrouping, | |||||
SerializeOptions, | |||||
DeserializeOptions | |||||
> {} |
@@ -0,0 +1 @@ | |||||
export * from './common'; |
@@ -1,4 +1,4 @@ | |||||
import {Backend} from '../backend'; | |||||
import {Backend, DataSource} from '../backend'; | |||||
import {Operation} from './operation'; | import {Operation} from './operation'; | ||||
import {App} from './app'; | import {App} from './app'; | ||||
import {Endpoint} from './endpoint'; | import {Endpoint} from './endpoint'; | ||||
@@ -6,6 +6,7 @@ import {Endpoint} from './endpoint'; | |||||
export interface RecipeState<A extends App = App> { | export interface RecipeState<A extends App = App> { | ||||
app: A; | app: A; | ||||
backend?: Backend<A>; | backend?: Backend<A>; | ||||
dataSource?: DataSource; | |||||
operations?: Record<string, Operation>; | operations?: Record<string, Operation>; | ||||
endpoints?: Record<string, Endpoint>; | endpoints?: Record<string, Endpoint>; | ||||
} | } | ||||
@@ -2,13 +2,13 @@ import {ErrorStatusCode, isErrorStatusCode, StatusCode} from './status-codes'; | |||||
type FetchResponse = Awaited<ReturnType<typeof fetch>>; | type FetchResponse = Awaited<ReturnType<typeof fetch>>; | ||||
export interface Response { | |||||
export interface Response<B extends unknown = unknown> { | |||||
statusCode: number; | statusCode: number; | ||||
statusMessage: string; | statusMessage: string; | ||||
body?: Buffer; | |||||
body?: B; | |||||
} | } | ||||
export interface ErrorResponse extends Error, Response {} | |||||
export interface ErrorResponse<B extends unknown = unknown> extends Error, Response<B> {} | |||||
export interface HttpResponseConstructor<R extends Response> { | export interface HttpResponseConstructor<R extends Response> { | ||||
new (...args: any[]): R; | new (...args: any[]): R; | ||||
@@ -20,24 +20,25 @@ export interface HttpResponseErrorConstructor<R extends Response> extends HttpRe | |||||
} | } | ||||
export interface HttpSuccessResponseConstructor<R extends Response> extends HttpResponseConstructor<R> { | export interface HttpSuccessResponseConstructor<R extends Response> extends HttpResponseConstructor<R> { | ||||
new (response: Partial<Omit<Response, 'statusCode' | 'res'>>): R; | |||||
new (response: Partial<Omit<Response, 'statusCode'>>): R; | |||||
} | } | ||||
export interface HttpErrorOptions extends ErrorOptions { | |||||
body?: Response['body']; | |||||
export interface HttpErrorOptions<B extends unknown = unknown> extends ErrorOptions { | |||||
body?: B; | |||||
} | } | ||||
export const HttpResponse = < | export const HttpResponse = < | ||||
B extends unknown = unknown, | |||||
T extends StatusCode = StatusCode, | T extends StatusCode = StatusCode, | ||||
R extends Response = T extends ErrorStatusCode ? ErrorResponse : Response, | |||||
R extends Response = T extends ErrorStatusCode ? ErrorResponse<B> : Response<B>, | |||||
>(statusCode: T): T extends ErrorStatusCode ? HttpResponseErrorConstructor<R> : HttpSuccessResponseConstructor<R> => { | >(statusCode: T): T extends ErrorStatusCode ? HttpResponseErrorConstructor<R> : HttpSuccessResponseConstructor<R> => { | ||||
if (isErrorStatusCode(statusCode)) { | if (isErrorStatusCode(statusCode)) { | ||||
return class HttpErrorResponse extends Error implements ErrorResponse { | |||||
return class HttpErrorResponse extends Error implements ErrorResponse<B> { | |||||
readonly statusMessage: string; | readonly statusMessage: string; | ||||
readonly statusCode: T; | readonly statusCode: T; | ||||
readonly body?: Buffer; | |||||
readonly body?: B; | |||||
constructor(message?: string, options?: HttpErrorOptions) { | |||||
constructor(message?: string, options?: HttpErrorOptions<B>) { | |||||
super(message, options); | super(message, options); | ||||
this.name = this.statusMessage = message ?? ''; | this.name = this.statusMessage = message ?? ''; | ||||
this.statusCode = statusCode; | this.statusCode = statusCode; | ||||
@@ -47,11 +48,11 @@ export const HttpResponse = < | |||||
} as unknown as HttpResponseErrorConstructor<R>; | } as unknown as HttpResponseErrorConstructor<R>; | ||||
} | } | ||||
return class HttpSuccessResponse implements Response { | |||||
return class HttpSuccessResponse implements Response<B> { | |||||
readonly statusMessage: string; | readonly statusMessage: string; | ||||
readonly statusCode: T; | readonly statusCode: T; | ||||
readonly body?: Buffer; | |||||
constructor(params: Partial<Omit<Response, 'statusCode'>>) { | |||||
readonly body?: B; | |||||
constructor(params: Partial<Omit<Response<B>, 'statusCode'>>) { | |||||
this.statusCode = statusCode; | this.statusCode = statusCode; | ||||
this.statusMessage = params.statusMessage ?? ''; | this.statusMessage = params.statusMessage ?? ''; | ||||
this.body = params.body; | this.body = params.body; | ||||
@@ -1,18 +1,18 @@ | |||||
import {parseToEndpointQueue, ServiceParams} from '../../../common'; | |||||
import {ErrorResponse, parseToEndpointQueue, ServiceParams} from '../../../common'; | |||||
import { | import { | ||||
Backend as BaseBackend, | Backend as BaseBackend, | ||||
Server, | Server, | ||||
ServerRequest, | |||||
ServerResponse, | |||||
ServerRequestContext, | |||||
ServerResponseContext, | |||||
ServerParams, | ServerParams, | ||||
} from '../../../backend'; | } from '../../../backend'; | ||||
import http from 'http'; | import http from 'http'; | ||||
import { statusCodes } from '../../../common'; | import { statusCodes } from '../../../common'; | ||||
declare module '../../../backend' { | declare module '../../../backend' { | ||||
interface ServerRequest extends http.IncomingMessage {} | |||||
interface ServerRequestContext extends http.IncomingMessage {} | |||||
interface ServerResponse extends http.ServerResponse {} | |||||
interface ServerResponseContext extends http.ServerResponse {} | |||||
} | } | ||||
class ServerInstance<Backend extends BaseBackend> implements Server<Backend> { | class ServerInstance<Backend extends BaseBackend> implements Server<Backend> { | ||||
@@ -23,7 +23,7 @@ class ServerInstance<Backend extends BaseBackend> implements Server<Backend> { | |||||
this.backend = params.backend; | this.backend = params.backend; | ||||
} | } | ||||
private readonly requestListener = async (req: ServerRequest, res: ServerResponse) => { | |||||
private readonly requestListener = async (req: ServerRequestContext, res: ServerResponseContext) => { | |||||
// const endpoints = this.backend.app.endpoints; | // const endpoints = this.backend.app.endpoints; | ||||
if (typeof req.method === 'undefined') { | if (typeof req.method === 'undefined') { | ||||
res.writeHead(statusCodes.HTTP_STATUS_BAD_REQUEST, {}); | res.writeHead(statusCodes.HTTP_STATUS_BAD_REQUEST, {}); | ||||
@@ -81,21 +81,31 @@ class ServerInstance<Backend extends BaseBackend> implements Server<Backend> { | |||||
// TODO add flag on implementation context if CQRS should be enabled | // TODO add flag on implementation context if CQRS should be enabled | ||||
const responseSpec = await implementation({ | |||||
endpoint, | |||||
params: endpointParams ?? {}, | |||||
query: typeof search !== 'undefined' ? new URLSearchParams(search) : undefined, | |||||
}); | |||||
try { | |||||
const responseSpec = await implementation({ | |||||
endpoint, | |||||
params: endpointParams ?? {}, | |||||
query: typeof search !== 'undefined' ? new URLSearchParams(search) : undefined, | |||||
}); | |||||
if (typeof responseSpec === 'undefined') { | |||||
res.writeHead(statusCodes.HTTP_STATUS_UNPROCESSABLE_ENTITY, {}); | |||||
res.end(); | |||||
return; | |||||
} | |||||
if (typeof responseSpec === 'undefined') { | |||||
res.writeHead(statusCodes.HTTP_STATUS_UNPROCESSABLE_ENTITY, {}); | |||||
const bodyToSerialize = responseSpec.body; | |||||
console.log(bodyToSerialize); | |||||
res.statusMessage = responseSpec.statusMessage; // TODO add default status message per status code | |||||
res.writeHead(responseSpec.statusCode, {}); | |||||
res.end(); | |||||
} catch (errorResponseSpecRaw) { | |||||
const responseSpec = errorResponseSpecRaw as ErrorResponse; | |||||
res.statusMessage = responseSpec.statusMessage; // TODO add default status message per status code | |||||
res.writeHead(responseSpec.statusCode, {}); | |||||
res.end(); | res.end(); | ||||
return; | |||||
} | } | ||||
res.statusMessage = responseSpec.statusMessage; // TODO add default status message per status code | |||||
res.writeHead(responseSpec.statusCode, {}); | |||||
res.end(); | |||||
}; | }; | ||||
serve(params: ServiceParams) { | serve(params: ServiceParams) { | ||||
@@ -1,52 +0,0 @@ | |||||
import {Recipe} from '../common/recipe'; | |||||
import {endpoint, HttpResponse, operation, validation as v} from '../common'; | |||||
import {backend} from '../backend'; | |||||
interface AddResourceRecipeParams { | |||||
endpointName: string; | |||||
} | |||||
export const addResourceRecipe = (params: AddResourceRecipeParams): Recipe => (a) => { | |||||
const operations = { | |||||
fetch: operation({ | |||||
name: 'fetch' as const, | |||||
}), | |||||
}; | |||||
const theEndpoint = endpoint({ | |||||
name: params.endpointName, | |||||
schema: v.object({ | |||||
username: v.string(), | |||||
}), | |||||
}) | |||||
.param('resourceId') | |||||
.can('fetch'); | |||||
const enhancedApp = a.app | |||||
.operation(operations.fetch) | |||||
.endpoint(theEndpoint); | |||||
const theBackend = a.backend ?? backend({ | |||||
app: enhancedApp, | |||||
}); | |||||
theBackend | |||||
.implementOperation('fetch', async (ctx) => { | |||||
// need to genericise the response here so we don't depend on the HTTP responses. | |||||
return new YesResponse({ | |||||
statusMessage: 'Yes', | |||||
}); | |||||
}); | |||||
return { | |||||
operations, | |||||
app: enhancedApp, | |||||
backend: theBackend, | |||||
endpoints: { | |||||
[params.endpointName]: theEndpoint, | |||||
}, | |||||
}; | |||||
}; | |||||
export class YesResponse extends HttpResponse(204) {} |
@@ -0,0 +1,84 @@ | |||||
import {Recipe} from '../../common/recipe'; | |||||
import {endpoint, operation, validation as v} from '../../common'; | |||||
import {backend, DataSource} from '../../backend'; | |||||
import { | |||||
DataSourceNotFoundResponseError, | |||||
ItemNotFoundReponseError, | |||||
ResourceCollectionFetchedResponse, | |||||
ResourceItemFetchedResponse, | |||||
} from './response'; | |||||
interface AddResourceRecipeParams { | |||||
endpointName: string; | |||||
dataSource?: DataSource; | |||||
} | |||||
export const addResourceRecipe = (params: AddResourceRecipeParams): Recipe => (a) => { | |||||
const operations = { | |||||
fetch: operation({ | |||||
name: 'fetch' as const, | |||||
}), | |||||
}; | |||||
const theEndpoint = endpoint({ | |||||
name: params.endpointName, | |||||
schema: v.object({ | |||||
username: v.string(), | |||||
}), | |||||
}) | |||||
.param('resourceId') | |||||
.can('fetch'); | |||||
const enhancedApp = a.app | |||||
.operation(operations.fetch) | |||||
.endpoint(theEndpoint); | |||||
const theBackend = a.backend ?? backend({ | |||||
app: enhancedApp, | |||||
dataSource: params.dataSource, | |||||
}); | |||||
theBackend | |||||
.implementOperation('fetch', async (ctx) => { | |||||
const dataSource: DataSource = ctx.endpoint.dataSource ?? theBackend.dataSource ?? {} as DataSource; | |||||
// need to genericise the response here so we don't depend on the HTTP responses. | |||||
const { resourceId } = ctx.params; | |||||
const { getById, getMultiple } = dataSource; | |||||
if (typeof resourceId === 'undefined') { | |||||
if (typeof getMultiple === 'undefined') { | |||||
throw new DataSourceNotFoundResponseError(); | |||||
} | |||||
// TODO add query here | |||||
const items = await getMultiple(); | |||||
return new ResourceCollectionFetchedResponse({ | |||||
statusMessage: 'Resource Collection Fetched', | |||||
body: items, | |||||
}); | |||||
} | |||||
if (typeof getById === 'undefined') { | |||||
throw new DataSourceNotFoundResponseError(); | |||||
} | |||||
const item = await getById(resourceId); | |||||
if (!item) { | |||||
throw new ItemNotFoundReponseError(); | |||||
} | |||||
return new ResourceItemFetchedResponse({ | |||||
statusMessage: 'Resource Item Fetched', | |||||
body: item, | |||||
}); | |||||
}); | |||||
return { | |||||
operations, | |||||
app: enhancedApp, | |||||
backend: theBackend, | |||||
endpoints: { | |||||
[params.endpointName]: theEndpoint, | |||||
}, | |||||
}; | |||||
}; |
@@ -0,0 +1,2 @@ | |||||
export * from './core'; | |||||
export * from './response'; |
@@ -0,0 +1,9 @@ | |||||
import {HttpResponse, statusCodes} from '../../common'; | |||||
export class ResourceItemFetchedResponse extends HttpResponse(statusCodes.HTTP_STATUS_OK) {} | |||||
export class ResourceCollectionFetchedResponse extends HttpResponse(statusCodes.HTTP_STATUS_OK) {} | |||||
export class DataSourceNotFoundResponseError extends HttpResponse(statusCodes.HTTP_STATUS_INTERNAL_SERVER_ERROR) {} | |||||
export class ItemNotFoundReponseError extends HttpResponse(statusCodes.HTTP_STATUS_NOT_FOUND) {} |
@@ -4,6 +4,7 @@ import { | |||||
afterAll, | afterAll, | ||||
it, | it, | ||||
expect, | expect, | ||||
vi, Mock, | |||||
} from 'vitest'; | } from 'vitest'; | ||||
import { | import { | ||||
@@ -11,18 +12,38 @@ import { | |||||
Endpoint, | Endpoint, | ||||
Operation, | Operation, | ||||
} from '../../src/common'; | } from '../../src/common'; | ||||
import {Server} from '../../src/backend'; | |||||
import {DataSource, DataSourceQuery, EmplaceDetails, Server} from '../../src/backend'; | |||||
import {Client} from '../../src/client'; | import {Client} from '../../src/client'; | ||||
import {server} from '../../src/extenders/http/backend'; | import {server} from '../../src/extenders/http/backend'; | ||||
import {client} from '../../src/extenders/http/client'; | import {client} from '../../src/extenders/http/client'; | ||||
import {composeRecipes} from '../../src/common/recipe'; | import {composeRecipes} from '../../src/common/recipe'; | ||||
import {addResourceRecipe, YesResponse} from '../../src/recipes/resource'; | |||||
import {addResourceRecipe, ResourceItemFetchedResponse} from '../../src/recipes/resource'; | |||||
describe('default', () => { | describe('default', () => { | ||||
let theClient: Client; | let theClient: Client; | ||||
let theServer: Server; | let theServer: Server; | ||||
let theRawEndpoint: Endpoint; | let theRawEndpoint: Endpoint; | ||||
let theOperation: Operation; | let theOperation: Operation; | ||||
let dataSource: Record<keyof DataSource, Mock>; | |||||
beforeAll(() => { | |||||
dataSource = { | |||||
create: vi.fn(async (data) => data), | |||||
getById: vi.fn(async () => ({})), | |||||
delete: vi.fn(), | |||||
emplace: vi.fn(async () => [{}, { isCreated: false }]), | |||||
getMultiple: vi.fn(async () => []), | |||||
getSingle: vi.fn(async () => ({})), | |||||
getTotalCount: vi.fn(async () => 1), | |||||
newId: vi.fn(async () => 1), | |||||
patch: vi.fn(async (id, data) => ({ ...data, id })), | |||||
initialize: vi.fn(async () => {}), | |||||
}; | |||||
}); | |||||
afterAll(() => { | |||||
dataSource.getById.mockReset(); | |||||
}); | |||||
beforeAll(async () => { | beforeAll(async () => { | ||||
const theRawApp = app({ | const theRawApp = app({ | ||||
@@ -34,8 +55,8 @@ describe('default', () => { | |||||
operations, | operations, | ||||
backend: theBackend, | backend: theBackend, | ||||
} = composeRecipes([ | } = composeRecipes([ | ||||
addResourceRecipe({ endpointName: 'users' }), | |||||
addResourceRecipe({ endpointName: 'posts' }) | |||||
addResourceRecipe({ endpointName: 'users', dataSource, }), | |||||
addResourceRecipe({ endpointName: 'posts', dataSource, }) | |||||
])({ | ])({ | ||||
app: theRawApp, | app: theRawApp, | ||||
}); | }); | ||||
@@ -78,9 +99,28 @@ describe('default', () => { | |||||
foo: 'bar', | foo: 'bar', | ||||
})); | })); | ||||
const response = YesResponse.fromFetchResponse(responseRaw); | |||||
const response = ResourceItemFetchedResponse.fromFetchResponse(responseRaw); | |||||
expect(response).toHaveProperty('statusCode', 200); | |||||
expect(response).toHaveProperty('statusMessage', 'Resource Collection Fetched'); | |||||
}); | |||||
it('works for items', async () => { | |||||
// TODO create wrapper for fetch's Response here | |||||
// | |||||
// should we create a helper object to process client-side received response from server's sent response? | |||||
// | |||||
// the motivation is to remove the manual deserialization from the client (provide serialization on the response | |||||
// object so as the client is not limited to .text(), .json(), .arrayBuffer() etc) | |||||
const responseRaw = await theClient | |||||
.at(theRawEndpoint, { resourceId: 3 }) | |||||
.makeRequest(theOperation, new URLSearchParams({ | |||||
foo: 'bar', | |||||
})); | |||||
const response = ResourceItemFetchedResponse.fromFetchResponse(responseRaw); | |||||
expect(response).toHaveProperty('statusCode', 204); | |||||
expect(response).toHaveProperty('statusMessage', 'Yes'); | |||||
expect(response).toHaveProperty('statusCode', 200); | |||||
expect(response).toHaveProperty('statusMessage', 'Resource Item Fetched'); | |||||
}); | }); | ||||
}); | }); |
@@ -2,7 +2,6 @@ import {describe, it, expect, beforeAll, afterAll} from 'vitest'; | |||||
import { | import { | ||||
App, | App, | ||||
app, | app, | ||||
DataSource, | |||||
Endpoint, | Endpoint, | ||||
endpoint, | endpoint, | ||||
Operation, | Operation, | ||||
@@ -10,7 +9,7 @@ import { | |||||
statusCodes, | statusCodes, | ||||
validation as v, | validation as v, | ||||
} from '../src/common'; | } from '../src/common'; | ||||
import {Backend, backend, Server} from '../src/backend'; | |||||
import {Backend, backend, DataSource, Server} from '../src/backend'; | |||||
import {Client} from '../src/client'; | import {Client} from '../src/client'; | ||||
import {server} from '../src/extenders/http/backend'; | import {server} from '../src/extenders/http/backend'; | ||||
import {client} from '../src/extenders/http/client'; | import {client} from '../src/extenders/http/client'; | ||||