@common-grants/core 0.1.0-alpha.9 → 0.1.1

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 (35) hide show
  1. package/README.md +5 -6
  2. package/lib/api.tsp +23 -6
  3. package/lib/core/fields/address.tsp +86 -0
  4. package/lib/core/fields/custom-field.tsp +6 -5
  5. package/lib/core/fields/email.tsp +35 -0
  6. package/lib/core/fields/event.tsp +136 -14
  7. package/lib/core/fields/index.tsp +8 -0
  8. package/lib/core/fields/money.tsp +2 -2
  9. package/lib/core/fields/name.tsp +30 -0
  10. package/lib/core/fields/pcs.tsp +112 -0
  11. package/lib/core/fields/phone.tsp +65 -0
  12. package/lib/core/filters/base.tsp +83 -0
  13. package/lib/core/filters/date.tsp +38 -0
  14. package/lib/core/filters/index.tsp +40 -0
  15. package/lib/core/filters/money.tsp +38 -0
  16. package/lib/core/filters/numeric.tsp +50 -0
  17. package/lib/core/filters/string.tsp +31 -0
  18. package/lib/core/models/application.tsp +142 -0
  19. package/lib/core/models/index.tsp +4 -1
  20. package/lib/core/models/opportunity/base.tsp +8 -7
  21. package/lib/core/models/opportunity/funding.tsp +10 -5
  22. package/lib/core/models/opportunity/index.tsp +1 -0
  23. package/lib/core/models/opportunity/search.tsp +102 -0
  24. package/lib/core/models/opportunity/status.tsp +0 -3
  25. package/lib/core/models/opportunity/timeline.tsp +19 -10
  26. package/lib/core/models/organization.tsp +105 -0
  27. package/lib/core/models/person.tsp +43 -0
  28. package/lib/core/pagination.tsp +57 -0
  29. package/lib/core/responses/index.tsp +3 -0
  30. package/lib/core/responses/success.tsp +83 -28
  31. package/lib/core/routes/opportunities.tsp +45 -5
  32. package/lib/core/sorting.tsp +75 -0
  33. package/lib/core/types.tsp +48 -7
  34. package/package.json +9 -12
  35. package/lib/core/routes/utils.tsp +0 -19
package/README.md CHANGED
@@ -45,7 +45,7 @@ namespace CustomAPI.CustomModels;
45
45
  // Define a custom field
46
46
  model Agency extends CustomField {
47
47
  name: "Agency";
48
- type: CustomFieldType.string;
48
+ fieldType: CustomFieldType.string;
49
49
 
50
50
  @example("Department of Transportation")
51
51
  value: string;
@@ -76,13 +76,14 @@ using CommonGrants.Routes;
76
76
  using TypeSpec.Http;
77
77
 
78
78
  @tag("Search")
79
- @route("/opportunities")
79
+ @route("/common-grants/opportunities")
80
80
  namespace CustomAPI.CustomRoutes {
81
81
  alias OpportunitiesRouter = Opportunities;
82
82
 
83
- // Use the default model for list but custom model for read
83
+ // Use the default model for list but custom model for read and search
84
84
  op list is OpportunitiesRouter.list;
85
85
  op read is OpportunitiesRouter.read<CustomModels.CustomOpportunity>;
86
+ op search is OpportunitiesRouter.search<CustomModels.CustomOpportunity>;
86
87
  }
87
88
  ```
88
89
 
@@ -100,9 +101,7 @@ import "./routes.tsp"; // Import the routes from above
100
101
  using TypeSpec.Http;
101
102
 
102
103
  /** Description of your API goes here */
103
- @service({
104
- title: "Custom API",
105
- })
104
+ @service(#{ title: "Custom API" })
106
105
  namespace CustomAPI;
107
106
  ```
108
107
 
package/lib/api.tsp CHANGED
@@ -1,27 +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
+ import "@typespec/openapi";
4
5
 
5
6
  using TypeSpec.Http;
7
+ using TypeSpec.OpenAPI;
6
8
 
7
9
  /** The base OpenAPI specification for a CommonGrants API
8
10
  *
9
11
  * In order for an API to be "compliant" with the CommonGrants protocol,
10
12
  * it must implement all of the routes with the "required" tag in this specification.
11
13
  */
12
- @service({
13
- title: "CommonGrants Base API",
14
- })
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
+ )
15
29
  namespace CommonGrants.API;
16
30
 
17
31
  @tag("Opportunities")
