@common-grants/core 0.1.0-alpha.10

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.
@@ -0,0 +1,31 @@
1
+ import "./base.tsp";
2
+
3
+ namespace CommonGrants.Filters;
4
+
5
+ // ############################################################################
6
+ // String comparison filter
7
+ // ############################################################################
8
+
9
+ /** A filter that applies a comparison to a string value */
10
+ model StringComparisonFilter {
11
+ /** The operator to apply to the filter value */
12
+ operator: EquivalenceOperators | StringOperators;
13
+
14
+ /** The value to use for the filter operation */
15
+ @example("value")
16
+ value: string;
17
+ }
18
+
19
+ // ############################################################################
20
+ // String array filter
21
+ // ############################################################################
22
+
23
+ /** A filter that applies a filter to an array of strings */
24
+ model StringArrayFilter {
25
+ /** The operator to apply to the filter value */
26
+ operator: ArrayOperators;
27
+
28
+ /** The value to use for the filter operation */
29
+ @example(#["value1", "value2"])
30
+ value: Array<string>;
31
+ }
@@ -0,0 +1,8 @@
1
+ import "./types.tsp";
2
+ import "./fields/index.tsp";
3
+ import "./models/index.tsp";
4
+ import "./routes/index.tsp";
5
+ import "./responses/index.tsp";
6
+
7
+ /** An internal namespace for the `@common-grants/core` package */
8
+ namespace CommonGrants;
@@ -0,0 +1,22 @@
1
+ import "@typespec/json-schema";
2
+
3
+ // Import individual schemas to provide a consistent interface
4
+ // and make them available throughout the namespace
5
+ import "./opportunity/index.tsp";
6
+
7
+ using TypeSpec.JsonSchema;
8
+
9
+ /** A collection of models for the CommonGrants API
10
+ *
11
+ * @example How to use the `Models` namespace
12
+ *
13
+ * ```typespec
14
+ * import "@common-grants/core";
15
+ *
16
+ * using CommonGrants; // exposes the Models namespace
17
+ *
18
+ * model MyModel extends Models.OpportunityBase {}
19
+ * ```
20
+ */
21
+ @jsonSchema // and emit these Schemas.as JSON schemas
22
+ namespace CommonGrants.Models;
@@ -0,0 +1,72 @@
1
+ import "./status.tsp";
2
+ import "./funding.tsp";
3
+ import "./timeline.tsp";
4
+ import "../../fields/index.tsp";
5
+ import "../../types.tsp";
6
+
7
+ /** Namespace for Core that are specific to funding opportunities */
8
+ namespace CommonGrants.Models;
9
+
10
+ using CommonGrants.Fields;
11
+ using CommonGrants.Types;
12
+
13
+ // ########################################
14
+ // Model definition
15
+ // ########################################
16
+
17
+ /** The base model for a funding opportunity.
18
+ *
19
+ * It supports customization by extending the `customFields` property.
20
+ *
21
+ * @example How to declare a new Opportunity model with custom fields
22
+ *
23
+ * ```typespec
24
+ * using CommonGrants.Fields;
25
+ * using CommonGrants.Models;
26
+ *
27
+ * model Agency extends CustomField {
28
+ * type: CustomFieldType.string;
29
+ *
30
+ * @example("Department of Transportation")
31
+ * value: string;
32
+ * }
33
+ *
34
+ * model NewFields extends CustomFieldMap {
35
+ * agency: Agency;
36
+ * }
37
+ *
38
+ * model CustomOpportunity extends OpportunityBase {
39
+ * customFields: NewFields;
40
+ * }
41
+ * ```
42
+ */
43
+ @doc("A funding opportunity") // Overrides internal docstrings when emitting OpenAPI
44
+ model OpportunityBase {
45
+ /** Globally unique id for the opportunity */
46
+ @visibility(Lifecycle.Read)
47
+ id: uuid;
48
+
49
+ /** Title or name of the funding opportunity */
50
+ title: string;
51
+
52
+ /** Status of the opportunity */
53
+ status: OppStatus;
54
+
55
+ /** Description of the opportunity's purpose and scope */
56
+ description: string;
57
+
58
+ /** Details about the funding available */
59
+ funding: OppFunding;
60
+
61
+ /** Key dates for the opportunity, such as when the application opens and closes */
62
+ keyDates: OppTimeline;
63
+
64
+ /** URL for the original source of the opportunity */
65
+ source?: url;
66
+
67
+ /** Additional custom fields specific to this opportunity */
68
+ customFields?: Record<CustomField>;
69
+
70
+ // Spreads the fields from the Metadata model into the Opportunity model
71
+ ...SystemMetadata;
72
+ }
@@ -0,0 +1,71 @@
1
+ import "../index.tsp";
2
+ import "../../fields/index.tsp";
3
+
4
+ namespace CommonGrants.Models;
5
+
6
+ using CommonGrants.Fields;
7
+
8
+ // ########################################
9
+ // Model definition
10
+ // ########################################
11
+
12
+ /** Details about the funding available for this opportunity */
13
+ @example(
14
+ Examples.Funding.awardRange,
15
+ #{ title: "Award range but no total limit" }
16
+ )
17
+ @example(
18
+ Examples.Funding.onlyLimit,
19
+ #{ title: "Total funding limit but no award range" }
20
+ )
21
+ @example(Examples.Funding.allFields, #{ title: "All fields defined" })
22
+ model OppFunding {
23
+ /** Total amount of funding available for this opportunity */
24
+ totalAmountAvailable?: Money;
25
+
26
+ /** Minimum amount of funding granted per award */
27
+ minAwardAmount?: Money;
28
+
29
+ /** Maximum amount of funding granted per award */
30
+ maxAwardAmount?: Money;
31
+
32
+ /** Minimum number of awards granted */
33
+ minAwardCount?: integer;
34
+
35
+ /** Maximum number of awards granted */
36
+ maxAwardCount?: integer;
37
+
38
+ /** Estimated number of awards that will be granted */
39
+ estimatedAwardCount?: integer;
40
+ }
41
+
42
+ // ########################################
43
+ // Model examples
44
+ // ########################################
45
+
46
+ /** Examples of the OppFunding model */
47
+ namespace Examples.Funding {
48
+ /** A FundingDetails example in which all of the fields are defined */
49
+ const allFields = #{
50
+ totalAmountAvailable: #{ amount: "1000000.00", currency: "USD" },
51
+ minAwardAmount: #{ amount: "10000.00", currency: "USD" },
52
+ maxAwardAmount: #{ amount: "50000.00", currency: "USD" },
53
+ minAwardCount: 5,
54
+ maxAwardCount: 20,
55
+ estimatedAwardCount: 10,
56
+ };
57
+
58
+ /** A FundingDetails example that has an award range but no total limit */
59
+ const awardRange = #{
60
+ minAwardAmount: #{ amount: "10000.00", currency: "USD" },
61
+ maxAwardAmount: #{ amount: "50000.00", currency: "USD" },
62
+ minAwardCount: 5,
63
+ maxAwardCount: 20,
64
+ };
65
+
66
+ /** A FundingDetails example that has a total limit but no award range */
67
+ const onlyLimit = #{
68
+ totalAmountAvailable: #{ amount: "1000000.00", currency: "USD" },
69
+ estimatedAwardCount: 10,
70
+ };
71
+ }
@@ -0,0 +1,7 @@
1
+ import "./status.tsp";
2
+ import "./funding.tsp";
3
+ import "./timeline.tsp";
4
+ import "./base.tsp";
5
+ import "./search.tsp";
6
+
7
+ namespace CommonGrants.Models;
@@ -0,0 +1,102 @@
1
+ import "../../filters/index.tsp";
2
+ import "../../sorting.tsp";
3
+
4
+ namespace CommonGrants.Models;
5
+
6
+ // ############################################################################
7
+ // Filter models
8
+ // ############################################################################
9
+
10
+ /** Filters to apply when searching for opportunities */
11
+ model OppFilters {
12
+ /** The default filters to apply to the search */
13
+ ...OppDefaultFilters;
14
+
15
+ /** Additional implementation-defined filters to apply to the search */
16
+ customFilters?: Record<Filters.DefaultFilter>;
17
+ }
18
+
19
+ /** The standard set of filters supported for opportunity searches */
20
+ model OppDefaultFilters extends Record<Filters.DefaultFilter> {
21
+ /** `status.value` matches one of the following values */
22
+ @example(#{
23
+ operator: Filters.ArrayOperators.in,
24
+ value: #["forecasted", "open"],
25
+ })
26
+ status?: Filters.StringArrayFilter;
27
+
28
+ /** `keyDates.closeDate` is between the given range */
29
+ @example(#{
30
+ operator: Filters.RangeOperators.between,
31
+ value: #{
32
+ min: Types.isoDate.fromISO("2021-01-01"),
33
+ max: Types.isoDate.fromISO("2021-01-02"),
34
+ },
35
+ })
36
+ closeDateRange?: Filters.DateRangeFilter;
37
+
38
+ /** `funding.totalAmountAvailable` is between the given range
39
+ *
40
+ * Funding amounts that are denominated in a different currency will
41
+ * be excluded from the search.
42
+ */
43
+ @example(#{
44
+ operator: Filters.RangeOperators.between,
45
+ value: #{
46
+ min: #{ amount: "1000000", currency: "USD" },
47
+ max: #{ amount: "2000000", currency: "USD" },
48
+ },
49
+ })
50
+ totalFundingAvailableRange?: Filters.MoneyRangeFilter;
51
+
52
+ /** `funding.minAwardAmount` is between the given range
53
+ *
54
+ * Funding amounts that are denominated in a different currency will
55
+ * be excluded from the search.
56
+ */
57
+ @example(#{
58
+ operator: Filters.RangeOperators.between,
59
+ value: #{
60
+ min: #{ amount: "1000000", currency: "USD" },
61
+ max: #{ amount: "2000000", currency: "USD" },
62
+ },
63
+ })
64
+ minAwardAmountRange?: Filters.MoneyRangeFilter;
65
+
66
+ /** `funding.maxAwardAmount` is between the given range.
67
+ *
68
+ * Funding amounts that are denominated in a different currency will
69
+ * be excluded from the search.
70
+ */
71
+ @example(#{
72
+ operator: Filters.RangeOperators.between,
73
+ value: #{
74
+ min: #{ amount: "1000000", currency: "USD" },
75
+ max: #{ amount: "2000000", currency: "USD" },
76
+ },
77
+ })
78
+ maxAwardAmountRange?: Filters.MoneyRangeFilter;
79
+ }
80
+
81
+ // ############################################################################
82
+ // Sorting models
83
+ // ############################################################################
84
+
85
+ enum OppSortBy {
86
+ lastModifiedAt,
87
+ createdAt,
88
+ title,
89
+ status: "status.value",
90
+ closeDate: "keyDates.closeDate",
91
+ maxAwardAmount: "funding.maxAwardAmount",
92
+ minAwardAmount: "funding.minAwardAmount",
93
+ totalFundingAvailable: "funding.totalAmountAvailable",
94
+ estimatedAwardCount: "funding.estimatedAwardCount",
95
+ custom,
96
+ }
97
+
98
+ model OppSorting extends Sorting.SortBodyParams {
99
+ /** The field to sort by */
100
+ @example(OppSortBy.lastModifiedAt)
101
+ sortBy: OppSortBy;
102
+ }
@@ -0,0 +1,52 @@
1
+ import "@typespec/json-schema";
2
+ import "@typespec/openapi3";
3
+
4
+ namespace CommonGrants.Models;
5
+
6
+ // ########################################
7
+ // Opportunity status options
8
+ // ########################################
9
+
10
+ /** The set of values accepted for opportunity status */
11
+ enum OppStatusOptions {
12
+ forecasted,
13
+ open,
14
+ closed,
15
+ custom,
16
+ }
17
+
18
+ // ########################################
19
+ // Opportunity status model
20
+ // ########################################
21
+
22
+ /** The status of the opportunity */
23
+ @example(Examples.OppStatus.custom)
24
+ @example(Examples.OppStatus.default)
25
+ model OppStatus {
26
+ /** The status value, from a predefined set of options */
27
+ value: OppStatusOptions;
28
+
29
+ /** A custom status value */
30
+ customValue?: string;
31
+
32
+ /** A human-readable description of the status */
33
+ description?: string;
34
+ }
35
+
36
+ // ########################################
37
+ // Opportunity status model examples
38
+ // ########################################
39
+
40
+ /** Examples of the OppStatus model */
41
+ namespace Examples.OppStatus {
42
+ const default = #{
43
+ value: OppStatusOptions.open,
44
+ description: "The opportunity is currently accepting applications",
45
+ };
46
+
47
+ const custom = #{
48
+ value: OppStatusOptions.custom,
49
+ customValue: "archived",
50
+ description: "The opportunity is archived and shouldn't appear in search results",
51
+ };
52
+ }
@@ -0,0 +1,46 @@
1
+ import "../../fields/index.tsp";
2
+ import "../../types.tsp";
3
+
4
+ namespace CommonGrants.Models;
5
+
6
+ using CommonGrants.Fields;
7
+ using CommonGrants.Types;
8
+
9
+ // ########################################
10
+ // Model definition
11
+ // ########################################
12
+
13
+ /** Key dates in the opportunity's timeline, such as when the application opens and closes */
14
+ @example(Examples.Timeline.opportunity)
15
+ model OppTimeline {
16
+ /** The date (and time) at which the opportunity begins accepting applications */
17
+ appOpens?: Event;
18
+
19
+ /** The final deadline for submitting applications */
20
+ appDeadline?: Event;
21
+
22
+ /** An optional map of other key dates in the opportunity timeline
23
+ *
24
+ * Examples might include a deadline for questions, anticipated award date, etc.
25
+ */
26
+ otherDates?: Record<Event>;
27
+ }
28
+
29
+ // ########################################
30
+ // Model examples
31
+ // ########################################
32
+
33
+ /** Examples of the OppTimeline model */
34
+ namespace Examples.Timeline {
35
+ const opportunity = #{
36
+ appOpens: Fields.Examples.Event.openDate,
37
+ appDeadline: Fields.Examples.Event.deadline,
38
+ otherDates: #{
39
+ anticipatedAward: #{
40
+ name: "Anticipated award date",
41
+ date: isoDate.fromISO("2025-03-15"),
42
+ description: "When we expect to announce awards for this opportunity.",
43
+ },
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,55 @@
1
+ import "@typespec/http";
2
+
3
+ using TypeSpec.Http;
4
+
5
+ /** Models and utilities for pagination */
6
+ namespace CommonGrants.Pagination;
7
+
8
+ /** Query parameters for paginated routes */
9
+ model PaginatedQueryParams {
10
+ /** The page to return */
11
+ @query
12
+ @pageIndex
13
+ @minValue(1)
14
+ page?: int32 = 1;
15
+
16
+ /** The number of items to return per page */
17
+ @query
18
+ @pageSize
19
+ @minValue(1)
20
+ pageSize?: int32 = 100;
21
+ }
22
+
23
+ /** Body parameters for paginated routes */
24
+ model PaginatedBodyParams {
25
+ /** The page to return */
26
+ @pageIndex
27
+ @minValue(1)
28
+ page?: int32 = 1;
29
+
30
+ /** The number of items to return per page */
31
+ @pageSize
32
+ @minValue(1)
33
+ pageSize?: int32 = 100;
34
+ }
35
+
36
+ /** Details about the paginated results */
37
+ model PaginationInfo {
38
+ /** Current page number (indexing starts at 1) */
39
+ @example(1)
40
+ @minValue(1)
41
+ page: int32;
42
+
43
+ /** Number of items per page */
44
+ @example(20)
45
+ @minValue(1)
46
+ pageSize: integer;
47
+
48
+ /** Total number of items across all pages */
49
+ @example(100)
50
+ totalItems?: integer;
51
+
52
+ /** Total number of pages */
53
+ @example(5)
54
+ totalPages?: integer;
55
+ }
@@ -0,0 +1,30 @@
1
+ import "@typespec/http";
2
+
3
+ namespace CommonGrants.Responses;
4
+
5
+ /** A standard error response schema, used to create custom error responses
6
+ *
7
+ * @example - How to use this model to create custom error response schemas
8
+ *
9
+ * ```
10
+ * import "@typespec/http"
11
+ *
12
+ * alias Unauthorized = Error & Http.UnauthorizedResponse
13
+ * ```
14
+ */
15
+ @error
16
+ @doc("A non-2xx response schema")
17
+ model Error {
18
+ @example(400)
19
+ status: int32;
20
+
21
+ /** Human-readable error message */
22
+ @example("Error")
23
+ message: string;
24
+
25
+ /** List of errors */
26
+ errors: Array<unknown>;
27
+ }
28
+
29
+ alias Unauthorized = Error & Http.UnauthorizedResponse;
30
+ alias NotFound = Error & Http.NotFoundResponse;
@@ -0,0 +1,24 @@
1
+ import "@typespec/http";
2
+
3
+ import "./error.tsp";
4
+ import "./success.tsp";
5
+
6
+ /** A standardized set of response schemas for CommonGrants API routes
7
+ *
8
+ * @example How to use the `Responses` namespace
9
+ *
10
+ * ```typespec
11
+ * import "@typespec/http";
12
+ * import "@common-grants/core";
13
+ *
14
+ * using CommonGrants; // exposes the Responses namespace
15
+ * using TypeSpec.Http;
16
+ *
17
+ * @route("/pets")
18
+ * namespace Pets {
19
+ * @get
20
+ * op getPets(): Responses.Ok<Pet> | Responses.Unauthorized;
21
+ * }
22
+ * ```
23
+ */
24
+ namespace CommonGrants.Responses;
@@ -0,0 +1,141 @@
1
+ import "../pagination.tsp";
2
+ import "../sorting.tsp";
3
+ import "@typespec/http";
4
+
5
+ namespace CommonGrants.Responses;
6
+
7
+ // ############################################################################
8
+ // Default success response
9
+ // ############################################################################
10
+
11
+ model Success {
12
+ @example(200)
13
+ status: int32;
14
+
15
+ @example("Success")
16
+ message: string;
17
+ }
18
+
19
+ // ############################################################################
20
+ // 200 response
21
+ // ############################################################################
22
+
23
+ /** Template for a 200 response with data
24
+ *
25
+ * @template T The schema for the value of the `"data"` property in this response
26
+ * @example How to specify a custom 200 response model
27
+ *
28
+ * ```typespec
29
+ * // Define a model
30
+ * model CustomModel {
31
+ * id: string;
32
+ * description: string;
33
+ * }
34
+ *
35
+ * // Pass that model to the `Ok` template
36
+ * alias CustomModel200 = Responses.Ok<CustomModel>;
37
+ * ```
38
+ */
39
+ @doc("A 200 response with data")
40
+ model Ok<T> extends Success {
41
+ // Inherit the 200 status code
42
+ ...Http.OkResponse;
43
+
44
+ /** Response data */
45
+ data: T;
46
+ }
47
+
48
+ // ############################################################################
49
+ // 200 paginated response
50
+ // ############################################################################
51
+
52
+ /** Template for a 200 response with paginated list of items
53
+ *
54
+ * @template T The schema for the value of the `"items"` property in this response
55
+ * @example How to specify a custom paginated response model
56
+ *
57
+ * ```typespec
58
+ * // Define a model
59
+ * model CustomModel {
60
+ * id: string;
61
+ * description: string;
62
+ * }
63
+ *
64
+ * // Pass that model to the `Ok` template
65
+ * alias CustomModelResponse = Responses.Paginated<CustomModel>;
66
+ * ```
67
+ */
68
+ @doc("A 200 response with a paginated list of items")
69
+ model Paginated<T> extends Success {
70
+ // Inherit the 200 status code
71
+ ...Http.OkResponse;
72
+
73
+ /** Items from the current page */
74
+ @pageItems
75
+ items: T[];
76
+
77
+ /** Details about the paginated results */
78
+ paginationInfo: Pagination.PaginationInfo;
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.SortInfo;
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: FilterT;
141
+ }
@@ -0,0 +1,22 @@
1
+ import "@typespec/http";
2
+ import "@typespec/rest";
3
+
4
+ // Import individual route files to provide a consistent interface
5
+ import "./opportunities.tsp";
6
+
7
+ /** A series of routing interfaces for CommonGrants API endpoints
8
+ *
9
+ * @example How to use the `Routes` namespace
10
+ *
11
+ * ```typespec
12
+ * import "@common-grants/core";
13
+ *
14
+ * using CommonGrants; // exposes the Routes namespace
15
+ *
16
+ * namespace MyRoutes {
17
+ * alias OpportunityRouter = Routes.Opportunities;
18
+ *
19
+ * op list is OpportunityRouter.list;
20
+ * }
21
+ */
22
+ namespace CommonGrants.Routes;