diff --git a/packages/core/src/backend/common.ts b/packages/core/src/backend/common.ts index ecb987e..27ca780 100644 --- a/packages/core/src/backend/common.ts +++ b/packages/core/src/backend/common.ts @@ -10,9 +10,10 @@ export interface ImplementationContext { endpoint: Endpoint; params: Record; query?: URLSearchParams; + dataSource?: DataSource; } -type ImplementationFunction = (params: ImplementationContext) => Promise; +export type ImplementationFunction = (params: ImplementationContext) => Promise; export interface Backend { app: App; diff --git a/packages/core/src/extenders/http/backend/core.ts b/packages/core/src/extenders/http/backend/core.ts index 3bb2ee5..f12bca0 100644 --- a/packages/core/src/extenders/http/backend/core.ts +++ b/packages/core/src/extenders/http/backend/core.ts @@ -88,6 +88,7 @@ class ServerInstance implements Server { endpoint, params: endpointParams ?? {}, query: typeof search !== 'undefined' ? new URLSearchParams(search) : undefined, + dataSource: this.backend.dataSource, }); if (typeof responseSpec === 'undefined') { diff --git a/packages/core/src/extenders/http/client.ts b/packages/core/src/extenders/http/client.ts index 4e1d4b5..1e0c841 100644 --- a/packages/core/src/extenders/http/client.ts +++ b/packages/core/src/extenders/http/client.ts @@ -16,6 +16,14 @@ declare module '../../client' { } } +const DEFAULT_HOST = '0.0.0.0' as const; + +const DEFAULT_PORT = 80 as const; + +const DEFAULT_METHOD = 'GET' as const; + +const EXTENSION_METHOD_EFFECTIVE_METHOD = 'POST' as const; + class ClientInstance implements Client { readonly app: App; private readonly fetchFn: typeof fetch; @@ -29,8 +37,8 @@ class ClientInstance implements Client { async connect(params: ServiceParams): Promise { const connection = { - host: params.host ?? '0.0.0.0', - port: params.port ?? 80, + host: params.host ?? DEFAULT_HOST, + port: params.port ?? DEFAULT_PORT, basePath: params.basePath ?? '', }; @@ -52,14 +60,15 @@ class ClientInstance implements Client { makeRequest(operation: Operation) { const baseUrlFragments = [ - this.connection?.host ?? '0.0.0.0' + this.connection?.host ?? DEFAULT_HOST ]; - const thePort = (this.connection?.port ?? 80); - if (thePort !== 80) { + const thePort = (this.connection?.port ?? DEFAULT_PORT); + if (thePort !== DEFAULT_PORT) { baseUrlFragments.push(thePort.toString()); } + // TODO how to set to https? const scheme = 'http'; // todo need a way to decode url back to endpoint queue const urlString = serializeEndpointQueue(this.endpointQueue); @@ -72,9 +81,9 @@ class ClientInstance implements Client { if (typeof operation.searchParams !== 'undefined') { url.search = operation.searchParams.toString(); } - const rawEffectiveMethod = (operation.method ?? 'GET').toUpperCase(); + const rawEffectiveMethod = (operation.method ?? DEFAULT_METHOD).toUpperCase(); const finalEffectiveMethod = (AVAILABLE_EXTENSION_METHODS as unknown as string[]).includes(rawEffectiveMethod) - ? 'POST' as const + ? EXTENSION_METHOD_EFFECTIVE_METHOD : rawEffectiveMethod; if (typeof operation.body !== 'undefined') { diff --git a/packages/core/src/recipes/resource/core.ts b/packages/core/src/recipes/resource/core.ts index f3dc6b8..399331e 100644 --- a/packages/core/src/recipes/resource/core.ts +++ b/packages/core/src/recipes/resource/core.ts @@ -1,12 +1,13 @@ import {Recipe} from '../../common/recipe'; -import {endpoint, operation, validation as v} from '../../common'; +import {endpoint, validation as v} from '../../common'; import {backend, DataSource} from '../../backend'; -import { - DataSourceNotFoundResponseError, - ItemNotFoundReponseError, - ResourceCollectionFetchedResponse, - ResourceItemFetchedResponse, -} from './response'; +import * as fetchOperation from './implementation/fetch'; +import * as createOperation from './implementation/create'; +import * as emplaceOperation from './implementation/emplace'; +import * as patchDeltaOperation from './implementation/patch-delta'; +import * as patchMergeOperation from './implementation/patch-merge'; +import * as queryOperation from './implementation/query'; +import * as deleteOperation from './implementation/delete'; interface AddResourceRecipeParams { endpointName: string; @@ -15,9 +16,13 @@ interface AddResourceRecipeParams { export const addResourceRecipe = (params: AddResourceRecipeParams): Recipe => (a) => { const operations = { - fetch: operation({ - name: 'fetch' as const, - }), + fetch: fetchOperation.operation, + create: createOperation.operation, + emplace: emplaceOperation.operation, + patchMerge: patchMergeOperation.operation, + patchDelta: patchDeltaOperation.operation, + query: queryOperation.operation, + delete: deleteOperation.operation, }; const theEndpoint = endpoint({ @@ -27,10 +32,22 @@ export const addResourceRecipe = (params: AddResourceRecipeParams): Recipe => (a }), }) .param('resourceId') - .can('fetch'); + .can(fetchOperation.name) + .can(createOperation.name) + .can(emplaceOperation.name) + .can(patchMergeOperation.name) + .can(patchDeltaOperation.name) + .can(queryOperation.name) + .can(deleteOperation.name); const enhancedApp = a.app .operation(operations.fetch) + .operation(operations.create) + .operation(operations.emplace) + .operation(operations.patchMerge) + .operation(operations.patchDelta) + .operation(operations.query) + .operation(operations.delete) .endpoint(theEndpoint); const theBackend = a.backend ?? backend({ @@ -39,39 +56,13 @@ export const addResourceRecipe = (params: AddResourceRecipeParams): Recipe => (a }); 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, - }); - }); + .implementOperation(fetchOperation.name, fetchOperation.implementation) + .implementOperation(createOperation.name, createOperation.implementation) + .implementOperation(deleteOperation.name, deleteOperation.implementation) + .implementOperation(queryOperation.name, queryOperation.implementation) + .implementOperation(patchDeltaOperation.name, patchDeltaOperation.implementation) + .implementOperation(patchMergeOperation.name, patchMergeOperation.implementation) + .implementOperation(emplaceOperation.name, emplaceOperation.implementation); return { operations, diff --git a/packages/core/src/recipes/resource/implementation/create.ts b/packages/core/src/recipes/resource/implementation/create.ts new file mode 100644 index 0000000..c4fc692 --- /dev/null +++ b/packages/core/src/recipes/resource/implementation/create.ts @@ -0,0 +1,15 @@ +import {ImplementationFunction} from '../../../backend'; +import {operation as defineOperation} from '../../../common'; + +export const name = 'create' as const; + +export const method = 'POST' as const; + +export const operation = defineOperation({ + name, + method, +}); + +export const implementation: ImplementationFunction = async (ctx) => { + +}; diff --git a/packages/core/src/recipes/resource/implementation/delete.ts b/packages/core/src/recipes/resource/implementation/delete.ts new file mode 100644 index 0000000..1e8c252 --- /dev/null +++ b/packages/core/src/recipes/resource/implementation/delete.ts @@ -0,0 +1,15 @@ +import {ImplementationFunction} from '../../../backend'; +import {operation as defineOperation} from '../../../common'; + +export const name = 'delete' as const; + +export const method = 'DELETE' as const; + +export const operation = defineOperation({ + name, + method, +}); + +export const implementation: ImplementationFunction = async (ctx) => { + +}; diff --git a/packages/core/src/recipes/resource/implementation/emplace.ts b/packages/core/src/recipes/resource/implementation/emplace.ts new file mode 100644 index 0000000..7b09a44 --- /dev/null +++ b/packages/core/src/recipes/resource/implementation/emplace.ts @@ -0,0 +1,15 @@ +import {ImplementationFunction} from '../../../backend'; +import {operation as defineOperation} from '../../../common'; + +export const name = 'emplace' as const; + +export const method = 'PUT' as const; + +export const operation = defineOperation({ + name, + method, +}); + +export const implementation: ImplementationFunction = async (ctx) => { + +}; diff --git a/packages/core/src/recipes/resource/implementation/fetch.ts b/packages/core/src/recipes/resource/implementation/fetch.ts new file mode 100644 index 0000000..8b791f1 --- /dev/null +++ b/packages/core/src/recipes/resource/implementation/fetch.ts @@ -0,0 +1,51 @@ +import {DataSource, ImplementationFunction} from '../../../backend'; +import { + DataSourceNotFoundResponseError, + ItemNotFoundReponseError, + ResourceCollectionFetchedResponse, + ResourceItemFetchedResponse, +} from '../response'; +import {operation as defineOperation} from '../../../common'; + +export const name = 'fetch' as const; + +export const method = 'GET' as const; + +export const operation = defineOperation({ + name, + method, +}); + +export const implementation: ImplementationFunction = async (ctx) => { + const dataSource: DataSource = ctx.endpoint.dataSource ?? ctx.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, + }); +}; diff --git a/packages/core/src/recipes/resource/implementation/patch-delta.ts b/packages/core/src/recipes/resource/implementation/patch-delta.ts new file mode 100644 index 0000000..2c5e9c0 --- /dev/null +++ b/packages/core/src/recipes/resource/implementation/patch-delta.ts @@ -0,0 +1,15 @@ +import {ImplementationFunction} from '../../../backend'; +import {operation as defineOperation} from '../../../common'; + +export const name = 'patchDelta' as const; + +export const method = 'PATCH' as const; + +export const operation = defineOperation({ + name, + method, +}); + +export const implementation: ImplementationFunction = async (ctx) => { + +}; diff --git a/packages/core/src/recipes/resource/implementation/patch-merge.ts b/packages/core/src/recipes/resource/implementation/patch-merge.ts new file mode 100644 index 0000000..c0dc79d --- /dev/null +++ b/packages/core/src/recipes/resource/implementation/patch-merge.ts @@ -0,0 +1,15 @@ +import {ImplementationFunction} from '../../../backend'; +import {operation as defineOperation} from '../../../common'; + +export const name = 'patchMerge' as const; + +export const method = 'PATCH' as const; + +export const operation = defineOperation({ + name, + method, +}); + +export const implementation: ImplementationFunction = async (ctx) => { + +}; diff --git a/packages/core/src/recipes/resource/implementation/query.ts b/packages/core/src/recipes/resource/implementation/query.ts new file mode 100644 index 0000000..e3c63eb --- /dev/null +++ b/packages/core/src/recipes/resource/implementation/query.ts @@ -0,0 +1,15 @@ +import {ImplementationFunction} from '../../../backend'; +import {operation as defineOperation} from '../../../common'; + +export const name = 'query' as const; + +export const method = 'QUERY' as const; + +export const operation = defineOperation({ + name, + method, +}); + +export const implementation: ImplementationFunction = async (ctx) => { + +};