@common-grants/core 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +3 -0
- package/dist/src/lib.d.ts +13 -0
- package/dist/src/lib.js +10 -0
- package/lib/api.tsp +32 -0
- package/lib/core/fields/custom-field.tsp +85 -0
- package/lib/core/fields/event.tsp +47 -0
- package/lib/core/fields/index.tsp +20 -0
- package/lib/core/fields/metadata.tsp +42 -0
- package/lib/core/fields/money.tsp +43 -0
- package/lib/core/filters/base.tsp +82 -0
- package/lib/core/filters/date.tsp +38 -0
- package/lib/core/filters/index.tsp +37 -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 +8 -0
- package/lib/core/models/index.tsp +22 -0
- package/lib/core/models/opportunity/base.tsp +72 -0
- package/lib/core/models/opportunity/funding.tsp +71 -0
- package/lib/core/models/opportunity/index.tsp +7 -0
- package/lib/core/models/opportunity/search.tsp +102 -0
- package/lib/core/models/opportunity/status.tsp +52 -0
- package/lib/core/models/opportunity/timeline.tsp +46 -0
- package/lib/core/pagination.tsp +55 -0
- package/lib/core/responses/error.tsp +30 -0
- package/lib/core/responses/index.tsp +24 -0
- package/lib/core/responses/success.tsp +141 -0
- package/lib/core/routes/index.tsp +22 -0
- package/lib/core/routes/opportunities.tsp +98 -0
- package/lib/core/sorting.tsp +71 -0
- package/lib/core/types.tsp +42 -0
- package/lib/main.tsp +9 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# CommonGrants core library
|
|
2
|
+
|
|
3
|
+
Code for the CommonGrants core specification library, written in TypeSpec. This library is designed to be imported and extended by individual implementations of the CommonGrants protocol.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quickstart
|
|
6
|
+
|
|
7
|
+
### Install the library
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @common-grants/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Project setup
|
|
14
|
+
|
|
15
|
+
A basic project structure that uses the library might look like this:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
.
|
|
19
|
+
├── models.tsp # Extends @common-grants/core models with custom fields
|
|
20
|
+
├── routes.tsp # Overrides @common-grants/core routes to use the custom models
|
|
21
|
+
├── main.tsp # Defines an API service that uses the custom models and routes
|
|
22
|
+
|
|
|
23
|
+
├── tsp-output/ # Directory that stores the output of `tsp compile`, often .gitignored
|
|
24
|
+
|
|
|
25
|
+
├── package.json # Manages dependencies, commands, and library metadata
|
|
26
|
+
└── tspconfig.yaml # Manages TypeSpec configuration, including emitters
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Define custom fields
|
|
30
|
+
|
|
31
|
+
The Opportunity model is templated to support custom fields. First define your custom fields by extending the `CustomField` model:
|
|
32
|
+
|
|
33
|
+
```typespec
|
|
34
|
+
// models.tsp
|
|
35
|
+
|
|
36
|
+
import "@common-grants/core"; // Import the base specification library
|
|
37
|
+
|
|
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
|
+
using CommonGrants.Models;
|
|
41
|
+
using CommonGrants.Fields;
|
|
42
|
+
|
|
43
|
+
namespace CustomAPI.CustomModels;
|
|
44
|
+
|
|
45
|
+
// Define a custom field
|
|
46
|
+
model Agency extends CustomField {
|
|
47
|
+
name: "Agency";
|
|
48
|
+
type: CustomFieldType.string;
|
|
49
|
+
|
|
50
|
+
@example("Department of Transportation")
|
|
51
|
+
value: string;
|
|
52
|
+
|
|
53
|
+
description: "The agency responsible for this opportunity";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extend the `OpportunityBase` model to create a new `CustomOpportunity` model
|
|
57
|
+
// that includes the new `Agency` field in its `customFields` property
|
|
58
|
+
model CustomOpportunity extends OpportunityBase {
|
|
59
|
+
customFields: {
|
|
60
|
+
agency: Agency;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Override default routes
|
|
66
|
+
|
|
67
|
+
The router interfaces are templated to support your custom models. Override them like this:
|
|
68
|
+
|
|
69
|
+
```typespec
|
|
70
|
+
// routes.tsp
|
|
71
|
+
|
|
72
|
+
import "@common-grants/core";
|
|
73
|
+
import "./models.tsp"; // Import the custom field and model from above
|
|
74
|
+
|
|
75
|
+
using CommonGrants.Routes;
|
|
76
|
+
using TypeSpec.Http;
|
|
77
|
+
|
|
78
|
+
@tag("Search")
|
|
79
|
+
@route("/common-grants/opportunities")
|
|
80
|
+
namespace CustomAPI.CustomRoutes {
|
|
81
|
+
alias OpportunitiesRouter = Opportunities;
|
|
82
|
+
|
|
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>;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Define an API service
|
|
91
|
+
|
|
92
|
+
Next, use these updated routes to define an API service:
|
|
93
|
+
|
|
94
|
+
```typespec
|
|
95
|
+
// main.tsp
|
|
96
|
+
|
|
97
|
+
import "@typespec/http";
|
|
98
|
+
|
|
99
|
+
import "./routes.tsp"; // Import the routes from above
|
|
100
|
+
|
|
101
|
+
using TypeSpec.Http;
|
|
102
|
+
|
|
103
|
+
/** Description of your API goes here */
|
|
104
|
+
@service({
|
|
105
|
+
title: "Custom API",
|
|
106
|
+
})
|
|
107
|
+
namespace CustomAPI;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Generate the OpenAPI spec
|
|
111
|
+
|
|
112
|
+
Generate an OpenAPI specification from your `main.tsp` file using either the CLI:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx tsp compile main.tsp --emit "@typespec/openapi3"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Or specify the emitter in `tspconfig.yaml`:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
# tspconfig.yaml
|
|
122
|
+
emitters:
|
|
123
|
+
- "@typespec/openapi3"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Both strategies will generate an OpenAPI specification in the `tsp-output/` directory.
|
|
127
|
+
|
|
128
|
+
### Further reading
|
|
129
|
+
|
|
130
|
+
- See the [TypeSpec documentation](https://typespec.org/docs/getting-started/overview) for more information on how to use TypeSpec.
|
|
131
|
+
- See the [CommonGrants docs](https://hhs.github.io/simpler-grants-protocol/) to learn more about the CommonGrants protocol.
|
|
132
|
+
- See the [CommonGrants CLI](https://www.npmjs.com/package/@common-grants/cli) for more developer tools related to the CommonGrants protocol.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const $lib: import("@typespec/compiler").TypeSpecLibrary<{
|
|
2
|
+
[code: string]: import("@typespec/compiler").DiagnosticMessages;
|
|
3
|
+
}, Record<string, any>, never>;
|
|
4
|
+
export declare const reportDiagnostic: <C extends string | number, M extends keyof {
|
|
5
|
+
[code: string]: import("@typespec/compiler").DiagnosticMessages;
|
|
6
|
+
}[C]>(program: import("@typespec/compiler").Program, diag: import("@typespec/compiler").DiagnosticReport<{
|
|
7
|
+
[code: string]: import("@typespec/compiler").DiagnosticMessages;
|
|
8
|
+
}, C, M>) => void, createDiagnostic: <C extends string | number, M extends keyof {
|
|
9
|
+
[code: string]: import("@typespec/compiler").DiagnosticMessages;
|
|
10
|
+
}[C]>(diag: import("@typespec/compiler").DiagnosticReport<{
|
|
11
|
+
[code: string]: import("@typespec/compiler").DiagnosticMessages;
|
|
12
|
+
}, C, M>) => import("@typespec/compiler").Diagnostic;
|
|
13
|
+
//# sourceMappingURL=lib.d.ts.map
|
package/dist/src/lib.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createTypeSpecLibrary } from "@typespec/compiler";
|
|
2
|
+
export const $lib = createTypeSpecLibrary({
|
|
3
|
+
name: "@common-grants/core",
|
|
4
|
+
diagnostics: {
|
|
5
|
+
// We'll add diagnostics later if needed
|
|
6
|
+
},
|
|
7
|
+
});
|
|
8
|
+
// Optional but convenient, these are meant to be used locally in your library
|
|
9
|
+
export const { reportDiagnostic, createDiagnostic } = $lib;
|
|
10
|
+
//# sourceMappingURL=lib.js.map
|
package/lib/api.tsp
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Import Schemas.and Routes to make them available outside the package
|
|
2
|
+
import "./core/index.tsp";
|
|
3
|
+
import "@typespec/http";
|
|
4
|
+
import "@typespec/openapi";
|
|
5
|
+
|
|
6
|
+
using TypeSpec.Http;
|
|
7
|
+
using TypeSpec.OpenAPI;
|
|
8
|
+
|
|
9
|
+
/** The base OpenAPI specification for a CommonGrants API
|
|
10
|
+
*
|
|
11
|
+
* In order for an API to be "compliant" with the CommonGrants protocol,
|
|
12
|
+
* it must implement all of the routes with the "required" tag in this specification.
|
|
13
|
+
*/
|
|
14
|
+
@service({
|
|
15
|
+
title: "CommonGrants Base API",
|
|
16
|
+
})
|
|
17
|
+
namespace CommonGrants.API;
|
|
18
|
+
|
|
19
|
+
@tag("Opportunities")
|
|
20
|
+
@route("/common-grants/opportunities")
|
|
21
|
+
namespace Opportunities {
|
|
22
|
+
alias Router = Routes.Opportunities;
|
|
23
|
+
|
|
24
|
+
@tag("required")
|
|
25
|
+
op list is Router.list;
|
|
26
|
+
|
|
27
|
+
@tag("required")
|
|
28
|
+
op read is Router.read;
|
|
29
|
+
|
|
30
|
+
@tag("optional")
|
|
31
|
+
op search is Router.search;
|
|
32
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
|
+
|
|
3
|
+
// ########################################
|
|
4
|
+
// Field types definition
|
|
5
|
+
// ########################################
|
|
6
|
+
|
|
7
|
+
/** The set of JSON schema types supported by a custom field */
|
|
8
|
+
enum CustomFieldType {
|
|
9
|
+
string,
|
|
10
|
+
number,
|
|
11
|
+
boolean,
|
|
12
|
+
object,
|
|
13
|
+
array,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ########################################
|
|
17
|
+
// Model definition
|
|
18
|
+
// ########################################
|
|
19
|
+
|
|
20
|
+
/** Model for defining custom fields that are specific to a given implementation.
|
|
21
|
+
*
|
|
22
|
+
* @example How to define a custom field using this model
|
|
23
|
+
*
|
|
24
|
+
* ```typespec
|
|
25
|
+
* model Agency extends CustomField {
|
|
26
|
+
* name: "agency";
|
|
27
|
+
*
|
|
28
|
+
* type: CustomFieldType.string;
|
|
29
|
+
*
|
|
30
|
+
* @example("Department of Transportation")
|
|
31
|
+
* value: string;
|
|
32
|
+
*
|
|
33
|
+
* description?: "The agency responsible for managing this opportunity";
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
@example(
|
|
38
|
+
Examples.CustomField.programArea,
|
|
39
|
+
#{ title: "String field for program area" }
|
|
40
|
+
)
|
|
41
|
+
@example(
|
|
42
|
+
Examples.CustomField.eligibilityTypes,
|
|
43
|
+
#{ title: "Array field for eligibility types" }
|
|
44
|
+
)
|
|
45
|
+
@doc("A custom field on a model") // Overrides internal docstrings when emitting OpenAPI
|
|
46
|
+
model CustomField {
|
|
47
|
+
/** Name of the custom field */
|
|
48
|
+
name: string;
|
|
49
|
+
|
|
50
|
+
/** The JSON schema type to use when de-serializing the `value` field */
|
|
51
|
+
type: CustomFieldType;
|
|
52
|
+
|
|
53
|
+
/** Link to the full JSON schema for this custom field */
|
|
54
|
+
schema?: url;
|
|
55
|
+
|
|
56
|
+
/** Value of the custom field */
|
|
57
|
+
value: unknown;
|
|
58
|
+
|
|
59
|
+
/** Description of the custom field's purpose */
|
|
60
|
+
description?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ########################################
|
|
64
|
+
// Model examples
|
|
65
|
+
// ########################################
|
|
66
|
+
|
|
67
|
+
/** Examples of the CustomField model */
|
|
68
|
+
namespace Examples.CustomField {
|
|
69
|
+
/** An example of a string custom field */
|
|
70
|
+
const programArea = #{
|
|
71
|
+
name: "programArea",
|
|
72
|
+
type: CustomFieldType.string,
|
|
73
|
+
value: "Healthcare Innovation",
|
|
74
|
+
description: "Primary focus area of the grant program",
|
|
75
|
+
schema: "https://example.com/program-areas.json",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** An example of an array custom field */
|
|
79
|
+
const eligibilityTypes = #{
|
|
80
|
+
name: "eligibilityType",
|
|
81
|
+
type: CustomFieldType.array,
|
|
82
|
+
value: #["nonprofit", "academic"],
|
|
83
|
+
description: "Types of eligible organizations",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import "../types.tsp";
|
|
2
|
+
|
|
3
|
+
namespace CommonGrants.Fields;
|
|
4
|
+
|
|
5
|
+
using CommonGrants.Types;
|
|
6
|
+
|
|
7
|
+
// ########################################
|
|
8
|
+
// Model definition
|
|
9
|
+
// ########################################
|
|
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 {
|
|
15
|
+
/** Human-readable name of the event (e.g., 'Application posted', 'Question deadline') */
|
|
16
|
+
name: string;
|
|
17
|
+
|
|
18
|
+
/** Date of the event in in ISO 8601 format: YYYY-MM-DD */
|
|
19
|
+
date: isoDate;
|
|
20
|
+
|
|
21
|
+
/** Time of the event in ISO 8601 format: HH:MM:SS */
|
|
22
|
+
time?: isoTime;
|
|
23
|
+
|
|
24
|
+
/** Description of what this event represents */
|
|
25
|
+
description?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ########################################
|
|
29
|
+
// Model examples
|
|
30
|
+
// ########################################
|
|
31
|
+
|
|
32
|
+
namespace Examples.Event {
|
|
33
|
+
/** An example of a deadline event with a specific time */
|
|
34
|
+
const deadline = #{
|
|
35
|
+
name: "Application Deadline",
|
|
36
|
+
date: isoDate.fromISO("2024-12-31"),
|
|
37
|
+
time: isoTime.fromISO("17:00:00"),
|
|
38
|
+
description: "Final submission deadline for all grant applications",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** An example of an opening date without a specific time */
|
|
42
|
+
const openDate = #{
|
|
43
|
+
name: "Open Date",
|
|
44
|
+
date: isoDate.fromISO("2024-01-15"),
|
|
45
|
+
description: "Applications begin being accepted",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import "./custom-field.tsp";
|
|
2
|
+
import "./metadata.tsp";
|
|
3
|
+
import "./money.tsp";
|
|
4
|
+
import "./event.tsp";
|
|
5
|
+
|
|
6
|
+
/** A standard set of fields, e.g. `money` that can be reused across models
|
|
7
|
+
*
|
|
8
|
+
* @example How to use the `Fields` namespace
|
|
9
|
+
*
|
|
10
|
+
* ```typespec
|
|
11
|
+
* import "@common-grants/core";
|
|
12
|
+
*
|
|
13
|
+
* using CommonGrants; // exposes the Fields namespace
|
|
14
|
+
*
|
|
15
|
+
* model MyModel {
|
|
16
|
+
* amount: Fields.money;
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
namespace CommonGrants.Fields;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
namespace CommonGrants.Fields;
|
|
2
|
+
|
|
3
|
+
// ########################################
|
|
4
|
+
// Model definition
|
|
5
|
+
// ########################################
|
|
6
|
+
|
|
7
|
+
/** Standard system-level metadata about a given record.
|
|
8
|
+
*
|
|
9
|
+
* @example How to spread the SystemMetadata props into another record
|
|
10
|
+
*
|
|
11
|
+
* ```typespec
|
|
12
|
+
* model Opportunity {
|
|
13
|
+
* id: uuid;
|
|
14
|
+
* title: string;
|
|
15
|
+
*
|
|
16
|
+
* // Includes SystemMetadata props in the root of the Opportunity model
|
|
17
|
+
* ...SystemMetadata;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
* */
|
|
21
|
+
@example(Examples.Metadata.system)
|
|
22
|
+
model SystemMetadata {
|
|
23
|
+
/** The timestamp (in UTC) at which the record was created. */
|
|
24
|
+
@visibility(Lifecycle.Read)
|
|
25
|
+
createdAt: utcDateTime;
|
|
26
|
+
|
|
27
|
+
/** The timestamp (in UTC) at which the record was last modified. */
|
|
28
|
+
@visibility(Lifecycle.Read)
|
|
29
|
+
lastModifiedAt: utcDateTime;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ########################################
|
|
33
|
+
// Model examples
|
|
34
|
+
// ########################################
|
|
35
|
+
|
|
36
|
+
/** Examples of the SystemMetadata model */
|
|
37
|
+
namespace Examples.Metadata {
|
|
38
|
+
const system = #{
|
|
39
|
+
createdAt: utcDateTime.fromISO("2025-01-01T17:01:01"),
|
|
40
|
+
lastModifiedAt: utcDateTime.fromISO("2025-01-02T17:30:00"),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "../types.tsp";
|
|
2
|
+
|
|
3
|
+
namespace CommonGrants.Fields;
|
|
4
|
+
|
|
5
|
+
using CommonGrants.Types;
|
|
6
|
+
|
|
7
|
+
// ########################################
|
|
8
|
+
// Model definition
|
|
9
|
+
// ########################################
|
|
10
|
+
|
|
11
|
+
/** A monetary amount and the currency in which its denominated */
|
|
12
|
+
@example(Examples.Money.usdWithCents, #{ title: "US dollars and cents" })
|
|
13
|
+
@example(
|
|
14
|
+
Examples.Money.euroWithoutCents,
|
|
15
|
+
#{ title: "Euros displayed without cents" }
|
|
16
|
+
)
|
|
17
|
+
@example(
|
|
18
|
+
Examples.Money.usdNegative,
|
|
19
|
+
#{ title: "A negative amount of US dollars and cents" }
|
|
20
|
+
)
|
|
21
|
+
model Money {
|
|
22
|
+
/** The amount of money */
|
|
23
|
+
amount: decimalString;
|
|
24
|
+
|
|
25
|
+
/** The ISO 4217 currency code in which the amount is denominated */
|
|
26
|
+
currency: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ########################################
|
|
30
|
+
// Model examples
|
|
31
|
+
// ########################################
|
|
32
|
+
|
|
33
|
+
/** Examples of the Money model */
|
|
34
|
+
namespace Examples.Money {
|
|
35
|
+
/** An example of a positive USD amount with cents */
|
|
36
|
+
const usdWithCents = #{ amount: "10000.50", currency: "USD" };
|
|
37
|
+
|
|
38
|
+
/** An example of a positive EUR amount without cents */
|
|
39
|
+
const euroWithoutCents = #{ amount: "5000", currency: "EUR" };
|
|
40
|
+
|
|
41
|
+
/** An example of a negative USD amount in accounting format */
|
|
42
|
+
const usdNegative = #{ amount: "-50.50", currency: "USD" };
|
|
43
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
namespace CommonGrants.Filters;
|
|
2
|
+
|
|
3
|
+
// ############################################################################
|
|
4
|
+
// Filter operators
|
|
5
|
+
// ############################################################################
|
|
6
|
+
|
|
7
|
+
/** Operators that filter a field based on an exact match to a value */
|
|
8
|
+
enum EquivalenceOperators {
|
|
9
|
+
/** Equal to a value */
|
|
10
|
+
eq,
|
|
11
|
+
|
|
12
|
+
/** Not equal to a value */
|
|
13
|
+
neq,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Operators that filter a field based on a comparison to a value */
|
|
17
|
+
enum ComparisonOperators {
|
|
18
|
+
/** Greater than a value */
|
|
19
|
+
gt,
|
|
20
|
+
|
|
21
|
+
/** Greater than or equal to a value */
|
|
22
|
+
gte,
|
|
23
|
+
|
|
24
|
+
/** Less than a value */
|
|
25
|
+
lt,
|
|
26
|
+
|
|
27
|
+
/** Less than or equal to a value */
|
|
28
|
+
lte,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Operators that filter a field based on an array of values */
|
|
32
|
+
enum ArrayOperators {
|
|
33
|
+
/** In an array of values */
|
|
34
|
+
in,
|
|
35
|
+
|
|
36
|
+
/** Not in an array of values */
|
|
37
|
+
not_in,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Operators that filter a field based on a string value */
|
|
41
|
+
enum StringOperators {
|
|
42
|
+
/** Like */
|
|
43
|
+
like,
|
|
44
|
+
|
|
45
|
+
/** Not like */
|
|
46
|
+
not_like,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Operators that filter a field based on a range of values */
|
|
50
|
+
enum RangeOperators {
|
|
51
|
+
/** Between a range of values */
|
|
52
|
+
between,
|
|
53
|
+
|
|
54
|
+
/** Outside a range of values */
|
|
55
|
+
outside,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
enum AllOperators {
|
|
59
|
+
...EquivalenceOperators,
|
|
60
|
+
...ComparisonOperators,
|
|
61
|
+
...ArrayOperators,
|
|
62
|
+
...RangeOperators,
|
|
63
|
+
...StringOperators,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ############################################################################
|
|
67
|
+
// Filter model
|
|
68
|
+
// ############################################################################
|
|
69
|
+
|
|
70
|
+
/** A base filter model that can be used to create more specific filter models */
|
|
71
|
+
model DefaultFilter {
|
|
72
|
+
/** The operator to apply to the filter value */
|
|
73
|
+
operator:
|
|
74
|
+
| ComparisonOperators
|
|
75
|
+
| ArrayOperators
|
|
76
|
+
| StringOperators
|
|
77
|
+
| RangeOperators
|
|
78
|
+
| AllOperators;
|
|
79
|
+
|
|
80
|
+
/** The value to use for the filter operation */
|
|
81
|
+
value: unknown;
|
|
82
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "./base.tsp";
|
|
2
|
+
import "../types.tsp";
|
|
3
|
+
|
|
4
|
+
namespace CommonGrants.Filters;
|
|
5
|
+
|
|
6
|
+
// ############################################################################
|
|
7
|
+
// Date comparison filter
|
|
8
|
+
// ############################################################################
|
|
9
|
+
|
|
10
|
+
/** Filters by comparing a field to a date value */
|
|
11
|
+
model DateComparisonFilter {
|
|
12
|
+
/** The operator to apply to the filter value */
|
|
13
|
+
operator: ComparisonOperators;
|
|
14
|
+
|
|
15
|
+
/** The value to use for the filter operation */
|
|
16
|
+
@example(Types.isoDate.fromISO("2021-01-01"))
|
|
17
|
+
value: Types.isoDate | utcDateTime | offsetDateTime;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ############################################################################
|
|
21
|
+
// Date range filter
|
|
22
|
+
// ############################################################################
|
|
23
|
+
|
|
24
|
+
/** Filters by comparing a field to a range of date values */
|
|
25
|
+
model DateRangeFilter {
|
|
26
|
+
/** The operator to apply to the filter value */
|
|
27
|
+
operator: RangeOperators;
|
|
28
|
+
|
|
29
|
+
/** The value to use for the filter operation */
|
|
30
|
+
@example(#{
|
|
31
|
+
min: Types.isoDate.fromISO("2021-01-01"),
|
|
32
|
+
max: Types.isoDate.fromISO("2021-01-02"),
|
|
33
|
+
})
|
|
34
|
+
value: {
|
|
35
|
+
min: Types.isoDate | utcDateTime | offsetDateTime;
|
|
36
|
+
max: Types.isoDate | utcDateTime | offsetDateTime;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import "./base.tsp";
|
|
2
|
+
import "./date.tsp";
|
|
3
|
+
import "./numeric.tsp";
|
|
4
|
+
import "./money.tsp";
|
|
5
|
+
import "./string.tsp";
|
|
6
|
+
|
|
7
|
+
/** A standard set of filters, e.g. `StringArrayFilter`, that can be reused across models
|
|
8
|
+
*
|
|
9
|
+
* @example How to use the `Filters` namespace
|
|
10
|
+
*
|
|
11
|
+
* ```typespec
|
|
12
|
+
* import "@common-grants/core";
|
|
13
|
+
*
|
|
14
|
+
* using CommonGrants; // exposes the Filters namespace
|
|
15
|
+
*
|
|
16
|
+
* model MyFilters extends Record<Filters.DefaultFilter> {
|
|
17
|
+
* @example(#{
|
|
18
|
+
* operator: Filters.FilterOperators.in,
|
|
19
|
+
* value: #["foo", "bar", "baz"],
|
|
20
|
+
* })
|
|
21
|
+
* stringField: Filters.StringArrayFilter;
|
|
22
|
+
*
|
|
23
|
+
* @example(#{ operator: Filters.FilterOperators.gt, value: 10 })
|
|
24
|
+
* numberField: Filters.NumberComparisonFilter;
|
|
25
|
+
*
|
|
26
|
+
* @example(#{
|
|
27
|
+
* operator: Filters.FilterOperators.between,
|
|
28
|
+
* value: #{
|
|
29
|
+
* min: Types.isoDate.fromISO("2021-01-01"),
|
|
30
|
+
* max: Types.isoDate.fromISO("2021-01-02"),
|
|
31
|
+
* },
|
|
32
|
+
* })
|
|
33
|
+
* dateField: Filters.DateRangeFilter;
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
namespace CommonGrants.Filters;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "./base.tsp";
|
|
2
|
+
import "../fields/index.tsp";
|
|
3
|
+
|
|
4
|
+
namespace CommonGrants.Filters;
|
|
5
|
+
|
|
6
|
+
// ############################################################################
|
|
7
|
+
// Money comparison filter
|
|
8
|
+
// ############################################################################
|
|
9
|
+
|
|
10
|
+
/** Filters by comparing a field to a monetary value */
|
|
11
|
+
model MoneyComparisonFilter {
|
|
12
|
+
/** The operator to apply to the filter value */
|
|
13
|
+
operator: ComparisonOperators;
|
|
14
|
+
|
|
15
|
+
/** The value to use for the filter operation */
|
|
16
|
+
@example(#{ amount: "1000", currency: "USD" })
|
|
17
|
+
value: Fields.Money;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ############################################################################
|
|
21
|
+
// Date range filter
|
|
22
|
+
// ############################################################################
|
|
23
|
+
|
|
24
|
+
/** Filters by comparing a field to a range of monetary values */
|
|
25
|
+
model MoneyRangeFilter {
|
|
26
|
+
/** The operator to apply to the filter value */
|
|
27
|
+
operator: RangeOperators;
|
|
28
|
+
|
|
29
|
+
/** The value to use for the filter operation */
|
|
30
|
+
@example(#{
|
|
31
|
+
min: #{ amount: "1000", currency: "USD" },
|
|
32
|
+
max: #{ amount: "10000", currency: "USD" },
|
|
33
|
+
})
|
|
34
|
+
value: {
|
|
35
|
+
min: Fields.Money;
|
|
36
|
+
max: Fields.Money;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import "../types.tsp";
|
|
2
|
+
import "./base.tsp";
|
|
3
|
+
import "../fields/index.tsp";
|
|
4
|
+
|
|
5
|
+
namespace CommonGrants.Filters;
|
|
6
|
+
|
|
7
|
+
// ############################################################################
|
|
8
|
+
// Number comparison filter
|
|
9
|
+
// ############################################################################
|
|
10
|
+
|
|
11
|
+
/** Filters by comparing a field to a numeric value */
|
|
12
|
+
model NumberComparisonFilter {
|
|
13
|
+
/** The comparison operator to apply to the filter value */
|
|
14
|
+
operator: ComparisonOperators;
|
|
15
|
+
|
|
16
|
+
/** The value to use for the filter operation */
|
|
17
|
+
@example(1000)
|
|
18
|
+
value: numeric;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ############################################################################
|
|
22
|
+
// Number range filter
|
|
23
|
+
// ############################################################################
|
|
24
|
+
|
|
25
|
+
/** Filters by comparing a field to a numeric range */
|
|
26
|
+
model NumberRangeFilter {
|
|
27
|
+
/** The operator to apply to the filter value */
|
|
28
|
+
operator: RangeOperators;
|
|
29
|
+
|
|
30
|
+
/** The value to use for the filter operation */
|
|
31
|
+
@example(#{ min: 1000, max: 10000 })
|
|
32
|
+
value: {
|
|
33
|
+
min: numeric;
|
|
34
|
+
max: numeric;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ############################################################################
|
|
39
|
+
// Number array filter
|
|
40
|
+
// ############################################################################
|
|
41
|
+
|
|
42
|
+
/** Filters by comparing a field to an array of numeric values */
|
|
43
|
+
model NumberArrayFilter {
|
|
44
|
+
/** The operator to apply to the filter value */
|
|
45
|
+
operator: ArrayOperators;
|
|
46
|
+
|
|
47
|
+
/** The value to use for the filter operation */
|
|
48
|
+
@example(#[1000, 2000, 3000])
|
|
49
|
+
value: Array<numeric>;
|
|
50
|
+
}
|