@common-grants/core 0.2.0-alpha.1 → 0.2.1

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 CHANGED
@@ -34,12 +34,14 @@ The Opportunity model is templated to support custom fields. First define your c
34
34
  // models.tsp
35
35
 
36
36
  import "@common-grants/core"; // Import the base specification library
37
+ import "@typespec/versioning";
37
38
 
38
39
  // Allows us to use models and fields defined in the core library without
39
40
  // prefixing each item with `CommonGrants.Models` or `CommonGrants.Fields`
40
41
  using CommonGrants.Models;
41
42
  using CommonGrants.Fields;
42
43
 
44
+ @Versioning.useDependency(CommonGrants.Versions.v0_2) // Specify the version of the core library to use
43
45
  namespace CustomAPI.CustomModels;
44
46
 
45
47
  // Define a custom field
@@ -77,6 +79,7 @@ using TypeSpec.Http;
77
79
 
78
80
  @tag("Search")
79
81
  @route("/common-grants/opportunities")
82
+ @Versioning.useDependency(CommonGrants.Versions.v0_2)
80
83
  namespace CustomAPI.CustomRoutes {
81
84
  alias OpportunitiesRouter = Opportunities;
82
85
 
@@ -117,10 +120,16 @@ Or specify the emitter in `tspconfig.yaml`:
117
120
 
118
121
  ```yaml
119
122
  # tspconfig.yaml
120
- emitters:
123
+ emit:
121
124
  - "@typespec/openapi3"
122
125
  ```
123
126
 
127
+ And run the following command:
128
+
129
+ ```bash
130
+ npx tsp compile main.tsp
131
+ ```
132
+
124
133
  Both strategies will generate an OpenAPI specification in the `tsp-output/` directory.
125
134
 
126
135
  ### Further reading
package/lib/api.tsp CHANGED
@@ -30,16 +30,22 @@ using Versioning;
30
30
  description: "Endpoints that MUST be implemented by all CommonGrants APIs",
31
31
  }
32
32
  )
33
+ @tagMetadata("Forms", #{ description: "Endpoints related to forms" })
33
34
  @tagMetadata(
34
35
  "Applications",
36
+ #{ description: "Endpoints related to applications for a given competition" }
37
+ )
38
+ @tagMetadata(
39
+ "Competitions",
35
40
  #{
36
- description: "Endpoints related to applications for funding opportunities",
41
+ description: "Endpoints related to competitions, which are distinct application processes for the same funding opportunity",
37
42
  }
38
43
  )
39
44
  @tagMetadata(
40
45
  "Opportunities",
41
46
  #{ description: "Endpoints related to funding opportunities" }
42
47
  )
48
+ @route("/common-grants") // Prefixes all routes with `/common-grants/`
43
49
  namespace CommonGrants.API;
44
50
 
45
51
  // #########################################################
@@ -47,7 +53,7 @@ namespace CommonGrants.API;
47
53
  // #########################################################
48
54
 
49
55
  @tag("Opportunities")
