Skip to content

Commit

Permalink
Merge pull request #13 from qwacko/dev
Browse files Browse the repository at this point in the history
Added Page Info Store For Better Approach In The Frontend
  • Loading branch information
qwacko authored Sep 15, 2023
2 parents 62d979b + b3c1d29 commit da03af3
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 4 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ pnpm add skroutes
- Validation of route parameters and search parameters.
- Fully typed params and search params for use throughout the application.
- Use of nested search pararms.
- Typescript validation of URL addresses (changing URLs will cause typescript errors)
- Typescript validation of URL addresses (changing URLs will cause typescript errors).
- Ability to access params and searchParams as a store, with automatic navigation (with debouncing)

## Usage

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "skroutes",
"version": "0.0.8",
"version": "0.0.9",
"scripts": {
"dev": "vite dev",
"build": "vite build && npm run package",
Expand Down
77 changes: 76 additions & 1 deletion src/lib/skRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { isArray, merge, mergeWith } from 'lodash-es';
import { getUrlParams, objectToSearchParams } from './helpers.js';
import type { Readable } from 'svelte/store';
import { writable, get } from 'svelte/store';

// Define the types for the route configuration
type ValidationFunction<T> = (input: T) => T;
Expand Down Expand Up @@ -145,6 +147,79 @@ export function skRoutes<Config extends RouteConfig>({
}
};

const pageInfoStore = <
Address extends keyof Config,
PageInfo extends { params: Record<string, string>; url: { search: string } }
>({
routeId,
pageInfo,
updateDelay = 1000, // Default to 1 second if not provided
onUpdate
}: {
routeId: Address;
pageInfo: Readable<PageInfo>;
updateDelay?: number;
onUpdate: (newUrl: string) => unknown;
}) => {
let timeoutId: NodeJS.Timeout | null = null;
const initialData = get(pageInfo);
//@ts-expect-error This has uncertainty about what should be available
const initialURLData = urlGenerator({
address: routeId,
paramsValue: initialData.params,
searchParamsValue: getUrlParams(initialData.url.search)
});

const store = writable({
params: initialURLData.params,
searchParams: initialURLData.searchParams
});

const originalSubscribe = store.subscribe;

store.subscribe = (run, invalidate) => {
const unsubscribeFromPageInfo = pageInfo.subscribe((data) => {
//@ts-expect-error This has uncertainty about what should be available
const current = urlGenerator({
address: routeId,
paramsValue: data.params,
searchParamsValue: getUrlParams(data.url.search)
});

store.set({
params: current.params,
searchParams: current.searchParams
});
});

const unsubscribeFromStore = originalSubscribe((updatedData) => {
run(updatedData); // Call the original subscriber

if (timeoutId) {
clearTimeout(timeoutId);
}

timeoutId = setTimeout(() => {
//@ts-expect-error This has uncertainty about what should be available
const generatedUrl = urlGenerator({
address: routeId,
paramsValue: updatedData.params,
searchParamsValue: updatedData.searchParams
});

onUpdate(generatedUrl.url);
}, updateDelay);
}, invalidate);

return () => {
unsubscribeFromStore();
unsubscribeFromPageInfo();
};
};

return store;
};

const pageInfo = <
Address extends keyof Config,
PageInfo extends { params: Record<string, string>; url: { search: string } }
Expand Down Expand Up @@ -229,5 +304,5 @@ export function skRoutes<Config extends RouteConfig>({
return { current, updateParams };
};

return { urlGenerator, pageInfo, serverPageInfo };
return { urlGenerator, pageInfo, serverPageInfo, pageInfoStore };
}
14 changes: 13 additions & 1 deletion src/routes/routeConfig.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { skRoutes } from '$lib/skRoutes.js';
import { z } from 'zod';

export const { pageInfo, urlGenerator, serverPageInfo } = skRoutes({
export const { pageInfo, urlGenerator, serverPageInfo, pageInfoStore } = skRoutes({
config: {
'/[id]': {
paramsValidation: z.object({ id: z.string() }).parse
},
'/server/[id]': {
paramsValidation: z.object({ id: z.string() }).parse,
searchParamsValidation: z.object({ data: z.string().optional() }).parse
},
'/store/[id]/': {
paramsValidation: z.object({ id: z.string() }).parse,
searchParamsValidation: z
.object({
topLevel: z.string().optional(),
nested: z
.object({ item1: z.string().optional(), item2: z.string().optional() })
.optional()
})
.optional()
.catch({}).parse
}
},
errorURL: '/error'
Expand Down
38 changes: 38 additions & 0 deletions src/routes/store/[id]/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { objectToSearchParams } from '$lib/helpers.js';
import { set, update } from 'lodash-es';
import { pageInfoStore } from '../../routeConfig.js';
const pageStore = pageInfoStore({
routeId: '/store/[id]/',
pageInfo: page,
updateDelay: 500,
onUpdate: (newURL) => (browser ? goto(newURL) : undefined)
});
</script>

{#if $pageStore.params}
<label for="topLevel">ID</label>
<input id="topLevel" type="string" bind:value={$pageStore.params.id} />
{/if}

{#if $pageStore.searchParams}
<label for="topLevel">item1</label>
<input
id="topLevel"
type="string"
value={$pageStore.searchParams?.nested?.item1}
on:input={(newValue) => {
if (newValue.target && 'value' in newValue.target) {
const newString = newValue.target.value;
const newObject = set($pageStore, 'searchParams.nested.item1', newString);
$pageStore = newObject;
}
}}
/>
{/if}

<pre>{JSON.stringify($pageStore, null, 2)}</pre>

0 comments on commit da03af3

Please sign in to comment.