18
- @route("/opportunities")
32
+ @route("/common-grants/opportunities")
19
33
  namespace Opportunities {
20
34
  alias Router = Routes.Opportunities;
21
35
 
22
36
  @tag("required")
23
- op List is Router.list;
37
+ op list is Router.list;
24
38
 
25
39
  @tag("required")
26
- op Read is Router.read;
40
+ op read is Router.read;
41
+
42
+ @tag("optional")
43
+ op search is Router.search;
27
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
+ }
@@ -8,6 +8,7 @@ namespace CommonGrants.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;
@@ -42,13 +43,13 @@ enum CustomFieldType {
42
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;
@@ -69,7 +70,7 @@ namespace Examples.CustomField {
69
70
  /** An example of a string custom field */
70
71
  const programArea = #{
71
72
  name: "programArea",
72
- type: CustomFieldType.string,
73
+ fieldType: CustomFieldType.string,
73
74
  value: "Healthcare Innovation",
74
75
  description: "Primary focus area of the grant program",
75
76
  schema: "https://example.com/program-areas.json",
@@ -78,7 +79,7 @@ namespace Examples.CustomField {
78
79
  /** An example of an array custom field */
79
80
  const eligibilityTypes = #{
80
81
  name: "eligibilityType",
81
- type: CustomFieldType.array,
82
+ fieldType: CustomFieldType.array,
82
83
  value: #["nonprofit", "academic"],
83
84
  description: "Types of eligible organizations",
84
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
+ }
@@ -2,26 +2,119 @@ import "../types.tsp";
2
2
 
3
3
  namespace CommonGrants.Fields;
4
4
 
5
- using CommonGrants.Types;
5
+ using 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(Examples.Event.deadline, #{ title: "Application deadline with time" })
13
- @example(Examples.Event.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
 
@@ -31,17 +124,46 @@ model Event {
31
124
 
32
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,6 +2,13 @@ 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";
10
+
11
+ using TypeSpec.JsonSchema;
5
12
 
6
13
  /** A standard set of fields, e.g. `money` that can be reused across models
7
14
  *
@@ -17,4 +24,5 @@ import "./event.tsp";
17
24
  * }
18
25
  * ```
19
26
  */
27
+ @jsonSchema
20
28
  namespace CommonGrants.Fields;
@@ -2,13 +2,13 @@ import "../types.tsp";
2
2
 
3
3
  namespace CommonGrants.Fields;
4
4
 
5
- using CommonGrants.Types;
5
+ using Types;
6
6
 
7
7
  // ########################################
8
8
  // Model definition
9
9
  // ########################################
10
10
 
11
- /** A monetary amount and the currency in which its denominated */
11
+ /** A monetary amount and the currency in which it's denominated */
12
12
  @example(Examples.Money.usdWithCents, #{ title: "US dollars and cents" })
13
13
  @example(
14
14
  Examples.Money.euroWithoutCents,
@@ -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
+ }
@@ -0,0 +1,112 @@
1
+ namespace CommonGrants.Fields;
2
+
3
+ /** A Philanthropy Classification System (PCS) term.
4
+ *
5
+ * The PCS is a hierarchical classification system for categorizing data related to
6
+ * philanthropic activities. It supports the following classes:
7
+ * - Organization types
8
+ * - Subjects
9
+ * - Population groups
10
+ * - Transaction types
11
+ * - Support strategies
12
+ *
13
+ * See https://taxonomy.candid.org/ for more information.
14
+ */
15
+ @example(Examples.PCS.orgTypeTerm)
16
+ model PCSTerm {
17
+ /** The plain language PCS term. */
18
+ term: string;
19
+
20
+ /** The class to which the PCS term belongs. */
21
+ class: PCSClass;
22
+
23
+ /** The code for this PCS term. */
24
+ @pattern("^[A-Z]{2}[0-9]{6}$")
25
+ @example("UC000000")
26
+ code: string;
27
+
28
+ /** Description of the PCS term */
29
+ description?: string;
30
+ }
31
+
32
+ // #########################################################
33
+ // PCSClass
34
+ // #########################################################
35
+
36
+ /** The class to which the PCS term belongs. */
37
+ enum PCSClass {
38
+ orgTypes: "Organization types",
39
+ subjects: "Subjects",
40
+ populationGroups: "Population groups",
41
+ transactionTypes: "Transaction types",
42
+ supportStrategies: "Support strategies",
43
+ }
44
+
45
+ // #########################################################
46
+ // PCS class types
47
+ // #########################################################
48
+
49
+ /** A Philanthropy Classification System (PCS) term for organization types.
50
+ *
51
+ * See https://taxonomy.candid.org/organization-type for more information.
52
+ */
53
+ model PCSOrgType extends PCSTerm {
54
+ /** The PCS term for the organization type. */
55
+ class: PCSClass.orgTypes;
56
+ }
57
+
58
+ /** A Philanthropy Classification System (PCS) term for the subject of the grant.
59
+ *
60
+ * See https://taxonomy.candid.org/subjects for more information.
61
+ */
62
+ model PCSSubject extends PCSTerm {
63
+ /** The PCS term for the subject. */
64
+ class: PCSClass.subjects;
65
+ }
66
+
67
+ /** A Philanthropy Classification System (PCS) term for populations served.
68
+ *
69
+ * See https://taxonomy.candid.org/populations for more information.
70
+ */
71
+ model PCSPopulation extends PCSTerm {
72
+ /** The PCS term for the population. */
73
+ class: PCSClass.populationGroups;
74
+ }
75
+
76
+ /** A Philanthropy Classification System (PCS) term for support strategies.
77
+ *
78
+ * See https://taxonomy.candid.org/support-strategies for more information.
79
+ */
80
+ model PCSSupportStrategy extends PCSTerm {
81
+ /** The PCS term for the support strategy. */
82
+ class: PCSClass.supportStrategies;
83
+ }
84
+
85
+ /** A Philanthropy Classification System (PCS) term for transaction types.
86
+ *
87
+ * See https://taxonomy.candid.org/transaction-types for more information.
88
+ */
89
+ model PCSTransactionType extends PCSTerm {
90
+ /** The PCS term for the transaction type. */
91
+ class: PCSClass.transactionTypes;
92
+ }
93
+
94
+ // #########################################################
95
+ // Examples
96
+ // #########################################################
97
+
98
+ namespace Examples.PCS {
99
+ const orgTypeTerm = #{
100
+ term: "Hospital",
101
+ class: PCSClass.orgTypes,
102
+ description: "Institutions with the primary purpose of providing in-patient physical and mental health services...",
103
+ code: "EO000000",
104
+ };
105
+
106
+ const subjectTerm = #{
107
+ term: "Education",
108
+ class: PCSClass.subjects,
109
+ description: "All formally constituted educational institutions (except art and performing art schools) and projects or activities...",
110
+ code: "SB000000",
111
+ };
112
+ }
@@ -0,0 +1,65 @@
1
+ namespace CommonGrants.Fields;
2
+
3
+ /** A phone number. */
4
+ @example(Examples.Phone.mobile)
5
+ @example(Examples.Phone.withExtension)
6
+ model Phone {
7
+ /** The international country code (e.g., "+1" for US/Canada). */
8
+ @pattern("^\\+[1-9][0-9]{1,3}$")
9
+ countryCode: string;
10
+
11
+ /** The local phone number without the country code. */
12
+ number: string;
13
+
14
+ /** Optional extension number for the phone line. */
15
+ extension?: string;
16
+
17
+ /** Indicates whether this is a mobile/cell phone number. */
18
+ isMobile?: boolean = false;
19
+ }
20
+
21
+ // #########################################################
22
+ // Phone Collection
23
+ // #########################################################
24
+
25
+ /** A collection of phone numbers for a person. */
26
+ @example(Examples.Phone.personalCollection)
27
+ @example(Examples.Phone.orgCollection)
28
+ model PhoneCollection {
29
+ /** The person's primary phone number. */
30
+ primary: Fields.Phone;
31
+
32
+ /** The person's fax number, if applicable. */
33
+ fax?: Fields.Phone;
34
+
35
+ /** Additional phone numbers not covered by the standard fields. */
36
+ otherPhones?: Record<Fields.Phone>;
37
+ }
38
+
39
+ // #########################################################
40
+ // Examples
41
+ // #########################################################
42
+
43
+ namespace Examples.Phone {
44
+ const mobile = #{ countryCode: "+1", number: "444-456-1230", isMobile: true };
45
+
46
+ const home = #{ countryCode: "+1", number: "333-456-1230", isMobile: false };
47
+
48
+ const withExtension = #{
49
+ countryCode: "+1",
50
+ number: "555-123-4567",
51
+ extension: "123",
52
+ isMobile: false,
53
+ };
54
+
55
+ const personalCollection = #{ primary: mobile, otherPhones: #{ home: home } };
56
+
57
+ const orgCollection = #{
58
+ primary: mobile,
59
+ fax: withExtension,
60
+ otherPhones: #{
61
+ support: #{ countryCode: "+1", number: "333-456-1230", isMobile: false },
62
+ marketing: #{ countryCode: "+1", number: "444-456-1230", isMobile: true },
63
+ },
64
+ };
65
+ }