From 25d49018c06c876211b098d8e5f3c8c6f752c9be Mon Sep 17 00:00:00 2001 From: GenaRazmakhnin Date: Thu, 7 Nov 2024 19:08:57 +0100 Subject: [PATCH] feat(typescript): add .where() for conditional methods --- resources/sdk/typescript/index.ts | 221 ++++++++++++++---------- resources/sdk/typescript/types/index.ts | 2 +- 2 files changed, 130 insertions(+), 93 deletions(-) diff --git a/resources/sdk/typescript/index.ts b/resources/sdk/typescript/index.ts index c52f217..b5772cb 100644 --- a/resources/sdk/typescript/index.ts +++ b/resources/sdk/typescript/index.ts @@ -89,14 +89,13 @@ export type CreateQueryBody = { "count-query": string; }; -type Link = { relation: string; url: string }; - +// TODO: change to FHIR Bundle export type BaseResponseResources = { meta: { versionId: string }; type: string; resourceType: string; total: number; - link: Link[]; + link: { relation: string; url: string }[]; entry?: { resource: ResourceTypeMap[T] }[]; "query-timeout": number; "query-time": number; @@ -581,6 +580,8 @@ const resourceOwnerLogout = auth.storage.set(undefined); }; +export type ProcessableBundle = Omit & { type: 'batch' | 'transaction' } + export type BasicAuthorization = { method: "basic"; credentials: { username: string; password: string }; @@ -668,97 +669,85 @@ export class Client { head: request(this.client.head), }); - - - // request = (params: RequestWithData | RequestWithoutData) => { - - // } - - private search = (resourceName: T) => { - return new GetResources(this.client, resourceName); + return new GetResources((searchParams: URLSearchParams) => { + return this.client.get(buildResourceUrl(resourceName), { searchParams }) + }, resourceName); } resource = { - processBundle: (data: ResourceTypeMap['Bundle']): Promise => { + processBundle: (data: ProcessableBundle): Promise => { return this.client .post(buildResourceUrl(''), { json: data }) .json(); }, conditionalUpdate: ( resourceName: T, - search: string, input: PartialResourceBody ) => { - return this.client - .patch(`${buildResourceUrl(resourceName)}?${search}`, { json: input }) - .json>(); + return new FindBeforeAction((searchParams: URLSearchParams) => { + return this.client.patch(buildResourceUrl(resourceName), { json: input, searchParams }) + }, resourceName) }, conditionalCreate: ( resourceName: T, - search: string, input: PartialResourceBody ) => { - return this.client - .post(`${buildResourceUrl(resourceName)}?${search}`, { json: input }) - .json>(); + return new FindBeforeAction((searchParams: URLSearchParams) => { + return this.client.post(buildResourceUrl(resourceName), { json: input, searchParams }) + }, resourceName) }, conditionalOverride: ( resourceName: T, - search: string, input: PartialResourceBody ) => { - return this.client - .put(`${buildResourceUrl(resourceName)}?${search}`, { json: input }) - .json>(); + return new FindBeforeAction((searchParams: URLSearchParams) => { + return this.client.put(buildResourceUrl(resourceName), { json: input, searchParams }) + }, resourceName) }, - conditionalDelete: ( - resourceName: T, - search: string - ) => { - return this.client - .delete(`${buildResourceUrl(resourceName)}?${search}`) - .json>(); + // TODO: no body in response if resource doesn't exist -> lead to parsing error + conditionalDelete: (resourceName: T) => { + return new FindBeforeAction((searchParams: URLSearchParams) => { + return this.client.delete(buildResourceUrl(resourceName), { searchParams }) + }, resourceName) }, search: this.search, list: this.search, - get: async ( - resourceName: T, - id: string, - ): Promise> => { + get: async (resourceName: T, id: string): Promise => { return this.client .get(buildResourceUrl(resourceName, id)) - .json>(); + .json(); }, - delete: ( - resourceName: T, - id: string, - ) => { + delete: (resourceName: T, id: string): Promise => { return this.client - .delete(buildResourceUrl(resourceName, id)); + .delete(buildResourceUrl(resourceName, id)) + .json(); }, update: ( resourceName: T, id: string, input: PartialResourceBody, - ) => { + ): Promise => { return this.client - .patch(buildResourceUrl(resourceName, id), { json: input }); + .patch(buildResourceUrl(resourceName, id), { json: input }) + .json(); }, create: ( resourceName: T, input: SetOptional, ) => { return this.client - .post(buildResourceUrl(resourceName), { json: input }); + .post(buildResourceUrl(resourceName), { json: input }) + .json(); }, override: ( resourceName: T, id: string, input: PartialResourceBody, - ) => { + ): Promise => { return this.client - .put(buildResourceUrl(resourceName, id), { json: input }); + .put(buildResourceUrl(resourceName, id), { json: input }) + .json(); }, }; @@ -827,19 +816,16 @@ export class Client { } } -export class GetResources< - T extends keyof ResourceTypeMap, - R extends ResourceTypeMap[T], -> implements PromiseLike> +export class GetResources implements PromiseLike> { - private searchParamsObject: URLSearchParams; + protected searchParamsObject: URLSearchParams; resourceName: T; - client: HttpClientInstance; + fun: (params: URLSearchParams) => ResponsePromise; - constructor(client: HttpClientInstance, resourceName: T) { + constructor(fun: (params: URLSearchParams) => ResponsePromise, resourceName: T) { this.searchParamsObject = new URLSearchParams(); this.resourceName = resourceName; - this.client = client; + this.fun = fun; } where< @@ -857,32 +843,40 @@ export class GetResources< where< K extends keyof SearchParams[T], SP extends SearchParams[T][K], - PR extends SP extends number ? Prefix : never, >(key: K | string, value: SP | SP[], prefix?: Prefix | never): this { - if (Array.isArray(value)) { - const val = value as SP[]; - if (prefix) { - if (prefix === "eq") { - this.searchParamsObject.append(key.toString(), val.join(",")); - return this; - } - - val.forEach((item) => { - this.searchParamsObject.append(key.toString(), `${prefix}${item}`); - }); - - return this; - } - - const queryValues = val.join(","); - this.searchParamsObject.append(key.toString(), queryValues); + if (!Array.isArray(value)) { + this.searchParamsObject.append(key.toString(), `${prefix || ''}${value}`); + return this; + } + if (prefix && prefix === 'eq') { + this.searchParamsObject.append(key.toString(), value.join(',')); return this; } - const queryValue = `${prefix ?? ""}${value}`; + + if (prefix) { + value.forEach((item) => this.searchParamsObject.append(key.toString(), `${prefix}${item}`)); + return this; + } + + this.searchParamsObject.append(key.toString(), value.join(',')); + return this; + } - this.searchParamsObject.append(key.toString(), queryValue); - return this; + async then, TResult2 = never>( + onfulfilled?: ((value: BaseResponseResources) => PromiseLike | TResult1), + onrejected?: ((reason: unknown) => PromiseLike | TResult2) + ): Promise { + return this.fun(this.searchParamsObject) + .then((response) => { + return onfulfilled + ? response.json>().then((data) => onfulfilled(data)) + : response.json(); + }, onrejected); + } + + async catch(onRejected: (reason: unknown) => Promise) { + return this.then(undefined, onRejected); } contained( @@ -910,7 +904,7 @@ export class GetResources< return this; } - elements(args: ElementsParams) { + elements(args: ElementsParams) { const queryValue = args.join(","); this.searchParamsObject.set("_elements", queryValue); @@ -943,27 +937,70 @@ export class GetResources< return this; } +} - then, TResult2 = never>( - onfulfilled?: - | ((value: BaseResponseResources) => PromiseLike | TResult1) - | undefined - | null, - onrejected?: - | ((reason: unknown) => PromiseLike | TResult2) - | undefined - | null, - ): PromiseLike { - return this.client - .get(buildResourceUrl(this.resourceName), { - searchParams: this.searchParamsObject, - }) +export class FindBeforeAction implements PromiseLike> +{ + protected searchParamsObject: URLSearchParams; + resourceName: T; + fun: (params: URLSearchParams) => ResponsePromise; + + constructor(fun: (params: URLSearchParams) => ResponsePromise, resourceName: T) { + this.searchParamsObject = new URLSearchParams(); + this.resourceName = resourceName; + this.fun = fun; + } + + where< + K extends keyof SearchParams[T], + SP extends SearchParams[T][K], + PR extends PrefixWithArray, + >(key: K | string, value: SP | SP[], prefix?: PR): this; + + where< + K extends keyof SearchParams[T], + SP extends SearchParams[T][K], + PR extends Exclude, + >(key: K | string, value: SP, prefix?: PR): this; + + where< + K extends keyof SearchParams[T], + SP extends SearchParams[T][K], + >(key: K | string, value: SP | SP[], prefix?: Prefix | never): this { + if (!Array.isArray(value)) { + this.searchParamsObject.append(key.toString(), `${prefix || ''}${value}`); + return this; + } + + if (prefix && prefix === 'eq') { + this.searchParamsObject.append(key.toString(), value.join(',')); + return this; + } + + if (prefix) { + value.forEach((item) => this.searchParamsObject.append(key.toString(), `${prefix}${item}`)); + return this; + } + + this.searchParamsObject.append(key.toString(), value.join(',')); + return this; + } + + async then, TResult2 = never>( + onfulfilled?: ((value: BaseResponseResource) => PromiseLike | TResult1), + onrejected?: ((reason: unknown) => PromiseLike | TResult2) + ): Promise { + return this.fun(this.searchParamsObject) .then((response) => { return onfulfilled - ? response.json>().then((data) => onfulfilled(data)) - : (response.json() as TResult1); + ? response.json>().then((data) => onfulfilled(data)) + : response.json(); }, onrejected); } + + async catch(onRejected: (reason: unknown) => Promise) { + return this.then(undefined, onRejected); + } } type EventType = diff --git a/resources/sdk/typescript/types/index.ts b/resources/sdk/typescript/types/index.ts index 3e30cd9..2a90f16 100644 --- a/resources/sdk/typescript/types/index.ts +++ b/resources/sdk/typescript/types/index.ts @@ -14,7 +14,7 @@ export interface SubsSubscription { export interface ResourceTypeMap { 'placeholder-1': object; 'placeholder-2': object; - "Bundle": object, + "Bundle": { entry?: [] }, "Patient": object }