@makegov/tango-node 0.1.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.
Files changed (125) hide show
  1. package/.editorconfig +13 -0
  2. package/.eslintrc.cjs +20 -0
  3. package/.prettierrc +8 -0
  4. package/CHANGELOG.md +15 -0
  5. package/LICENSE +21 -0
  6. package/README.md +331 -0
  7. package/dist/client.d.ts +48 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +321 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/config.d.ts +11 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +21 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/errors.d.ts +39 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +78 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +6 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/models/Agency.d.ts +8 -0
  24. package/dist/models/Agency.d.ts.map +1 -0
  25. package/dist/models/Agency.js +2 -0
  26. package/dist/models/Agency.js.map +1 -0
  27. package/dist/models/Contract.d.ts +17 -0
  28. package/dist/models/Contract.d.ts.map +1 -0
  29. package/dist/models/Contract.js +2 -0
  30. package/dist/models/Contract.js.map +1 -0
  31. package/dist/models/Department.d.ts +5 -0
  32. package/dist/models/Department.d.ts.map +1 -0
  33. package/dist/models/Department.js +2 -0
  34. package/dist/models/Department.js.map +1 -0
  35. package/dist/models/Entity.d.ts +11 -0
  36. package/dist/models/Entity.d.ts.map +1 -0
  37. package/dist/models/Entity.js +2 -0
  38. package/dist/models/Entity.js.map +1 -0
  39. package/dist/models/Forecast.d.ts +12 -0
  40. package/dist/models/Forecast.d.ts.map +1 -0
  41. package/dist/models/Forecast.js +2 -0
  42. package/dist/models/Forecast.js.map +1 -0
  43. package/dist/models/Grant.d.ts +10 -0
  44. package/dist/models/Grant.d.ts.map +1 -0
  45. package/dist/models/Grant.js +2 -0
  46. package/dist/models/Grant.js.map +1 -0
  47. package/dist/models/Location.d.ts +17 -0
  48. package/dist/models/Location.d.ts.map +1 -0
  49. package/dist/models/Location.js +2 -0
  50. package/dist/models/Location.js.map +1 -0
  51. package/dist/models/Notice.d.ts +9 -0
  52. package/dist/models/Notice.d.ts.map +1 -0
  53. package/dist/models/Notice.js +2 -0
  54. package/dist/models/Notice.js.map +1 -0
  55. package/dist/models/Opportunity.d.ts +11 -0
  56. package/dist/models/Opportunity.d.ts.map +1 -0
  57. package/dist/models/Opportunity.js +2 -0
  58. package/dist/models/Opportunity.js.map +1 -0
  59. package/dist/models/RecipientProfile.d.ts +12 -0
  60. package/dist/models/RecipientProfile.d.ts.map +1 -0
  61. package/dist/models/RecipientProfile.js +2 -0
  62. package/dist/models/RecipientProfile.js.map +1 -0
  63. package/dist/models/index.d.ts +11 -0
  64. package/dist/models/index.d.ts.map +1 -0
  65. package/dist/models/index.js +2 -0
  66. package/dist/models/index.js.map +1 -0
  67. package/dist/shapes/explicitSchemas.d.ts +24 -0
  68. package/dist/shapes/explicitSchemas.d.ts.map +1 -0
  69. package/dist/shapes/explicitSchemas.js +1818 -0
  70. package/dist/shapes/explicitSchemas.js.map +1 -0
  71. package/dist/shapes/factory.d.ts +29 -0
  72. package/dist/shapes/factory.d.ts.map +1 -0
  73. package/dist/shapes/factory.js +93 -0
  74. package/dist/shapes/factory.js.map +1 -0
  75. package/dist/shapes/generator.d.ts +53 -0
  76. package/dist/shapes/generator.d.ts.map +1 -0
  77. package/dist/shapes/generator.js +117 -0
  78. package/dist/shapes/generator.js.map +1 -0
  79. package/dist/shapes/index.d.ts +8 -0
  80. package/dist/shapes/index.d.ts.map +1 -0
  81. package/dist/shapes/index.js +8 -0
  82. package/dist/shapes/index.js.map +1 -0
  83. package/dist/shapes/parser.d.ts +36 -0
  84. package/dist/shapes/parser.d.ts.map +1 -0
  85. package/dist/shapes/parser.js +169 -0
  86. package/dist/shapes/parser.js.map +1 -0
  87. package/dist/shapes/schema.d.ts +30 -0
  88. package/dist/shapes/schema.d.ts.map +1 -0
  89. package/dist/shapes/schema.js +63 -0
  90. package/dist/shapes/schema.js.map +1 -0
  91. package/dist/shapes/schemaTypes.d.ts +42 -0
  92. package/dist/shapes/schemaTypes.d.ts.map +1 -0
  93. package/dist/shapes/schemaTypes.js +2 -0
  94. package/dist/shapes/schemaTypes.js.map +1 -0
  95. package/dist/shapes/types.d.ts +46 -0
  96. package/dist/shapes/types.d.ts.map +1 -0
  97. package/dist/shapes/types.js +25 -0
  98. package/dist/shapes/types.js.map +1 -0
  99. package/dist/types.d.ts +28 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +2 -0
  102. package/dist/types.js.map +1 -0
  103. package/dist/utils/dates.d.ts +14 -0
  104. package/dist/utils/dates.d.ts.map +1 -0
  105. package/dist/utils/dates.js +25 -0
  106. package/dist/utils/dates.js.map +1 -0
  107. package/dist/utils/http.d.ts +23 -0
  108. package/dist/utils/http.d.ts.map +1 -0
  109. package/dist/utils/http.js +158 -0
  110. package/dist/utils/http.js.map +1 -0
  111. package/dist/utils/number.d.ts +14 -0
  112. package/dist/utils/number.d.ts.map +1 -0
  113. package/dist/utils/number.js +14 -0
  114. package/dist/utils/number.js.map +1 -0
  115. package/dist/utils/unflatten.d.ts +11 -0
  116. package/dist/utils/unflatten.d.ts.map +1 -0
  117. package/dist/utils/unflatten.js +49 -0
  118. package/dist/utils/unflatten.js.map +1 -0
  119. package/docs/API_REFERENCE.md +201 -0
  120. package/docs/DYNAMIC_MODELS.md +127 -0
  121. package/docs/SHAPES.md +94 -0
  122. package/eslint.config.js +37 -0
  123. package/package.json +73 -0
  124. package/tsconfig.json +21 -0
  125. package/vitest.config.ts +18 -0
