@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
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 specification library
39
- // without prefixing each model with `CommonGrants.Models.`
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
- name: "Agency";
47
- type: CustomFieldType.string;
47
+ name: "Agency";
48
+ fieldType: CustomFieldType.string;
48
49
 
49
- @example("Department of Transportation")
50
- value: string;
50
+ @example("Department of Transportation")
51
+ value: string;
51
52
 
52
- description: "The agency responsible for this opportunity";
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
- customFields: {
59
- agency: Agency;
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
- alias OpportunitiesRouter = Opportunities;
81
+ alias OpportunitiesRouter = Opportunities;
81
82
 
82
- // Use the default model for list but custom model for read
83
- op list is OpportunitiesRouter.list;
84
- op read is OpportunitiesRouter.read<CustomModels.CustomOpportunity>;
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
- @service({
102
- title: "Custom API",
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
- title: "CommonGrants Base API",
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 List is Router.list;
37
+ op list is Router.list;
26
38
 
27
39
  @tag("required")
28
- op Read is Router.read;
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 Core.Fields;
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
- * type: CustomFieldType.string;
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
- CustomFieldExamples.programArea,
39
+ Examples.CustomField.programArea,
39
40
  #{ title: "String field for program area" }
40
41
  )
41
42
  @example(
42
- CustomFieldExamples.eligibilityTypes,
43
+ Examples.CustomField.eligibilityTypes,
43
44
  #{ title: "Array field for eligibility types" }
44
45
  )
45
- @doc("A custom field on a model") // Overrides internal docstrings when emitting OpenAPI
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
- type: CustomFieldType;
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
- namespace CustomFieldExamples {
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
- type: CustomFieldType.string,
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
- type: CustomFieldType.array,
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 Core.Fields;
3
+ namespace CommonGrants.Fields;
4
4
 
5
- using Core.Types;
5
+ using CommonGrants.Types;
6
6
 
7
7
  // ########################################
8
- // Model definition
8
+ // Event type
9
9
  // ########################################
10
10
 
11
- /** Description of an event that has a date (and possible time) associated with it */
12
- @example(EventExamples.deadline, #{ title: "Application deadline with time" })
13
- @example(EventExamples.openDate, #{ title: "Opening date without time" })
14
- model Event {
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
- /** Description of what this event represents */
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 EventExamples {
125
+ namespace Examples.Event {
33
126
  /** An example of a deadline event with a specific time */
34
- const deadline = #{
35
- name: "Application Deadline",
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: "Final submission deadline for all grant applications",
132
+ description: "Opportunity closes for all applications",
39
133
  };
40
134
 
41
- /** An example of an opening date without a specific time */
42
- const openDate = #{
43
- name: "Open Date",
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: "Applications begin being accepted",
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
- namespace Internals.Schemas.Fields;
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 Core.Fields;
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(MetadataExample.system)
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
- namespace MetadataExample {
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 Core.Fields;
3
+ namespace CommonGrants.Fields;
4
4
 
5
- using Core.Types;
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 its denominated */
12
- @example(MoneyExamples.usdWithCents, #{ title: "US dollars and cents" })
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
- MoneyExamples.euroWithoutCents,
14
+ Examples.Money.euroWithoutCents,
15
15
  #{ title: "Euros displayed without cents" }
16
16
  )
17
17
  @example(
18
- MoneyExamples.usdNegative,
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
- namespace MoneyExamples {
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
+ }