@common-grants/core 0.1.0-alpha.9 → 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.
@@ -10,16 +10,16 @@ using CommonGrants.Types;
10
10
  // Model definition
11
11
  // ########################################
12
12
 
13
- /** Key dates in the opportunity's timeline, such as when the application opens and closes */
13
+ /** Key dates and events in the opportunity's timeline, such as when the opportunity is posted and closes */
14
14
  @example(Examples.Timeline.opportunity)
15
15
  model OppTimeline {
16
- /** The date (and time) at which the opportunity begins accepting applications */
17
- appOpens?: Event;
16
+ /** The date (and time) at which the opportunity is posted */
17
+ postDate?: Event;
18
18
 
19
- /** The final deadline for submitting applications */
20
- appDeadline?: Event;
19
+ /** The date (and time) at which the opportunity closes */
20
+ closeDate?: Event;
21
21
 
22
- /** An optional map of other key dates in the opportunity timeline
22
+ /** An optional map of other key dates or events in the opportunity timeline
23
23
  *
24
24
  * Examples might include a deadline for questions, anticipated award date, etc.
25
25
  */
@@ -33,14 +33,23 @@ model OppTimeline {
33
33
  /** Examples of the OppTimeline model */
34
34
  namespace Examples.Timeline {
35
35
  const opportunity = #{
36
- appOpens: Fields.Examples.Event.openDate,
37
- appDeadline: Fields.Examples.Event.deadline,
36
+ postDate: Fields.Examples.Event.postedDate,
37
+ closeDate: Fields.Examples.Event.closeDate,
38
38
  otherDates: #{
39
39
  anticipatedAward: #{
40
40
  name: "Anticipated award date",
41
+ eventType: EventType.singleDate,
41
42
  date: isoDate.fromISO("2025-03-15"),
42
43
  description: "When we expect to announce awards for this opportunity.",
43
44
  },
45
+ applicationPeriod: Fields.Examples.Event.applicationPeriod,
46
+ performancePeriod: Fields.Examples.Event.performancePeriod,
47
+ infoSessions: #{
48
+ name: "Info sessions",
49
+ eventType: EventType.other,
50
+ details: "Every other Tuesday",
51
+ description: "Info sessions for the opportunity",
52
+ },
44
53
  },
45
54
  };
46
55
  }
