@common-grants/core 0.1.0-alpha.8 → 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.
Files changed (40) hide show
  1. package/README.md +21 -62
  2. package/lib/api.tsp +23 -8
  3. package/lib/core/fields/address.tsp +86 -0
  4. package/lib/core/fields/custom-field.tsp +11 -9
  5. package/lib/core/fields/email.tsp +35 -0
  6. package/lib/core/fields/event.tsp +138 -16
  7. package/lib/core/fields/index.tsp +23 -1
  8. package/lib/core/fields/metadata.tsp +4 -3
  9. package/lib/core/fields/money.tsp +8 -7
  10. package/lib/core/fields/name.tsp +30 -0
  11. package/lib/core/fields/pcs.tsp +112 -0
  12. package/lib/core/fields/phone.tsp +65 -0
  13. package/lib/core/filters/base.tsp +83 -0
  14. package/lib/core/filters/date.tsp +38 -0
  15. package/lib/core/filters/index.tsp +40 -0
  16. package/lib/core/filters/money.tsp +38 -0
  17. package/lib/core/filters/numeric.tsp +50 -0
  18. package/lib/core/filters/string.tsp +31 -0
  19. package/lib/core/index.tsp +1 -1
  20. package/lib/core/models/application.tsp +142 -0
  21. package/lib/core/models/index.tsp +10 -24
  22. package/lib/core/models/opportunity/base.tsp +34 -38
  23. package/lib/core/models/opportunity/funding.tsp +16 -10
  24. package/lib/core/models/opportunity/index.tsp +2 -1
  25. package/lib/core/models/opportunity/search.tsp +102 -0
  26. package/lib/core/models/opportunity/status.tsp +22 -2
  27. package/lib/core/models/opportunity/timeline.tsp +23 -13
  28. package/lib/core/models/organization.tsp +105 -0
  29. package/lib/core/models/person.tsp +43 -0
  30. package/lib/core/pagination.tsp +56 -0
  31. package/lib/core/responses/error.tsp +1 -1
  32. package/lib/core/responses/index.tsp +22 -1
  33. package/lib/core/responses/success.tsp +84 -29
  34. package/lib/core/routes/index.tsp +5 -6
  35. package/lib/core/routes/opportunities.tsp +46 -7
  36. package/lib/core/sorting.tsp +75 -0
  37. package/lib/core/types.tsp +66 -7
  38. package/lib/main.tsp +0 -85
  39. package/package.json +16 -10
  40. package/lib/core/routes/utils.tsp +0 -19
@@ -1,6 +1,12 @@
1
+ import "../pagination.tsp";
2
+ import "../sorting.tsp";
1
3
  import "@typespec/http";
2
4
 
3
- namespace Core.Responses;
5
+ namespace CommonGrants.Responses;
6
+
7
+ // ############################################################################
8
+ // Default success response
9
+ // ############################################################################
4
10
 
5
11
  model Success {
6
12
  @example(200)
@@ -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
  }
@@ -4,20 +4,19 @@ import "@typespec/rest";
4
4
  // Import individual route files to provide a consistent interface
5
5
  import "./opportunities.tsp";
6
6
 
7
- /** A series of interfaces for CommonGrants API routes
7
+ /** A series of routing interfaces for CommonGrants API endpoints
8
8
  *
9
9
  * @example How to use the `Routes` namespace
10
10
  *
11
- * ```tsp
11
+ * ```typespec
12
12
  * import "@common-grants/core";
13
13
  *
14
- * using CommonGrants;
14
+ * using CommonGrants; // exposes the Routes namespace
15
15
  *
16
16
  * namespace MyRoutes {
17
17
  * alias OpportunityRouter = Routes.Opportunities;
18
18
  *
19
- * op List is OpportunityRouter.list;
19
+ * op list is OpportunityRouter.list;
20
20
  * }
21
- * ```
22
21
  */
23
- namespace Core.Routes;
22
+ namespace CommonGrants.Routes;
@@ -1,14 +1,13 @@
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
- namespace Core.Routes;
6
+ namespace CommonGrants.Routes;
7
7
 
8
8
  // Expose the contents of the Http and Rest namespaces
9
9
  // these include the decorators @route, @get, etc.
10
10
  using TypeSpec.Http;
11
- using Core;
12
11
 
13
12
  /** A re-usable interface for an Opportunities router
14
13
  *
@@ -20,7 +19,7 @@ using Core;
20
19
  * For more information, see
21
20
  * [TypeSpec docs](https://typespec.io/docs/language-basics/interfaces/#templating-interface-operations)
22
21
  *
23
- * @example Using the the default type for the list operation and
22
+ * @example Using the default type for the list operation and
24
23
  * a custom model for the read operation:
25
24
  * ```typespec
26
25
  * using TypeSpec.Http;
@@ -36,6 +35,10 @@ using Core;
36
35
  * ```
37
36
  */