50
- @route("/common-grants/opportunities")
56
+ @route("/opportunities")
51
57
  namespace Opportunities {
52
58
  alias Router = Routes.Opportunities;
53
59
 
@@ -55,69 +61,82 @@ namespace Opportunities {
55
61
  op list is Router.list;
56
62
 
57
63
  @tag("required")
64
+ @returnTypeChangedFrom(
65
+ CommonGrants.Versions.v0_2,
66
+ Responses.Ok<Models.OpportunityBase> | Responses.NotFound
67
+ )
58
68
  op read is Router.read;
59
69
 
60
70
  @tag("optional")
61
71
  op search is Router.search;
62
72
  }
63
73
 
74
+ // #########################################################
75
+ // Competitions
76
+ // #########################################################
77
+
78
+ @tag("Competitions")
79
+ @tag("experimental")
80
+ @route("/competitions")
81
+ namespace Competitions {
82
+ alias Router = Routes.Competitions;
83
+
84
+ @added(Versions.v0_2)
85
+ op read is Router.read;
86
+ }
87
+
64
88
  // #########################################################
65
89
  // Applications
66
90
  // #########################################################
67
91
 
68
92
  @tag("Applications")
69
93
  @tag("experimental")
70
- @route("/common-grants/")
71
- namespace Apply {
72
- // #########################################################
73
- // Direct apply workflow
74
- // #########################################################
75
- @tag("experimental")
76
- @route("/competitions")
77
- namespace DirectApplyWorkflow {
78
- alias Router = Routes.Competitions;
79
-
80
- @added(Versions.v0_2)
81
- op competitionDetails is Router.read;
82
-
83
- @added(Versions.v0_2)
84
- op apply is Router.apply;
85
- }
94
+ @route("/applications")
95
+ namespace Applications {
96
+ alias ApplicationRouter = Routes.Applications;
97
+ alias FormResponseRouter = Routes.FormResponses;
86
98
 
87
- // #########################################################
88
- // Multi-step workflow
89
- // #########################################################
99
+ // ################################
100
+ // Start an application workflow
101
+ // ################################
90
102
 
91
- @route("/applications")
92
- namespace MultiStepWorkflow {
93
- alias ApplicationRouter = Routes.Applications;
94
- alias FormResponseRouter = Routes.FormResponses;
103
+ @added(Versions.v0_2)
104
+ op startApplication is ApplicationRouter.startApplication;
95
105
 
96
- // ################################
97
- // Start an application workflow
98
- // ################################
106
+ @added(Versions.v0_2)
107
+ op getApplication is ApplicationRouter.getApplication;
99
108
 
100
- @added(Versions.v0_2)
101
- op startApplication is ApplicationRouter.startApplication;
109
+ // ################################
110
+ // Update form responses
111
+ // ################################
102
112
 
103
- @added(Versions.v0_2)
104
- op getApplication is ApplicationRouter.getApplication;
113
+ @added(Versions.v0_2)
114
+ op setFormResponse is FormResponseRouter.setFormResponse;
105
115
 
106
- // ################################
107
- // Update form responses
108
- // ################################
116
+ @added(Versions.v0_2)
117
+ op getFormResponse is FormResponseRouter.getFormResponse;
109
118
 
110
- @added(Versions.v0_2)
111
- op setFormResponse is FormResponseRouter.setFormResponse;
119
+ // ################################
120
+ // Submit an application after completing all forms
121
+ // ################################
112
122
 
113
- @added(Versions.v0_2)
114
- op getFormResponse is FormResponseRouter.getFormResponse;
123
+ @added(Versions.v0_2)
124
+ op submitApplication is ApplicationRouter.submitApplication;
125
+ }
115
126
 
116
- // ################################
117
- // Submit an application after completing all forms
118
- // ################################
127
+ // #########################################################
128
+ // Forms
129
+ // #########################################################
119
130
 
120
- @added(Versions.v0_2)
121
- op submitApplication is ApplicationRouter.submitApplication;
122
- }
131
+ @tag("Forms")
132
+ @tag("experimental")
133
+ @route("/forms")
134
+ namespace Forms {
135
+ alias Router = Routes.Forms;
136
+
137
+ @added(Versions.v0_2)
138
+ op list is Router.list;
139
+
140
+ @added(Versions.v0_2)
141
+ op read is Router.read;
123
142
  }
@@ -2,6 +2,7 @@ namespace CommonGrants.Fields;
2
2
 
3
3
  /** A mailing address. */
4
4
  @example(Examples.Address.apartment)
5
+ @Versioning.added(CommonGrants.Versions.v0_2)
5
6
  model Address {
6
7
  /** The primary street address line. */
7
8
  street1: string;
@@ -34,6 +35,7 @@ model Address {
34
35
  /** A collection of addresses. */
35
36
  @example(Examples.Address.personalCollection)
36
37
  @example(Examples.Address.orgCollection)
38
+ @Versioning.added(CommonGrants.Versions.v0_2)
37
39
  model AddressCollection {
38
40
  /** The primary address for a person or organization. */
39
41
  primary: Address;
@@ -7,6 +7,7 @@ alias Email = Types.email; // Exposes Email from CommonGrants.Fields
7
7
 
8
8
  /** A collection of email addresses. */
9
9
  @example(Examples.Email.personalCollection)
10
+ @Versioning.added(CommonGrants.Versions.v0_2)
10
11
  model EmailCollection {
11
12
  /** The primary email address for a person or organization. */
12
13
  primary: Types.email;
@@ -3,6 +3,7 @@ namespace CommonGrants.Fields;
3
3
  /** A field representing a downloadable file. */
4
4
  @example(Examples.File.imageFile, #{ title: "An image file" })
5
5
  @example(Examples.File.pdfFile, #{ title: "A PDF file" })
6
+ @Versioning.added(CommonGrants.Versions.v0_2)
6
7
  model File {
7
8
  /** The file's download URL. */
8
9
  downloadUrl: url;
@@ -2,6 +2,7 @@ namespace CommonGrants.Fields;
2
2
 
3
3
  /** A person's name. */
4
4
  @example(Examples.Name.janeDoe)
5
+ @Versioning.added(CommonGrants.Versions.v0_2)
5
6
  model Name {
6
7
  /** Honorific prefix (e.g., Mr., Mrs., Dr., Prof.). */
7
8
  prefix?: string;
@@ -13,6 +13,7 @@ namespace CommonGrants.Fields;
13
13
  * See https://taxonomy.candid.org/ for more information.
14
14
  */
15
15
  @example(Examples.PCS.orgTypeTerm)
16
+ @Versioning.added(CommonGrants.Versions.v0_2)
16
17
  model PCSTerm {
17
18
  /** The plain language PCS term. */
18
19
  term: string;
@@ -34,6 +35,7 @@ model PCSTerm {
34
35
  // #########################################################
35
36
 
36
37
  /** The class to which the PCS term belongs. */
38
+ @Versioning.added(CommonGrants.Versions.v0_2)
37
39
  enum PCSClass {
38
40
  orgTypes: "Organization types",
39
41
  subjects: "Subjects",
@@ -50,6 +52,7 @@ enum PCSClass {
50
52
  *
51
53
  * See https://taxonomy.candid.org/organization-type for more information.
52
54
  */
55
+ @Versioning.added(CommonGrants.Versions.v0_2)
53
56
  model PCSOrgType extends PCSTerm {
54
57
  /** The PCS term for the organization type. */
55
58
  class: PCSClass.orgTypes;
@@ -59,6 +62,7 @@ model PCSOrgType extends PCSTerm {
59
62
  *
60
63
  * See https://taxonomy.candid.org/subjects for more information.
61
64
  */
65
+ @Versioning.added(CommonGrants.Versions.v0_2)
62
66
  model PCSSubject extends PCSTerm {
63
67
  /** The PCS term for the subject. */
64
68
  class: PCSClass.subjects;
@@ -68,6 +72,7 @@ model PCSSubject extends PCSTerm {
68
72
  *
69
73
  * See https://taxonomy.candid.org/populations for more information.
70
74
  */
75
+ @Versioning.added(CommonGrants.Versions.v0_2)
71
76
  model PCSPopulation extends PCSTerm {
72
77
  /** The PCS term for the population. */
73
78
  class: PCSClass.populationGroups;
@@ -77,6 +82,7 @@ model PCSPopulation extends PCSTerm {
77
82
  *
78
83
  * See https://taxonomy.candid.org/support-strategies for more information.
79
84
  */
85
+ @Versioning.added(CommonGrants.Versions.v0_2)
80
86
  model PCSSupportStrategy extends PCSTerm {
81
87
  /** The PCS term for the support strategy. */
82
88
  class: PCSClass.supportStrategies;
@@ -86,6 +92,7 @@ model PCSSupportStrategy extends PCSTerm {
86
92
  *
87
93
  * See https://taxonomy.candid.org/transaction-types for more information.
88
94
  */
95
+ @Versioning.added(CommonGrants.Versions.v0_2)
89
96
  model PCSTransactionType extends PCSTerm {
90
97
  /** The PCS term for the transaction type. */
91
98
  class: PCSClass.transactionTypes;
@@ -0,0 +1,98 @@
1
+ namespace CommonGrants.Models;
2
+
3
+ // #########################################################
4
+ // ApplicantType
5
+ // #########################################################
6
+
7
+ /** The type of applicant eligible to apply for funding */
8
+ @example(Examples.ApplicantType.organization)
9
+ @example(Examples.ApplicantType.individual)
10
+ @Versioning.added(CommonGrants.Versions.v0_2)
11
+ model ApplicantType {
12
+ /** The type of applicant */
13
+ value: ApplicantTypeOptions;
14
+
15
+ /** The custom value for the applicant type */
16
+ customValue?: string;
17
+
18
+ /** The description of the applicant type */
19
+ description?: string;
20
+ }
21
+
22
+ // #########################################################
23
+ // ApplicantTypeOptions
24
+ // #########################################################
25
+
26
+ /** The set of possible applicant types */
27
+ @Versioning.added(CommonGrants.Versions.v0_2)
28
+ enum ApplicantTypeOptions {
29
+ /** The applicant is an individual */
30
+ individual,
31
+
32
+ /** Any type of organization */
33
+ organization,
34
+
35
+ /** State government */
36
+ government_state,
37
+
38
+ /** County government */
39
+ government_county,
40
+
41
+ /** City or township government */
42
+ government_municipal,
43
+
44
+ /** Special district government */
45
+ government_special_district,
46
+
47
+ /** Federally recognized Native American tribal government */
48
+ government_tribal,
49
+
50
+ /** Native American tribal organization that is not federally recognized */
51
+ organization_tribal_other,
52
+
53
+ /** Independent school district */
54
+ school_district_independent,
55
+
56
+ /** Public or state institution of higher education */
57
+ higher_education_public,
58
+
59
+ /** Private institution of higher education */
60
+ higher_education_private,
61
+
62
+ /** Non-profit organization with 501(c)(3) status */
63
+ non_profit_with_501c3,
64
+
65
+ /** Non-profit organization without 501(c)(3) status */
66
+ nonprofit_without_501c3,
67
+
68
+ /** For-profit small business */
69
+ for_profit_small_business,
70
+
71
+ /** For-profit organization that is not a small business */
72
+ for_profit_not_small_business,
73
+
74
+ /** Anyone can apply (unrestricted) */
75
+ unrestricted,
76
+
77
+ /** Custom applicant type */
78
+ custom,
79
+ }
80
+
81
+ // #########################################################
82
+ // ApplicantTypeExamples
83
+ // #########################################################
84
+
85
+ /** Examples of applicant types */
86
+ namespace Examples.ApplicantType {
87
+ /** The applicant is an individual */
88
+ const individual = #{
89
+ value: ApplicantTypeOptions.individual,
90
+ description: "An individual applicant",
91
+ };
92
+
93
+ /** Any type of organization */
94
+ const organization = #{
95
+ value: ApplicantTypeOptions.organization,
96
+ description: "Any type of organization",
97
+ };
98
+ }
@@ -1,5 +1,8 @@
1
1
  namespace CommonGrants.Models;
2
2
 
3
+ /** The base model for an application to a competition for a funding opportunity */
4
+ @example(Examples.Application.applicationBase)
5
+ @Versioning.added(CommonGrants.Versions.v0_2)
3
6
  model ApplicationBase {
4
7
  /** The unique identifier for the application */
5
8
  id: Types.uuid;
@@ -19,6 +22,9 @@ model ApplicationBase {
19
22
  /** The date and time the application was submitted */
20
23
  submittedAt?: utcDateTime | null;
21
24
 
25
+ /** The validation errors for the application and form responses */
26
+ validationErrors?: Array<unknown>;
27
+
22
28
  /** The custom fields about the application */
23
29
  customFields?: Record<Fields.CustomField>;
24
30
 
@@ -30,16 +36,17 @@ model ApplicationBase {
30
36
  // AppStatus
31
37
  // #########################################################
32
38
 
33
- /** The status of the application. */
39
+ /** The status of the application */
34
40
  @example(Examples.Application.submittedStatus)
41
+ @Versioning.added(CommonGrants.Versions.v0_2)
35
42
  model AppStatus {
36
- /** The status of the application. */
43
+ /** The status of the application, from a predefined set of options */
37
44
  value: AppStatusOptions;
38
45
 
39
- /** A custom value for the status. */
46
+ /** A custom value for the status */
40
47
  customValue?: string;
41
48
 
42
- /** A description of the status. */
49
+ /** A human-readable description of the status */
43
50
  description?: string;
44
51
  }
45
52
 
@@ -47,21 +54,18 @@ model AppStatus {
47
54
  // AppStatusOptions
48
55
  // #########################################################
49
56
 
50
- /** The default set of values accepted for application status. */
57
+ /** The default set of values accepted for application status:
58
+ * - `inProgress`: The application is in progress
59
+ * - `submitted`: The application has been submitted and is being reviewed
60
+ * - `accepted`: The application has been accepted
61
+ * - `rejected`: The application has been rejected
62
+ * - `custom`: A custom status
63
+ */
51
64
  enum AppStatusOptions {
52
- /** The application is a draft */
53
- draft,
54
-
55
- /** The application has been submitted */
65
+ inProgress,
56
66
  submitted,
57
-
58
- /** The application has been accepted */
59
67
  accepted,
60
-
61
- /** The application has been rejected */
62
68
  rejected,
63
-
64
- /** The application has a custom status */
65
69
  custom,
66
70
  }
67
71
 
@@ -69,9 +73,15 @@ enum AppStatusOptions {
69
73
  // ApplicationFormResponse
70
74
  // #########################################################
71
75
 
72
- model AppFormResponse extends FormResponseBase {
76
+ /** The model for a form response included in an application */
77
+ @example(Examples.Application.formResponse)
78
+ @Versioning.added(CommonGrants.Versions.v0_2)
79
+ model AppFormResponse {
73
80
  /** The unique identifier for the application */
74
81
  applicationId: Types.uuid;
82
+
83
+ /** Includes all the fields from the FormResponseBase model */
84
+ ...FormResponseBase;
75
85
  }
76
86
 
77
87
  // #########################################################
@@ -84,9 +94,30 @@ namespace Examples.Application {
84
94
  description: "The application has been submitted.",
85
95
  };
86
96
 
97
+ const inProgressStatus = #{
98
+ value: AppStatusOptions.inProgress,
99
+ description: "The application is in progress.",
100
+ };
101
+
87
102
  const customStatus = #{
88
103
  value: AppStatusOptions.custom,
89
- customValue: "draft",
90
- description: "Application is started but not yet submitted.",
104
+ customValue: "cancelled",
105
+ description: "Application was cancelled before it was submitted.",
106
+ };
107
+
108
+ const formResponse = #{
109
+ applicationId: "123e4567-e89b-12d3-a456-426614174000",
110
+ ...Examples.FormResponse.formResponse,
111
+ };
112
+
113
+ const applicationBase = #{
114
+ id: "123e4567-e89b-12d3-a456-426614174000",
115
+ name: "My Application",
116
+ competitionId: "123e4567-e89b-12d3-a456-426614174000",
117
+ formResponses: #{ formA: formResponse },
118
+ status: inProgressStatus,
119
+ submittedAt: null,
120
+ createdAt: utcDateTime.fromISO("2021-01-01T00:00:00Z"),
121
+ lastModifiedAt: utcDateTime.fromISO("2021-01-01T00:00:00Z"),
91
122
  };
92
123
  }
@@ -7,6 +7,7 @@ namespace CommonGrants.Models;
7
7
  * distinct application period and set of application forms.
8
8
  */
9
9
  @example(Examples.Competition.competition)
10
+ @Versioning.added(CommonGrants.Versions.v0_2)
10
11
  model CompetitionBase {
11
12
  /** Globally unique id for the competition */
12
13
  id: Types.uuid;
@@ -21,17 +22,20 @@ model CompetitionBase {
21
22
  description?: string;
22
23
 
23
24
  /** The instructions for the competition */
24
- instructions?: string | Fields.File;
25
+ instructions?: string | Fields.File[];
25
26
 
26
27
  /** The status of the competition */
27
28
  status: CompetitionStatus;
28
29
 
29
30
  /** The key dates in the competition timeline */
30
- keyDates: CompetitionTimeline;
31
+ keyDates?: CompetitionTimeline;
31
32
 
32
33
  /** The forms for the competition */
33
34
  forms: CompetitionForms;
34
35
 
36
+ /** Accepted applicant types for the competition */
37
+ acceptedApplicantTypes?: ApplicantType[];
38
+
35
39
  /** The custom fields for the competition */
36
40
  customFields?: Record<Fields.CustomField>;
37
41
 
@@ -46,8 +50,13 @@ model CompetitionBase {
46
50
  /** The status of the competition */
47
51
  @example(Examples.Competition.status)
48
52
  model CompetitionStatus {
53
+ /** The status of the competition, from a predefined set of options */
49
54
  value: CompetitionStatusOptions;
55
+
56
+ /** A custom value for the status */
50
57
  customValue?: string;
58
+
59
+ /** A human-readable description of the status */
51
60
  description?: string;
52
61
  }
53
62
 
@@ -55,14 +64,14 @@ model CompetitionStatus {
55
64
  // CompetitionStatusOptions
56
65
  // #########################################################
57
66
 
67
+ /** The set of values accepted for competition status
68
+ * - `open`: The competition is open for applications
69
+ * - `closed`: The competition is no longer accepting applications
70
+ * - `custom`: A custom status
71
+ */
58
72
  enum CompetitionStatusOptions {
59
- /** The competition is open for applications */
60
73
  open,
61
-
62
- /** The competition is closed for applications */
63
74
  closed,
64
-
65
- /** The competition is in a custom status */
66
75
  custom,
67
76
  }
68
77
 
@@ -77,7 +86,7 @@ model CompetitionForms {
77
86
  forms: Record<Models.Form>;
78
87
 
79
88
  /** The validation rules for the competition forms */
80
- validation: Record<unknown>;
89
+ validation?: Record<unknown>;
81
90
  }
82
91
 
83
92
  // #########################################################
@@ -87,10 +96,10 @@ model CompetitionForms {
87
96
  @example(Examples.Competition.keyDates)
88
97
  model CompetitionTimeline {
89
98
  /** The start date of the competition */
90
- openDate: Fields.Event;
99
+ openDate?: Fields.Event;
91
100
 
92
101
  /** The end date of the competition */
93
- closeDate: Fields.Event;
102
+ closeDate?: Fields.Event;
94
103
 
95
104
  /** The date the competition was created */
96
105
  otherDates?: Record<Fields.Event>;