Inga 🏳‍🌈 10 months ago
parent 847abe3d99
commit 47bc8b1672
  1. 22
      sitemap-parser/src/createSitemap.spec.ts
  2. 31
      sitemap-parser/src/createSitemap.ts
  3. 13
      sitemap-parser/src/resolveByPath.ts
  4. 29
      sitemap-parser/src/types.ts

@ -1,5 +1,5 @@
import { createSitemap } from './createSitemap'; import { TypedSitemap, createSitemap } from './createSitemap';
import { ApiResponse, Sitemap } from './types'; import { ApiResponse, PathsResolver, Paths, ResolverForPaths, Sitemap, Simplify } from './types';
describe('createSitemap', () => { describe('createSitemap', () => {
it('creates correct sitemap for sample data', () => { it('creates correct sitemap for sample data', () => {
@ -16,6 +16,24 @@ describe('createSitemap', () => {
{ id: 10, slug: 'children', parent: 8 }, { id: 10, slug: 'children', parent: 8 },
] as const satisfies ApiResponse; ] as const satisfies ApiResponse;
const x: TypedSitemap<typeof sampleInput> = null as any;
const y: Simplify<Paths<typeof x>> = null as any;
if (y[0] === '/') {
let f = y[1]
}
const z: ResolverForPaths<typeof y> = null as any;
const a = z('/books/children')
const b = z('huihuoj');
const z2: PathsResolver<typeof x> = null as any;
const m = z2('/');
//const c = z2('');
const d = z2('abc');
const e = z2('/');
//const z: Sitemap = x;
const f = sampleInput as ApiResponse;
const g: TypedSitemap<ApiResponse> = null as any;
const sampleOutput = { const sampleOutput = {
name: 'root', name: 'root',
id: 1, id: 1,

@ -1,4 +1,4 @@
import { ApiResponse, Sitemap } from './types'; import { ApiResponse, Sitemap, Simplify } from './types';
type Subtree = { type Subtree = {
name: string; name: string;
@ -6,10 +6,35 @@ type Subtree = {
children: Sitemap[]; children: Sitemap[];
}; };
type GetBaseParentIdType<TParentId extends number | null> = TParentId extends null ? null : number;
type ExtractEntryByParentId<TEntry extends ApiResponse[number], TParentId extends number | null> =
//Extract<TEntry, { readonly parent: TParentId }>;
Simplify<{ readonly parent: GetBaseParentIdType<TParentId> } extends Pick<TEntry, 'parent'> ? TEntry : Extract<TEntry, { readonly parent: TParentId }>>;
type ExtractEntriesByParentId<TApiResponse extends ApiResponse, TParentId extends number | null> = ExtractEntryByParentId<TApiResponse[number], TParentId>;
type TypedSitemapEntry<TApiResponse extends ApiResponse, TEntry extends TApiResponse[number]> = TEntry extends unknown ? {
name: TEntry['slug'],
id: TEntry['id'],
children: TypedSitemapEntries<TApiResponse, TEntry['id']>
} : never;
type TypedSitemapEntries<TApiResponse extends ApiResponse, TParentId extends number | null> = TypedSitemapEntry<TApiResponse, ExtractEntriesByParentId<TApiResponse, TParentId>>[];
//type NeverToNull<TSitemap> = TSitemap extends never ? null : TSitemap;
//export type TypedSitemap<TApiResponse extends ApiResponse> = Simplify<NeverToNull<Simplify<TypedSitemapEntries<TApiResponse, null>[number]>>>;
export type TypedSitemap<TApiResponse extends ApiResponse> = Simplify<TypedSitemapEntries<TApiResponse, null>[number]>;
//const z = [{slug: 'a', id: 1, parent: 1 }];
//type Q = NeverToNull<TypedSitemap<typeof z>>;
//type A = never extends never ? 'a' : 'b';
// alternatively, for full TS experience, if I had more time I'd also implement parsing in TS so that this declaration would look like: // alternatively, for full TS experience, if I had more time I'd also implement parsing in TS so that this declaration would look like:
// export const createSitemap = <TResponse extends ApiResponse>(apiResponse: TResponse): Sitemap<TResponse> => { // export const createSitemap = <TResponse extends ApiResponse>(apiResponse: TResponse): Sitemap<TResponse> => {
// and calling e.g. `createSitemap(const sample data json)` would return value with type `const sample sitemap`, rather than just general sitemap // and calling e.g. `createSitemap(const sample data json)` would return value with type `const sample sitemap`, rather than just general sitemap
export const createSitemap = (apiResponse: ApiResponse): Sitemap | null => { export const createSitemap = <TResponse extends ApiResponse>(apiResponse: ApiResponse): TypedSitemap<TResponse> => {
const subtrees = new Map<number, Subtree>( const subtrees = new Map<number, Subtree>(
apiResponse.map(({ id, slug }) => [ apiResponse.map(({ id, slug }) => [
id, id,
@ -36,5 +61,5 @@ export const createSitemap = (apiResponse: ApiResponse): Sitemap | null => {
} }
} }
return root; return root as any;
}; };

@ -16,19 +16,20 @@ import { Sitemap } from './types';
); );
};*/ };*/
const createPaths = (node: Sitemap): Readonly<[string, number]>[] => [ const createPaths = (
['', node.id], node: Sitemap,
prefix: string,
): Readonly<[string, number]>[] => [
[prefix, node.id],
...node.children.flatMap((childNode) => ...node.children.flatMap((childNode) =>
createPaths(childNode).map( createPaths(childNode, `${prefix}/${childNode.name}`),
([path, id]) => [`/${childNode.name}${path}`, id] as const,
),
), ),
]; ];
// Second, improved implementation, where we build a static hashmap from complete paths to IDs once, // Second, improved implementation, where we build a static hashmap from complete paths to IDs once,
// and then use it as a single lookup table. // and then use it as a single lookup table.
export const createPathResolver = (sitemap: Sitemap | null) => { export const createPathResolver = (sitemap: Sitemap | null) => {
const rawPathsData = sitemap ? createPaths(sitemap) : []; const rawPathsData = sitemap ? createPaths(sitemap, '') : [];
const pathsMap = new Map( const pathsMap = new Map(
rawPathsData.map(([path, id]) => [path.length ? path : '/', id]), rawPathsData.map(([path, id]) => [path.length ? path : '/', id]),
); );

@ -7,7 +7,30 @@ export type ApiResponse = Readonly<
>; >;
export type Sitemap = Readonly<{ export type Sitemap = Readonly<{
name: string; readonly name: string;
id: number; readonly id: number;
children: readonly Sitemap[]; readonly children: readonly Sitemap[];
}>; }>;
export type Simplify<T> = T extends Record<string, unknown> | unknown[]
? T extends infer O
? { [K in keyof O]: Simplify<O[K]> }
: never
: T;
type PathsForEntry<TEntry extends Sitemap, TPrefix extends string> = TEntry extends unknown ? [`${TPrefix}/${TEntry['name']}`, TEntry['id']] | PathsForEntry<TEntry['children'][number], `${TPrefix}/${TEntry['name']}`> : never;
export type Paths<TSitemap extends Sitemap> = Simplify<PathsForEntry<TSitemap['children'][number], ''> | ['/', TSitemap['id']]>;
type Mew<T> = T extends readonly [string, number] ? T : never;
type ResetStack<T> = T extends infer R ? R & readonly [string, number] : never;
export type ResolverForPaths<TPaths extends readonly [string, number]> = {
<TPath extends TPaths[0]>(path: TPath): Extract<TPaths, readonly [TPath, number]>[1];
(unknownPath: string): null;
};
export type PathsResolver<TSitemap extends Sitemap> = ResolverForPaths<Extract<Paths<TSitemap> extends infer R ? R : never, readonly [string, number]>>;
//export type PathsResolver<TSitemap extends Sitemap> = ResolverForPaths<Paths<TSitemap>>;

Loading…
Cancel
Save