38
37
  interface Opportunities {
38
+ // ###############################
39
+ // List opportunities
40
+ // ###############################
41
+
39
42
  /** `GET /opportunities/` Get a paginated list of opportunities
40
43
  *
41
44
  * @template T Type of the paginated response model.
@@ -45,8 +48,12 @@ interface Opportunities {
45
48
  @doc("Get a paginated list of opportunities, sorted by `lastModifiedAt` with most recent first.")
46
49
  @list
47
50
  list<T extends Models.OpportunityBase = Models.OpportunityBase>(
48
- ...Utils.PaginatedQuery,
49
- ): Responses.Paginated<T> | Responses.Unauthorized;
51
+ ...Pagination.PaginatedQueryParams,
52
+ ): Responses.Paginated<T>;
53
+
54
+ // ##############################
55
+ // View an opportunity
56
+ // ##############################
50
57
 
51
58
  /** `GET /opportunities/<id>` View opportunity details
52
59
  *
@@ -59,5 +66,37 @@ interface Opportunities {
59
66
  read<T extends Models.OpportunityBase = Models.OpportunityBase>(
60
67
  /** The ID of the opportunity to view */
61
68
  @path id: Types.uuid,
62
- ): 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>;
63
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,18 +1,60 @@
1
- namespace Core.Types;
1
+ using TypeSpec.JsonSchema;
2
2
 
3
- /** A time on a clock, without a timezone, in ISO 8601 format HH:mm:ss */
4
- @example(isoTime.fromISO("17:00:00"))
5
- scalar isoTime extends plainTime;
3
+ /** A collection of base data types used throughout the CommonGrants API
4
+ *
5
+ * @example How to use the `Types` namespace
6
+ *
7
+ * ```typespec
8
+ * import "@common-grants/core";
9
+ *
10
+ * using CommonGrants; // exposes the Types namespace
11
+ *
12
+ * model MyModel {
13
+ * @example("30a12e5e-5940-4c08-921c-17a8960fcf4b")
14
+ * id: Types.uuid;
15
+ *
16
+ * @example(Types.isoDate.fromISO("2025-01-01"))
17
+ * keyDate: Types.isoDate;
18
+ * }
19
+ * ```
20
+ */
21
+ @jsonSchema
22
+ namespace CommonGrants.Types;
6
23
 
7
- /** A date on a calendar in ISO 8601 format YYYY-MM-DD */
8
- @example(isoDate.fromISO("2025-01-01"))
9
- scalar isoDate extends plainTime;
24
+ // ########################################
25
+ // String types
26
+ // ########################################
10
27
 
11
28
  /** A universally unique identifier */
12
29
  @example("30a12e5e-5940-4c08-921c-17a8960fcf4b")
13
30
  @format("uuid")
14
31
  scalar uuid extends string;
15
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
+
16
58
  /** A decimal number (with variable scale) encoded as a string, to avoid floating point issues */
