@common-grants/core 0.1.0-alpha.4

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 ADDED
@@ -0,0 +1,66 @@
1
+ # Simpler grant protocol specifications
2
+
3
+ Code for the simpler grant protocol base specification libraries, written in TypeSpec. They are designed to be imported and extended by individual implementations of the grant protocol.
4
+
5
+ ## 🚀 Project Structure
6
+
7
+ The `specs/` sub-directory is organized like this:
8
+
9
+ ```
10
+ .
11
+ ├── lib/ # Defines reusable models and routes for the library
12
+ │ ├── models/ # Defines base models like Opportunity, CustomField, etc.
13
+ │ ├── routes/ # Defines base routes like GET /opportunities
14
+ │ └── main.tsp # Exposes models and routes from the root of the library
15
+ |
16
+ ├── src/
17
+ │ ├── index.ts # Defines the entry point for the library
18
+ │ └── lib.ts # Creates a new TypeSpec library definition
19
+ |
20
+ ├── dist/ # .gitignored directory that stores the output of `npm build`
21
+ |
22
+ ├── package.json # Manages dependencies, commands, and library metadata
23
+ ├── tsconfig.json # Manages TypeScript configuration
24
+ └── tspconfig.yaml # Manages TypeSpec configuration
25
+ ```
26
+
27
+ ## 💻 Local development
28
+
29
+ ### Pre-requisites
30
+
31
+ Node version 20 or later. Check with `node --version`
32
+
33
+ ### Commands
34
+
35
+ All commands are run from the root of the project, from a terminal:
36
+
37
+ | Command | Action |
38
+ | :--------------------- | :----------------------------------------- |
39
+ | `npm install` | Installs dependencies |
40
+ | `npm run build` | Build package locally |
41
+ | `npm pack` | Create a tarball from the package |
42
+ | `npm typespec` | Compile and emit the library with TypeSpec |
43
+ | `npm run format` | Run automatic formatting and fix issues |
44
+ | `npm run lint` | Run automatic linting and fix issues |
45
+ | `npm run check:format` | Check formatting, fail if issues are found |
46
+ | `npm run check:lint` | Check linting, fail if issues are found |
47
+
48
+ ### Installing the library locally
49
+
50
+ The medium-term goal is to publish this library to npm so that it can be installed directly using `npm install`. For the interim, however, you can install the library locally by running the following steps from the root of this directory:
51
+
52
+ 1. Build the library: `npm build`
53
+ 2. Package the library as a tarball: `npm pack`
54
+ 3. Change directory into the node project where you want to install this library: `cd $path_to_other_project`
55
+ 4. Add the following to that project's `package.json` (replacing the values between the angled brackets `<>`):
56
+ ```json
57
+ "peerDependencies": {
58
+ "@typespec/compiler": "^0.63.0",
59
+ "@common-grants/core": "file:<relative-path-to-library>/common-grants-core-<version>.tgz"
60
+ },
61
+ ```
62
+ 5. Then run `npm install` to install this package
63
+
64
+ ### Using the library
65
+
66
+ Check out the [Custom API codebase](../examples/custom-api/) for an example of how to use this library within your own project. A more detailed tutorial is in the works.
@@ -0,0 +1,2 @@
1
+ export { $lib } from "./lib.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ // Re-export $lib so the compiler can access it and register your library correctly
2
+ export { $lib } from "./lib.js";
3
+ //# sourceMappingURL=index.js.map
@@ -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
@@ -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/main.tsp ADDED
@@ -0,0 +1,11 @@
1
+ // Import the JS entry point for this library
2
+ // For more details see:
3
+ // https://typespec.io/docs/extending-typespec/basics/#h-add-your-main-typespec-file
4
+ import "../dist/src/index.js";
5
+
6
+ // Import Models and Routes to make them available outside the package
7
+ import "./models/index.tsp";
8
+ import "./routes/index.tsp";
9
+
10
+ // Define the top-level namespace for the library
11
+ namespace CommonGrants;
@@ -0,0 +1,24 @@
1
+ namespace CommonGrants.Models;
2
+
3
+ /** A time on a clock, without a timezone, in ISO 8601 format HH:mm:ss */
4
+ @example(isoTime.fromISO("17:00:00"))
5
+ scalar isoTime extends plainTime;
6
+
7
+ /** A date on a calendar in ISO 8601 format YYYY-MM-DD */
8
+ @example(isoDate.fromISO("2025-01-01"))
9
+ scalar isoDate extends plainTime;
10
+
11
+ /** A universally unique identifier */
12
+ @example("30a12e5e-5940-4c08-921c-17a8960fcf4b")
13
+ @format("uuid")
14
+ scalar uuid extends string;
15
+
16
+ /** A decimal number (with variable scale) encoded as a string, to avoid floating point issues */
17
+ @pattern(
18
+ "^-?[0-9]+\\.?[0-9]*$",
19
+ "Must be a valid decimal number represented as a string"
20
+ )
21
+ @example("100", #{ title: "Scale 0" })
22
+ @example("100.5", #{ title: "Scale 1" })
23
+ @example("-100.5", #{ title: "Negative, scale 2" })
24
+ scalar decimalString extends string;
@@ -0,0 +1,62 @@
1
+ namespace CommonGrants.Models;
2
+
3
+ // ########################################
4
+ // Model 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
+ @example(
17
+ CustomFieldExamples.programArea,
18
+ #{ title: "String field for program area" }
19
+ )
20
+ @example(
21
+ CustomFieldExamples.eligibilityTypes,
22
+ #{ title: "Array field for eligibility types" }
23
+ )
24
+ model CustomField {
25
+ /** Name of the custom field */
26
+ name: string;
27
+
28
+ /** The JSON schema type to use when de-serializing the `value` field */
29
+ type: CustomFieldType;
30
+
31
+ /** Link to the full JSON schema for this custom field */
32
+ schema?: url;
33
+
34
+ /** Value of the custom field */
35
+ value: unknown;
36
+
37
+ /** Description of the custom field's purpose */
38
+ description?: string;
39
+ }
40
+
41
+ // ########################################
42
+ // Model examples
43
+ // ########################################
44
+
45
+ namespace CustomFieldExamples {
46
+ /** An example of a string custom field */
47
+ const programArea = #{
48
+ name: "programArea",
49
+ type: CustomFieldType.string,
50
+ value: "Healthcare Innovation",
51
+ description: "Primary focus area of the grant program",
52
+ schema: "https://example.com/program-areas.json",
53
+ };
54
+
55
+ /** An example of an array custom field */
56
+ const eligibilityTypes = #{
57
+ name: "eligibilityType",
58
+ type: CustomFieldType.array,
59
+ value: #["nonprofit", "academic"],
60
+ description: "Types of eligible organizations",
61
+ };
62
+ }
@@ -0,0 +1,42 @@
1
+ namespace CommonGrants.Models;
2
+
3
+ // ########################################
4
+ // Model definition
5
+ // ########################################
6
+
7
+ @example(EventExamples.deadline, #{ title: "Application deadline with time" })
8
+ @example(EventExamples.openDate, #{ title: "Opening date without time" })
9
+ model Event {
10
+ /** Name of the timeline event (e.g., 'Open Date', 'Deadline') */
11
+ name: string;
12
+
13
+ /** Date of the event in in ISO 8601 format: YYYY-MM-DD */
14
+ date: isoDate;
15
+
16
+ /** Time of the event in ISO 8601 format: HH:MM:SS */
17
+ time?: isoTime;
18
+
19
+ /** Detailed description of the timeline event */
20
+ description?: string;
21
+ }
22
+
23
+ // ########################################
24
+ // Model examples
25
+ // ########################################
26
+
27
+ namespace EventExamples {
28
+ /** An example of a deadline event with a specific time */
29
+ const deadline = #{
30
+ name: "Application Deadline",
31
+ date: isoDate.fromISO("2024-12-31"),
32
+ time: isoTime.fromISO("17:00:00"),
33
+ description: "Final submission deadline for all grant applications",
34
+ };
35
+
36
+ /** An example of an opening date without a specific time */
37
+ const openDate = #{
38
+ name: "Open Date",
39
+ date: isoDate.fromISO("2024-01-15"),
40
+ description: "Applications begin being accepted",
41
+ };
42
+ }
@@ -0,0 +1,55 @@
1
+ using TypeSpec.Http;
2
+
3
+ namespace CommonGrants.Models;
4
+
5
+ // ########################################
6
+ // Model definition
7
+ // ########################################
8
+
9
+ @example(FundingExamples.allFields, #{ title: "All fields defined" })
10
+ @example(
11
+ FundingExamples.awardRange,
12
+ #{ title: "Award range but no total limit" }
13
+ )
14
+ @example(
15
+ FundingExamples.onlyLimit,
16
+ #{ title: "Total funding limit but no award range" }
17
+ )
18
+ model FundingDetails {
19
+ totalAmountAvailable?: Money;
20
+ minAwardAmount?: Money;
21
+ maxAwardAmount?: Money;
22
+ minAwardCount?: integer;
23
+ maxAwardCount?: integer;
24
+ estimatedAwardCount?: integer;
25
+ }
26
+
27
+ // ########################################
28
+ // Model examples
29
+ // ########################################
30
+
31
+ namespace FundingExamples {
32
+ /** A FundingDetails example in which all of the fields are defined */
33
+ const allFields = #{
34
+ totalAmountAvailable: #{ amount: "1000000.00", currency: "USD" },
35
+ minAwardAmount: #{ amount: "10000.00", currency: "USD" },
36
+ maxAwardAmount: #{ amount: "50000.00", currency: "USD" },
37
+ minAwardCount: 5,
38
+ maxAwardCount: 20,
39
+ estimatedAwardCount: 10,
40
+ };
41
+
42
+ /** A FundingDetails example that has an award range but no total limit */
43
+ const awardRange = #{
44
+ minAwardAmount: #{ amount: "10000.00", currency: "USD" },
45
+ maxAwardAmount: #{ amount: "50000.00", currency: "USD" },
46
+ minAwardCount: 5,
47
+ maxAwardCount: 20,
48
+ };
49
+
50
+ /** A FundingDetails example that has a total limit but no award range */
51
+ const onlyLimit = #{
52
+ totalAmountAvailable: #{ amount: "1000000.00", currency: "USD" },
53
+ estimatedAwardCount: 10,
54
+ };
55
+ }
@@ -0,0 +1,17 @@
1
+ import "@typespec/json-schema";
2
+
3
+ // Import individual models to provide a consistent interface
4
+ // and make them available throughout the namespace
5
+ import "./base.tsp";
6
+ import "./custom-field.tsp";
7
+ import "./money.tsp";
8
+ import "./funding.tsp";
9
+ import "./event.tsp";
10
+ import "./opportunity.tsp";
11
+
12
+ using TypeSpec.JsonSchema;
13
+
14
+ // Define the top-level namespace for the models
15
+ // and emit these models as JSON schemas
16
+ @jsonSchema
17
+ namespace CommonGrants.Models;
@@ -0,0 +1,37 @@
1
+ namespace CommonGrants.Models;
2
+
3
+ // ########################################
4
+ // Model definition
5
+ // ########################################
6
+
7
+ @example(MoneyExamples.usdWithCents, #{ title: "US dollars and cents" })
8
+ @example(
9
+ MoneyExamples.euroWithoutCents,
10
+ #{ title: "Euros displayed without cents" }
11
+ )
12
+ @example(
13
+ MoneyExamples.usdNegative,
14
+ #{ title: "A negative amount of US dollars and cents" }
15
+ )
16
+ model Money {
17
+ /** The amount of money */
18
+ amount: decimalString;
19
+
20
+ /** The ISO 4217 currency code in which the amount is denominated */
21
+ currency: string;
22
+ }
23
+
24
+ // ########################################
25
+ // Model examples
26
+ // ########################################
27
+
28
+ namespace MoneyExamples {
29
+ /** An example of a positive USD amount with cents */
30
+ const usdWithCents = #{ amount: "10000.50", currency: "USD" };
31
+
32
+ /** An example of a positive EUR amount without cents */
33
+ const euroWithoutCents = #{ amount: "5000", currency: "EUR" };
34
+
35
+ /** An example of a negative USD amount in accounting format */
36
+ const usdNegative = #{ amount: "-50.50", currency: "USD" };
37
+ }
@@ -0,0 +1,65 @@
1
+ namespace CommonGrants.Models;
2
+
3
+ // ########################################
4
+ // Model definition
5
+ // ########################################
6
+
7
+ @example(
8
+ OpportunityExamples.complete,
9
+ #{ title: "Complete opportunity with all fields" }
10
+ )
11
+ @example(
12
+ OpportunityExamples.minimal,
13
+ #{ title: "Minimal opportunity with required fields only" }
14
+ )
15
+ model Opportunity {
16
+ /** Globally unique id for the opportunity */
17
+ id: uuid;
18
+
19
+ /** URL for the original source of the opportunity */
20
+ source: url;
21
+
22
+ /** Title or name of the funding opportunity */
23
+ title: string;
24
+
25
+ /** Description of the opportunity's purpose and scope */
26
+ description: string;
27
+
28
+ /** Details about the funding available */
29
+ fundingDetails: FundingDetails;
30
+
31
+ /** Key dates and milestones in the application process */
32
+ applicationTimeline?: Event[];
33
+
34
+ /** Additional custom fields specific to this opportunity */
35
+ customFields?: Record<CustomField>;
36
+ }
37
+
38
+ // ########################################
39
+ // Model examples
40
+ // ########################################
41
+
42
+ namespace OpportunityExamples {
43
+ /** A complete opportunity example with all optional fields defined */
44
+ const complete = #{
45
+ id: "049b4b15-f219-4037-901e-cd95ac32fbc8",
46
+ source: "https://grants.gov/opportunity/123",
47
+ title: "Healthcare Innovation Research Grant",
48
+ description: "Funding for innovative healthcare delivery solutions",
49
+ fundingDetails: FundingExamples.allFields,
50
+ applicationTimeline: #[EventExamples.openDate, EventExamples.deadline],
51
+ customFields: #{
52
+ programArea: CustomFieldExamples.programArea,
53
+ eligibilityType: CustomFieldExamples.programArea,
54
+ },
55
+ };
56
+
57
+ /** A minimal opportunity example with only required fields */
58
+ const minimal = #{
59
+ id: "550e8400-e29b-41d4-a716-446655440001",
60
+ source: "https://grants.gov/opportunity/456",
61
+ title: "Small Business Innovation Grant",
62
+ description: "Supporting small business innovation projects",
63
+ fundingDetails: FundingExamples.onlyLimit,
64
+ };
65
+ }
@@ -0,0 +1,8 @@
1
+ import "@typespec/http";
2
+ import "@typespec/rest";
3
+
4
+ // Import individual route files to provide a consistent interface
5
+ import "./opportunities.tsp";
6
+
7
+ // Define the
8
+ namespace CommonGrants.Routes;
@@ -0,0 +1,23 @@
1
+ import "../models/index.tsp";
2
+
3
+ // Define the top-level namespace for CommonGrants routes
4
+ namespace CommonGrants.Routes;
5
+
6
+ // Expose the contents of the Http and Rest namespaces
7
+ // these include the decorators @route, @get, etc.
8
+ using TypeSpec.Http;
9
+ using TypeSpec.Rest;
10
+
11
+ // Define the `/opportunities` router
12
+ @route("/opportunities")
13
+ interface Opportunities {
14
+ @summary("List opportunities")
15
+ @doc("Get a list of opportunities")
16
+ @get
17
+ list(): Models.Opportunity[];
18
+
19
+ @summary("View opportunity")
20
+ @doc("View additional details about an opportunity")
21
+ @get
22
+ read(@path title: string): Models.Opportunity;
23
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@common-grants/core",
3
+ "version": "0.1.0-alpha.4",
4
+ "description": "TypeSpec library for defining grant opportunity data models and APIs",
5
+ "type": "module",
6
+ "main": "dist/src/index.js",
7
+ "types": "dist/src/index.d.ts",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "typespec": "./lib/main.tsp",
14
+ "default": "./dist/src/index.js",
15
+ "types": "./dist/src/index.d.ts"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist/src/**/*.js",
20
+ "dist/src/**/*.d.ts",
21
+ "lib/**/*.tsp",
22
+ "!src/**/*.test.*"
23
+ ],
24
+ "scripts": {
25
+ "clean": "rimraf dist tsp-output",
26
+ "build": "tsc -p .",
27
+ "watch": "tsc -p . --watch",
28
+ "typespec": "tsp compile lib/main.tsp",
29
+ "prepare": "npm run build",
30
+ "lint": "eslint . --fix",
31
+ "format": "prettier --write . && tsp format lib",
32
+ "check:lint": "eslint",
33
+ "check:format": "prettier --check . && tsp format lib --check"
34
+ },
35
+ "keywords": [
36
+ "typespec",
37
+ "api",
38
+ "grants",
39
+ "opportunities"
40
+ ],
41
+ "author": "CommonGrants",
42
+ "license": "CC0-1.0",
43
+ "peerDependencies": {
44
+ "@typespec/compiler": "^0.63.0",
45
+ "@typespec/http": "^0.63.0",
46
+ "@typespec/json-schema": "^0.63.0",
47
+ "@typespec/openapi3": "^0.63.0",
48
+ "@typespec/rest": "^0.63.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^20.10.6",
52
+ "eslint": "^9.18.0",
53
+ "prettier": "^3.4.2",
54
+ "rimraf": "^5.0.5",
55
+ "source-map-support": "^0.5.21",
56
+ "typescript": "^5.3.3",
57
+ "typescript-eslint": "^8.20.0"
58
+ }
59
+ }