@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.
- package/README.md +21 -62
- package/lib/api.tsp +23 -8
- package/lib/core/fields/address.tsp +86 -0
- package/lib/core/fields/custom-field.tsp +11 -9
- package/lib/core/fields/email.tsp +35 -0
- package/lib/core/fields/event.tsp +138 -16
- package/lib/core/fields/index.tsp +23 -1
- package/lib/core/fields/metadata.tsp +4 -3
- package/lib/core/fields/money.tsp +8 -7
- package/lib/core/fields/name.tsp +30 -0
- package/lib/core/fields/pcs.tsp +112 -0
- package/lib/core/fields/phone.tsp +65 -0
- package/lib/core/filters/base.tsp +83 -0
- package/lib/core/filters/date.tsp +38 -0
- package/lib/core/filters/index.tsp +40 -0
- package/lib/core/filters/money.tsp +38 -0
- package/lib/core/filters/numeric.tsp +50 -0
- package/lib/core/filters/string.tsp +31 -0
- package/lib/core/index.tsp +1 -1
- package/lib/core/models/application.tsp +142 -0
- package/lib/core/models/index.tsp +10 -24
- package/lib/core/models/opportunity/base.tsp +34 -38
- package/lib/core/models/opportunity/funding.tsp +16 -10
- package/lib/core/models/opportunity/index.tsp +2 -1
- package/lib/core/models/opportunity/search.tsp +102 -0
- package/lib/core/models/opportunity/status.tsp +22 -2
- package/lib/core/models/opportunity/timeline.tsp +23 -13
- package/lib/core/models/organization.tsp +105 -0
- package/lib/core/models/person.tsp +43 -0
- package/lib/core/pagination.tsp +56 -0
- package/lib/core/responses/error.tsp +1 -1
- package/lib/core/responses/index.tsp +22 -1
- package/lib/core/responses/success.tsp +84 -29
- package/lib/core/routes/index.tsp +5 -6
- package/lib/core/routes/opportunities.tsp +46 -7
- package/lib/core/sorting.tsp +75 -0
- package/lib/core/types.tsp +66 -7
- package/lib/main.tsp +0 -85
- package/package.json +16 -10
- package/lib/core/routes/utils.tsp +0 -19
package/README.md
CHANGED
|
@@ -35,30 +35,31 @@ The Opportunity model is templated to support custom fields. First define your c
|
|
|
35
35
|
|
|
36
36
|
import "@common-grants/core"; // Import the base specification library
|
|
37
37
|
|
|
38
|
-
// Allows us to use models defined in the
|
|
39
|
-
//
|
|
38
|
+
// Allows us to use models and fields defined in the core library without
|
|
39
|
+
// prefixing each item with `CommonGrants.Models` or `CommonGrants.Fields`
|
|
40
40
|
using CommonGrants.Models;
|
|
41
|
+
using CommonGrants.Fields;
|
|
41
42
|
|
|
42
43
|
namespace CustomAPI.CustomModels;
|
|
43
44
|
|
|
44
45
|
// Define a custom field
|
|
45
46
|
model Agency extends CustomField {
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
name: "Agency";
|
|
48
|
+
fieldType: CustomFieldType.string;
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
@example("Department of Transportation")
|
|
51
|
+
value: string;
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
description: "The agency responsible for this opportunity";
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// Extend the `OpportunityBase` model to create a new `CustomOpportunity` model
|
|
56
57
|
// that includes the new `Agency` field in its `customFields` property
|
|
57
58
|
model CustomOpportunity extends OpportunityBase {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
59
|
+
customFields: {
|
|
60
|
+
agency: Agency;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
62
63
|
```
|
|
63
64
|
|
|
64
65
|
### Override default routes
|
|
@@ -75,13 +76,14 @@ using CommonGrants.Routes;
|
|
|
75
76
|
using TypeSpec.Http;
|
|
76
77
|
|
|
77
78
|
@tag("Search")
|
|
78
|
-
@route("/opportunities")
|
|
79
|
+
@route("/common-grants/opportunities")
|
|
79
80
|
namespace CustomAPI.CustomRoutes {
|
|
80
|
-
|
|
81
|
+
alias OpportunitiesRouter = Opportunities;
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// Use the default model for list but custom model for read and search
|
|
84
|
+
op list is OpportunitiesRouter.list;
|
|
85
|
+
op read is OpportunitiesRouter.read<CustomModels.CustomOpportunity>;
|
|
86
|
+
op search is OpportunitiesRouter.search<CustomModels.CustomOpportunity>;
|
|
85
87
|
}
|
|
86
88
|
```
|
|
87
89
|
|
|
@@ -98,9 +100,8 @@ import "./routes.tsp"; // Import the routes from above
|
|
|
98
100
|
|
|
99
101
|
using TypeSpec.Http;
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
})
|
|
103
|
+
/** Description of your API goes here */
|
|
104
|
+
@service(#{ title: "Custom API" })
|
|
104
105
|
namespace CustomAPI;
|
|
105
106
|
```
|
|
106
107
|
|
|
@@ -126,46 +127,4 @@ Both strategies will generate an OpenAPI specification in the `tsp-output/` dire
|
|
|
126
127
|
|
|
127
128
|
- See the [TypeSpec documentation](https://typespec.org/docs/getting-started/overview) for more information on how to use TypeSpec.
|
|
128
129
|
- See the [CommonGrants docs](https://hhs.github.io/simpler-grants-protocol/) to learn more about the CommonGrants protocol.
|
|
129
|
-
|
|
130
|
-
## 💻 Contributing to the library
|
|
131
|
-
|
|
132
|
-
### Project structure
|
|
133
|
-
|
|
134
|
-
The `specs/` sub-directory is organized like this:
|
|
135
|
-
|
|
136
|
-
```
|
|
137
|
-
.
|
|
138
|
-
├── lib/ # Defines reusable models and routes for the library
|
|
139
|
-
│ ├── models/ # Defines base models like Opportunity, CustomField, etc.
|
|
140
|
-
│ ├── routes/ # Defines base routes like GET /opportunities
|
|
141
|
-
│ └── main.tsp # Exposes models and routes from the root of the library
|
|
142
|
-
|
|
|
143
|
-
├── src/
|
|
144
|
-
│ ├── index.ts # Defines the entry point for the library
|
|
145
|
-
│ └── lib.ts # Creates a new TypeSpec library definition
|
|
146
|
-
|
|
|
147
|
-
├── dist/ # .gitignored directory that stores the output of `npm build`
|
|
148
|
-
|
|
|
149
|
-
├── package.json # Manages dependencies, commands, and library metadata
|
|
150
|
-
├── tsconfig.json # Manages TypeScript configuration
|
|
151
|
-
└── tspconfig.yaml # Manages TypeSpec configuration
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Pre-requisites
|
|
155
|
-
|
|
156
|
-
Node version 20 or later. Check with `node --version`
|
|
157
|
-
|
|
158
|
-
### Commands
|
|
159
|
-
|
|
160
|
-
All commands are run from the root of the project, from a terminal:
|
|
161
|
-
|
|
162
|
-
| Command | Action |
|
|
163
|
-
| :--------------------- | :----------------------------------------- |
|
|
164
|
-
| `npm install` | Installs dependencies |
|
|
165
|
-
| `npm run build` | Build package locally |
|
|
166
|
-
| `npm pack` | Create a tarball from the package |
|
|
167
|
-
| `npm typespec` | Compile and emit the library with TypeSpec |
|
|
168
|
-
| `npm run format` | Run automatic formatting and fix issues |
|
|
169
|
-
| `npm run lint` | Run automatic linting and fix issues |
|
|
170
|
-
| `npm run check:format` | Check formatting, fail if issues are found |
|
|
171
|
-
| `npm run check:lint` | Check linting, fail if issues are found |
|
|
130
|
+
- See the [CommonGrants CLI](https://www.npmjs.com/package/@common-grants/cli) for more developer tools related to the CommonGrants protocol.
|
package/lib/api.tsp
CHANGED
|
@@ -1,29 +1,44 @@
|
|
|
1
1
|
// Import Schemas.and Routes to make them available outside the package
|
|
2
2
|
import "./core/index.tsp";
|
|
3
3
|
import "@typespec/http";
|
|
4
|
-
|
|
5
|
-
using Core;
|
|
4
|
+
import "@typespec/openapi";
|
|
6
5
|
|
|
7
6
|
using TypeSpec.Http;
|
|
7
|
+
using TypeSpec.OpenAPI;
|
|
8
8
|
|
|
9
9
|
/** The base OpenAPI specification for a CommonGrants API
|
|
10
10
|
*
|
|
11
11
|
* In order for an API to be "compliant" with the CommonGrants protocol,
|
|
12
12
|
* it must implement all of the routes with the "required" tag in this specification.
|
|
13
13
|
*/
|
|
14
|
-
@service({
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
@service(#{ title: "CommonGrants Base API" })
|
|
15
|
+
@tagMetadata(
|
|
16
|
+
"optional",
|
|
17
|
+
#{ description: "Endpoints that MAY be implemented by CommonGrants APIs" }
|
|
18
|
+
)
|
|
19
|
+
@tagMetadata(
|
|
20
|
+
"required",
|
|
21
|
+
#{
|
|
22
|
+
description: "Endpoints that MUST be implemented by all CommonGrants APIs",
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
@tagMetadata(
|
|
26
|
+
"Opportunities",
|
|
27
|
+
#{ description: "Endpoints related to funding opportunities" }
|
|
28
|
+
)
|
|
17
29
|
namespace CommonGrants.API;
|
|
18
30
|
|
|
19
31
|
@tag("Opportunities")
|
|
20
|
-
@route("/opportunities")
|
|
32
|
+
@route("/common-grants/opportunities")
|
|
21
33
|
namespace Opportunities {
|
|
22
34
|
alias Router = Routes.Opportunities;
|
|
23
35
|
|
|
24
36
|
@tag("required")
|
|
25
|
-
op
|
|
37
|
+
op list is Router.list;
|
|
26
38
|
|
|
27
39
|
@tag("required")
|
|
28
|
-
op
|
|
40
|
+
op read is Router.read;
|
|
41
|
+
|
|
42
|
+
@tag("optional")
|
|
43
|
+
op search is Router.search;
|
|
29
44
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
|
+
|
|
3
|
+
/** A mailing address. */
|
|
4
|
+
@example(Examples.Address.apartment)
|
|
5
|
+
model Address {
|
|
6
|
+
/** The primary street address line. */
|
|
7
|
+
street1: string;
|
|
8
|
+
|
|
9
|
+
/** Additional street address information (e.g., apartment number, suite, etc.). */
|
|
10
|
+
street2?: string;
|
|
11
|
+
|
|
12
|
+
/** The city or municipality name. */
|
|
13
|
+
city: string;
|
|
14
|
+
|
|
15
|
+
/** The state, province, or region name. */
|
|
16
|
+
stateOrProvince: string;
|
|
17
|
+
|
|
18
|
+
/** The country name or ISO country code. */
|
|
19
|
+
country: string;
|
|
20
|
+
|
|
21
|
+
/** The postal or ZIP code for the address. */
|
|
22
|
+
postalCode: string;
|
|
23
|
+
|
|
24
|
+
/** The latitude coordinate of the address location. */
|
|
25
|
+
latitude?: numeric;
|
|
26
|
+
|
|
27
|
+
/** The longitude coordinate of the address location. */
|
|
28
|
+
longitude?: numeric;
|
|
29
|
+
|
|
30
|
+
/** Additional geospatial data in GeoJSON format. */
|
|
31
|
+
geography?: Record<unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A collection of addresses. */
|
|
35
|
+
@example(Examples.Address.personalCollection)
|
|
36
|
+
@example(Examples.Address.orgCollection)
|
|
37
|
+
model AddressCollection {
|
|
38
|
+
/** The primary address for a person or organization. */
|
|
39
|
+
primary: Address;
|
|
40
|
+
|
|
41
|
+
/** Additional addresses keyed by a descriptive label (e.g., "work", "home", "international"). */
|
|
42
|
+
otherAddresses?: Record<Address>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
namespace Examples.Address {
|
|
46
|
+
const apartment = #{
|
|
47
|
+
street1: "123 Main St",
|
|
48
|
+
city: "Anytown",
|
|
49
|
+
stateOrProvince: "CA",
|
|
50
|
+
country: "US",
|
|
51
|
+
postalCode: "12345",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const orgAddress = #{
|
|
55
|
+
street1: "456 Main St",
|
|
56
|
+
street2: "Suite 100",
|
|
57
|
+
city: "Anytown",
|
|
58
|
+
stateOrProvince: "CA",
|
|
59
|
+
country: "US",
|
|
60
|
+
postalCode: "12345",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const international = #{
|
|
64
|
+
street1: "123 Rue Principale",
|
|
65
|
+
city: "Montreal",
|
|
66
|
+
stateOrProvince: "QC",
|
|
67
|
+
country: "CA",
|
|
68
|
+
postalCode: "H2Y 1C6",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const personalCollection = #{
|
|
72
|
+
primary: Examples.Address.apartment,
|
|
73
|
+
otherAddresses: #{
|
|
74
|
+
work: Examples.Address.apartment,
|
|
75
|
+
home: Examples.Address.apartment,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const orgCollection = #{
|
|
80
|
+
primary: Examples.Address.orgAddress,
|
|
81
|
+
otherAddresses: #{
|
|
82
|
+
satellite: Examples.Address.orgAddress,
|
|
83
|
+
international: Examples.Address.international,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
namespace
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
2
|
|
|
3
3
|
// ########################################
|
|
4
4
|
// Field types definition
|
|
@@ -8,6 +8,7 @@ namespace Core.Fields;
|
|
|
8
8
|
enum CustomFieldType {
|
|
9
9
|
string,
|
|
10
10
|
number,
|
|
11
|
+
integer,
|
|
11
12
|
boolean,
|
|
12
13
|
object,
|
|
13
14
|
array,
|
|
@@ -25,7 +26,7 @@ enum CustomFieldType {
|
|
|
25
26
|
* model Agency extends CustomField {
|
|
26
27
|
* name: "agency";
|
|
27
28
|
*
|
|
28
|
-
*
|
|
29
|
+
* fieldType: CustomFieldType.string;
|
|
29
30
|
*
|
|
30
31
|
* @example("Department of Transportation")
|
|
31
32
|
* value: string;
|
|
@@ -35,20 +36,20 @@ enum CustomFieldType {
|
|
|
35
36
|
* ```
|
|
36
37
|
*/
|
|
37
38
|
@example(
|
|
38
|
-
|
|
39
|
+
Examples.CustomField.programArea,
|
|
39
40
|
#{ title: "String field for program area" }
|
|
40
41
|
)
|
|
41
42
|
@example(
|
|
42
|
-
|
|
43
|
+
Examples.CustomField.eligibilityTypes,
|
|
43
44
|
#{ title: "Array field for eligibility types" }
|
|
44
45
|
)
|
|
45
|
-
@doc("A custom field on a model")
|
|
46
|
+
@doc("A custom field on a model")
|
|
46
47
|
model CustomField {
|
|
47
48
|
/** Name of the custom field */
|
|
48
49
|
name: string;
|
|
49
50
|
|
|
50
51
|
/** The JSON schema type to use when de-serializing the `value` field */
|
|
51
|
-
|
|
52
|
+
fieldType: CustomFieldType;
|
|
52
53
|
|
|
53
54
|
/** Link to the full JSON schema for this custom field */
|
|
54
55
|
schema?: url;
|
|
@@ -64,11 +65,12 @@ model CustomField {
|
|
|
64
65
|
// Model examples
|
|
65
66
|
// ########################################
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
/** Examples of the CustomField model */
|
|
69
|
+
namespace Examples.CustomField {
|
|
68
70
|
/** An example of a string custom field */
|
|
69
71
|
const programArea = #{
|
|
70
72
|
name: "programArea",
|
|
71
|
-
|
|
73
|
+
fieldType: CustomFieldType.string,
|
|
72
74
|
value: "Healthcare Innovation",
|
|
73
75
|
description: "Primary focus area of the grant program",
|
|
74
76
|
schema: "https://example.com/program-areas.json",
|
|
@@ -77,7 +79,7 @@ namespace CustomFieldExamples {
|
|
|
77
79
|
/** An example of an array custom field */
|
|
78
80
|
const eligibilityTypes = #{
|
|
79
81
|
name: "eligibilityType",
|
|
80
|
-
|
|
82
|
+
fieldType: CustomFieldType.array,
|
|
81
83
|
value: #["nonprofit", "academic"],
|
|
82
84
|
description: "Types of eligible organizations",
|
|
83
85
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import "../types.tsp";
|
|
2
|
+
|
|
3
|
+
namespace CommonGrants.Fields;
|
|
4
|
+
|
|
5
|
+
/** An email address. */
|
|
6
|
+
alias Email = Types.email; // Exposes Email from CommonGrants.Fields
|
|
7
|
+
|
|
8
|
+
/** A collection of email addresses. */
|
|
9
|
+
@example(Examples.Email.personalCollection)
|
|
10
|
+
model EmailCollection {
|
|
11
|
+
/** The primary email address for a person or organization. */
|
|
12
|
+
primary: Types.email;
|
|
13
|
+
|
|
14
|
+
/** Additional email addresses keyed by a descriptive label (e.g., "work", "personal", "support"). */
|
|
15
|
+
otherEmails?: Record<Types.email>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
namespace Examples.Email {
|
|
19
|
+
const personalCollection = #{
|
|
20
|
+
primary: "john.doe@example.com",
|
|
21
|
+
otherEmails: #{
|
|
22
|
+
work: "john.doe@work.com",
|
|
23
|
+
personal: "john.doe@gmail.com",
|
|
24
|
+
school: "john.doe@school.edu",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const orgCollection = #{
|
|
29
|
+
primary: "info@example.com",
|
|
30
|
+
otherEmails: #{
|
|
31
|
+
support: "support@example.com",
|
|
32
|
+
marketing: "marketing@example.com",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -1,27 +1,120 @@
|
|
|
1
1
|
import "../types.tsp";
|
|
2
2
|
|
|
3
|
-
namespace
|
|
3
|
+
namespace CommonGrants.Fields;
|
|
4
4
|
|
|
5
|
-
using
|
|
5
|
+
using CommonGrants.Types;
|
|
6
6
|
|
|
7
7
|
// ########################################
|
|
8
|
-
//
|
|
8
|
+
// Event type
|
|
9
9
|
// ########################################
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/** Type of event (e.g., a single date, a date range, or a custom event)
|
|
12
|
+
*
|
|
13
|
+
* - singleDate: A single date (and possible time)
|
|
14
|
+
* - dateRange: A period of time with a start and end date
|
|
15
|
+
* - other: Other event type (e.g., a recurring event)
|
|
16
|
+
*/
|
|
17
|
+
enum EventType {
|
|
18
|
+
/** A single date with no time */
|
|
19
|
+
singleDate,
|
|
20
|
+
|
|
21
|
+
/** A range of dates with no time */
|
|
22
|
+
dateRange,
|
|
23
|
+
|
|
24
|
+
/** Other event type */
|
|
25
|
+
other,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ########################################
|
|
29
|
+
// Event
|
|
30
|
+
// ########################################
|
|
31
|
+
|
|
32
|
+
/** Union of all event types */
|
|
33
|
+
union Event {
|
|
34
|
+
/** A single date event */
|
|
35
|
+
singleDate: SingleDateEvent,
|
|
36
|
+
|
|
37
|
+
/** A date range event */
|
|
38
|
+
dateRange: DateRangeEvent,
|
|
39
|
+
|
|
40
|
+
/** Other event type */
|
|
41
|
+
other: OtherEvent,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ########################################
|
|
45
|
+
// Event base
|
|
46
|
+
// ########################################
|
|
47
|
+
|
|
48
|
+
/** Base model for all events */
|
|
49
|
+
@discriminator("eventType")
|
|
50
|
+
model EventBase {
|
|
15
51
|
/** Human-readable name of the event (e.g., 'Application posted', 'Question deadline') */
|
|
16
52
|
name: string;
|
|
17
53
|
|
|
54
|
+
/** Type of event */
|
|
55
|
+
eventType: EventType;
|
|
56
|
+
|
|
57
|
+
/** Description of what this event represents */
|
|
58
|
+
description?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ########################################
|
|
62
|
+
// Single date event
|
|
63
|
+
// ########################################
|
|
64
|
+
|
|
65
|
+
/** Description of an event that has a date (and possible time) associated with it */
|
|
66
|
+
@example(Examples.Event.postedDate, #{ title: "Opportunity posted date" })
|
|
67
|
+
@example(Examples.Event.closeDate, #{ title: "Opportunity close date" })
|
|
68
|
+
model SingleDateEvent extends EventBase {
|
|
69
|
+
/** Type of event */
|
|
70
|
+
eventType: EventType.singleDate;
|
|
71
|
+
|
|
18
72
|
/** Date of the event in in ISO 8601 format: YYYY-MM-DD */
|
|
19
73
|
date: isoDate;
|
|
20
74
|
|
|
21
75
|
/** Time of the event in ISO 8601 format: HH:MM:SS */
|
|
22
76
|
time?: isoTime;
|
|
77
|
+
}
|
|
23
78
|
|
|
24
|
-
|
|
79
|
+
// ########################################
|
|
80
|
+
// Date range event
|
|
81
|
+
// ########################################
|
|
82
|
+
|
|
83
|
+
/** Description of an event that has a start and end date (and possible time) associated with it */
|
|
84
|
+
@example(Examples.Event.performancePeriod, #{ title: "Period of performance" })
|
|
85
|
+
@example(Examples.Event.applicationPeriod, #{ title: "Application period" })
|
|
86
|
+
model DateRangeEvent extends EventBase {
|
|
87
|
+
/** Type of event */
|
|
88
|
+
eventType: EventType.dateRange;
|
|
89
|
+
|
|
90
|
+
/** Start date of the event in ISO 8601 format: YYYY-MM-DD */
|
|
91
|
+
startDate: isoDate;
|
|
92
|
+
|
|
93
|
+
/** Start time of the event in ISO 8601 format: HH:MM:SS */
|
|
94
|
+
startTime?: isoTime;
|
|
95
|
+
|
|
96
|
+
/** End date of the event in ISO 8601 format: YYYY-MM-DD */
|
|
97
|
+
endDate: isoDate;
|
|
98
|
+
|
|
99
|
+
/** End time of the event in ISO 8601 format: HH:MM:SS */
|
|
100
|
+
endTime?: isoTime;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ########################################
|
|
104
|
+
// Custom event
|
|
105
|
+
// ########################################
|
|
106
|
+
|
|
107
|
+
/** Description of an event that is not a single date or date range */
|
|
108
|
+
@example(Examples.Event.infoSessions, #{ title: "Info sessions" })
|
|
109
|
+
model OtherEvent extends EventBase {
|
|
110
|
+
/** Type of event */
|
|
111
|
+
eventType: EventType.other;
|
|
112
|
+
|
|
113
|
+
/** Details of the event's timeline (e.g. "Every other Tuesday") */
|
|
114
|
+
details?: string;
|
|
115
|
+
|
|
116
|
+
/** Description of the event */
|
|
117
|
+
@example("Applications begin being accepted")
|
|
25
118
|
description?: string;
|
|
26
119
|
}
|
|
27
120
|
|
|
@@ -29,19 +122,48 @@ model Event {
|
|
|
29
122
|
// Model examples
|
|
30
123
|
// ########################################
|
|
31
124
|
|
|
32
|
-
namespace
|
|
125
|
+
namespace Examples.Event {
|
|
33
126
|
/** An example of a deadline event with a specific time */
|
|
34
|
-
const
|
|
35
|
-
name: "
|
|
127
|
+
const closeDate = #{
|
|
128
|
+
name: "Opportunity close date",
|
|
129
|
+
eventType: EventType.singleDate,
|
|
36
130
|
date: isoDate.fromISO("2024-12-31"),
|
|
37
131
|
time: isoTime.fromISO("17:00:00"),
|
|
38
|
-
description: "
|
|
132
|
+
description: "Opportunity closes for all applications",
|
|
39
133
|
};
|
|
40
134
|
|
|
41
|
-
/** An example of an
|
|
42
|
-
const
|
|
43
|
-
name: "
|
|
135
|
+
/** An example of an opportunity posted date without a specific time */
|
|
136
|
+
const postedDate = #{
|
|
137
|
+
name: "Application posted date",
|
|
138
|
+
eventType: EventType.singleDate,
|
|
44
139
|
date: isoDate.fromISO("2024-01-15"),
|
|
45
|
-
description: "
|
|
140
|
+
description: "Opportunity is posted publicly",
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** Period of application for the grant */
|
|
144
|
+
const applicationPeriod = #{
|
|
145
|
+
name: "Application period",
|
|
146
|
+
eventType: EventType.dateRange,
|
|
147
|
+
startDate: isoDate.fromISO("2024-01-01"),
|
|
148
|
+
endDate: isoDate.fromISO("2024-01-31"),
|
|
149
|
+
endTime: isoTime.fromISO("17:00:00"),
|
|
150
|
+
description: "Primary application period for the grant opportunity",
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Period of performance for the grant */
|
|
154
|
+
const performancePeriod = #{
|
|
155
|
+
name: "Period of Performance",
|
|
156
|
+
eventType: EventType.dateRange,
|
|
157
|
+
startDate: isoDate.fromISO("2024-01-01"),
|
|
158
|
+
endDate: isoDate.fromISO("2024-12-31"),
|
|
159
|
+
description: "Period of performance for the grant",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** Info sessions for the opportunity */
|
|
163
|
+
const infoSessions = #{
|
|
164
|
+
name: "Info sessions",
|
|
165
|
+
eventType: EventType.other,
|
|
166
|
+
details: "Every other Tuesday at 10:00 AM during the application period",
|
|
167
|
+
description: "Info sessions for the opportunity",
|
|
46
168
|
};
|
|
47
169
|
}
|
|
@@ -2,5 +2,27 @@ import "./custom-field.tsp";
|
|
|
2
2
|
import "./metadata.tsp";
|
|
3
3
|
import "./money.tsp";
|
|
4
4
|
import "./event.tsp";
|
|
5
|
+
import "./address.tsp";
|
|
6
|
+
import "./name.tsp";
|
|
7
|
+
import "./phone.tsp";
|
|
8
|
+
import "./pcs.tsp";
|
|
9
|
+
import "./email.tsp";
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
using TypeSpec.JsonSchema;
|
|
12
|
+
|
|
13
|
+
/** A standard set of fields, e.g. `money` that can be reused across models
|
|
14
|
+
*
|
|
15
|
+
* @example How to use the `Fields` namespace
|
|
16
|
+
*
|
|
17
|
+
* ```typespec
|
|
18
|
+
* import "@common-grants/core";
|
|
19
|
+
*
|
|
20
|
+
* using CommonGrants; // exposes the Fields namespace
|
|
21
|
+
*
|
|
22
|
+
* model MyModel {
|
|
23
|
+
* amount: Fields.money;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
@jsonSchema
|
|
28
|
+
namespace CommonGrants.Fields;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
namespace
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
2
|
|
|
3
3
|
// ########################################
|
|
4
4
|
// Model definition
|
|
@@ -18,7 +18,7 @@ namespace Core.Fields;
|
|
|
18
18
|
* }
|
|
19
19
|
* ```
|
|
20
20
|
* */
|
|
21
|
-
@example(
|
|
21
|
+
@example(Examples.Metadata.system)
|
|
22
22
|
model SystemMetadata {
|
|
23
23
|
/** The timestamp (in UTC) at which the record was created. */
|
|
24
24
|
@visibility(Lifecycle.Read)
|
|
@@ -33,7 +33,8 @@ model SystemMetadata {
|
|
|
33
33
|
// Model examples
|
|
34
34
|
// ########################################
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
/** Examples of the SystemMetadata model */
|
|
37
|
+
namespace Examples.Metadata {
|
|
37
38
|
const system = #{
|
|
38
39
|
createdAt: utcDateTime.fromISO("2025-01-01T17:01:01"),
|
|
39
40
|
lastModifiedAt: utcDateTime.fromISO("2025-01-02T17:30:00"),
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import "../types.tsp";
|
|
2
2
|
|
|
3
|
-
namespace
|
|
3
|
+
namespace CommonGrants.Fields;
|
|
4
4
|
|
|
5
|
-
using
|
|
5
|
+
using CommonGrants.Types;
|
|
6
6
|
|
|
7
7
|
// ########################################
|
|
8
8
|
// Model definition
|
|
9
9
|
// ########################################
|
|
10
10
|
|
|
11
|
-
/** A monetary amount and the currency in which
|
|
12
|
-
@example(
|
|
11
|
+
/** A monetary amount and the currency in which it's denominated */
|
|
12
|
+
@example(Examples.Money.usdWithCents, #{ title: "US dollars and cents" })
|
|
13
13
|
@example(
|
|
14
|
-
|
|
14
|
+
Examples.Money.euroWithoutCents,
|
|
15
15
|
#{ title: "Euros displayed without cents" }
|
|
16
16
|
)
|
|
17
17
|
@example(
|
|
18
|
-
|
|
18
|
+
Examples.Money.usdNegative,
|
|
19
19
|
#{ title: "A negative amount of US dollars and cents" }
|
|
20
20
|
)
|
|
21
21
|
model Money {
|
|
@@ -30,7 +30,8 @@ model Money {
|
|
|
30
30
|
// Model examples
|
|
31
31
|
// ########################################
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
/** Examples of the Money model */
|
|
34
|
+
namespace Examples.Money {
|
|
34
35
|
/** An example of a positive USD amount with cents */
|
|
35
36
|
const usdWithCents = #{ amount: "10000.50", currency: "USD" };
|
|
36
37
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
|
+
|
|
3
|
+
/** A person's name. */
|
|
4
|
+
@example(Examples.Name.janeDoe)
|
|
5
|
+
model Name {
|
|
6
|
+
/** Honorific prefix (e.g., Mr., Mrs., Dr., Prof.). */
|
|
7
|
+
prefix?: string;
|
|
8
|
+
|
|
9
|
+
/** The person's first or given name. */
|
|
10
|
+
firstName: string;
|
|
11
|
+
|
|
12
|
+
/** The person's middle name or names. */
|
|
13
|
+
middleName?: string;
|
|
14
|
+
|
|
15
|
+
/** The person's last name or family name. */
|
|
16
|
+
lastName: string;
|
|
17
|
+
|
|
18
|
+
/** Name suffix (e.g., Jr., Sr., III, Ph.D.). */
|
|
19
|
+
suffix?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
namespace Examples.Name {
|
|
23
|
+
const janeDoe = #{
|
|
24
|
+
prefix: "Dr.",
|
|
25
|
+
firstName: "Jane",
|
|
26
|
+
middleName: "Edward",
|
|
27
|
+
lastName: "Doe",
|
|
28
|
+
suffix: "Jr.",
|
|
29
|
+
};
|
|
30
|
+
}
|