1- import { defineCollection , z } from 'astro:content' ;
2- import { glob } from 'astro/loaders' ;
1+ import { defineCollection , reference , z } from 'astro:content' ;
2+ import { glob , file } from 'astro/loaders' ;
33
44const badgeEnum = z . enum ( [ 'FREE' , 'PROMO' , 'PAID' ] ) ;
55
6+ const categories = defineCollection ( {
7+ loader : file ( "src/data/shared/categories.yaml" ) ,
8+ schema : z . object ( { id : z . string ( ) , name : z . string ( ) } )
9+ } ) ;
10+
11+ const features = defineCollection ( {
12+ loader : file ( "src/data/shared/features.yaml" ) ,
13+ schema : z . object ( { id : z . string ( ) , name : z . string ( ) } )
14+ } ) ;
15+
16+ const tags = defineCollection ( {
17+ loader : file ( "src/data/shared/tags.yaml" ) ,
18+ schema : z . object ( { id : z . string ( ) , name : z . string ( ) } )
19+ } ) ;
20+
21+ const providers = defineCollection ( {
22+ loader : file ( "src/data/shared/providers.yaml" ) ,
23+ schema : z . object ( { id : z . string ( ) , name : z . string ( ) , url : z . string ( ) . url ( ) . optional ( ) } )
24+ } ) ;
25+
626const plans = defineCollection ( {
727 loader : glob ( { pattern : '**/*.yaml' , base : './src/data/plans' } ) ,
828 schema : z . object ( {
929 name : z . string ( ) ,
1030 slug : z . string ( ) ,
11- provider : z . string ( ) ,
31+ provider : reference ( 'providers' ) . optional ( ) ,
1232 badge : badgeEnum ,
13- price_monthly : z . number ( ) . min ( 0 ) ,
33+
34+ quotas : z . array ( z . object ( {
35+ measure : z . enum ( [ 'currency' , 'requests' , 'input_tokens' , 'output_tokens' , 'credits' ] ) ,
36+ amount : z . number ( ) . nullable ( ) ,
37+ window : z . enum ( [ '5-hour' , 'daily' , 'weekly' , 'monthly' , 'unlimited' , 'rolling' ] ) ,
38+ models : z . array ( reference ( 'models' ) ) . optional ( ) ,
39+ aliases : z . array ( z . object ( {
40+ measure : z . enum ( [ 'currency' , 'requests' , 'input_tokens' , 'output_tokens' , 'credits' ] ) ,
41+ model : reference ( 'models' ) ,
42+ amount : z . number ( )
43+ } ) ) . optional ( )
44+ } ) ) . optional ( ) ,
45+
46+ price_monthly : z . number ( ) . min ( 0 ) . optional ( ) ,
1447 promotional_price : z . number ( ) . min ( 0 ) . nullable ( ) . optional ( ) ,
1548 promotional_duration : z . string ( ) . nullable ( ) . optional ( ) ,
16- description : z . string ( ) ,
17- external_url : z . string ( ) . url ( ) ,
18- models : z . array ( z . string ( ) ) ,
1949 limits : z . object ( {
2050 requests_per_minute : z . number ( ) . nullable ( ) . optional ( ) ,
2151 tokens_per_minute : z . number ( ) . nullable ( ) . optional ( ) ,
2252 context_window : z . number ( ) . nullable ( ) . optional ( ) ,
2353 daily_message_limit : z . union ( [ z . number ( ) , z . string ( ) ] ) . nullable ( ) . optional ( ) ,
24- } ) ,
25- features : z . array ( z . string ( ) ) ,
26- categories : z . array ( z . string ( ) ) ,
54+ } ) . optional ( ) ,
55+
56+ overages : z . discriminatedUnion ( "allowed" , [
57+ z . object ( { allowed : z . literal ( false ) } ) ,
58+ z . object ( {
59+ allowed : z . literal ( true ) ,
60+ type : z . enum ( [ "payg" , "credits_purchase" ] ) ,
61+ pricing_model : z . string ( ) . optional ( ) ,
62+ auto_recharge_supported : z . boolean ( ) . optional ( )
63+ } )
64+ ] ) . optional ( ) ,
65+
66+ restrictions : z . object ( {
67+ allowed_tools : z . array ( reference ( 'tools' ) ) . nullable ( ) . optional ( ) ,
68+ banned_tools : z . array ( reference ( 'tools' ) ) . nullable ( ) . optional ( ) ,
69+ violation_penalty : z . string ( ) . optional ( )
70+ } ) . optional ( ) ,
71+
72+ description : z . string ( ) ,
73+ external_url : z . string ( ) . url ( ) . optional ( ) ,
74+
75+ models : z . array ( reference ( 'models' ) ) . optional ( ) ,
76+ compatible_tools : z . array ( reference ( 'tools' ) ) . optional ( ) ,
77+ compatible_tools : z . array ( reference ( 'tools' ) ) . optional ( ) , // legacy
78+
79+ features : z . array ( z . union ( [ z . string ( ) , reference ( 'features' ) ] ) ) . optional ( ) ,
80+ categories : z . array ( z . union ( [ z . string ( ) , reference ( 'categories' ) ] ) ) . optional ( ) ,
81+
2782 student_discount : z . boolean ( ) . default ( false ) ,
2883 startup_credits : z . boolean ( ) . default ( false ) ,
29- tools_compatible : z . array ( z . string ( ) ) ,
84+
3085 history : z . array ( z . object ( {
3186 date : z . string ( ) ,
3287 event : z . string ( ) ,
3388 } ) ) . optional ( ) ,
89+
3490 community_reviews_summary : z . string ( ) . optional ( ) ,
3591 community_score : z . number ( ) . min ( 0 ) . max ( 100 ) . optional ( ) ,
3692 latency : z . object ( {
3793 average_ms : z . number ( ) . optional ( ) ,
3894 uptime_percent : z . number ( ) . min ( 0 ) . max ( 100 ) . optional ( ) ,
3995 } ) . optional ( ) ,
40- updated_at : z . string ( ) ,
96+ updated_at : z . string ( ) . optional ( ) ,
4197 } ) ,
4298} ) ;
4399
@@ -46,14 +102,15 @@ const models = defineCollection({
46102 schema : z . object ( {
47103 name : z . string ( ) ,
48104 slug : z . string ( ) ,
49- provider : z . string ( ) ,
105+ provider : z . union ( [ z . string ( ) , reference ( 'providers' ) ] ) . optional ( ) ,
50106 description : z . string ( ) ,
51107 context_window : z . number ( ) ,
52108 strengths : z . array ( z . string ( ) ) ,
53109 weaknesses : z . array ( z . string ( ) ) ,
54- vibe_coding_score : z . number ( ) . min ( 0 ) . max ( 10 ) ,
55- model_type : z . enum ( [ 'open_weight' , 'closed_source' ] ) ,
56- plans_available : z . array ( z . string ( ) ) ,
110+ vibe_coding_score : z . number ( ) . min ( 0 ) . max ( 10 ) . optional ( ) ,
111+ model_type : z . enum ( [ 'open_weight' , 'closed_source' ] ) . optional ( ) ,
112+ // We remove the required manual reverse relationship
113+ plans_available : z . array ( reference ( 'plans' ) ) . optional ( ) ,
57114 updated_at : z . string ( ) . optional ( ) ,
58115 } ) ,
59116} ) ;
@@ -64,10 +121,10 @@ const tools = defineCollection({
64121 name : z . string ( ) ,
65122 slug : z . string ( ) ,
66123 description : z . string ( ) ,
67- external_url : z . string ( ) . url ( ) ,
68- tool_type : z . enum ( [ 'cli' , 'ide' , 'extension' ] ) ,
69- features : z . array ( z . string ( ) ) ,
70- plans_compatible : z . array ( z . string ( ) ) ,
124+ external_url : z . string ( ) . url ( ) . optional ( ) ,
125+ tool_type : z . enum ( [ 'cli' , 'ide' , 'extension' ] ) . optional ( ) ,
126+ features : z . array ( z . union ( [ z . string ( ) , reference ( 'features' ) ] ) ) . optional ( ) ,
127+ plans_compatible : z . array ( reference ( 'plans' ) ) . optional ( ) ,
71128 updated_at : z . string ( ) . optional ( ) ,
72129 } ) ,
73130} ) ;
@@ -78,7 +135,7 @@ const stacks = defineCollection({
78135 id : z . string ( ) ,
79136 author : z . string ( ) ,
80137 title : z . string ( ) ,
81- tools : z . array ( z . string ( ) ) ,
138+ tools : z . array ( reference ( 'tools' ) ) ,
82139 monthly_cost : z . number ( ) . min ( 0 ) ,
83140 description : z . string ( ) ,
84141 upvotes : z . number ( ) . min ( 0 ) . default ( 0 ) ,
@@ -87,6 +144,10 @@ const stacks = defineCollection({
87144} ) ;
88145
89146export const collections = {
147+ categories,
148+ features,
149+ tags,
150+ providers,
90151 plans,
91152 models,
92153 tools,
0 commit comments