@codecks/fetch 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.ts +44 -34
- package/dist/index.js +166 -141
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
## Usage
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import {
|
|
6
|
+
import {buildFetchersWithSimpleLoader} from "@codecks/fetch";
|
|
7
7
|
|
|
8
|
-
const {fetchFromRoot, fetchInstance} =
|
|
8
|
+
const {fetchFromRoot, fetchInstance} = buildFetchersWithSimpleLoader({
|
|
9
9
|
baseUrl: "https://api.codecks.io/",
|
|
10
10
|
subdomain: "my-org",
|
|
11
11
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -2716,7 +2716,7 @@ declare const modelMap: {
|
|
|
2716
2716
|
};
|
|
2717
2717
|
//#endregion
|
|
2718
2718
|
//#region src/has-many-filter-type.d.ts
|
|
2719
|
-
type ModelMap$
|
|
2719
|
+
type ModelMap$3 = Record<string, AnyDesc>;
|
|
2720
2720
|
type OneOrArray<T> = T | T[];
|
|
2721
2721
|
type NormalAndNegatedKeys<T> = T & { [K in keyof T & string as `!${K}`]: T[K] };
|
|
2722
2722
|
type OrderString<T extends string> = `${T}` | `-${T}`;
|
|
@@ -2745,20 +2745,20 @@ type Operators<T> = {
|
|
|
2745
2745
|
value: El[];
|
|
2746
2746
|
} : never);
|
|
2747
2747
|
type FieldQuery<T extends AnyDesc> = { [K in keyof T["fields"]]?: null | OneOrArray<InferFieldType<T["fields"][K]>> | Operators<InferFieldType<T["fields"][K]>> };
|
|
2748
|
-
type RelationQuery<T extends AnyDesc, TMap extends ModelMap$
|
|
2749
|
-
type SimpleFilter<T extends AnyDesc, TMap extends ModelMap$
|
|
2750
|
-
type Filter<T extends AnyDesc, TMap extends ModelMap$
|
|
2748
|
+
type RelationQuery<T extends AnyDesc, TMap extends ModelMap$3> = NormalAndNegatedKeys<{ [K in keyof T["relations"]]?: SimpleFilter<TMap[T["relations"][K]["relName"]], TMap> }>;
|
|
2749
|
+
type SimpleFilter<T extends AnyDesc, TMap extends ModelMap$3> = FieldQuery<T> | RelationQuery<T, TMap>;
|
|
2750
|
+
type Filter<T extends AnyDesc, TMap extends ModelMap$3> = SimpleFilter<T, TMap> & {
|
|
2751
2751
|
$and?: Filter<T, TMap>[];
|
|
2752
2752
|
$or?: Filter<T, TMap>[];
|
|
2753
2753
|
};
|
|
2754
2754
|
//#endregion
|
|
2755
2755
|
//#region src/query-type.d.ts
|
|
2756
|
-
type ModelMap$
|
|
2757
|
-
interface ModelQuery<T extends AnyDesc, TMap extends ModelMap$
|
|
2758
|
-
fields?: (keyof T["fields"])[];
|
|
2756
|
+
type ModelMap$2 = Record<string, AnyDesc>;
|
|
2757
|
+
interface ModelQuery<T extends AnyDesc, TMap extends ModelMap$2> {
|
|
2758
|
+
fields?: (keyof T["fields"] & string)[];
|
|
2759
2759
|
relations?: RelQuery<T, TMap>;
|
|
2760
2760
|
}
|
|
2761
|
-
interface SimpleHasManyQuery<T extends AnyDesc, TMap extends ModelMap$
|
|
2761
|
+
interface SimpleHasManyQuery<T extends AnyDesc, TMap extends ModelMap$2> extends ModelQuery<T, TMap> {
|
|
2762
2762
|
type?: "query";
|
|
2763
2763
|
orderBy?: Order<T>;
|
|
2764
2764
|
limit?: number;
|
|
@@ -2766,20 +2766,20 @@ interface SimpleHasManyQuery<T extends AnyDesc, TMap extends ModelMap$1> extends
|
|
|
2766
2766
|
filter?: Filter<T, TMap>;
|
|
2767
2767
|
as?: string;
|
|
2768
2768
|
}
|
|
2769
|
-
interface CountOrExistQuery<T extends AnyDesc, TMap extends ModelMap$
|
|
2769
|
+
interface CountOrExistQuery<T extends AnyDesc, TMap extends ModelMap$2> {
|
|
2770
2770
|
type: "count" | "exists";
|
|
2771
2771
|
filter?: Filter<T, TMap>;
|
|
2772
2772
|
as: string;
|
|
2773
2773
|
fields?: never;
|
|
2774
2774
|
relations?: never;
|
|
2775
2775
|
}
|
|
2776
|
-
interface FirstOrLastQuery<T extends AnyDesc, TMap extends ModelMap$
|
|
2776
|
+
interface FirstOrLastQuery<T extends AnyDesc, TMap extends ModelMap$2> extends ModelQuery<T, TMap> {
|
|
2777
2777
|
type: "first";
|
|
2778
2778
|
filter?: Filter<T, TMap>;
|
|
2779
2779
|
as: string;
|
|
2780
2780
|
orderBy: Order<T>;
|
|
2781
2781
|
}
|
|
2782
|
-
type HasManyQuery<T extends AnyDesc, TMap extends ModelMap$
|
|
2782
|
+
type HasManyQuery<T extends AnyDesc, TMap extends ModelMap$2> = SimpleHasManyQuery<T, TMap> | CountOrExistQuery<T, TMap> | FirstOrLastQuery<T, TMap>;
|
|
2783
2783
|
type AbstractHasManyQuery = (ModelQuery<any, any> & {
|
|
2784
2784
|
type?: "query" | "first";
|
|
2785
2785
|
as?: string;
|
|
@@ -2787,57 +2787,57 @@ type AbstractHasManyQuery = (ModelQuery<any, any> & {
|
|
|
2787
2787
|
type: "count" | "exists";
|
|
2788
2788
|
as: string;
|
|
2789
2789
|
};
|
|
2790
|
-
type HasManyQueryWithAs<T extends AnyDesc, TMap extends ModelMap$
|
|
2790
|
+
type HasManyQueryWithAs<T extends AnyDesc, TMap extends ModelMap$2> = HasManyQuery<T, TMap> & {
|
|
2791
2791
|
as: string;
|
|
2792
2792
|
};
|
|
2793
|
-
type HasManyRelQueryEntry<T extends AnyDesc, TMap extends ModelMap$
|
|
2794
|
-
type RelQueryEntry<T extends AnyDesc, TMap extends ModelMap$
|
|
2793
|
+
type HasManyRelQueryEntry<T extends AnyDesc, TMap extends ModelMap$2, K extends keyof ExtractHasMany<T, TMap>> = HasManyQuery<TMap[T["relations"][K]["relName"]], TMap> | HasManyQueryWithAs<TMap[T["relations"][K]["relName"]], TMap>[];
|
|
2794
|
+
type RelQueryEntry<T extends AnyDesc, TMap extends ModelMap$2, K extends keyof T["relations"]> = K extends keyof ExtractHasMany<T, TMap> ? HasManyRelQueryEntry<T, TMap, K> : ModelQuery<TMap[T["relations"][K]["relName"]], TMap>;
|
|
2795
2795
|
/**
|
|
2796
2796
|
* examples for account:
|
|
2797
2797
|
* {disabledBy: {fields: []}} // belongst
|
|
2798
2798
|
* {cards: {fields: []}}
|
|
2799
2799
|
* {cards: [{as: "myCards", fields: []}]}
|
|
2800
2800
|
*/
|
|
2801
|
-
type RelQuery<T extends AnyDesc, TMap extends ModelMap$
|
|
2801
|
+
type RelQuery<T extends AnyDesc, TMap extends ModelMap$2> = { [K in keyof T["relations"]]?: RelQueryEntry<T, TMap, K> };
|
|
2802
2802
|
type EnsureModelQuery<T> = T extends ModelQuery<any, any> ? T : never;
|
|
2803
2803
|
type EnsureHasManyQuery<T> = T extends AbstractHasManyQuery ? T : never;
|
|
2804
|
-
type EnrichBelongsTo<M extends AnyDesc, TMap extends ModelMap$
|
|
2804
|
+
type EnrichBelongsTo<M extends AnyDesc, TMap extends ModelMap$2> = FilterNeverKeys<{ [K in keyof M["relations"]]: M["relations"][K] extends RelationEntry<any, infer Opts> ? Opts extends BelongsToOpts<infer TFk> ? {
|
|
2805
2805
|
model: TMap[M["relations"][K]["relName"]];
|
|
2806
2806
|
fk: TFk;
|
|
2807
2807
|
field: M["fields"][TFk] extends TypedField<"belongsTo", infer FieldType, infer FieldOpts> ? {
|
|
2808
2808
|
type: FieldType;
|
|
2809
2809
|
} & FieldOpts : never;
|
|
2810
2810
|
} : never : never }>;
|
|
2811
|
-
type ExtractHasMany<M extends AnyDesc, TMap extends ModelMap$
|
|
2811
|
+
type ExtractHasMany<M extends AnyDesc, TMap extends ModelMap$2> = FilterNeverKeys<{ [K in keyof M["relations"]]: M["relations"][K] extends RelationEntry<infer RelName, {
|
|
2812
2812
|
type: "hasMany";
|
|
2813
2813
|
}> ? {
|
|
2814
2814
|
model: TMap[RelName];
|
|
2815
2815
|
} : never }>;
|
|
2816
|
-
type ExtractHasOne<M extends AnyDesc, TMap extends ModelMap$
|
|
2816
|
+
type ExtractHasOne<M extends AnyDesc, TMap extends ModelMap$2> = FilterNeverKeys<{ [K in keyof M["relations"]]: M["relations"][K] extends RelationEntry<infer RelName, {
|
|
2817
2817
|
type: "hasOne";
|
|
2818
2818
|
}> ? {
|
|
2819
2819
|
model: TMap[RelName];
|
|
2820
2820
|
} : never }>;
|
|
2821
|
-
type InferModelQuery<M extends AnyDesc, Q extends ModelQuery<M, TMap>, TMap extends ModelMap$
|
|
2821
|
+
type InferModelQuery<M extends AnyDesc, Q extends ModelQuery<M, TMap>, TMap extends ModelMap$2> = (Q["fields"] extends (keyof M["fields"])[] ? { [K in Q["fields"][number]]: InferFieldType<M["fields"][K]> } : {}) & (Q["relations"] extends RelQuery<M, TMap> ? InferRelQuery<M, Q["relations"], TMap> : {}) & { [K in M["keys"][number] & string]: InferFieldType<M["fields"][K]> } & {
|
|
2822
2822
|
"~model": M["name"];
|
|
2823
2823
|
"~key": string;
|
|
2824
2824
|
};
|
|
2825
2825
|
type IsOptional<T, Test> = Test extends true ? T | null : T;
|
|
2826
2826
|
type EmptyObjIfNever<T> = [T] extends [never] ? {} : T;
|
|
2827
|
-
type ExtractRelQueryArray<M extends AnyDesc, TMap extends ModelMap$
|
|
2828
|
-
type InferHasMany<M extends AnyDesc, TMap extends ModelMap$
|
|
2827
|
+
type ExtractRelQueryArray<M extends AnyDesc, TMap extends ModelMap$2, Q extends RelQuery<M, TMap>> = { [K in keyof Q]: K extends keyof ExtractHasMany<M, TMap> ? Q[K] extends HasManyQueryWithAs<any, any>[] ? { [Item in Q[K][number] as Item["as"]]: InferHasMany<ExtractHasMany<M, TMap>[K]["model"], TMap, Item> } : never : never }[keyof Q];
|
|
2828
|
+
type InferHasMany<M extends AnyDesc, TMap extends ModelMap$2, QM extends HasManyQuery<M, TMap>> = QM extends {
|
|
2829
2829
|
type: "count";
|
|
2830
2830
|
} ? number : QM extends {
|
|
2831
2831
|
type: "exist";
|
|
2832
2832
|
} ? boolean : QM extends {
|
|
2833
2833
|
type: "first";
|
|
2834
2834
|
} ? InferModelQuery<M, EnsureModelQuery<QM>, TMap> | null : InferModelQuery<M, EnsureModelQuery<QM>, TMap>[];
|
|
2835
|
-
type InferRelEntry<M extends AnyDesc, TMap extends ModelMap$
|
|
2835
|
+
type InferRelEntry<M extends AnyDesc, TMap extends ModelMap$2, Q extends RelQuery<any, any>, K extends keyof Q> = K extends keyof EnrichBelongsTo<M, TMap> ? IsOptional<InferModelQuery<EnrichBelongsTo<M, TMap>[K]["model"], EnsureModelQuery<Q[K]>, TMap>, EnrichBelongsTo<M, TMap>[K]["field"]["optional"]> : K extends keyof ExtractHasMany<M, TMap> ? InferHasMany<ExtractHasMany<M, TMap>[K]["model"], TMap, EnsureHasManyQuery<Q[K]>> : K extends keyof ExtractHasOne<M, TMap> ? InferModelQuery<ExtractHasOne<M, TMap>[K]["model"], EnsureModelQuery<Q[K]>, TMap> : never;
|
|
2836
2836
|
type InferKeyName<Q extends RelQuery<any, any>, K extends keyof Q> = Q[K] extends Array<any> ? never : Q[K] extends {
|
|
2837
2837
|
as: string;
|
|
2838
2838
|
} ? K extends keyof ExtractHasMany<any, any> ? Q[K]["as"] : K : K;
|
|
2839
|
-
type InferRelQuery<M extends AnyDesc, Q extends RelQuery<M, TMap>, TMap extends ModelMap$
|
|
2840
|
-
type Instance<M extends keyof ModelMap$
|
|
2839
|
+
type InferRelQuery<M extends AnyDesc, Q extends RelQuery<M, TMap>, TMap extends ModelMap$2> = EmptyObjIfNever<ExtractRelQueryArray<M, TMap, Q>> & EmptyObjIfNever<{ [K in keyof Q as InferKeyName<Q, K>]: InferRelEntry<M, TMap, Q, K> }>;
|
|
2840
|
+
type Instance<M extends keyof ModelMap$2> = {
|
|
2841
2841
|
"~model": M;
|
|
2842
2842
|
"~key": string;
|
|
2843
2843
|
};
|
|
@@ -2873,16 +2873,13 @@ declare const _rootDesc: ModelDesc<"_root", {}, {
|
|
|
2873
2873
|
}>;
|
|
2874
2874
|
}, []>;
|
|
2875
2875
|
//#endregion
|
|
2876
|
-
//#region src/
|
|
2877
|
-
type ModelMap = typeof modelMap;
|
|
2878
|
-
type
|
|
2879
|
-
|
|
2880
|
-
fetchFromInstance: <M extends keyof ModelMap, const Q extends ModelQuery<ModelMap[M], ModelMap>>(instance: Instance<M>, q: Q) => Promise<InferModelQuery<ModelMap[M], Q, ModelMap>>;
|
|
2881
|
-
fetchInstance: <K extends keyof ModelMap, const Q extends RelQuery<ModelMap[K], ModelMap>>(model: K, id: string, q: Q) => Promise<InferModelQuery<ModelMap[K], Q, ModelMap>>;
|
|
2882
|
-
fetchInstances: <K extends keyof ModelMap, Id extends string, const Q extends RelQuery<ModelMap[K], ModelMap>>(model: K, id: Id[], q: Q) => Promise<Record<Id, InferModelQuery<ModelMap[K], Q, ModelMap>>>;
|
|
2876
|
+
//#region src/loaders/loader-utils.d.ts
|
|
2877
|
+
type ModelMap$1 = typeof modelMap;
|
|
2878
|
+
type DataLoader = {
|
|
2879
|
+
fetchModel: <K extends keyof ModelMap$1, Id extends string, const Q extends ModelQuery<ModelMap$1[K], ModelMap$1>>(model: K, id: Id[], q: Q) => Promise<Record<Id, InferModelQuery<ModelMap$1[K], Q, ModelMap$1>>>;
|
|
2883
2880
|
};
|
|
2884
2881
|
type FetchFunction = (url: string, init?: RequestInit) => Promise<Response>;
|
|
2885
|
-
type
|
|
2882
|
+
type FetchOptions = {
|
|
2886
2883
|
fetch?: FetchFunction;
|
|
2887
2884
|
accessToken?: string;
|
|
2888
2885
|
subdomain?: string;
|
|
@@ -2890,6 +2887,19 @@ type FetcherOptions = {
|
|
|
2890
2887
|
headers?: Record<string, string>;
|
|
2891
2888
|
timeout?: number;
|
|
2892
2889
|
};
|
|
2893
|
-
declare const buildFetchers: (opts?: FetcherOptions) => Fetchers;
|
|
2894
2890
|
//#endregion
|
|
2895
|
-
|
|
2891
|
+
//#region src/loaders/simple-loader.d.ts
|
|
2892
|
+
type SimpleLoaderOptions = FetchOptions;
|
|
2893
|
+
//#endregion
|
|
2894
|
+
//#region src/index.d.ts
|
|
2895
|
+
type ModelMap = typeof modelMap;
|
|
2896
|
+
type Fetchers = {
|
|
2897
|
+
fetchFromRoot: <const Q extends RelQuery<typeof _rootDesc, ModelMap>>(q: Q) => Promise<InferRelQuery<typeof _rootDesc, Q, ModelMap>>;
|
|
2898
|
+
fetchFromInstance: <M extends keyof ModelMap, const Q extends ModelQuery<ModelMap[M], ModelMap>>(instance: Instance<M>, q: Q) => Promise<InferModelQuery<ModelMap[M], Q, ModelMap>>;
|
|
2899
|
+
fetchInstance: <K extends keyof ModelMap, const Q extends ModelQuery<ModelMap[K], ModelMap>>(model: K, id: string, q: Q) => Promise<InferModelQuery<ModelMap[K], Q, ModelMap>>;
|
|
2900
|
+
fetchInstances: <K extends keyof ModelMap, Id extends string, const Q extends ModelQuery<ModelMap[K], ModelMap>>(model: K, id: Id[], q: Q) => Promise<Record<Id, InferModelQuery<ModelMap[K], Q, ModelMap>>>;
|
|
2901
|
+
};
|
|
2902
|
+
declare const buildFetchersWithSimpleLoader: (opts: SimpleLoaderOptions) => Fetchers;
|
|
2903
|
+
declare const buildFetchers: (loader: DataLoader) => Fetchers;
|
|
2904
|
+
//#endregion
|
|
2905
|
+
export { buildFetchers, buildFetchersWithSimpleLoader };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
//#region src/collection-utils.ts
|
|
2
|
-
const ensureMapValue = (map, key, fallback) => {
|
|
3
|
-
const exist = map.get(key);
|
|
4
|
-
if (exist) return exist;
|
|
5
|
-
const newValue = fallback();
|
|
6
|
-
map.set(key, newValue);
|
|
7
|
-
return newValue;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
//#endregion
|
|
11
1
|
//#region src/models/_desc.ts
|
|
12
2
|
const makeModel = (opts) => {
|
|
13
3
|
return opts;
|
|
@@ -38,57 +28,6 @@ const _rootDesc = makeModel({
|
|
|
38
28
|
keys: []
|
|
39
29
|
});
|
|
40
30
|
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region src/model-pool.ts
|
|
43
|
-
const ROOT_ID = "";
|
|
44
|
-
const dateStrToDay = (dateAsStr) => {
|
|
45
|
-
const parts = dateAsStr.split("-");
|
|
46
|
-
return {
|
|
47
|
-
year: parseInt(parts[0], 10),
|
|
48
|
-
month: parseInt(parts[1], 10),
|
|
49
|
-
day: parseInt(parts[2], 10)
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
const parseField = (v, field) => {
|
|
53
|
-
if (!field) return v;
|
|
54
|
-
switch (field.type) {
|
|
55
|
-
case "date": return new Date(v);
|
|
56
|
-
case "day": return dateStrToDay(v);
|
|
57
|
-
default: return v;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
const transformData = (desc, data) => {
|
|
61
|
-
return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, parseField(v, desc.fields[k])]));
|
|
62
|
-
};
|
|
63
|
-
var ModelPool = class {
|
|
64
|
-
data = new Map();
|
|
65
|
-
modelMap;
|
|
66
|
-
constructor(modelMap$1) {
|
|
67
|
-
this.modelMap = modelMap$1;
|
|
68
|
-
}
|
|
69
|
-
addModelInstance(model, id$1, _data) {
|
|
70
|
-
const instanceById = ensureMapValue(this.data, model, () => new Map());
|
|
71
|
-
const exists = instanceById.get(id$1);
|
|
72
|
-
const desc = this.modelMap[model];
|
|
73
|
-
if (!desc) throw new Error(`Unknown model: ${model}`);
|
|
74
|
-
const data = transformData(desc, _data);
|
|
75
|
-
if (exists) Object.assign(exists, data);
|
|
76
|
-
else instanceById.set(id$1, data);
|
|
77
|
-
}
|
|
78
|
-
add(data) {
|
|
79
|
-
Object.entries(data).forEach(([modelName, payload]) => {
|
|
80
|
-
if (modelName === _rootDesc.name) this.addModelInstance(modelName, ROOT_ID, payload);
|
|
81
|
-
else Object.entries(payload).forEach(([id$1, payload$1]) => {
|
|
82
|
-
this.addModelInstance(modelName, id$1, payload$1);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
get(model, id$1) {
|
|
87
|
-
const instances = this.data.get(model);
|
|
88
|
-
return instances?.get(id$1) ?? null;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
31
|
//#endregion
|
|
93
32
|
//#region src/models/_fields.ts
|
|
94
33
|
const id = () => {
|
|
@@ -3012,9 +2951,100 @@ const modelMap = {
|
|
|
3012
2951
|
workflowItem: workflowItemDesc
|
|
3013
2952
|
};
|
|
3014
2953
|
|
|
2954
|
+
//#endregion
|
|
2955
|
+
//#region src/loaders/loader-utils.ts
|
|
2956
|
+
const configuredFetch = async (opts, url, init = {}) => {
|
|
2957
|
+
const fetchImpl = opts.fetch || globalThis.fetch;
|
|
2958
|
+
const headers = new Headers(init.headers);
|
|
2959
|
+
if (opts.accessToken) headers.set("X-Auth-Token", opts.accessToken);
|
|
2960
|
+
if (opts.subdomain) headers.set("X-Account", opts.subdomain);
|
|
2961
|
+
if (opts.headers) Object.entries(opts.headers).forEach(([key, value]) => {
|
|
2962
|
+
headers.set(key, value);
|
|
2963
|
+
});
|
|
2964
|
+
const fullUrl = opts.baseUrl ? `${opts.baseUrl}${url}` : url;
|
|
2965
|
+
return fetchImpl(fullUrl, {
|
|
2966
|
+
...init,
|
|
2967
|
+
headers
|
|
2968
|
+
}).then(async (r) => {
|
|
2969
|
+
const content = await r.json();
|
|
2970
|
+
if (r.status !== 200) throw new Error(`[${r.status}] ${JSON.stringify(content)}`);
|
|
2971
|
+
return content;
|
|
2972
|
+
});
|
|
2973
|
+
};
|
|
2974
|
+
|
|
2975
|
+
//#endregion
|
|
2976
|
+
//#region src/collection-utils.ts
|
|
2977
|
+
const ensureMapValue = (map, key, fallback) => {
|
|
2978
|
+
const exist = map.get(key);
|
|
2979
|
+
if (exist) return exist;
|
|
2980
|
+
const newValue = fallback();
|
|
2981
|
+
map.set(key, newValue);
|
|
2982
|
+
return newValue;
|
|
2983
|
+
};
|
|
2984
|
+
|
|
2985
|
+
//#endregion
|
|
2986
|
+
//#region src/model-pool.ts
|
|
2987
|
+
const ROOT_ID = "";
|
|
2988
|
+
const dateStrToDay = (dateAsStr) => {
|
|
2989
|
+
const parts = dateAsStr.split("-");
|
|
2990
|
+
return {
|
|
2991
|
+
year: parseInt(parts[0], 10),
|
|
2992
|
+
month: parseInt(parts[1], 10),
|
|
2993
|
+
day: parseInt(parts[2], 10)
|
|
2994
|
+
};
|
|
2995
|
+
};
|
|
2996
|
+
const parseField = (v, field) => {
|
|
2997
|
+
if (!field) return v;
|
|
2998
|
+
switch (field.type) {
|
|
2999
|
+
case "date": return new Date(v);
|
|
3000
|
+
case "day": return dateStrToDay(v);
|
|
3001
|
+
default: return v;
|
|
3002
|
+
}
|
|
3003
|
+
};
|
|
3004
|
+
const transformData = (desc, data) => {
|
|
3005
|
+
return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, parseField(v, desc.fields[k])]));
|
|
3006
|
+
};
|
|
3007
|
+
var ModelPool = class {
|
|
3008
|
+
data = new Map();
|
|
3009
|
+
modelMap;
|
|
3010
|
+
constructor(modelMap$1) {
|
|
3011
|
+
this.modelMap = modelMap$1;
|
|
3012
|
+
}
|
|
3013
|
+
addModelInstance(model, id$1, _data) {
|
|
3014
|
+
const instanceById = ensureMapValue(this.data, model, () => new Map());
|
|
3015
|
+
const exists = instanceById.get(id$1);
|
|
3016
|
+
const desc = this.modelMap[model];
|
|
3017
|
+
if (!desc) throw new Error(`Unknown model: ${model}`);
|
|
3018
|
+
const data = transformData(desc, _data);
|
|
3019
|
+
if (exists) Object.assign(exists, data);
|
|
3020
|
+
else instanceById.set(id$1, data);
|
|
3021
|
+
}
|
|
3022
|
+
add(data) {
|
|
3023
|
+
Object.entries(data).forEach(([modelName, payload]) => {
|
|
3024
|
+
if (modelName === _rootDesc.name) this.addModelInstance(modelName, ROOT_ID, payload);
|
|
3025
|
+
else Object.entries(payload).forEach(([id$1, payload$1]) => {
|
|
3026
|
+
this.addModelInstance(modelName, id$1, payload$1);
|
|
3027
|
+
});
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
get(model, id$1) {
|
|
3031
|
+
const instances = this.data.get(model);
|
|
3032
|
+
return instances?.get(id$1) ?? null;
|
|
3033
|
+
}
|
|
3034
|
+
};
|
|
3035
|
+
|
|
3015
3036
|
//#endregion
|
|
3016
3037
|
//#region src/query-helpers.ts
|
|
3038
|
+
const normalizeQuery = (q) => {
|
|
3039
|
+
return JSON.stringify(q);
|
|
3040
|
+
};
|
|
3017
3041
|
const getRelKey = (r, k) => {
|
|
3042
|
+
const getSuffix = () => {
|
|
3043
|
+
if (r.type === "exists") return `exists:`;
|
|
3044
|
+
if (r.type === "count") return `count:`;
|
|
3045
|
+
return "";
|
|
3046
|
+
};
|
|
3047
|
+
const suffix = getSuffix();
|
|
3018
3048
|
const isPlainKey = () => {
|
|
3019
3049
|
if (r.type === "first") return false;
|
|
3020
3050
|
if (r.filter && Object.keys(r.filter).length > 0) return false;
|
|
@@ -3023,7 +3053,7 @@ const getRelKey = (r, k) => {
|
|
|
3023
3053
|
}
|
|
3024
3054
|
return true;
|
|
3025
3055
|
};
|
|
3026
|
-
if (isPlainKey()) return k
|
|
3056
|
+
if (isPlainKey()) return `${suffix}${k}`;
|
|
3027
3057
|
const q = { ...r.filter };
|
|
3028
3058
|
if (r.type === "first") {
|
|
3029
3059
|
q.$first = true;
|
|
@@ -3034,52 +3064,65 @@ const getRelKey = (r, k) => {
|
|
|
3034
3064
|
if (r.offset) q.$offset = r.offset;
|
|
3035
3065
|
if (r.orderBy) q.$order = r.orderBy;
|
|
3036
3066
|
}
|
|
3037
|
-
return `${k}(${
|
|
3067
|
+
return `${suffix}${k}(${normalizeQuery(q)})`;
|
|
3068
|
+
};
|
|
3069
|
+
const processRelations = (rels) => {
|
|
3070
|
+
const result = {};
|
|
3071
|
+
for (const [key, relq] of Object.entries(rels)) {
|
|
3072
|
+
const asArray = Array.isArray(relq) ? relq : [relq];
|
|
3073
|
+
for (const rel of asArray) {
|
|
3074
|
+
if (!rel) continue;
|
|
3075
|
+
result[getRelKey(rel, key)] = makeRelQuerySerializable(rel);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
return result;
|
|
3079
|
+
};
|
|
3080
|
+
const makeModelQuerySerializable = (q) => {
|
|
3081
|
+
if (!q.relations) return q;
|
|
3082
|
+
return {
|
|
3083
|
+
...q,
|
|
3084
|
+
relations: processRelations(q.relations)
|
|
3085
|
+
};
|
|
3038
3086
|
};
|
|
3039
|
-
const
|
|
3087
|
+
const makeRelQuerySerializable = (r) => {
|
|
3088
|
+
if (r.type === "exists" || r.type === "count") return { asField: true };
|
|
3089
|
+
return {
|
|
3090
|
+
asField: false,
|
|
3091
|
+
fields: r.fields,
|
|
3092
|
+
relations: r.relations ? processRelations(r.relations) : void 0
|
|
3093
|
+
};
|
|
3094
|
+
};
|
|
3095
|
+
const serializeModel = (q) => {
|
|
3040
3096
|
const list = q.fields ? [...q.fields] : [];
|
|
3041
3097
|
if (!q.relations) return list;
|
|
3042
3098
|
const relObj = {};
|
|
3043
|
-
Object.entries(q.relations).forEach(([k,
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
switch (r?.type) {
|
|
3047
|
-
case "count": {
|
|
3048
|
-
list.push(`count:${getRelKey(r, k)}`);
|
|
3049
|
-
break;
|
|
3050
|
-
}
|
|
3051
|
-
case "exists": {
|
|
3052
|
-
list.push(`exists:${getRelKey(r, k)}`);
|
|
3053
|
-
break;
|
|
3054
|
-
}
|
|
3055
|
-
default:
|
|
3056
|
-
relObj[getRelKey(r, k)] = serializeModel(r, modelDesc);
|
|
3057
|
-
break;
|
|
3058
|
-
}
|
|
3059
|
-
});
|
|
3099
|
+
Object.entries(q.relations).forEach(([k, r]) => {
|
|
3100
|
+
if (r.asField) list.push(k);
|
|
3101
|
+
else relObj[k] = serializeModel(r);
|
|
3060
3102
|
});
|
|
3061
3103
|
if (Object.keys(relObj).length) list.push(relObj);
|
|
3062
3104
|
return list;
|
|
3063
3105
|
};
|
|
3064
|
-
const
|
|
3065
|
-
return { _root: serializeModel({ relations: q }, _rootDesc) };
|
|
3066
|
-
};
|
|
3067
|
-
const serializeInstanceQuery = (q, instances, modelMap$1) => {
|
|
3106
|
+
const serializeInstanceQuery = (q, instances) => {
|
|
3068
3107
|
return Object.fromEntries(instances.map((instance) => {
|
|
3069
3108
|
const name = instance["~model"];
|
|
3070
|
-
|
|
3109
|
+
const getKey = () => {
|
|
3110
|
+
if (name === "_root") return "_root";
|
|
3111
|
+
return `${name}(${instance["~key"]})`;
|
|
3112
|
+
};
|
|
3113
|
+
return [getKey(), serializeModel(q)];
|
|
3071
3114
|
}));
|
|
3072
3115
|
};
|
|
3073
3116
|
|
|
3074
3117
|
//#endregion
|
|
3075
3118
|
//#region src/reconcile-query.ts
|
|
3076
|
-
const reconcileInstanceQuery = (query, response, instanceModel, key, store
|
|
3119
|
+
const reconcileInstanceQuery = (query, response, instanceModel, key, store) => {
|
|
3077
3120
|
const instance = store.get(instanceModel.name, key);
|
|
3078
3121
|
if (!instance) {
|
|
3079
3122
|
console.warn(`no instance found in pool: [${instanceModel.name}, ${key}]`);
|
|
3080
3123
|
return null;
|
|
3081
3124
|
}
|
|
3082
|
-
const result =
|
|
3125
|
+
const result = instanceModel.name === "_root" ? {} : {
|
|
3083
3126
|
"~model": instanceModel.name,
|
|
3084
3127
|
"~key": key
|
|
3085
3128
|
};
|
|
@@ -3106,12 +3149,9 @@ const reconcileInstanceQuery = (query, response, instanceModel, key, store, skip
|
|
|
3106
3149
|
const relKey = getRelKey(relEntry, relName);
|
|
3107
3150
|
const val = instance[relKey];
|
|
3108
3151
|
switch (relEntry.type) {
|
|
3109
|
-
case "count":
|
|
3110
|
-
result[asName] = instance[`count:${relKey}`];
|
|
3111
|
-
break;
|
|
3112
|
-
}
|
|
3152
|
+
case "count":
|
|
3113
3153
|
case "exists": {
|
|
3114
|
-
result[asName] = instance[
|
|
3154
|
+
result[asName] = instance[relKey];
|
|
3115
3155
|
break;
|
|
3116
3156
|
}
|
|
3117
3157
|
case "first":
|
|
@@ -3131,67 +3171,52 @@ const reconcileInstanceQuery = (query, response, instanceModel, key, store, skip
|
|
|
3131
3171
|
};
|
|
3132
3172
|
|
|
3133
3173
|
//#endregion
|
|
3134
|
-
//#region src/
|
|
3135
|
-
const
|
|
3136
|
-
const fetchImpl = opts.fetch || globalThis.fetch;
|
|
3137
|
-
const configuredFetch = async (url, init = {}) => {
|
|
3138
|
-
const headers = new Headers(init.headers);
|
|
3139
|
-
if (opts.accessToken) headers.set("X-Auth-Token", opts.accessToken);
|
|
3140
|
-
if (opts.subdomain) headers.set("X-Account", opts.subdomain);
|
|
3141
|
-
if (opts.headers) Object.entries(opts.headers).forEach(([key, value]) => {
|
|
3142
|
-
headers.set(key, value);
|
|
3143
|
-
});
|
|
3144
|
-
const fullUrl = opts.baseUrl ? `${opts.baseUrl}${url}` : url;
|
|
3145
|
-
return fetchImpl(fullUrl, {
|
|
3146
|
-
...init,
|
|
3147
|
-
headers
|
|
3148
|
-
});
|
|
3149
|
-
};
|
|
3174
|
+
//#region src/loaders/simple-loader.ts
|
|
3175
|
+
const createSimpleLoader = (opts = {}) => {
|
|
3150
3176
|
const fetchWithQuery = async (query) => {
|
|
3151
|
-
|
|
3177
|
+
return configuredFetch(opts, "", {
|
|
3152
3178
|
method: "POST",
|
|
3153
3179
|
headers: { "Content-Type": "application/json" },
|
|
3154
3180
|
body: JSON.stringify({ query })
|
|
3155
|
-
}).then(async (r) => {
|
|
3156
|
-
const content = await r.json();
|
|
3157
|
-
if (r.status !== 200) throw new Error(`[${r.status}] ${JSON.stringify(content)}`);
|
|
3158
|
-
return content;
|
|
3159
3181
|
});
|
|
3160
|
-
return response;
|
|
3161
3182
|
};
|
|
3162
|
-
|
|
3163
|
-
const
|
|
3183
|
+
return { fetchModel: async (model, ids, q) => {
|
|
3184
|
+
const modelDesc = modelMap[model];
|
|
3185
|
+
const instances = ids.map((id$1) => ({
|
|
3186
|
+
"~model": model,
|
|
3187
|
+
"~key": id$1
|
|
3188
|
+
}));
|
|
3189
|
+
const response = await fetchWithQuery(serializeInstanceQuery(makeModelQuerySerializable(q), instances));
|
|
3164
3190
|
const pool = new ModelPool(modelMap);
|
|
3165
3191
|
pool.add(response);
|
|
3166
|
-
return reconcileInstanceQuery(q, response,
|
|
3167
|
-
};
|
|
3192
|
+
return Object.fromEntries(ids.map((id$1) => [id$1, reconcileInstanceQuery(q, response, modelDesc, id$1, pool)]));
|
|
3193
|
+
} };
|
|
3194
|
+
};
|
|
3195
|
+
|
|
3196
|
+
//#endregion
|
|
3197
|
+
//#region src/index.ts
|
|
3198
|
+
const buildFetchersWithSimpleLoader = (opts) => {
|
|
3199
|
+
const loader = createSimpleLoader(opts);
|
|
3200
|
+
return buildFetchers(loader);
|
|
3201
|
+
};
|
|
3202
|
+
const buildFetchers = (loader) => {
|
|
3168
3203
|
return {
|
|
3169
3204
|
fetchFromRoot: async (q) => {
|
|
3170
|
-
const
|
|
3171
|
-
|
|
3172
|
-
pool.add(response);
|
|
3173
|
-
return reconcileInstanceQuery({ relations: q }, response, _rootDesc, "", pool, true);
|
|
3205
|
+
const res = await loader.fetchModel("_root", [""], { relations: q });
|
|
3206
|
+
return res[""];
|
|
3174
3207
|
},
|
|
3175
|
-
fetchFromInstance:
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
"~key": id$1
|
|
3180
|
-
}, q);
|
|
3208
|
+
fetchFromInstance: async (instance, q) => {
|
|
3209
|
+
const key = instance["~key"];
|
|
3210
|
+
const res = await loader.fetchModel(instance["~model"], [key], q);
|
|
3211
|
+
return res[key];
|
|
3181
3212
|
},
|
|
3182
|
-
|
|
3183
|
-
const
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
}));
|
|
3188
|
-
const response = await fetchWithQuery(serializeInstanceQuery(q, instances, modelMap));
|
|
3189
|
-
const pool = new ModelPool(modelMap);
|
|
3190
|
-
pool.add(response);
|
|
3191
|
-
return Object.fromEntries(ids.map((id$1) => [id$1, reconcileInstanceQuery(q, response, modelDesc, id$1, pool)]));
|
|
3192
|
-
}
|
|
3213
|
+
fetchInstance: async (model, key, q) => {
|
|
3214
|
+
const res = await loader.fetchModel(model, [key], q);
|
|
3215
|
+
return res[key];
|
|
3216
|
+
},
|
|
3217
|
+
fetchInstances: loader.fetchModel
|
|
3193
3218
|
};
|
|
3194
3219
|
};
|
|
3195
3220
|
|
|
3196
3221
|
//#endregion
|
|
3197
|
-
export { buildFetchers };
|
|
3222
|
+
export { buildFetchers, buildFetchersWithSimpleLoader };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codecks/fetch",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Quickly create deeply nested queries for the Codecks API with advanced TypeScript support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": "./dist/index.js",
|
|
24
|
+
"./_exploration/store": "./dist/_exploration/store.js",
|
|
25
|
+
"./_exploration/loader": "./dist/_exploration/loader.js",
|
|
26
|
+
"./_exploration/api-requester": "./dist/_exploration/api-requester.js",
|
|
27
|
+
"./_exploration/react": "./dist/_exploration/create-hooks.js",
|
|
24
28
|
"./package.json": "./package.json"
|
|
25
29
|
},
|
|
26
30
|
"publishConfig": {
|
|
@@ -32,19 +36,24 @@
|
|
|
32
36
|
"check-format": "prettier --check .",
|
|
33
37
|
"dev": "tsdown --watch",
|
|
34
38
|
"test": "vitest",
|
|
39
|
+
"test:run": "vitest run",
|
|
35
40
|
"typecheck": "tsc --noEmit",
|
|
36
|
-
"ci": "npm run typecheck && npm run build && npm run check-format && npm run test",
|
|
41
|
+
"ci": "npm run typecheck && npm run build && npm run check-format && npm run test:run",
|
|
37
42
|
"local-release": "changeset version && changeset publish",
|
|
38
43
|
"prepublishOnly": "npm run ci"
|
|
39
44
|
},
|
|
40
45
|
"devDependencies": {
|
|
41
46
|
"@changesets/cli": "^2.29.7",
|
|
42
47
|
"@types/node": "^22.15.17",
|
|
48
|
+
"@types/react": "^19.2.2",
|
|
43
49
|
"msw": "^2.10.4",
|
|
44
50
|
"prettier": "^3.6.2",
|
|
45
51
|
"tsdown": "^0.11.9",
|
|
46
52
|
"typescript": "^5.9.2",
|
|
47
53
|
"vite-node": "^3.2.4",
|
|
48
54
|
"vitest": "^3.1.3"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"react": "^19.0.0"
|
|
49
58
|
}
|
|
50
59
|
}
|