17
59
  @pattern(
18
60
  "^-?[0-9]+\\.?[0-9]*$",
@@ -22,3 +64,20 @@ scalar uuid extends string;
22
64
  @example("100.5", #{ title: "Scale 1" })
23
65
  @example("-100.5", #{ title: "Negative, scale 2" })
24
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;
package/lib/main.tsp CHANGED
@@ -3,92 +3,7 @@
3
3
  // https://typespec.io/docs/extending-typespec/basics/#h-add-your-main-typespec-file
4
4
  import "../dist/src/index.js";
5
5
 
6
- // Import items from the Core namespace to make them available outside the package
7
6
  import "./core/index.tsp";
8
7
  import "./api.tsp";
9
8
 
10
9
  namespace CommonGrants;
11
-
12
- /** Base data types, e.g. `uuid` and `isoDate`, used to build more complex fields and models
13
- *
14
- * @example How to use the `Types` namespace
15
- *
16
- * ```tsp
17
- * import "@common-grants/core";
18
- *
19
- * using CommonGrants; // exposes the Types namespace
20
- *
21
- * model MyModel {
22
- * id: Types.uuid;
23
- * createdAt: Types.isoDate;
24
- * }
25
- * ```
26
- */
27
- alias Types = Core.Types;
28
-
29
- /** A standard set of fields, e.g. `money` that can be reused across models
30
- *
31
- * @example How to use the `Fields` namespace
32
- *
33
- * ```tsp
34
- * import "@common-grants/core";
35
- *
36
- * using CommonGrants; // exposes the Fields namespace
37
- *
38
- * model MyModel {
39
- * amount: Fields.money;
40
- * }
41
- * ```
42
- */
43
- alias Fields = Core.Fields;
44
-
45
- /** A standardized set of response schemas for CommonGrants API routes
46
- *
47
- * @example How to use the `Responses` namespace
48
- *
49
- * ```tsp
50
- * import "@typespec/http";
51
- * import "@common-grants/core";
52
- *
53
- * using CommonGrants; // exposes the Responses namespace
54
- * using TypeSpec.Http;
55
- *
56
- * @route("/pets")
57
- * namespace Pets {
58
- * @get
59
- * getPets(): Responses.Ok<Pet> | Responses.Unauthorized;
60
- * }
61
- * ```
62
- */
63
- alias Responses = Core.Responses;
64
-
65
- /** A collection of models for the CommonGrants API
66
- *
67
- * @example How to use the `Models` namespace
68
- *
69
- * ```tsp
70
- * import "@common-grants/core";
71
- *
72
- * using CommonGrants; // exposes the Models namespace
73
- *
74
- * model MyModel extends Models.OpportunityBase {}
75
- * ```
76
- */
77
- alias Models = Core.Models;
78
-
79
- /** A series of routing interfaces for CommonGrants API endpoints
80
- *
81
- * @example How to use the `Routes` namespace
82
- *
83
- * ```tsp
84
- * import "@common-grants/core";
85
- *
86
- * using CommonGrants; // exposes the Routes namespace
87
- *
88
- * namespace MyRoutes {
89
- * alias OpportunityRouter = Routes.Opportunities;
90
- *
91
- * op List is OpportunityRouter.list;
92
- * }
93
- */
94
- alias Routes = Core.Routes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@common-grants/core",
3
- "version": "0.1.0-alpha.8",
3
+ "version": "0.1.0",
4
4
  "description": "TypeSpec library for defining grant opportunity data models and APIs",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -21,6 +21,15 @@
21
21
  "lib/**/*.tsp",
22
22
  "!src/**/*.test.*"
23
23
  ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/HHS/simpler-grants-protocol.git",
27
+ "directory": "lib/core"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/HHS/simpler-grants-protocol/issues"
31
+ },
32
+ "homepage": "https://github.com/HHS/simpler-grants-protocol/tree/main/lib/core/#readme",
24
33
  "scripts": {
25
34
  "clean": "rimraf dist tsp-output",
26
35
  "build": "tsc -p .",
@@ -31,10 +40,7 @@
31
40
  "format": "prettier --write . && tsp format lib",
32
41
  "check:lint": "eslint",
33
42
  "check:format": "prettier --check . && tsp format lib --check",
34
- "checks": "npm run check:lint && npm run check:format",
35
- "docs:build": "npx @redocly/cli build-docs tsp-output/@typespec/openapi3/openapi.yaml --output ./dist/redocly.html",
36
- "docs:preview": "open ./dist/redocly.html",
37
- "docs": "npm run typespec && npm run docs:build && npm run docs:preview"
43
+ "checks": "npm run check:lint && npm run check:format"
38
44
  },
39
45
  "keywords": [
40
46
  "typespec",
@@ -45,11 +51,11 @@
45
51
  "author": "CommonGrants",
46
52
  "license": "CC0-1.0",
47
53
  "peerDependencies": {
48
- "@typespec/compiler": "^0.63.0",
49
- "@typespec/http": "^0.63.0",
50
- "@typespec/json-schema": "^0.63.0",
51
- "@typespec/openapi3": "^0.63.0",
52
- "@typespec/rest": "^0.63.0"
54
+ "@typespec/compiler": "^0.66.0",
55
+ "@typespec/http": "^0.66.0",
56
+ "@typespec/json-schema": "^0.66.0",
57
+ "@typespec/openapi3": "^0.66.0",
58
+ "@typespec/rest": "^0.66.0"
53
59
  },
54
60
  "devDependencies": {
55
61
  "@types/node": "^20.10.6",
@@ -1,19 +0,0 @@
1
- import "@typespec/http";
2
-
3
- using TypeSpec.Http;
4
-
5
- /** Utility models and functions for API routes */
6
- namespace Core.Routes.Utils;
7
-
8
- /** A set of query parameters for paginated routes */
9
- model PaginatedQuery {
10
- /** The page to return */
11
- @query
12
- @pageIndex
13
- page?: int32 = 1;
14
-
15
- /** The number of items to return per page */
16
- @query
17
- @pageSize
18
- pageSize?: int32 = 100;
19
- }