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 { ApiResponse, Sitemap } from './types';
import { TypedSitemap, createSitemap } from './createSitemap';
import { ApiResponse, PathsResolver, Paths, ResolverForPaths, Sitemap, Simplify } from './types';
describe('createSitemap', () => {
it('creates correct sitemap for sample data', () => {
@ -16,6 +16,24 @@ describe('createSitemap', () => {
{ id: 10, slug: 'children', parent: 8 },
] 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 = {
name: 'root',
id: 1,

@ -1,4 +1,4 @@
import { ApiResponse, Sitemap } from './types';
import { ApiResponse, Sitemap, Simplify } from './types';
type Subtree = {
name: string;
@ -6,10 +6,35 @@ type Subtree = {
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:
// 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
export const createSitemap = (apiResponse: ApiResponse): Sitemap | null => {
export const createSitemap = <TResponse extends ApiResponse>(apiResponse: ApiResponse): TypedSitemap<TResponse> => {
const subtrees = new Map<number, Subtree>(
apiResponse.map(({ id, slug }) => [
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]>[] => [
['', node.id],
const createPaths = (
node: Sitemap,
prefix: string,
): Readonly<[string, number]>[] => [
[prefix, node.id],
...node.children.flatMap((childNode) =>
createPaths(childNode).map(
([path, id]) => [`/${childNode.name}${path}`, id] as const,
),
createPaths(childNode, `${prefix}/${childNode.name}`),
),
];
// Second, improved implementation, where we build a static hashmap from complete paths to IDs once,
// and then use it as a single lookup table.
export const createPathResolver = (sitemap: Sitemap | null) => {
const rawPathsData = sitemap ? createPaths(sitemap) : [];
const rawPathsData = sitemap ? createPaths(sitemap, '') : [];
const pathsMap = new Map(
rawPathsData.map(([path, id]) => [path.length ? path : '/', id]),
);

@ -7,7 +7,30 @@ export type ApiResponse = Readonly<
>;
export type Sitemap = Readonly<{
name: string;
id: number;
children: readonly Sitemap[];
readonly name: string;
readonly id: number;
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