@@ -0,0 +1,105 @@
1
+ import "../fields/index.tsp";
2
+
3
+ namespace CommonGrants.Models;
4
+
5
+ /** An organization that can apply for grants. */
6
+ @example(Examples.Organization.exampleOrg)
7
+ model OrganizationBase {
8
+ /** The organization's unique identifier. */
9
+ id: Types.uuid;
10
+
11
+ /** The organization's legal name as registered with relevant authorities. */
12
+ name: string;
13
+
14
+ /** The organization's type within the Philanthropy Classification System (PCS). */
15
+ orgType?: Fields.PCSOrgType;
16
+
17
+ /** The organization's Employer Identification Number (EIN), a unique identifier assigned by the IRS. */
18
+ ein?: Types.employerTaxId;
19
+
20
+ /** The organization's Unique Entity Identifier (UEI) from SAM.gov, used for federal contracting. */
21
+ uei?: Types.samUEI;
22
+
23
+ /** The organization's Data Universal Numbering System (DUNS) number, a unique identifier for businesses. */
24
+ duns?: Types.duns;
25
+
26
+ /** Collection of physical addresses associated with the organization. */
27
+ addresses?: Fields.AddressCollection;
28
+
29
+ /** Collection of phone numbers associated with the organization. */
30
+ phones?: Fields.PhoneCollection;
31
+
32
+ /** Collection of email addresses associated with the organization. */
33
+ emails?: Fields.EmailCollection;
34
+
35
+ /** The organization's mission statement. */
36
+ mission?: string;
37
+
38
+ /** The calendar year the organization was founded. */
39
+ yearFounded?: Types.calendarYear;
40
+
41
+ /** Collection of the organization's social media and web presence links. */
42
+ socials?: OrgSocialLinks;
43
+
44
+ /** Custom fields for the organization. */
45
+ customFields?: Record<Fields.CustomField>;
46
+ }
47
+
48
+ // #########################################################
49
+ // OrgSocialLinks
50
+ // #########################################################
51
+
52
+ /** A collection of social media and web presence links for an organization. */
53
+ @example(Examples.Organization.exampleSocials)
54
+ model OrgSocialLinks {
55
+ /** The organization's primary website URL. */
56
+ website?: url;
57
+
58
+ /** The organization's Facebook profile URL. */
59
+ facebook?: url;
60
+
61
+ /** The organization's Twitter/X profile URL. */
62
+ twitterOrX?: url;
63
+
64
+ /** The organization's BlueSky profile URL. */
65
+ bluesky?: url;
66
+
67
+ /** The organization's Instagram profile URL. */
68
+ instagram?: url;
69
+
70
+ /** The organization's LinkedIn profile URL. */
71
+ linkedin?: url;
72
+
73
+ /** Additional social media profiles not covered by the standard fields. */
74
+ otherSocials?: Record<url>;
75
+ }
76
+
77
+ // #########################################################
78
+ // Examples
79
+ // #########################################################
80
+
81
+ namespace Examples.Organization {
82
+ const exampleOrg = #{
83
+ id: "083b4567-e89d-42c8-a439-6c1234567890",
84
+ name: "Example Organization",
85
+ orgType: Fields.Examples.PCS.orgTypeTerm,
86
+ ein: "12-3456789",
87
+ uei: "ABC1234567890",
88
+ duns: "123456789012",
89
+ addresses: Fields.Examples.Address.orgCollection,
90
+ phones: Fields.Examples.Phone.orgCollection,
91
+ emails: Fields.Examples.Email.orgCollection,
92
+ mission: "To provide support and resources to the community.",
93
+ yearFounded: "2024",
94
+ socials: exampleSocials,
95
+ };
96
+
97
+ const exampleSocials = #{
98
+ website: "https://www.example.com",
99
+ facebook: "https://www.facebook.com/example",
100
+ twitterOrX: "https://x.com/example",
101
+ instagram: "https://www.instagram.com/example",
102
+ linkedin: "https://www.linkedin.com/company/example",
103
+ otherSocials: #{ youtube: "https://www.youtube.com/example" },
104
+ };
105
+ }
@@ -0,0 +1,43 @@
1
+ import "../fields/index.tsp";
2
+ import "../types.tsp";
3
+
4
+ namespace CommonGrants.Models;
5
+
6
+ /** A person affiliated with an organization or grant application. */
7
+ @example(Examples.Person.examplePerson)
8
+ model PersonBase {
9
+ /** The person's full name, including all relevant components (first, middle, last, etc.). */
10
+ name: Fields.Name;
11
+
12
+ /** The person's title, if applicable. */
13
+ title?: string;
14
+
15
+ /** Collection of physical addresses associated with the person. */
16
+ addresses?: Fields.AddressCollection;
17
+
18
+ /** Collection of phone numbers associated with the person. */
19
+ phones?: Fields.PhoneCollection;
20
+
21
+ /** Collection of email addresses associated with the person. */
22
+ emails?: Fields.EmailCollection;
23
+
24
+ /** The person's date of birth. */
25
+ dateOfBirth?: Types.isoDate;
26
+
27
+ /** Custom fields for the person. */
28
+ customFields?: Record<Fields.CustomField>;
29
+ }
30
+
31
+ // #########################################################
32
+ // Examples
33
+ // #########################################################
34
+
35
+ namespace Examples.Person {
36
+ const examplePerson = #{
37
+ name: Fields.Examples.Name.janeDoe,
38
+ title: "Chief Executive Officer",
39
+ addresses: Fields.Examples.Address.personalCollection,
40
+ phones: Fields.Examples.Phone.personalCollection,
41
+ emails: Fields.Examples.Email.personalCollection,
42
+ };
43
+ }
@@ -0,0 +1,56 @@
1
+ import "@typespec/http";
2
+
3
+ using TypeSpec.Http;
4
+ using TypeSpec.JsonSchema;
5
+ /** Models and utilities for pagination */
6
+ @jsonSchema
7
+ namespace CommonGrants.Pagination;
8
+
9
+ /** Query parameters for paginated routes */
10
+ model PaginatedQueryParams {
11
+ /** The page to return */
12
+ @query
13
+ @pageIndex
14
+ @minValue(1)
15
+ page?: int32 = 1;
16
+
17
+ /** The number of items to return per page */
18
+ @query
19
+ @pageSize
20
+ @minValue(1)
21
+ pageSize?: int32 = 100;
22
+ }
23
+
24
+ /** Body parameters for paginated routes */
25
+ model PaginatedBodyParams {
26
+ /** The page to return */
27
+ @pageIndex
28
+ @minValue(1)
29
+ page?: int32 = 1;
30
+
31
+ /** The number of items to return per page */
32
+ @pageSize
33
+ @minValue(1)
34
+ pageSize?: int32 = 100;
35
+ }
36
+
37
+ /** Details about the paginated results */
38
+ model PaginatedResultsInfo {
39
+ /** Current page number (indexing starts at 1) */
40
+ @example(1)
41
+ @minValue(1)
42
+ page: int32;
43
+
44
+ /** Number of items per page */
45
+ @example(20)
46
+ @minValue(1)
47
+ pageSize: integer;
48
+
49
+ /** Total number of items across all pages */
50
+ @example(100)
51
+ totalItems?: integer;
52
+
53
+ /** Total number of pages */
54
+ @example(5)
55
+ totalPages?: integer;
56
+ }
@@ -3,6 +3,8 @@ import "@typespec/http";
3
3
  import "./error.tsp";
