Skip to content

Commit

Permalink
cache feature and plugin support
Browse files Browse the repository at this point in the history
  • Loading branch information
anasmohammed361 committed Dec 17, 2023
1 parent 088f83d commit 48e7869
Show file tree
Hide file tree
Showing 34 changed files with 816 additions and 89 deletions.
2 changes: 1 addition & 1 deletion examples/complex/src/lib/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import { router } from "./router";


export const { serverHook, client } =
createRESTInterface<typeof router>(router);
createRESTInterface<typeof router>(router);
38 changes: 18 additions & 20 deletions package/src/lib/components/SvelteKitRest.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import type { z } from 'zod';
import type { Route, RouteMethod, Params, TContext, CombineTypes } from './types.js';
import type { RequestEvent } from '@sveltejs/kit';
type Options = {
contentType: string;
headers: {
client: Record<string, string>;
server: Record<string, string>;
};
};
import type { Route, Params, TContext, CombineTypes,Options } from './types.js';

class SvelteKitREST<Ttop = undefined> {
public get;
public post;
Expand Down Expand Up @@ -37,52 +30,57 @@ class SvelteKitREST<Ttop = undefined> {
return this.getRouter<U>(inp);
}

middleware<U>(func: (inp: { context: TContext<Ttop> }) => U) {
middleware<U extends Record<string,any>>(func: (inp: { context: TContext<Ttop> }) => U) {
const middlewares = this.middlewares;
return SvelteKitREST.getMiddlewareInstance<CombineTypes<TContext<Ttop>,Awaited<U>>>(middlewares, func);
}

private getRouter<T>(schema?: z.ZodSchema<T>) {
return {
get: <U>(cb: (inp: Params<T, Ttop>) => U): Route<T, U, Ttop> => {
get: <U>(cb: (inp: Params<T, Ttop>) => U,options?:Partial<Options>): Route<T, U, Ttop> => {
return {
method: 'GET',
cb,
schema: schema,
middlewares:this.middlewares
middlewares:this.middlewares,
options
};
},

post: <U>(cb: (inp: Params<T, Ttop>) => U): Route<T, U, Ttop> => {
post: <U>(cb: (inp: Params<T, Ttop>) => U,options?:Partial<Options>): Route<T, U, Ttop> => {
return {
method: 'POST',
cb,
schema: schema,
middlewares:this.middlewares
middlewares:this.middlewares,
options
};
},
put: <U>(cb: (inp: Params<T, Ttop>) => U): Route<T, U, Ttop> => {
put: <U>(cb: (inp: Params<T, Ttop>) => U,options?:Partial<Options>): Route<T, U, Ttop> => {
return {
method: 'PUT',
cb,
schema: schema,
middlewares:this.middlewares
middlewares:this.middlewares,
options
};
},
patch: <U>(cb: (inp: Params<T, Ttop>) => U): Route<T, U, Ttop> => {
patch: <U>(cb: (inp: Params<T, Ttop>) => U,options?:Partial<Options>): Route<T, U, Ttop> => {
return {
method: 'PATCH',
cb,
schema: schema,
middlewares:this.middlewares
middlewares:this.middlewares,
options
};
},
delete: <U>(cb: (inp: Params<T, Ttop>) => U): Route<T, U, Ttop> => {
delete: <U>(cb: (inp: Params<T, Ttop>) => U,options?:Partial<Options>): Route<T, U, Ttop> => {
return {
method: 'DELETE',
cb,
schema: schema,
middlewares:this.middlewares
middlewares:this.middlewares,
options
};
}
};
Expand Down
25 changes: 9 additions & 16 deletions package/src/lib/components/interface/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import type { RequestEvent } from '@sveltejs/kit';
import type { Context, SingleOrMultipleRoutes } from '../types.js';
import type { Context, RESTInterfaceOptions, SingleOrMultipleRoutes } from '../types.js';
import { createClient } from './client.js';
import { createServerHandle } from './server.js';


/**
* @param {Record<string, SingleOrMultipleRoutes>} input
* @param {{
* createContext?: Context<any>;
* routePrefiex?: `/${string}`
* }}
* @returns {{client: any;serverHook: any;}}
*/
export function createRESTInterface<T>(input: Record<string,SingleOrMultipleRoutes>,options:{ createContext?:Context<any> ,
/** Default to /api */
routePrefiex?: `/${string}`}={}) {
export function createRESTInterface<T>(
input: Record<string, SingleOrMultipleRoutes>,
options?: RESTInterfaceOptions
) {
options = options ? options : {};
if (!options.routePrefiex) {
options.routePrefiex = "/api"
options.routePrefiex = '/api';
}
return {
client: createClient<T>(input, options.routePrefiex),
serverHook: createServerHandle<any>(input,options.routePrefiex,options.createContext) // createContext makes user to use db on routes.
serverHook: createServerHandle<T>(input, options.routePrefiex, options.createContext,options.cacheContext) // createContext makes user to use db on routes.
};
}
}
69 changes: 44 additions & 25 deletions package/src/lib/components/interface/server.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
import { json, type Handle, error, type RequestEvent } from '@sveltejs/kit';
import type { SingleOrMultipleRoutes, Route, Context } from '../types.js';
import type { SingleOrMultipleRoutes, Route, Context, Options } from '../types.js';
import { handleCacheControl } from '../options.js';

export function createServerHandle<T>(
input: Record<string, SingleOrMultipleRoutes>,
routePrefiex: `/${string}`,
createContext?: Context<T>
createContext?: Context<T>,
cacheContext: boolean = false
): Handle {
// Regular Context with no cache
const createNonCachedContext = () => {
return async (event: RequestEvent) => {
const context = createContext ? await createContext(event) : undefined;
return context;
};
};
/* Caching the createContext might be good idea to avoid called db instances upon every request*/

const createCachedContext = () => {
let cachedContext: T | undefined;

return async (event: RequestEvent) => {
if (!cachedContext) {
cachedContext = createContext ? await createContext(event) : undefined;
if (cachedContext instanceof Promise) {
console.log('Context is promise type');
cachedContext
.then((val) => {
cachedContext = val;
})
.catch((err) => console.log(err));
}
cachedContext = createContext ? await createContext(event) : undefined;
}
return cachedContext;
};
};

const getContext = createCachedContext();
const getContext = cacheContext ? createCachedContext() : createNonCachedContext();

return async ({ event, resolve }) => {
const url = event.url.pathname;
Expand All @@ -45,15 +44,25 @@ export function createServerHandle<T>(
} else {
data = await event.request.json();
}

const parsedData = currentRouteObject.schema?.parse(data);
const cachedContext = getContext ? await getContext(event) : undefined; // cachedContext
const context = cachedContext ? cachedContext : { event }
const middlewaredContext =await handleMiddlewares(context,currentRouteObject.middlewares)
const obtainedContext = await getContext(event); // cachedContext
const context = obtainedContext ? obtainedContext : { event };
const middlewaredContext = await handleMiddlewares(context, currentRouteObject.middlewares);
const result = await currentRouteObject.cb({
input: parsedData,
context: middlewaredContext
});
return json({ output: result });
//handling custom headers and options
const headers = handleOptions(currentRouteObject.options);

//Return json
return json(
{ output: result },
{
headers
}
);
} else {
throw error(405);
}
Expand All @@ -76,18 +85,28 @@ function getCurrentObject(obj: Record<string, SingleOrMultipleRoutes>, keys: str
return undefined;
}
}
if (currentObj && 'cb' in currentObj && 'method' in currentObj) {
if (currentObj && 'cb' in currentObj && 'method' in currentObj && 'schema' in currentObj) {
return currentObj as Route<any, any, any>;
} else {
return undefined;
}
}

async function handleMiddlewares(currentContext:any,middlewares:((...inp:any)=>any)[]){
let context = {...currentContext}
async function handleMiddlewares(currentContext: any, middlewares: ((...inp: any) => any)[]) {
let context = { ...currentContext };
for (const middleware of middlewares) {
const result = await middleware({context})
context = {...context,...result}
const result = await middleware({ context });
context = { ...context, ...result };
}
return context;
}

function handleOptions(options?: Partial<Options>):Record<string,any> {
let cacheControl: string = '';
if (options?.cacheControl) {
cacheControl = `max-age=${handleCacheControl(options.cacheControl)}`;
}
return context
}
const headers = options?.responseHeaders ? options.responseHeaders : {};
const cacheHeaders = cacheControl? { 'Cache-Control': cacheControl } : {}
return { ...headers,...cacheHeaders};
}
27 changes: 27 additions & 0 deletions package/src/lib/components/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@


export function handleCacheControl(input:`${number}s` | `${number}h` | `${number}m` | `${number}d` ) {
const regex = /^(\d+)([smhd])$/; // Regex to match patterns like '2s', '4h', '5m', '2d'
const matches = input.match(regex);

if (matches) {
const value = parseInt(matches[1], 10); // Extract the numeric value
const unit = matches[2]; // Extract the unit

switch (unit) {
case 's':
return value; // Seconds
case 'm':
return value * 60; // Minutes to Seconds
case 'h':
return value * 60 * 60; // Hours to Seconds
case 'd':
return value * 24 * 60 * 60; // Days to Seconds
default:
throw new Error("Invalid Value for Cache Control , please use 1s or 1m or 1h or 1d ");
// Return the original string if the unit is not recognized
}
} else {
throw new Error("Invalid Value for Cache Control , please use 1s or 1m or 1h or 1d ");; // Return the original string if the input format doesn't match
}
}
21 changes: 18 additions & 3 deletions package/src/lib/components/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { RequestEvent } from '@sveltejs/kit';
import type { ResolvedUrl } from 'vite';
import type { z } from 'zod';

type TContext<T> = T extends undefined ? {event:RequestEvent} : ContextReturnType<T>;
type TContext<T> = T extends undefined ? { event: RequestEvent } : ContextReturnType<T>;

type Params<T, U> = T extends undefined
? { context: TContext<U> }
Expand All @@ -11,7 +11,8 @@ type Route<T, U, Ttop> = {
method: string;
cb: (inp: Params<T, Ttop>) => U;
schema: z.ZodSchema | undefined;
middlewares:((...inp:any)=>any)[]
middlewares: ((...inp: any) => any)[];
options?: Partial<Options>;
};

type SingleOrMultipleRoutes = Route<any, any, any> | Record<string, Route>;
Expand All @@ -34,4 +35,18 @@ type ContextReturnType<T> = T extends (...args: any[]) => infer R ? Awaited<R> :

type CombineTypes<A, B> = {
[K in keyof A]: K extends keyof B ? B[K] : A[K];
} & B;
} & B;

type Options = {
responseHeaders: {
[key: string]: string;
};
} & {
cacheControl: `${number}s` | `${number}h` | `${number}m` | `${number}d`;
};

type RESTInterfaceOptions = Partial<{
createContext: Context<any>;
routePrefiex: `/${string}`;
cacheContext: boolean;
}>;
Empty file added plugins/README.md
Empty file.
11 changes: 11 additions & 0 deletions plugins/svelte-query-plugin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.DS_Store
node_modules
/build
/dist
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
1 change: 1 addition & 0 deletions plugins/svelte-query-plugin/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict=true
58 changes: 58 additions & 0 deletions plugins/svelte-query-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# create-svelte

Everything you need to build a Svelte library, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).

Read more about creating a library [in the docs](https://kit.svelte.dev/docs/packaging).

## Creating a project

If you're seeing this, you've probably already done this step. Congrats!

```bash
# create a new project in the current directory
npm create svelte@latest

# create a new project in my-app
npm create svelte@latest my-app
```

## Developing

Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:

```bash
npm run dev

# or start the server and open the app in a new browser tab
npm run dev -- --open
```

Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.

## Building

To build your library:

```bash
npm run package
```

To create a production version of your showcase app:

```bash
npm run build
```

You can preview the production build with `npm run preview`.

> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
## Publishing

Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).

To publish your library to [npm](https://www.npmjs.com):

```bash
npm publish
```
Loading

0 comments on commit 48e7869

Please sign in to comment.