@@ -0,0 +1,201 @@
1
+ # Tango Node SDK – API Reference
2
+
3
+ This document provides the full API reference for the **Node.js / TypeScript**
4
+ version of the Tango SDK. It is a translation of the Python SDK documentation,
5
+ rewritten for JavaScript runtime semantics, async/await, and the TypeScript
6
+ type system.
7
+
8
+ ## Importing
9
+
10
+ ```ts
11
+ import { TangoClient, ShapeConfig } from "@makegov/tango-node";
12
+ // Models (optional)
13
+ import type { Contract } from "@makegov/tango-node/models";
14
+ ```
15
+
16
+ All methods are async and return Promises.
17
+
18
+ ---
19
+
20
+ ## Agencies
21
+
22
+ ### `listAgencies(options?)`
23
+
24
+ List federal departments and subagencies.
25
+
26
+ ```ts
27
+ const resp = await client.listAgencies({ page: 1, limit: 25 });
28
+ ```
29
+
30
+ #### Parameters
31
+
32
+ | Name | Type | Description |
33
+ | ------- | -------- | ------------------------------------------- |
34
+ | `page` | `number` | Page number (default 1). |
35
+ | `limit` | `number` | Max results per page (default 25, max 100). |
36
+
37
+ #### Returns
38
+
39
+ `PaginatedResponse<AgencyLike>`
40
+
41
+ ---
42
+
43
+ ### `getAgency(code)`
44
+
45
+ Fetch a single agency by its code.
46
+
47
+ ```ts
48
+ const agency = await client.getAgency("2000");
49
+ ```
50
+
51
+ Returns a shaped Agency object. Responses are materialized via the dynamic model pipeline (dates parsed, nested objects built).
52
+
53
+ ---
54
+
55
+ ## Business Types
56
+
57
+ ### `listBusinessTypes(options?)`
58
+
59
+ Lists SBA/USASpending business type entries.
60
+
61
+ ```ts
62
+ const types = await client.listBusinessTypes();
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Contracts
68
+
69
+ ### `listContracts(options)`
70
+
71
+ Search and list contract records.
72
+
73
+ ```ts
74
+ const resp = await client.listContracts({
75
+ keyword: "cloud",
76
+ naics_code: "541511",
77
+ shape: ShapeConfig.CONTRACTS_MINIMAL,
78
+ flat: true,
79
+ });
80
+ ```
81
+
82
+ #### Search / Filter Parameters
83
+
84
+ These mirror the Python SDK:
85
+
86
+ | Filter | Maps to API param |
87
+ | ---------------- | ----------------- |
88
+ | `keyword` | `search` |
89
+ | `naics_code` | `naics` |
90
+ | `psc_code` | `psc` |
91
+ | `recipient_name` | `recipient` |
92
+ | `recipient_uei` | `uei` |
93
+ | `set_aside_type` | `set_aside` |
94
+
95
+ Sorting:
96
+
97
+ ```ts
98
+ sort: "award_date",
99
+ order: "desc" // -> ordering="-award_date"
100
+ ```
101
+
102
+ Pagination + shaping options:
103
+
104
+ ```ts
105
+ shape: string,
106
+ flat: boolean,
107
+ flatLists: boolean,
108
+ page: number,
109
+ limit: number
110
+ ```
111
+
112
+ #### Returns
113
+
114
+ `PaginatedResponse<Contract>` materialized according to the requested shape. Date/datetime fields are parsed, decimals normalized to strings, nested recipients, agencies, and locations are objects.
115
+
116
+ ---
117
+
118
+ ## Entities
119
+
120
+ ### `listEntities(options)`
121
+
122
+ ```ts
123
+ const resp = await client.listEntities({
124
+ search: "Acme",
125
+ shape: ShapeConfig.ENTITIES_MINIMAL,
126
+ });
127
+ ```
128
+
129
+ Filters:
130
+
131
+ - `search`
132
+ - any field names supported by the API
133
+
134
+ ### `getEntity(uei, options?)`
135
+
136
+ Fetch a single entity by UEI or CAGE.
137
+
138
+ Returns a shaped entity object with nested addresses/fields based on the shape.
139
+
140
+ ---
141
+
142
+ ## Forecasts
143
+
144
+ ### `listForecasts(options)`
145
+
146
+ Forecast search, with optional shaping.
147
+
148
+ ---
149
+
150
+ ## Opportunities
151
+
152
+ ### `listOpportunities(options)`
153
+
154
+ Search SAM.gov opportunities with shaping.
155
+
156
+ ---
157
+
158
+ ## Notices
159
+
160
+ ### `listNotices(options)`
161
+
162
+ ---
163
+
164
+ ## Grants
165
+
166
+ ### `listGrants(options)`
167
+
168
+ ---
169
+
170
+ ## Error Types
171
+
172
+ All thrown by async methods:
173
+
174
+ - `TangoAPIError`
175
+ - `TangoAuthError`
176
+ - `TangoNotFoundError`
177
+ - `TangoRateLimitError`
178
+ - `TangoValidationError`
179
+ - `ShapeError`
180
+ - `ShapeParseError`
181
+ - `ShapeValidationError`
182
+ - `TypeGenerationError`
183
+ - `ModelInstantiationError`
184
+
185
+ ---
186
+
187
+ ## Pagination
188
+
189
+ All list endpoints return:
190
+
191
+ ```ts
192
+ interface PaginatedResponse<T> {
193
+ count: number;
194
+ next: string | null;
195
+ previous: string | null;
196
+ pageMetadata: Record<string, unknown> | null;
197
+ results: T[];
198
+ }
199
+ ```
200
+
201
+ You can follow `next` / `previous` manually or use your own wrapper.
@@ -0,0 +1,127 @@
1
+ # Tango Node SDK – Dynamic Models Guide
2
+
3
+ This document explains how the **Node.js dynamic shaping system** works.
4
+ It is a full translation of the Python `DEVELOPERS.md` shaping guide.
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ Tango’s dynamic modeling allows you to:
11
+
12
+ - Request _exactly the fields you want_
13
+ - Validate the shape string against Tango’s schemas
14
+ - Generate a typed model descriptor at runtime
15
+ - Materialize shaped objects using correct:
16
+ - date parsing
17
+ - datetime parsing
18
+ - decimal handling
19
+ - list vs scalar logic
20
+ - nested structure
21
+
22
+ ---
23
+
24
+ ## Components
25
+
26
+ ### ShapeParser
27
+
28
+ Parses shape strings into a `ShapeSpec`.
29
+
30
+ ```ts
31
+ import { ShapeParser } from "@makegov/tango-node/shapes";
32
+
33
+ const parser = new ShapeParser();
34
+ const spec = parser.parse("key,piid,recipient(display_name)");
35
+ ```
36
+
37
+ ### SchemaRegistry
38
+
39
+ Holds the field schemas for all models.
40
+
41
+ ```ts
42
+ import { SchemaRegistry } from "@makegov/tango-node/shapes";
43
+
44
+ const registry = new SchemaRegistry();
45
+ registry.getField("Contract", "award_date");
46
+ ```
47
+
48
+ ### TypeGenerator
49
+
50
+ Builds a `GeneratedModel` descriptor from `(baseModel, shapeSpec)`.
51
+
52
+ ```ts
53
+ import { TypeGenerator } from "@makegov/tango-node/shapes";
54
+
55
+ const gen = new TypeGenerator();
56
+ const model = gen.generateModelDescriptor("Contract", spec);
57
+ ```
58
+
59
+ ### ModelFactory
60
+
61
+ Takes a descriptor + raw API JSON and produces typed shaped objects. The TangoClient now uses this pipeline automatically after fetching data.
62
+
63
+ ```ts
64
+ import { TangoClient } from "@makegov/tango-node";
65
+
66
+ const client = new TangoClient({ apiKey: process.env.TANGO_API_KEY });
67
+ const contracts = await client.listContracts({
68
+ shape: "key,award_date,recipient(display_name)",
69
+ });
70
+
71
+ // contracts.results are materialized via ModelFactory:
72
+ // - date/datetime parsed to Date
73
+ // - decimals normalized to string
74
+ // - nested structures enforced
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Example: Full Shaping Pipeline (manual)
80
+
81
+ ```ts
82
+ const parser = new ShapeParser();
83
+ const spec = parser.parse("key,award_date,recipient(display_name)");
84
+
85
+ const gen = new TypeGenerator();
86
+ const descriptor = gen.generateModelDescriptor("Contract", spec);
87
+
88
+ const factory = new ModelFactory();
89
+ const shaped = factory.createOne("Contract", spec, {
90
+ key: "C-1",
91
+ award_date: "2024-01-15",
92
+ recipient: { display_name: "Acme" },
93
+ });
94
+ ```
95
+
96
+ `shaped` becomes:
97
+
98
+ ```ts
99
+ {
100
+ key: "C-1",
101
+ award_date: Date("2024-01-15"),
102
+ recipient: { display_name: "Acme" }
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Type Safety
109
+
110
+ Node SDK enforces shape correctness at runtime and guarantees nested structures. The client materializes responses through ModelFactory, so the shape schema is applied automatically. TypeScript interfaces are not codegenerated per shape at build time; the SDK exports lightweight model interfaces in `@makegov/tango-node/models` for convenience.
111
+
112
+ ---
113
+
114
+ ## Caching
115
+
116
+ TypeGenerator caches descriptors with FIFO eviction.
117
+
118
+ ShapeParser also caches parse results.
119
+
120
+ ---
121
+
122
+ ## Nested Models
123
+
124
+ If a field is nested in the schema (e.g. `"recipient"` → `RecipientProfile`),
125
+ the generator recursively builds the nested descriptor.
126
+
127
+ ---
package/docs/SHAPES.md ADDED
@@ -0,0 +1,94 @@
1
+ # Tango Node SDK – Shaping Guide
2
+
3
+ A complete translation of the Python SHAPES.md document for Node.
4
+
5
+ ---
6
+
7
+ ## Why Shapes?
8
+
9
+ Tango resources can have hundreds of fields. Shapes let you request:
10
+
11
+ - Only what you need
12
+ - In nested form
13
+ - With aliases
14
+ - With wildcards
15
+ - With flattening options
16
+
17
+ ---
18
+
19
+ ## Shape Grammar
20
+
21
+ ```
22
+ shape := field_list
23
+ field_list := field ("," field)*
24
+ field := field_name [alias] [nested]
25
+ field_name := identifier | "*"
26
+ alias := "::" identifier
27
+ nested := "(" field_list ")"
28
+ identifier := [a-zA-Z_][a-zA-Z0-9_]*
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Examples
34
+
35
+ ### Simple
36
+
37
+ ```ts
38
+ shape: "key,piid,award_date";
39
+ ```
40
+
41
+ ### Nested
42
+
43
+ ```ts
44
+ shape: "recipient(display_name,uei)";
45
+ ```
46
+
47
+ ### Aliases
48
+
49
+ ```ts
50
+ shape: "recipient::vendor(display_name)";
51
+ ```
52
+
53
+ ### Wildcard
54
+
55
+ ```ts
56
+ shape: "*";
57
+ ```
58
+
59
+ ### Wildcard nested
60
+
61
+ ```ts
62
+ shape: "recipient(*)";
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Flat Responses
68
+
69
+ ```ts
70
+ shape: ShapeConfig.CONTRACTS_MINIMAL,
71
+ flat: true
72
+ ```
73
+
74
+ The Tango API returns dotted keys; the SDK unflattens them:
75
+
76
+ ```ts
77
+ recipient.display_name → recipient.display_name
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Validation
83
+
84
+ ShapeParser enforces syntax.
85
+
86
+ TypeGenerator enforces semantic model rules (existence of fields, nested models).
87
+
88
+ ---
89
+
90
+ ## Performance Tips
91
+
92
+ - Use minimal shapes in production.
93
+ - Avoid full-wildcard unless you need all fields.
94
+ - Prefer shallow nested shapes for large nested structures.
@@ -0,0 +1,37 @@
1
+ import js from "@eslint/js";
2
+ import tsPlugin from "@typescript-eslint/eslint-plugin";
3
+ import tsParser from "@typescript-eslint/parser";
4
+ import globals from "globals";
5
+
6
+ export default [
7
+ {
8
+ ignores: ["dist/**", "node_modules/**"],
9
+ },
10
+ {
11
+ files: ["**/*.ts"],
12
+ languageOptions: {
13
+ parser: tsParser,
14
+ parserOptions: {
15
+ project: "./tsconfig.json",
16
+ tsconfigRootDir: import.meta.dirname,
17
+ sourceType: "module",
18
+ },
19
+ globals: {
20
+ ...globals.node,
21
+ ...globals.browser,
22
+ fetch: "readonly",
23
+ Request: "readonly",
24
+ Response: "readonly",
25
+ Headers: "readonly",
26
+ AbortController: "readonly",
27
+ },
28
+ },
29
+ plugins: {
30
+ "@typescript-eslint": tsPlugin,
31
+ },
32
+ rules: {
33
+ ...js.configs.recommended.rules,
34
+ ...tsPlugin.configs["recommended-type-checked"].rules,
35
+ },
36
+ },
37
+ ];
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@makegov/tango-node",
3
+ "version": "0.1.4",
4
+ "description": "Official Node.js SDK for the Tango API – dynamic response shaping, typed models, and full endpoint coverage.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./models": {
16
+ "import": "./dist/models/index.js",
17
+ "types": "./dist/models/index.d.ts",
18
+ "default": "./dist/models/index.js"
19
+ },
20
+ "./shapes": {
21
+ "import": "./dist/shapes/index.js",
22
+ "types": "./dist/shapes/index.d.ts",
23
+ "default": "./dist/shapes/index.js"
24
+ }
25
+ },
26
+ "sideEffects": false,
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "clean": "rm -rf dist",
30
+ "lint": "eslint src --ext .ts",
31
+ "format": "prettier --write .",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "vitest",
34
+ "coverage": "vitest run --coverage",
35
+ "prepare": "npm run build",
36
+ "prepublishOnly": "npm run lint && npm run test && npm run build"
37
+ },
38
+ "keywords": [
39
+ "tango",
40
+ "makegov",
41
+ "federal-contracts",
42
+ "procurement",
43
+ "grants",
44
+ "sdk",
45
+ "api-client",
46
+ "govtech"
47
+ ],
48
+ "author": "MakeGov",
49
+ "license": "MIT",
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/makegov/tango-node.git"
53
+ },
54
+ "bugs": {
55
+ "url": "https://github.com/makegov/tango-node/issues"
56
+ },
57
+ "homepage": "https://github.com/makegov/tango-node#readme",
58
+ "engines": {
59
+ "node": ">=18.0.0"
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^22.0.0",
63
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
64
+ "@typescript-eslint/parser": "^8.0.0",
65
+ "@vitest/coverage-v8": "^2.0.0",
66
+ "eslint": "^9.0.0",
67
+ "globals": "^15.12.0",
68
+ "prettier": "^3.0.0",
69
+ "typescript": "^5.6.0",
70
+ "vitest": "^2.0.0"
71
+ },
72
+ "dependencies": {}
73
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "allowSyntheticDefaultImports": true,
7
+ "esModuleInterop": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "outDir": "./dist",
15
+ "rootDir": "./src",
16
+ "resolveJsonModule": true,
17
+ "types": ["node", "vitest"]
18
+ },
19
+ "include": ["src"],
20
+ "exclude": ["dist", "node_modules", "tests"]
21
+ }
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ globals: true,
7
+ include: ["tests/**/*.test.ts"],
8
+ coverage: {
9
+ provider: "v8",
10
+ reporter: ["text", "html"],
11
+ exclude: [".*", "*.config.*s", "dist/**/**", "src/index.ts", "src/shapes/index.ts", "tests/**/**"],
12
+ },
13
+ },
14
+ esbuild: {
15
+ loader: "ts",
16
+ target: "es2020",
17
+ },
18
+ });