4
4
  import "./success.tsp";
5
5
 
6
+ using TypeSpec.JsonSchema;
7
+
6
8
  /** A standardized set of response schemas for CommonGrants API routes
7
9
  *
8
10
  * @example How to use the `Responses` namespace
@@ -21,4 +23,5 @@ import "./success.tsp";
21
23
  * }
22
24
  * ```
23
25
  */
26
+ @jsonSchema
24
27
  namespace CommonGrants.Responses;
@@ -1,7 +1,13 @@
1
+ import "../pagination.tsp";
2
+ import "../sorting.tsp";
1
3
  import "@typespec/http";
2
4
 
3
5
  namespace CommonGrants.Responses;
4
6
 
7
+ // ############################################################################
8
+ // Default success response
9
+ // ############################################################################
10
+
5
11
  model Success {
6
12
  @example(200)
7
13
  status: int32;
@@ -10,13 +16,16 @@ model Success {
10
16
  message: string;
11
17
  }
12
18
 
19
+ // ############################################################################
20
+ // 200 response
21
+ // ############################################################################
22
+
13
23
  /** Template for a 200 response with data
14
24
  *
15
25
  * @template T The schema for the value of the `"data"` property in this response
16
26
  * @example How to specify a custom 200 response model
17
27
  *
18
28
  * ```typespec
19
- *
20
29
  * // Define a model
21
30
  * model CustomModel {
22
31
  * id: string;
@@ -24,7 +33,7 @@ model Success {
24
33
  * }
25
34
  *
26
35
  * // Pass that model to the `Ok` template
27
- * alias CustomModel200 = Success.Ok<CustomModel>;
36
+ * alias CustomModel200 = Responses.Ok<CustomModel>;
28
37
  * ```
29
38
  */
30
39
  @doc("A 200 response with data")
@@ -36,13 +45,16 @@ model Ok<T> extends Success {
36
45
  data: T;
37
46
  }
38
47
 
48
+ // ############################################################################
49
+ // 200 paginated response
50
+ // ############################################################################
51
+
39
52
  /** Template for a 200 response with paginated list of items
40
53
  *
41
54
  * @template T The schema for the value of the `"items"` property in this response
42
55
  * @example How to specify a custom paginated response model
43
56
  *
44
57
  * ```typespec
45
- *
46
58
  * // Define a model
47
59
  * model CustomModel {
48
60
  * id: string;
@@ -50,7 +62,7 @@ model Ok<T> extends Success {
50
62
  * }
51
63
  *
52
64
  * // Pass that model to the `Ok` template
53
- * alias CustomModelResponse = Success.Paginated<CustomModel>;
65
+ * alias CustomModelResponse = Responses.Paginated<CustomModel>;
54
66
  * ```
55
67
  */
56
68
  @doc("A 200 response with a paginated list of items")
@@ -63,29 +75,72 @@ model Paginated<T> extends Success {
63
75
  items: T[];
64
76
 
65
77
  /** Details about the paginated results */
66
- paginationInfo: {
67
- /** Current page number (indexing starts at 1) */
68
- @example(1)
69
- page: int32;
70
-
71
- /** Number of items per page */
72
- @example(20)
73
- pageSize: integer;
74
-
75
- /** Total number of items across all pages */
76
- @example(100)
77
- totalItems: integer;
78
-
79
- /** Total number of pages */
80
- @example(5)
81
- totalPages: integer;
82
-
83
- /** URL for the next page if available */
84
- @example("/opportunities?page=2&pageSize=20")
85
- nextPageUrl?: string;
86
-
87
- /** URL for the previous page if available */
88
- @example("/opportunities?page=1&pageSize=20")
89
- previousPageUrl?: string;
78
+ paginationInfo: Pagination.PaginatedResultsInfo;
79
+ }
80
+
81
+ // ############################################################################
82
+ // 200 sorted response
83
+ // ############################################################################
84
+
85
+ /** A paginated list of items with a sort order
86
+ *
87
+ * @template T The schema for the value of the `"items"` property in this response
88
+ * @example How to specify a custom sorted response model
89
+ *
90
+ * ```typespec
91
+ * // Define a model
92
+ * model CustomModel {
93
+ * id: string;
94
+ * description: string;
95
+ * }
96
+ *
97
+ * // Pass that model to the `Sorted` template
98
+ * alias CustomModelSortedResponse = Responses.Sorted<CustomModel>;
99
+ * ```
100
+ */
101
+ model Sorted<T> {
102
+ // Inherit the properties of the Paginated response
103
+ ...Paginated<T>;
104
+
105
+ /** The sort order of the items */
106
+ sortInfo: Sorting.SortedResultsInfo;
107
+ }
108
+
109
+ // ############################################################################
110
+ // 200 filtered response
111
+ // ############################################################################
112
+
113
+ /** A paginated list of items with a filter
114
+ *
115
+ * @template ItemsT The schema for the value of the `"items"` property in this response
116
+ * @template FilterT The schema for the value of the `"filter"` property in this response
117
+ * @example How to specify a custom filtered response model
118
+ *
119
+ * ```typespec
120
+ * // Define a model for the items in the response
121
+ * model CustomModel {
122
+ * id: string;
123
+ * description: string;
124
+ * }
125
+ *
126
+ * // Define a model for the filter in the response
127
+ * model CustomFilter extends Record<Filters.DefaultFilter> {
128
+ * lastModifiedAt: Filters.DateComparisonFilter;
129
+ * }
130
+ *
131
+ * // Pass that model to the `Filtered` template
132
+ * alias CustomModelFilteredResponse = Responses.Filtered<CustomModel, CustomFilter>;
133
+ * ```
134
+ */
135
+ model Filtered<ItemsT, FilterT> extends Success {
136
+ // Inherit the properties of the Sorted response
137
+ ...Sorted<ItemsT>;
138
+
139
+ /** The filters applied to the response items */
140
+ filterInfo: {
141
+ filters: FilterT;
142
+
143
+ /** Non-fatal errors that occurred during filtering */
144
+ errors?: string[];
90
145
  };
91
146
  }
@@ -1,6 +1,6 @@
1
1
  import "../models/index.tsp";
2
2
  import "../responses/index.tsp";
3
- import "./utils.tsp";
3
+ import "../pagination.tsp";
4
4
 
5
5
  // Define the top-level namespace for CommonGrants routes
6
6
  namespace CommonGrants.Routes;
@@ -19,7 +19,7 @@ using TypeSpec.Http;
19
19
  * For more information, see
20
20
  * [TypeSpec docs](https://typespec.io/docs/language-basics/interfaces/#templating-interface-operations)
21
21
  *
22
- * @example Using the the default type for the list operation and
22
+ * @example Using the default type for the list operation and
23
23
  * a custom model for the read operation:
24
24
  * ```typespec
25
25
  * using TypeSpec.Http;
@@ -35,6 +35,10 @@ using TypeSpec.Http;
35
35
  * ```
36
36
  */
37
37
  interface Opportunities {
38
+ // ###############################
39
+ // List opportunities
40
+ // ###############################
41
+
38
42
  /** `GET /opportunities/` Get a paginated list of opportunities
39
43
  *
40
44
  * @template T Type of the paginated response model.
@@ -44,8 +48,12 @@ interface Opportunities {
44
48
  @doc("Get a paginated list of opportunities, sorted by `lastModifiedAt` with most recent first.")
45
49
  @list
46
50
  list<T extends Models.OpportunityBase = Models.OpportunityBase>(
47
- ...Utils.PaginatedQuery,
48
- ): Responses.Paginated<T> | Responses.Unauthorized;
51
+ ...Pagination.PaginatedQueryParams,
52
+ ): Responses.Paginated<T>;
53
+
54
+ // ##############################
55
+ // View an opportunity
56
+ // ##############################
49
57
 
50
58
  /** `GET /opportunities/<id>` View opportunity details
51
59
  *
@@ -58,5 +66,37 @@ interface Opportunities {
58
66
  read<T extends Models.OpportunityBase = Models.OpportunityBase>(
59
67
  /** The ID of the opportunity to view */
60
68
  @path id: Types.uuid,
61
- ): Responses.Ok<T> | Responses.NotFound | Responses.Unauthorized;
69
+ ): Responses.Ok<T> | Responses.NotFound;
70
+
71
+ // ###############################
72
+ // Search opportunities
73
+ // ###############################
74
+
75
+ /** `POST /opportunities/search` Search opportunities
76
+ *
77
+ * @template T Type of the response model.
78
+ * Must be an extension of Schemas.OpportunityBase. Default is Schemas.OpportunityBase.
79
+ */
80
+ @summary("Search opportunities")
81
+ @doc("Search for opportunities based on the provided filters")
82
+ @post
83
+ @route("/search")
84
+ search<T extends Models.OpportunityBase = Models.OpportunityBase>(
85
+ /** Opportunity search query */
86
+ @example("Pre-school education")
87
+ search?: string,
88
+
89
+ /** Filters to apply to the opportunity search
90
+ *
91
+ * Multiple filter conditions will be combined with AND logic, so that
92
+ * results only include opportunities that match all of the provided filters.
93
+ */
94
+ filters?: Models.OppFilters,
95
+
96
+ /** The sort order to apply to the results */
97
+ sorting?: Models.OppSorting,
98
+
99
+ /** Pagination instructions for the results */
100
+ pagination?: Pagination.PaginatedBodyParams,
101
+ ): Responses.Filtered<T, Models.OppFilters>;
62
102
  }
@@ -0,0 +1,75 @@
1
+ import "@typespec/http";
2
+
3
+ using TypeSpec.Http;
4
+ using TypeSpec.JsonSchema;
5
+ /** Models for sorting
6
+ *
7
+ * @example How to use the `Sorting` namespace
8
+ * ```typespec
9
+ *
10
+ * using CommonGrants // Exposes the `Sorting` and `Responses` namespaces
11
+ * using TypeSpec.Http;
12
+ *
13
+ * @route("/foo/")
14
+ * @get
15
+ * op list(sorting: Sorting.SortQueryParams): Responses.Sorted<MyModel>;
16
+ * ```
17
+ */
18
+ @jsonSchema
19
+ namespace CommonGrants.Sorting;
20
+
21
+ enum SortOrder {
22
+ asc,
23
+ desc,
24
+ }
25
+
26
+ /** Query parameters for sorting */
27
+ model SortQueryParams {
28
+ /** The field to sort by */
29
+ @query
30
+ @example("lastModifiedAt")
31
+ sortBy: unknown;
32
+
33
+ /** Implementation-defined sort key */
34
+ @query
35
+ @example("customField")
36
+ customSortBy?: string;
37
+
38
+ /** The order to sort by */
39
+ @query
40
+ @example(SortOrder.asc)
41
+ sortOrder?: SortOrder;
42
+ }
43
+
44
+ /** Sorting parameters included in the request body */
45
+ model SortBodyParams {
46
+ /** The field to sort by */
47
+ @example("lastModifiedAt")
48
+ sortBy: unknown;
49
+
50
+ /** Implementation-defined sort key */
51
+ @example("customField")
52
+ customSortBy?: string;
53
+
54
+ /** The order to sort by */
55
+ @example(SortOrder.asc)
56
+ sortOrder?: SortOrder;
57
+ }
58
+
59
+ /** Information about the sort order of the items returned */
60
+ model SortedResultsInfo {
61
+ /** The field results are sorted by, or "custom" if an implementation-defined sort key is used */
62
+ @example("lastModifiedAt")
63
+ sortBy: string;
64
+
65
+ /** Implementation-defined sort key used to sort the results, if applicable */
66
+ @example("customField")
67
+ customSortBy?: string;
68
+
69
+ /** The order in which the results are sorted, e.g. ascending or descending */
70
+ @example(SortOrder.asc)
71
+ sortOrder: SortOrder;
72
+
73
+ /** Non-fatal errors that occurred during sorting */
74
+ errors?: string[];
75
+ }
@@ -1,3 +1,5 @@
1
+ using TypeSpec.JsonSchema;
2
+
1
3
  /** A collection of base data types used throughout the CommonGrants API
2
4
  *
3
5
  * @example How to use the `Types` namespace
@@ -16,21 +18,43 @@
16
18
  * }
17
19
  * ```
18
20
  */
21
+ @jsonSchema
19
22
  namespace CommonGrants.Types;
20
23
 
21
- /** A time on a clock, without a timezone, in ISO 8601 format HH:mm:ss */
22
- @example(isoTime.fromISO("17:00:00"))
23
- scalar isoTime extends plainTime;
24
-
25
- /** A date on a calendar in ISO 8601 format YYYY-MM-DD */
26
- @example(isoDate.fromISO("2025-01-01"))
27
- scalar isoDate extends plainTime;
24
+ // ########################################
25
+ // String types
26
+ // ########################################
28
27
 
29
28
  /** A universally unique identifier */
30
29
  @example("30a12e5e-5940-4c08-921c-17a8960fcf4b")
31
30
  @format("uuid")
32
31
  scalar uuid extends string;
33
32
 
33
+ /** An email address */
34
+ @example("test@example.com")
35
+ @format("email")
36
+ scalar email extends string;
37
+
38
+ /** An Employer Identification Number (EIN) issued by the IRS */
39
+ @example("12-3456789")
40
+ @pattern("^[0-9]{2}-[0-9]{7}$")
41
+ scalar employerTaxId extends string;
42
+
43
+ /** A Unique Entity Identifier (UEI) issued by SAM.gov */
44
+ @example("12ABC34DEF56")
45
+ @pattern("^[A-z0-9]{12}$")
46
+ scalar samUEI extends string;
47
+
48
+ /** A Data Universal Numbering System (DUNS) number */
49
+ @example("123456789")
50
+ @example("12-345-6789")
51
+ @example("123456789-1234")
52
+ scalar duns extends string;
53
+
54
+ // ########################################
55
+ // Numeric types
56
+ // ########################################
57
+
34
58
  /** A decimal number (with variable scale) encoded as a string, to avoid floating point issues */
35
59
  @pattern(
36
60
  "^-?[0-9]+\\.?[0-9]*$",
@@ -40,3 +64,20 @@ scalar uuid extends string;
40
64
  @example("100.5", #{ title: "Scale 1" })
41
65
  @example("-100.5", #{ title: "Negative, scale 2" })
42
66
  scalar decimalString extends string;
67
+
68
+ // ########################################
69
+ // Date types
70
+ // ########################################
71
+
72
+ /** A time on a clock, without a timezone, in ISO 8601 format HH:mm:ss */
73
+ @example(isoTime.fromISO("17:00:00"))
74
+ scalar isoTime extends plainTime;
75
+
76
+ /** A date on a calendar in ISO 8601 format YYYY-MM-DD */
77
+ @example(isoDate.fromISO("2025-01-01"))
78
+ scalar isoDate extends plainDate;
79
+
80
+ /** A calendar year */
81
+ @example("2025")
82
+ @pattern("^[0-9]{4}$")
83
+ scalar calendarYear extends string;