[READ ONLY MIRROR] Spark Social AppView Server
github.com/sprksocial/server
atproto
deno
hono
lexicon
1import { mapDefined } from "@atp/common";
2import { HydrationState } from "./hydration/index.ts";
3
4export type SkeletonFn<Context, Params, Skeleton> = (
5 input: SkeletonFnInput<Context, Params>,
6) => Promise<Skeleton> | Skeleton;
7
8export type HydrationFn<Context, Params, Skeleton> = (
9 input: HydrationFnInput<Context, Params, Skeleton>,
10) => Promise<HydrationState>;
11
12export type RulesFn<Context, Params, Skeleton> = (
13 input: RulesFnInput<Context, Params, Skeleton>,
14) => Skeleton;
15
16export type PresentationFn<Context, Params, Skeleton, View> = (
17 input: PresentationFnInput<Context, Params, Skeleton>,
18) => View;
19
20export type PipelineDefinition<Params, Skeleton, View, Context> = {
21 skeleton: SkeletonFn<Context, Params, Skeleton>;
22 hydration: HydrationFn<Context, Params, Skeleton>;
23 rules?: RulesFn<Context, Params, Skeleton>;
24 presentation: PresentationFn<Context, Params, Skeleton, View>;
25};
26
27export function createPipeline<Params, Skeleton, View, Context>(
28 definition: PipelineDefinition<Params, Skeleton, View, Context>,
29): (params: Params, ctx: Context) => Promise<View>;
30export function createPipeline<Params, Skeleton, View, Context>(
31 definition: PipelineDefinition<Params, Skeleton, View, Context>,
32) {
33 const applyRules = definition.rules ??
34 ((input: RulesFnInput<Context, Params, Skeleton>) => input.skeleton);
35
36 return async (params: Params, ctx: Context) => {
37 const skeleton = await definition.skeleton({ ctx, params });
38 const hydration = await definition.hydration({ ctx, params, skeleton });
39 const appliedRules = applyRules({ ctx, params, skeleton, hydration });
40 return definition.presentation({
41 ctx,
42 params,
43 skeleton: appliedRules,
44 hydration,
45 });
46 };
47}
48
49export type SkeletonFnInput<Context, Params> = {
50 ctx: Context;
51 params: Params;
52};
53
54export type HydrationFnInput<Context, Params, Skeleton> = {
55 ctx: Context;
56 params: Params;
57 skeleton: Skeleton;
58};
59
60export type RulesFnInput<Context, Params, Skeleton> = {
61 ctx: Context;
62 params: Params;
63 skeleton: Skeleton;
64 hydration: HydrationState;
65};
66
67export type PresentationFnInput<Context, Params, Skeleton> = {
68 ctx: Context;
69 params: Params;
70 skeleton: Skeleton;
71 hydration: HydrationState;
72};
73
74type SkeletonListKey<S> = {
75 [K in keyof S]: S[K] extends readonly unknown[] ? K : never;
76}[keyof S];
77
78type SkeletonListItem<T> = T extends readonly (infer Item)[] ? Item : never;
79
80export function filterSkeletonList<
81 Skeleton extends Record<string, unknown>,
82 Key extends SkeletonListKey<Skeleton>,
83>(
84 skeleton: Skeleton,
85 key: Key,
86 predicate: (item: SkeletonListItem<Skeleton[Key]>) => boolean,
87): Skeleton {
88 const items = skeleton[key] as SkeletonListItem<Skeleton[Key]>[];
89 return {
90 ...skeleton,
91 [key]: items.filter(predicate),
92 } as Skeleton;
93}
94
95export function mapSkeletonList<
96 Skeleton extends Record<string, unknown>,
97 Key extends SkeletonListKey<Skeleton>,
98 View,
99>(
100 skeleton: Skeleton,
101 key: Key,
102 mapper: (item: SkeletonListItem<Skeleton[Key]>) => View | undefined,
103): View[] {
104 const items = skeleton[key] as SkeletonListItem<Skeleton[Key]>[];
105 return mapDefined(items, mapper);
106}