diff --git a/.editorconfig b/.editorconfig
index 6f31936..7371aa8 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -21,6 +21,9 @@ resharper_string_literal_typo_highlighting = none
indent_size = 4
indent_style = tab
+[Directory.Packages.props]
+insert_final_newline = false
+
[dotnet-tools.json]
insert_final_newline = false
@@ -91,6 +94,7 @@ csharp_style_prefer_local_over_anonymous_function = true : suggestion
csharp_style_prefer_not_pattern = true : suggestion
csharp_style_prefer_null_check_over_type_check = true : suggestion
csharp_style_prefer_pattern_matching = true : suggestion
+csharp_style_prefer_primary_constructors = true : suggestion
csharp_style_prefer_range_operator = true : suggestion
csharp_style_prefer_switch_expression = true : suggestion
csharp_style_prefer_tuple_swap = true : warning
@@ -207,6 +211,7 @@ dotnet_diagnostic.IDE0160.severity = suggestion
dotnet_diagnostic.IDE0161.severity = suggestion
dotnet_diagnostic.IDE0170.severity = suggestion
dotnet_diagnostic.IDE0180.severity = warning
+dotnet_diagnostic.IDE0290.severity = suggestion
dotnet_diagnostic.IDE1005.severity = suggestion
dotnet_diagnostic.SA0001.severity = none
dotnet_diagnostic.SA1003.severity = none
@@ -330,6 +335,7 @@ resharper_arrange_missing_parentheses_highlighting = hint
resharper_arrange_trailing_comma_in_multiline_lists_highlighting = warning
resharper_comment_typo_highlighting = none
resharper_compare_of_floats_by_equality_operator_highlighting = suggestion
+resharper_convert_to_primary_constructor_highlighting = hint
resharper_csharp_align_first_arg_by_paren = false
resharper_csharp_align_linq_query = false
resharper_csharp_align_multiline_argument = false
@@ -351,6 +357,7 @@ resharper_csharp_indent_anonymous_method_block = false
resharper_csharp_indent_nested_for_stmt = true
resharper_csharp_indent_nested_foreach_stmt = true
resharper_csharp_indent_nested_while_stmt = true
+resharper_csharp_indent_raw_literal_string = indent
resharper_csharp_int_align = false
resharper_csharp_keep_existing_arrangement = true
resharper_csharp_nested_ternary_style = simple_wrap
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 753fb90..4ea8dc8 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -1,24 +1,20 @@
name: Build
+
on:
- workflow_dispatch:
push:
- paths-ignore:
- - '*.md'
- - 'docs/**'
- branches:
- - 'master'
- tags-ignore:
- - '**'
+ branches: [master]
+ tags-ignore: ['**']
pull_request:
- paths-ignore:
- - '*.md'
- - 'docs/**'
+ workflow_dispatch:
+
env:
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
+
defaults:
run:
shell: pwsh
+
jobs:
build:
runs-on: ${{ matrix.os }}
@@ -26,14 +22,16 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
+ - name: Install .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: |
+ 6.0.x
+ 8.0.x
- name: Check out code
uses: actions/checkout@v3
with:
- fetch-depth: 0
- - name: Set up .NET 7
- uses: actions/setup-dotnet@v3
- with:
- dotnet-version: 7.0.x
+ fetch-depth: 0 # required to publish docs
- name: Restore
run: .\build.ps1 restore
- name: Build
diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml
new file mode 100644
index 0000000..ca0e689
--- /dev/null
+++ b/.github/workflows/publish-docs.yaml
@@ -0,0 +1,31 @@
+name: Publish Docs
+
+on:
+ workflow_dispatch:
+
+env:
+ DOTNET_NOLOGO: 1
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
+
+defaults:
+ run:
+ shell: pwsh
+
+jobs:
+ publish-docs:
+ runs-on: windows-latest
+ steps:
+ - name: Install .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: |
+ 6.0.x
+ 8.0.x
+ - name: Check out code
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0 # required to publish docs
+ - name: Publish Docs
+ env:
+ BUILD_BOT_PASSWORD: ${{ secrets.BUILD_BOT_PASSWORD }}
+ run: .\build.ps1 publish --no-test --trigger publish-docs
diff --git a/.gitignore b/.gitignore
index 4aa6cfb..a2ad192 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,15 +1,16 @@
+.vs/
+.idea/
+artifacts/
bin/
obj/
release/
-.vs/
-.idea/
-Thumbs.db
+
*.cache
-*.user
-*.userprefs
+*.log
*.ncrunchproject
*.ncrunchsolution
+*.user
+launchSettings.json
nCrunchTemp*
_ReSharper*
.DS_Store
-launchSettings.json
diff --git a/Directory.Build.props b/Directory.Build.props
index ba3e246..0faaf43 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,13 +1,14 @@
- 2.8.0
- 11.0
+ 3.0.0
+ 2.8.0
+ 12.0
enable
enable
true
+ $(NoWarn);1591;1998;NU1507;NU5105
en-US
- $(NoWarn);1591;1998;NU5105
embedded
FacilityApi
FacilityJavaScript
@@ -20,11 +21,16 @@
true
true
true
- AllEnabledByDefault
+ latest-all
true
true
false
false
+ false
+ true
+ true
+ true
+ true
@@ -32,14 +38,4 @@
true
-
- 2.11.0
-
-
-
-
-
-
-
-
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..ae712e5
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,19 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FacilityJavaScript.sln b/FacilityJavaScript.sln
index 611ebeb..215ed9c 100644
--- a/FacilityJavaScript.sln
+++ b/FacilityJavaScript.sln
@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
dotnet-tools.json = dotnet-tools.json
example\ExampleApi.fsd = example\ExampleApi.fsd
+ Directory.Packages.props = Directory.Packages.props
+ global.json = global.json
LICENSE = LICENSE
nuget.config = nuget.config
ts\package.json = ts\package.json
diff --git a/LICENSE b/LICENSE
index 2fd22c4..df586e7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright 2022 Ed Ball
+Copyright 2024 Ed Ball
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 29cf797..80b8c2d 100644
--- a/README.md
+++ b/README.md
@@ -10,3 +10,17 @@ fsdgenjs | A tool that generates JavaScript or TypeScript for a Facility Service
Facility.CodeGen.JavaScript | A library that generates JavaScript or TypeScript for a Facility Service Definition. | [](https://www.nuget.org/packages/Facility.CodeGen.JavaScript)
[Documentation](https://facilityapi.github.io/) | [Release Notes](https://github.com/FacilityApi/FacilityJavaScript/blob/master/ReleaseNotes.md) | [Contributing](https://github.com/FacilityApi/FacilityJavaScript/blob/master/CONTRIBUTING.md)
+
+## Conformance
+
+To run conformance tests, first start one of the conformance servers from within the `/conformance` folder:
+
+```
+npm run fastify
+```
+
+Then run the conformance tool against the running service.
+
+```
+npm run test
+```
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index 93fb9ec..75597e3 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -2,6 +2,31 @@
These are the NuGet package releases. See also [npm Release Notes](ReleaseNotesNpm.md).
+## 3.0.0
+
+* Support required fields as not optional in TypeScript.
+
+## 2.11.1
+
+* Ignore events for now.
+
+## 2.11.0
+
+* Enable use of external enumerations as URI and header request parameters.
+
+## 2.10.1
+
+* Fix TypeScript file name references when using `file-name-suffix` generator setting.
+
+## 2.10.0
+
+* Add `file-name-suffix` generator setting.
+* Support generating Fastify plugins.
+
+## 2.9.0
+
+* Generate error sets in TypeScript.
+
## 2.8.0
* Support `datetime`.
diff --git a/build.ps1 b/build.ps1
index 21a0bb6..9b32821 100755
--- a/build.ps1
+++ b/build.ps1
@@ -2,14 +2,9 @@
$ErrorActionPreference = 'Stop'
Push-Location $PSScriptRoot
try {
- if (-not (Test-Path ./tools/bin/Build) -or
- (Get-ChildItem ./tools/Build/* | Measure-Object LastWriteTime -Maximum).Maximum -gt
- (Get-ChildItem ./tools/bin/Build/* | Measure-Object LastWriteTime -Maximum).Maximum) {
- if (Test-Path ./tools/bin/Build) { Remove-Item ./tools/bin/Build -Recurse }
- dotnet publish ./tools/Build/Build.csproj --output ./tools/bin/Build --nologo --verbosity quiet
- if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- }
- dotnet ./tools/bin/Build/Build.dll $args
+ dotnet publish ./tools/Build/Build.csproj --artifacts-path ./artifacts --nologo --verbosity quiet
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ dotnet ./artifacts/publish/Build/release/Build.dll $args
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
finally {
diff --git a/conformance/.gitignore b/conformance/.gitignore
new file mode 100644
index 0000000..1eae0cf
--- /dev/null
+++ b/conformance/.gitignore
@@ -0,0 +1,2 @@
+dist/
+node_modules/
diff --git a/conformance/ConformanceApi.fsd b/conformance/ConformanceApi.fsd
index ff35857..85a03e2 100644
--- a/conformance/ConformanceApi.fsd
+++ b/conformance/ConformanceApi.fsd
@@ -253,6 +253,15 @@ service ConformanceApi
[http(from: body, type: "application/x-output")] content: bytes;
}
+ [http(method: GET)]
+ event fibonacci
+ {
+ count: int32!;
+ }:
+ {
+ value: int32!;
+ }
+
data Any
{
string: string;
diff --git a/conformance/ts/package-lock.json b/conformance/package-lock.json
similarity index 52%
rename from conformance/ts/package-lock.json
rename to conformance/package-lock.json
index da656ef..74552ef 100644
--- a/conformance/ts/package-lock.json
+++ b/conformance/package-lock.json
@@ -1,7 +1,7 @@
{
"name": "conformance-tester",
"version": "0.0.0",
- "lockfileVersion": 2,
+ "lockfileVersion": 3,
"requires": true,
"packages": {
"": {
@@ -9,7 +9,8 @@
"version": "0.0.0",
"license": "UNLICENSED",
"dependencies": {
- "facility-core": "file:../../ts"
+ "facility-core": "file:../ts",
+ "fastify": "^4.25.1"
},
"devDependencies": {
"@types/chai": "^4.2.18",
@@ -17,12 +18,16 @@
"@types/node": "^12.20.15",
"@types/node-fetch": "^2.5.10",
"chai": "^4.3.4",
+ "fastify-cli": "^5.9.0",
"mocha": "^10.0.0",
"node-fetch": "^2.6.7",
- "typescript": "~3.9.9"
+ "typescript": "^5.2.2"
}
},
"../../ts": {
+ "extraneous": true
+ },
+ "../ts": {
"name": "facility-core",
"version": "2.2.0",
"license": "MIT",
@@ -40,10 +45,38 @@
"typescript": "~3.9.9"
}
},
+ "node_modules/@fastify/ajv-compiler": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz",
+ "integrity": "sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==",
+ "dependencies": {
+ "ajv": "^8.11.0",
+ "ajv-formats": "^2.1.1",
+ "fast-uri": "^2.0.0"
+ }
+ },
+ "node_modules/@fastify/deepmerge": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz",
+ "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A=="
+ },
+ "node_modules/@fastify/error": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz",
+ "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ=="
+ },
+ "node_modules/@fastify/fast-json-stringify-compiler": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz",
+ "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==",
+ "dependencies": {
+ "fast-json-stringify": "^5.7.0"
+ }
+ },
"node_modules/@types/chai": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz",
- "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==",
+ "version": "4.3.11",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz",
+ "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==",
"dev": true
},
"node_modules/@types/mocha": {
@@ -59,20 +92,61 @@
"dev": true
},
"node_modules/@types/node-fetch": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz",
- "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==",
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.9.tgz",
+ "integrity": "sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==",
"dev": true,
"dependencies": {
"@types/node": "*",
- "form-data": "^3.0.0"
+ "form-data": "^4.0.0"
}
},
- "node_modules/@ungap/promise-all-settled": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
- "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
- "dev": true
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/abstract-logging": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
+ "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="
+ },
+ "node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
},
"node_modules/ansi-colors": {
"version": "4.1.1",
@@ -108,9 +182,9 @@
}
},
"node_modules/anymatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
- "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
@@ -120,6 +194,11 @@
"node": ">= 8"
}
},
+ "node_modules/archy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+ "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw=="
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -141,12 +220,49 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
},
+ "node_modules/atomic-sleep": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
+ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/avvio": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.2.1.tgz",
+ "integrity": "sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==",
+ "dependencies": {
+ "archy": "^1.0.0",
+ "debug": "^4.0.0",
+ "fastq": "^1.6.1"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -183,6 +299,29 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
@@ -196,18 +335,18 @@
}
},
"node_modules/chai": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz",
- "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==",
+ "version": "4.3.10",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz",
+ "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==",
"dev": true,
"dependencies": {
"assertion-error": "^1.1.0",
- "check-error": "^1.0.2",
- "deep-eql": "^3.0.1",
- "get-func-name": "^2.0.0",
- "loupe": "^2.3.1",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
"pathval": "^1.1.1",
- "type-detect": "^4.0.5"
+ "type-detect": "^4.0.8"
},
"engines": {
"node": ">=4"
@@ -242,10 +381,13 @@
}
},
"node_modules/check-error": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
- "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
"dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
"engines": {
"node": "*"
}
@@ -288,6 +430,12 @@
"wrap-ansi": "^7.0.0"
}
},
+ "node_modules/close-with-grace": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/close-with-grace/-/close-with-grace-1.2.0.tgz",
+ "integrity": "sha512-Xga0jyAb4fX98u5pZAgqlbqHP8cHuy5M3Wto0k0L/36aP2C25Cjp51XfPw3Hz7dNC2L2/hF/PK/KJhO275L+VA==",
+ "dev": true
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -306,6 +454,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -318,12 +472,56 @@
"node": ">= 0.8"
}
},
+ "node_modules/commist": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz",
+ "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==",
+ "dev": true
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/dateformat": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
+ "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/debug/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
"node_modules/decamelize": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
@@ -337,15 +535,15 @@
}
},
"node_modules/deep-eql": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
- "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
+ "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
"dev": true,
"dependencies": {
"type-detect": "^4.0.0"
},
"engines": {
- "node": ">=0.12"
+ "node": ">=6"
}
},
"node_modules/delayed-stream": {
@@ -366,12 +564,33 @@
"node": ">=0.3.1"
}
},
+ "node_modules/dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/motdotla/dotenv?sponsor=1"
+ }
+ },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -393,10 +612,162 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
"node_modules/facility-core": {
- "resolved": "../../ts",
+ "resolved": "../ts",
"link": true
},
+ "node_modules/fast-content-type-parse": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz",
+ "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ=="
+ },
+ "node_modules/fast-copy": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz",
+ "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==",
+ "dev": true
+ },
+ "node_modules/fast-decode-uri-component": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
+ "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/fast-json-stringify": {
+ "version": "5.9.1",
+ "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.9.1.tgz",
+ "integrity": "sha512-NMrf+uU9UJnTzfxaumMDXK1NWqtPCfGoM9DYIE+ESlaTQqjlANFBy0VAbsm6FB88Mx0nceyi18zTo5kIEUlzxg==",
+ "dependencies": {
+ "@fastify/deepmerge": "^1.0.0",
+ "ajv": "^8.10.0",
+ "ajv-formats": "^2.1.1",
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^2.1.0",
+ "json-schema-ref-resolver": "^1.0.1",
+ "rfdc": "^1.2.0"
+ }
+ },
+ "node_modules/fast-querystring": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz",
+ "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==",
+ "dependencies": {
+ "fast-decode-uri-component": "^1.0.1"
+ }
+ },
+ "node_modules/fast-redact": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz",
+ "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-safe-stringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
+ "dev": true
+ },
+ "node_modules/fast-uri": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.3.0.tgz",
+ "integrity": "sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw=="
+ },
+ "node_modules/fastify": {
+ "version": "4.25.1",
+ "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.25.1.tgz",
+ "integrity": "sha512-D8d0rv61TwqoAS7lom2tvIlgVMlx88lLsiwXyWNjA7CU/LC/mx/Gp2WAlC0S/ABq19U+y/aRvYFG5xLUu2aMrg==",
+ "dependencies": {
+ "@fastify/ajv-compiler": "^3.5.0",
+ "@fastify/error": "^3.4.0",
+ "@fastify/fast-json-stringify-compiler": "^4.3.0",
+ "abstract-logging": "^2.0.1",
+ "avvio": "^8.2.1",
+ "fast-content-type-parse": "^1.1.0",
+ "fast-json-stringify": "^5.8.0",
+ "find-my-way": "^7.7.0",
+ "light-my-request": "^5.11.0",
+ "pino": "^8.17.0",
+ "process-warning": "^3.0.0",
+ "proxy-addr": "^2.0.7",
+ "rfdc": "^1.3.0",
+ "secure-json-parse": "^2.7.0",
+ "semver": "^7.5.4",
+ "toad-cache": "^3.3.0"
+ }
+ },
+ "node_modules/fastify-cli": {
+ "version": "5.9.0",
+ "resolved": "https://registry.npmjs.org/fastify-cli/-/fastify-cli-5.9.0.tgz",
+ "integrity": "sha512-CaIte5SwkLuvlzpdd1Al1VRVVwm2TQVV4bfVP4oz/Z54KVSo6pqNTgnxWOmyzdcNUbFnhJ3Z4vRjzvHoymP5cQ==",
+ "dev": true,
+ "dependencies": {
+ "@fastify/deepmerge": "^1.2.0",
+ "chalk": "^4.1.2",
+ "chokidar": "^3.5.2",
+ "close-with-grace": "^1.1.0",
+ "commist": "^3.0.0",
+ "dotenv": "^16.0.0",
+ "fastify": "^4.0.0",
+ "fastify-plugin": "^4.0.0",
+ "generify": "^4.0.0",
+ "help-me": "^4.0.1",
+ "is-docker": "^2.0.0",
+ "make-promises-safe": "^5.1.0",
+ "pino-pretty": "^10.1.0",
+ "pkg-up": "^3.1.0",
+ "resolve-from": "^5.0.0",
+ "semver": "^7.3.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "fastify": "cli.js"
+ }
+ },
+ "node_modules/fastify-cli/node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/fastify-plugin": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz",
+ "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -409,6 +780,19 @@
"node": ">=8"
}
},
+ "node_modules/find-my-way": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.7.0.tgz",
+ "integrity": "sha512-+SrHpvQ52Q6W9f3wJoJBbAQULJuNEEQwBvlvYwACDhBTLOTMiQ0HYWh4+vC3OivGP2ENcTI1oKlFA2OepJNjhQ==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-querystring": "^1.0.0",
+ "safe-regex2": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -435,9 +819,9 @@
}
},
"node_modules/form-data": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
- "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"dependencies": {
"asynckit": "^0.4.0",
@@ -448,6 +832,14 @@
"node": ">= 6"
}
},
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -455,9 +847,9 @@
"dev": true
},
"node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
@@ -468,6 +860,44 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/generify": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/generify/-/generify-4.2.0.tgz",
+ "integrity": "sha512-b4cVhbPfbgbCZtK0dcUc1lASitXGEAIqukV5DDAyWm25fomWnV+C+a1yXvqikcRZXHN2j0pSDyj3cTfzq8pC7Q==",
+ "dev": true,
+ "dependencies": {
+ "isbinaryfile": "^4.0.2",
+ "pump": "^3.0.0",
+ "split2": "^3.0.0",
+ "walker": "^1.0.6"
+ },
+ "bin": {
+ "generify": "generify.js"
+ }
+ },
+ "node_modules/generify/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/generify/node_modules/split2": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
+ "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "^3.0.0"
+ }
+ },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -478,9 +908,9 @@
}
},
"node_modules/get-func-name": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
- "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
"dev": true,
"engines": {
"node": "*"
@@ -558,6 +988,68 @@
"he": "bin/he"
}
},
+ "node_modules/help-me": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz",
+ "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^8.0.0",
+ "readable-stream": "^3.6.0"
+ }
+ },
+ "node_modules/help-me/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/help-me/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -574,6 +1066,14 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -586,6 +1086,21 @@
"node": ">=8"
}
},
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -646,10 +1161,31 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "node_modules/isbinaryfile": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz",
+ "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/joycon": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
+ "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@@ -658,6 +1194,34 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/json-schema-ref-resolver": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz",
+ "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ },
+ "node_modules/light-my-request": {
+ "version": "5.11.0",
+ "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.11.0.tgz",
+ "integrity": "sha512-qkFCeloXCOMpmEdZ/MV91P8AT4fjwFXWaAFz3lUeStM8RcoM1ks4J/F8r1b3r6y/H4u3ACEJ1T+Gv5bopj7oDA==",
+ "dependencies": {
+ "cookie": "^0.5.0",
+ "process-warning": "^2.0.0",
+ "set-cookie-parser": "^2.4.1"
+ }
+ },
+ "node_modules/light-my-request/node_modules/process-warning": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz",
+ "integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA=="
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -690,12 +1254,38 @@
}
},
"node_modules/loupe": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz",
- "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==",
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/make-promises-safe": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/make-promises-safe/-/make-promises-safe-5.1.0.tgz",
+ "integrity": "sha512-AfdZ49rtyhQR/6cqVKGoH7y4ql7XkS5HJI1lZm0/5N6CQosy1eYbBJ/qbhkKHzo17UH7M918Bysf6XB9f3kS1g==",
+ "dev": true
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
"dev": true,
"dependencies": {
- "get-func-name": "^2.0.0"
+ "tmpl": "1.0.5"
}
},
"node_modules/mime-db": {
@@ -731,13 +1321,21 @@
"node": ">=10"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/mocha": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz",
- "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+ "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
"dev": true,
"dependencies": {
- "@ungap/promise-all-settled": "1.1.2",
"ansi-colors": "4.1.1",
"browser-stdout": "1.3.1",
"chokidar": "3.5.3",
@@ -772,30 +1370,7 @@
"url": "https://opencollective.com/mochajs"
}
},
- "node_modules/mocha/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/mocha/node_modules/debug/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/mocha/node_modules/ms": {
+ "node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
@@ -814,9 +1389,9 @@
}
},
"node_modules/node-fetch": {
- "version": "2.6.7",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
- "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dev": true,
"dependencies": {
"whatwg-url": "^5.0.0"
@@ -842,6 +1417,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/on-exit-leak-free": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
+ "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -881,6 +1464,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -920,6 +1512,198 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pino": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/pino/-/pino-8.17.1.tgz",
+ "integrity": "sha512-YoN7/NJgnsJ+fkADZqjhRt96iepWBndQHeClmSBH0sQWCb8zGD74t00SK4eOtKFi/f8TUmQnfmgglEhd2kI1RQ==",
+ "dependencies": {
+ "atomic-sleep": "^1.0.0",
+ "fast-redact": "^3.1.1",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "v1.1.0",
+ "pino-std-serializers": "^6.0.0",
+ "process-warning": "^2.0.0",
+ "quick-format-unescaped": "^4.0.3",
+ "real-require": "^0.2.0",
+ "safe-stable-stringify": "^2.3.1",
+ "sonic-boom": "^3.7.0",
+ "thread-stream": "^2.0.0"
+ },
+ "bin": {
+ "pino": "bin.js"
+ }
+ },
+ "node_modules/pino-abstract-transport": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz",
+ "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==",
+ "dependencies": {
+ "readable-stream": "^4.0.0",
+ "split2": "^4.0.0"
+ }
+ },
+ "node_modules/pino-pretty": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.0.tgz",
+ "integrity": "sha512-JthvQW289q3454mhM3/38wFYGWPiBMR28T3CpDNABzoTQOje9UKS7XCJQSnjWF9LQGQkGd8D7h0oq+qwiM3jFA==",
+ "dev": true,
+ "dependencies": {
+ "colorette": "^2.0.7",
+ "dateformat": "^4.6.3",
+ "fast-copy": "^3.0.0",
+ "fast-safe-stringify": "^2.1.1",
+ "help-me": "^5.0.0",
+ "joycon": "^3.1.1",
+ "minimist": "^1.2.6",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^1.0.0",
+ "pump": "^3.0.0",
+ "readable-stream": "^4.0.0",
+ "secure-json-parse": "^2.4.0",
+ "sonic-boom": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "bin": {
+ "pino-pretty": "bin.js"
+ }
+ },
+ "node_modules/pino-pretty/node_modules/help-me": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
+ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
+ "dev": true
+ },
+ "node_modules/pino-std-serializers": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz",
+ "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="
+ },
+ "node_modules/pino/node_modules/process-warning": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz",
+ "integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA=="
+ },
+ "node_modules/pkg-up": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
+ "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-up/node_modules/find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pkg-up/node_modules/locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pkg-up/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-up/node_modules/p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pkg-up/node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/process-warning": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz",
+ "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/quick-format-unescaped": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
+ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
+ },
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -929,6 +1713,21 @@
"safe-buffer": "^5.1.0"
}
},
+ "node_modules/readable-stream": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz",
+ "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==",
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "buffer": "^6.0.3",
+ "events": "^3.3.0",
+ "process": "^0.11.10",
+ "string_decoder": "^1.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -941,6 +1740,14 @@
"node": ">=8.10.0"
}
},
+ "node_modules/real-require": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
+ "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
+ "engines": {
+ "node": ">= 12.13.0"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -950,11 +1757,49 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ret": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz",
+ "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rfdc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
+ "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -970,6 +1815,41 @@
}
]
},
+ "node_modules/safe-regex2": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz",
+ "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==",
+ "dependencies": {
+ "ret": "~0.2.0"
+ }
+ },
+ "node_modules/safe-stable-stringify": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
+ "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/secure-json-parse": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
+ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
+ },
+ "node_modules/semver": {
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/serialize-javascript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
@@ -979,6 +1859,35 @@
"randombytes": "^2.1.0"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
+ "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
+ },
+ "node_modules/sonic-boom": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz",
+ "integrity": "sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==",
+ "dependencies": {
+ "atomic-sleep": "^1.0.0"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -1032,6 +1941,20 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/thread-stream": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz",
+ "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==",
+ "dependencies": {
+ "real-require": "^0.2.0"
+ }
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1044,6 +1967,14 @@
"node": ">=8.0"
}
},
+ "node_modules/toad-cache": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.4.1.tgz",
+ "integrity": "sha512-T0m3MxP3wcqW0LaV3dF1mHBU294sgYSm4FOpa5eEJaYO7PqJZBOjZEQI1y4YaKNnih1FXCEYTWDS9osCoTUefg==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -1060,16 +1991,39 @@
}
},
"node_modules/typescript": {
- "version": "3.9.10",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
- "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
+ "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=4.2.0"
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "dependencies": {
+ "makeerror": "1.0.12"
}
},
"node_modules/webidl-conversions": {
@@ -1126,6 +2080,11 @@
"node": ">=10"
}
},
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
"node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
@@ -1180,846 +2139,5 @@
"url": "https://github.com/sponsors/sindresorhus"
}
}
- },
- "dependencies": {
- "@types/chai": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz",
- "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==",
- "dev": true
- },
- "@types/mocha": {
- "version": "8.2.3",
- "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz",
- "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==",
- "dev": true
- },
- "@types/node": {
- "version": "12.20.55",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
- "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
- "dev": true
- },
- "@types/node-fetch": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz",
- "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==",
- "dev": true,
- "requires": {
- "@types/node": "*",
- "form-data": "^3.0.0"
- }
- },
- "@ungap/promise-all-settled": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
- "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
- "dev": true
- },
- "ansi-colors": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
- "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
- "dev": true
- },
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
- },
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "anymatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
- "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
- "dev": true,
- "requires": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- }
- },
- "argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
- },
- "assertion-error": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
- "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
- "dev": true
- },
- "asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "dev": true
- },
- "balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
- },
- "binary-extensions": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
- "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
- "dev": true
- },
- "brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "requires": {
- "balanced-match": "^1.0.0"
- }
- },
- "braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
- "requires": {
- "fill-range": "^7.0.1"
- }
- },
- "browser-stdout": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
- "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
- "dev": true
- },
- "camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
- "dev": true
- },
- "chai": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz",
- "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==",
- "dev": true,
- "requires": {
- "assertion-error": "^1.1.0",
- "check-error": "^1.0.2",
- "deep-eql": "^3.0.1",
- "get-func-name": "^2.0.0",
- "loupe": "^2.3.1",
- "pathval": "^1.1.1",
- "type-detect": "^4.0.5"
- }
- },
- "chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "requires": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "dependencies": {
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "requires": {
- "has-flag": "^4.0.0"
- }
- }
- }
- },
- "check-error": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
- "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
- "dev": true
- },
- "chokidar": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
- "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
- "dev": true,
- "requires": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "fsevents": "~2.3.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- }
- },
- "cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
- "dev": true,
- "requires": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
- "combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
- "requires": {
- "delayed-stream": "~1.0.0"
- }
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true
- },
- "decamelize": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
- "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
- "dev": true
- },
- "deep-eql": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
- "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
- "dev": true,
- "requires": {
- "type-detect": "^4.0.0"
- }
- },
- "delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "dev": true
- },
- "diff": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
- "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
- "dev": true
- },
- "emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
- },
- "escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
- "dev": true
- },
- "escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true
- },
- "facility-core": {
- "version": "file:../../ts",
- "requires": {
- "@types/chai": "^4.2.18",
- "@types/mocha": "^8.2.2",
- "@typescript-eslint/eslint-plugin": "^4.26.1",
- "@typescript-eslint/parser": "^4.26.1",
- "chai": "^4.3.4",
- "eslint": "^7.28.0",
- "eslint-config-prettier": "^8.3.0",
- "eslint-plugin-prettier": "^3.4.0",
- "mocha": "^10.0.0",
- "prettier": "^2.3.1",
- "typescript": "~3.9.9"
- }
- },
- "fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
- "requires": {
- "to-regex-range": "^5.0.1"
- }
- },
- "find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "requires": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- }
- },
- "flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "dev": true
- },
- "form-data": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
- "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
- "dev": true,
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true
- },
- "fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "optional": true
- },
- "get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true
- },
- "get-func-name": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
- "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
- "dev": true
- },
- "glob": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
- "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
- "dev": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "dependencies": {
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- }
- }
- },
- "glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "requires": {
- "is-glob": "^4.0.1"
- }
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
- },
- "he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "dev": true
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "dev": true,
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "requires": {
- "binary-extensions": "^2.0.0"
- }
- },
- "is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
- },
- "is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "requires": {
- "is-extglob": "^2.1.1"
- }
- },
- "is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true
- },
- "is-plain-obj": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
- "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
- "dev": true
- },
- "is-unicode-supported": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
- "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
- "dev": true
- },
- "js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "requires": {
- "argparse": "^2.0.1"
- }
- },
- "locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "requires": {
- "p-locate": "^5.0.0"
- }
- },
- "log-symbols": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
- "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
- "dev": true,
- "requires": {
- "chalk": "^4.1.0",
- "is-unicode-supported": "^0.1.0"
- }
- },
- "loupe": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz",
- "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==",
- "dev": true,
- "requires": {
- "get-func-name": "^2.0.0"
- }
- },
- "mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "dev": true
- },
- "mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dev": true,
- "requires": {
- "mime-db": "1.52.0"
- }
- },
- "minimatch": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
- "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
- "dev": true,
- "requires": {
- "brace-expansion": "^2.0.1"
- }
- },
- "mocha": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz",
- "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==",
- "dev": true,
- "requires": {
- "@ungap/promise-all-settled": "1.1.2",
- "ansi-colors": "4.1.1",
- "browser-stdout": "1.3.1",
- "chokidar": "3.5.3",
- "debug": "4.3.4",
- "diff": "5.0.0",
- "escape-string-regexp": "4.0.0",
- "find-up": "5.0.0",
- "glob": "7.2.0",
- "he": "1.2.0",
- "js-yaml": "4.1.0",
- "log-symbols": "4.1.0",
- "minimatch": "5.0.1",
- "ms": "2.1.3",
- "nanoid": "3.3.3",
- "serialize-javascript": "6.0.0",
- "strip-json-comments": "3.1.1",
- "supports-color": "8.1.1",
- "workerpool": "6.2.1",
- "yargs": "16.2.0",
- "yargs-parser": "20.2.4",
- "yargs-unparser": "2.0.0"
- },
- "dependencies": {
- "debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "requires": {
- "ms": "2.1.2"
- },
- "dependencies": {
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- }
- }
- },
- "ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true
- }
- }
- },
- "nanoid": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
- "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
- "dev": true
- },
- "node-fetch": {
- "version": "2.6.7",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
- "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
- "dev": true,
- "requires": {
- "whatwg-url": "^5.0.0"
- }
- },
- "normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "requires": {
- "wrappy": "1"
- }
- },
- "p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "requires": {
- "yocto-queue": "^0.1.0"
- }
- },
- "p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "requires": {
- "p-limit": "^3.0.2"
- }
- },
- "path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true
- },
- "pathval": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
- "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
- "dev": true
- },
- "picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true
- },
- "randombytes": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
- "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
- "requires": {
- "safe-buffer": "^5.1.0"
- }
- },
- "readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "requires": {
- "picomatch": "^2.2.1"
- }
- },
- "require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "dev": true
- },
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true
- },
- "serialize-javascript": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
- "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
- "dev": true,
- "requires": {
- "randombytes": "^2.1.0"
- }
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- },
- "strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true
- },
- "supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "dev": true,
- "requires": {
- "has-flag": "^4.0.0"
- }
- },
- "to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "requires": {
- "is-number": "^7.0.0"
- }
- },
- "tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
- "dev": true
- },
- "type-detect": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true
- },
- "typescript": {
- "version": "3.9.10",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
- "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
- "dev": true
- },
- "webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
- "dev": true
- },
- "whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
- "dev": true,
- "requires": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
- "workerpool": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
- "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
- "dev": true
- },
- "wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "requires": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- }
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true
- },
- "y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true
- },
- "yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "dev": true,
- "requires": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- }
- },
- "yargs-parser": {
- "version": "20.2.4",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
- "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
- "dev": true
- },
- "yargs-unparser": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
- "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
- "dev": true,
- "requires": {
- "camelcase": "^6.0.0",
- "decamelize": "^4.0.0",
- "flat": "^5.0.2",
- "is-plain-obj": "^2.1.0"
- }
- },
- "yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true
- }
}
}
diff --git a/conformance/ts/package.json b/conformance/package.json
similarity index 64%
rename from conformance/ts/package.json
rename to conformance/package.json
index d011dbc..9f58ec8 100644
--- a/conformance/ts/package.json
+++ b/conformance/package.json
@@ -4,7 +4,8 @@
"description": "Tests the ConformanceApi client.",
"scripts": {
"build": "tsc",
- "test": "npm run --silent build && mocha"
+ "test": "npm run --silent build && mocha dist/test/conformanceApiTests.js",
+ "fastify": "npm run --silent build && fastify start --options -p 4117 -w -l info -P dist/src/fastify/app.js"
},
"repository": {
"type": "git",
@@ -18,11 +19,13 @@
"@types/node": "^12.20.15",
"@types/node-fetch": "^2.5.10",
"chai": "^4.3.4",
+ "fastify-cli": "^5.9.0",
"mocha": "^10.0.0",
"node-fetch": "^2.6.7",
- "typescript": "~3.9.9"
+ "typescript": "^5.2.2"
},
"dependencies": {
- "facility-core": "file:../../ts"
+ "facility-core": "file:../ts",
+ "fastify": "^4.25.1"
}
}
diff --git a/conformance/ts/src/conformanceApi.ts b/conformance/src/conformanceApi.ts
similarity index 99%
rename from conformance/ts/src/conformanceApi.ts
rename to conformance/src/conformanceApi.ts
index e18424a..8c1e794 100644
--- a/conformance/ts/src/conformanceApi.ts
+++ b/conformance/src/conformanceApi.ts
@@ -2,7 +2,7 @@
/* eslint-disable */
import { HttpClientUtility, IServiceResult, IHttpClientOptions } from 'facility-core';
-import { IConformanceApi, IGetApiInfoRequest, IGetApiInfoResponse, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IMirrorFieldsRequest, IMirrorFieldsResponse, ICheckQueryRequest, ICheckQueryResponse, ICheckPathRequest, ICheckPathResponse, IMirrorHeadersRequest, IMirrorHeadersResponse, IMixedRequest, IMixedResponse, IRequiredRequest, IRequiredResponse, IMirrorBytesRequest, IMirrorBytesResponse, IMirrorTextRequest, IMirrorTextResponse, IBodyTypesRequest, IBodyTypesResponse, IWidget, IAny, IAnyArray, IAnyMap, IAnyResult, IAnyNullable, IHasWidget, Answer } from './conformanceApiTypes';
+import { IConformanceApi, IGetApiInfoRequest, IGetApiInfoResponse, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IMirrorFieldsRequest, IMirrorFieldsResponse, ICheckQueryRequest, ICheckQueryResponse, ICheckPathRequest, ICheckPathResponse, IMirrorHeadersRequest, IMirrorHeadersResponse, IMixedRequest, IMixedResponse, IRequiredRequest, IRequiredResponse, IMirrorBytesRequest, IMirrorBytesResponse, IMirrorTextRequest, IMirrorTextResponse, IBodyTypesRequest, IBodyTypesResponse, IWidget, IAny, IAnyArray, IAnyMap, IAnyResult, IAnyNullable, IHasWidget, Answer, ApiErrors } from './conformanceApiTypes';
export * from './conformanceApiTypes';
/** Provides access to ConformanceApi over HTTP via fetch. */
diff --git a/conformance/src/conformanceApiService.ts b/conformance/src/conformanceApiService.ts
new file mode 100644
index 0000000..b844b78
--- /dev/null
+++ b/conformance/src/conformanceApiService.ts
@@ -0,0 +1,108 @@
+import { IServiceError, IServiceResult } from "facility-core";
+import { IConformanceApi, IGetApiInfoRequest, IGetApiInfoResponse, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IMirrorFieldsRequest, IMirrorFieldsResponse, ICheckQueryRequest, ICheckQueryResponse, ICheckPathRequest, ICheckPathResponse, IMirrorHeadersRequest, IMirrorHeadersResponse, IMixedRequest, IMixedResponse, IRequiredRequest, IRequiredResponse, IMirrorBytesRequest, IMirrorBytesResponse, IMirrorTextRequest, IMirrorTextResponse, IBodyTypesRequest, IBodyTypesResponse } from "./conformanceApiTypes";
+
+export type ConformanceApiTest = {
+ test: string;
+ method: string;
+ request: unknown;
+ response?: unknown;
+ error?: IServiceError;
+};
+
+export class ConformanceApiService implements IConformanceApi {
+ constructor(tests: ConformanceApiTest[]) {
+ this._tests = tests;
+ }
+
+ getApiInfo(request: IGetApiInfoRequest, context?: unknown): Promise> {
+ return this.execute("getApiInfo", request);
+ }
+
+ getWidgets(request: IGetWidgetsRequest, context?: unknown): Promise> {
+ return this.execute("getWidgets", request);
+ }
+
+ createWidget(request: ICreateWidgetRequest, context?: unknown): Promise> {
+ return this.execute("createWidget", request);
+ }
+
+ getWidget(request: IGetWidgetRequest, context?: unknown): Promise> {
+ return this.execute("getWidget", request);
+ }
+
+ deleteWidget(request: IDeleteWidgetRequest, context?: unknown): Promise> {
+ return this.execute("deleteWidget", request);
+ }
+
+ getWidgetBatch(request: IGetWidgetBatchRequest, context?: unknown): Promise> {
+ return this.execute("getWidgetBatch", request);
+ }
+
+ mirrorFields(request: IMirrorFieldsRequest, context?: unknown): Promise> {
+ return this.execute("mirrorFields", request);
+ }
+
+ checkQuery(request: ICheckQueryRequest, context?: unknown): Promise> {
+ return this.execute("checkQuery", request);
+ }
+
+ checkPath(request: ICheckPathRequest, context?: unknown): Promise> {
+ return this.execute("checkPath", request);
+ }
+
+ mirrorHeaders(request: IMirrorHeadersRequest, context?: unknown): Promise> {
+ return this.execute("mirrorHeaders", request);
+ }
+
+ mixed(request: IMixedRequest, context?: unknown): Promise> {
+ return this.execute("mixed", request);
+ }
+
+ required(request: IRequiredRequest, context?: unknown): Promise> {
+ return this.execute("required", request);
+ }
+
+ mirrorBytes(request: IMirrorBytesRequest, context?: unknown): Promise> {
+ return this.execute("mirrorBytes", request);
+ }
+
+ mirrorText(request: IMirrorTextRequest, context?: unknown): Promise> {
+ return this.execute("mirrorText", request);
+ }
+
+ bodyTypes(request: IBodyTypesRequest, context?: unknown): Promise> {
+ return this.execute("bodyTypes", request);
+ }
+
+ private async execute(methodName: string, request: TRequest): Promise> {
+ const testsWithMethodName = this._tests.filter((x) => x.method === methodName);
+
+ if (testsWithMethodName.length === 0) {
+ return this.failure({
+ code: "InvalidRequest",
+ message: `No tests found for method ${methodName}.`,
+ });
+ }
+
+ const testsWithMatchingRequest = testsWithMethodName.filter((x) => {
+ return JSON.stringify(x.request) === JSON.stringify(request);
+ });
+
+ if (testsWithMatchingRequest.length !== 1) {
+ return this.failure({
+ code: "InvalidRequest",
+ message: `${testsWithMatchingRequest.length} of ${testsWithMethodName.length} tests for method ${methodName} matched request:`,
+ });
+ }
+
+ var testInfo = testsWithMatchingRequest[0];
+ return testInfo.error ? this.failure(testInfo.error) : this.success(testInfo.response as TResponse);
+ }
+
+ private success(result: T): IServiceResult { return { value: result }; }
+
+ private failure(error: IServiceError): IServiceResult { return { error}; }
+
+ private readonly _tests: readonly ConformanceApiTest[];
+
+}
diff --git a/conformance/ts/src/conformanceApiTypes.ts b/conformance/src/conformanceApiTypes.ts
similarity index 97%
rename from conformance/ts/src/conformanceApiTypes.ts
rename to conformance/src/conformanceApiTypes.ts
index 42586af..9fbcc8f 100644
--- a/conformance/ts/src/conformanceApiTypes.ts
+++ b/conformance/src/conformanceApiTypes.ts
@@ -88,7 +88,7 @@ export interface ICreateWidgetResponse {
/** Request for GetWidget. */
export interface IGetWidgetRequest {
/** The widget ID. */
- id?: number;
+ id: number;
/** Don't get the widget if it has this ETag. */
ifNotETag?: string;
@@ -127,7 +127,7 @@ export interface IDeleteWidgetResponse {
/** Request for GetWidgetBatch. */
export interface IGetWidgetBatchRequest {
/** The IDs of the widgets to return. */
- ids?: number[];
+ ids: number[];
}
/** Response for GetWidgetBatch. */
@@ -258,9 +258,9 @@ export interface IMixedResponse {
/** Request for Required. */
export interface IRequiredRequest {
- query?: string;
+ query: string;
- normal?: string;
+ normal: string;
widget?: IWidget;
@@ -281,7 +281,7 @@ export interface IRequiredRequest {
/** Response for Required. */
export interface IRequiredResponse {
- normal?: string;
+ normal: string;
}
/** Request for MirrorBytes. */
@@ -328,7 +328,7 @@ export interface IWidget {
id?: number;
/** The name of the widget. */
- name?: string;
+ name: string;
}
export interface IAny {
@@ -515,3 +515,12 @@ export enum Answer {
maybe = 'maybe',
}
+/** Custom errors. */
+export enum ApiErrors {
+ /** The user is not an administrator. */
+ NotAdmin = 'NotAdmin',
+
+ /** I'm "too" 😄! */
+ TooHappy = 'TooHappy',
+}
+
diff --git a/conformance/src/fastify/app.ts b/conformance/src/fastify/app.ts
new file mode 100644
index 0000000..6612dea
--- /dev/null
+++ b/conformance/src/fastify/app.ts
@@ -0,0 +1,21 @@
+import { FastifyPluginAsync, FastifyServerOptions } from "fastify";
+import { ConformanceApiService } from "../conformanceApiService.js";
+import conformanceTestsJson from "../../ConformanceTests.json";
+import { conformanceApiPlugin, ConformanceApiPluginOptions } from "./conformanceApiPlugin.js";
+import { jsConformanceApiPlugin } from "./jsConformanceApiPlugin.js";
+
+const app: FastifyPluginAsync = async (fastify): Promise => {
+ const conformanceApiPluginOptions: ConformanceApiPluginOptions = {
+ api: new ConformanceApiService(conformanceTestsJson.tests),
+ caseInsenstiveQueryStringKeys: true,
+ includeErrorDetails: true,
+ };
+
+ fastify.register(conformanceApiPlugin, conformanceApiPluginOptions);
+ fastify.register(jsConformanceApiPlugin, {...conformanceApiPluginOptions, prefix: "/js" });
+};
+
+export default app;
+export const options: FastifyServerOptions = {
+ caseSensitive: false,
+};
diff --git a/conformance/src/fastify/conformanceApiPlugin.ts b/conformance/src/fastify/conformanceApiPlugin.ts
new file mode 100644
index 0000000..79d2ee9
--- /dev/null
+++ b/conformance/src/fastify/conformanceApiPlugin.ts
@@ -0,0 +1,1342 @@
+// DO NOT EDIT: generated by fsdgenjs
+/* eslint-disable */
+
+import { FastifyPluginAsync, RegisterOptions } from 'fastify';
+import { IServiceResult, IServiceError } from 'facility-core';
+
+const standardErrorCodes: { [code: string]: number } = {
+ 'NotModified': 304,
+ 'InvalidRequest': 400,
+ 'NotAuthenticated': 401,
+ 'NotAuthorized': 403,
+ 'NotFound': 404,
+ 'Conflict': 409,
+ 'RequestTooLarge': 413,
+ 'TooManyRequests': 429,
+ 'InternalError': 500,
+ 'ServiceUnavailable': 503,
+ 'NotAdmin': 403,
+ 'TooHappy': 500,
+};
+
+function parseBoolean(value: string | undefined) {
+ if (typeof value === 'string') {
+ const lowerValue = value.toLowerCase();
+ if (lowerValue === 'true') {
+ return true;
+ }
+ if (lowerValue === 'false') {
+ return false;
+ }
+ }
+ return undefined;
+}
+
+export type ConformanceApiPluginOptions = RegisterOptions & {
+ api: IConformanceApi;
+ caseInsenstiveQueryStringKeys?: boolean;
+ includeErrorDetails?: boolean;
+}
+
+export const conformanceApiPlugin: FastifyPluginAsync = async (fastify, opts) => {
+ const { api, caseInsenstiveQueryStringKeys, includeErrorDetails } = opts;
+
+ for (const jsonSchema of jsonSchemas) {
+ fastify.addSchema(jsonSchema);
+ }
+
+ fastify.setErrorHandler((error, req, res) => {
+ req.log.error(error);
+ if (includeErrorDetails) {
+ res.status(500).send({
+ code: 'InternalError',
+ message: error.message,
+ details: {
+ stack: error.stack?.split('\n').filter((x) => x.length > 0),
+ }
+ });
+ }
+ else {
+ res.status(500).send({
+ code: 'InternalError',
+ message: 'The service experienced an unexpected internal error.',
+ });
+ }
+ });
+
+ if (caseInsenstiveQueryStringKeys) {
+ fastify.addHook('onRequest', async (req, res) => {
+ const query = req.query as Record;
+ for (const key of Object.keys(query)) {
+ const lowerKey = key.toLowerCase();
+ if (lowerKey !== key) {
+ query[lowerKey] = query[key];
+ delete query[key];
+ }
+ }
+ });
+ }
+
+ fastify.route({
+ url: '/',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ service: { type: 'string' },
+ version: { type: 'string' },
+ },
+ },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IGetApiInfoRequest = {};
+
+ const result = await api.getApiInfo(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ widgets: { type: 'array', items: { $ref: 'Widget' } },
+ },
+ },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IGetWidgetsRequest = {};
+
+ const query = req.query as Record;
+ if (typeof query['q'] === 'string') request.query = query['q'];
+
+ const result = await api.getWidgets(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets',
+ method: 'POST',
+ schema: {
+ response: {
+ 201: { $ref: 'Widget' },
+ },
+ },
+ handler: async function (req, res) {
+ const request: ICreateWidgetRequest = {};
+
+ request.widget = req.body as never;
+
+ const result = await api.createWidget(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.url != null) res.header('Location', result.value.url);
+ if (result.value.eTag != null) res.header('eTag', result.value.eTag);
+
+ if (result.value.widget) {
+ res.status(201).send(result.value.widget);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets/:id',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: { $ref: 'Widget' },
+ 304: { type: 'boolean' },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IGetWidgetRequest = {};
+
+ const params = req.params as Record;
+ if (typeof params['id'] === 'string') request.id = parseInt(params['id'], 10);
+
+ const headers = req.headers as Record;
+ if (typeof headers['if-none-match'] === 'string') request.ifNotETag = headers['if-none-match'];
+
+ const result = await api.getWidget(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.eTag != null) res.header('eTag', result.value.eTag);
+
+ if (result.value.widget) {
+ res.status(200).send(result.value.widget);
+ return;
+ }
+
+ if (result.value.notModified) {
+ res.status(304);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets/:id',
+ method: 'DELETE',
+ schema: {
+ response: {
+ 204: { type: 'object', additionalProperties: false },
+ 404: { type: 'boolean' },
+ 409: { type: 'boolean' },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IDeleteWidgetRequest = {};
+
+ const params = req.params as Record;
+ if (typeof params['id'] === 'string') request.id = parseInt(params['id'], 10);
+
+ const headers = req.headers as Record;
+ if (typeof headers['if-match'] === 'string') request.ifETag = headers['if-match'];
+
+ const result = await api.deleteWidget(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.notFound) {
+ res.status(404);
+ return;
+ }
+
+ if (result.value.conflict) {
+ res.status(409);
+ return;
+ }
+
+ res.status(204);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets/get',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: { type: 'array', items: { type: 'object', properties: { value: { $ref: 'Widget' }, error: { $ref: '_error' } } } },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IGetWidgetBatchRequest = {};
+
+ request.ids = req.body as never;
+
+ const result = await api.getWidgetBatch(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.results) {
+ res.status(200).send(result.value.results);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mirrorFields',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ field: { $ref: 'Any' },
+ matrix: { type: 'array', items: { type: 'array', items: { type: 'array', items: { type: 'number' } } } },
+ },
+ },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IMirrorFieldsRequest = {};
+
+ const body = req.body as Record;
+ request.field = body.field;
+ request.matrix = body.matrix;
+
+ const result = await api.mirrorFields(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/checkQuery',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: { type: 'object', additionalProperties: false },
+ },
+ },
+ handler: async function (req, res) {
+ const request: ICheckQueryRequest = {};
+
+ const query = req.query as Record;
+ if (typeof query['string'] === 'string') request.string = query['string'];
+ if (typeof query['boolean'] === 'string') request.boolean = parseBoolean(query['boolean']);
+ if (typeof query['double'] === 'string') request.double = parseFloat(query['double']);
+ if (typeof query['int32'] === 'string') request.int32 = parseInt(query['int32'], 10);
+ if (typeof query['int64'] === 'string') request.int64 = parseInt(query['int64'], 10);
+ if (typeof query['decimal'] === 'string') request.decimal = parseFloat(query['decimal']);
+ if (typeof query['enum'] === 'string') request.enum = query['enum'] as Answer;
+ if (typeof query['datetime'] === 'string') request.datetime = query['datetime'];
+
+ const result = await api.checkQuery(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/checkPath/:string/:boolean/:double/:int32/:int64/:decimal/:enum/:datetime',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: { type: 'object', additionalProperties: false },
+ },
+ },
+ handler: async function (req, res) {
+ const request: ICheckPathRequest = {};
+
+ const params = req.params as Record;
+ if (typeof params['string'] === 'string') request.string = params['string'];
+ if (typeof params['boolean'] === 'string') request.boolean = parseBoolean(params['boolean']);
+ if (typeof params['double'] === 'string') request.double = parseFloat(params['double']);
+ if (typeof params['int32'] === 'string') request.int32 = parseInt(params['int32'], 10);
+ if (typeof params['int64'] === 'string') request.int64 = parseInt(params['int64'], 10);
+ if (typeof params['decimal'] === 'string') request.decimal = parseFloat(params['decimal']);
+ if (typeof params['enum'] === 'string') request.enum = params['enum'] as Answer;
+ if (typeof params['datetime'] === 'string') request.datetime = params['datetime'];
+
+ const result = await api.checkPath(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mirrorHeaders',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: { type: 'object', additionalProperties: false },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IMirrorHeadersRequest = {};
+
+ const headers = req.headers as Record;
+ if (typeof headers['string'] === 'string') request.string = headers['string'];
+ if (typeof headers['boolean'] === 'string') request.boolean = parseBoolean(headers['boolean']);
+ if (typeof headers['double'] === 'string') request.double = parseFloat(headers['double']);
+ if (typeof headers['int32'] === 'string') request.int32 = parseInt(headers['int32'], 10);
+ if (typeof headers['int64'] === 'string') request.int64 = parseInt(headers['int64'], 10);
+ if (typeof headers['decimal'] === 'string') request.decimal = parseFloat(headers['decimal']);
+ if (typeof headers['enum'] === 'string') request.enum = headers['enum'] as Answer;
+ if (typeof headers['datetime'] === 'string') request.datetime = headers['datetime'];
+
+ const result = await api.mirrorHeaders(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.string != null) res.header('string', result.value.string);
+ if (result.value.boolean != null) res.header('boolean', result.value.boolean);
+ if (result.value.double != null) res.header('double', result.value.double);
+ if (result.value.int32 != null) res.header('int32', result.value.int32);
+ if (result.value.int64 != null) res.header('int64', result.value.int64);
+ if (result.value.decimal != null) res.header('decimal', result.value.decimal);
+ if (result.value.enum != null) res.header('enum', result.value.enum);
+ if (result.value.datetime != null) res.header('datetime', result.value.datetime);
+
+ res.status(200);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mixed/:path',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ normal: { type: 'string' },
+ },
+ },
+ 202: { type: 'object', additionalProperties: true },
+ 204: { type: 'boolean' },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IMixedRequest = {};
+
+ const params = req.params as Record;
+ if (typeof params['path'] === 'string') request.path = params['path'];
+
+ const query = req.query as Record;
+ if (typeof query['query'] === 'string') request.query = query['query'];
+
+ const headers = req.headers as Record;
+ if (typeof headers['header'] === 'string') request.header = headers['header'];
+
+ const body = req.body as Record;
+ request.normal = body.normal;
+
+ const result = await api.mixed(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.header != null) res.header('header', result.value.header);
+
+ if (result.value.body) {
+ res.status(202).send(result.value.body);
+ return;
+ }
+
+ if (result.value.empty) {
+ res.status(204);
+ return;
+ }
+
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/required',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ normal: { type: 'string' },
+ },
+ },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IRequiredRequest = {};
+
+ const query = req.query as Record;
+ if (typeof query['query'] === 'string') request.query = query['query'];
+
+ const body = req.body as Record;
+ request.normal = body.normal;
+ request.widget = body.widget;
+ request.widgets = body.widgets;
+ request.widgetMatrix = body.widgetMatrix;
+ request.widgetResult = body.widgetResult;
+ request.widgetResults = body.widgetResults;
+ request.widgetMap = body.widgetMap;
+ request.hasWidget = body.hasWidget;
+ request.point = body.point;
+
+ const result = await api.required(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mirrorBytes',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: { type: 'string' },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IMirrorBytesRequest = {};
+
+ const headers = req.headers as Record;
+ if (typeof headers['content-type'] === 'string') request.type = headers['content-type'];
+
+ request.content = req.body as never;
+
+ const result = await api.mirrorBytes(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.type != null) res.header('Content-Type', result.value.type);
+
+ if (result.value.content) {
+ res.status(200).send(result.value.content);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mirrorText',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: { type: 'string' },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IMirrorTextRequest = {};
+
+ const headers = req.headers as Record;
+ if (typeof headers['content-type'] === 'string') request.type = headers['content-type'];
+
+ request.content = req.body as never;
+
+ const result = await api.mirrorText(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.type != null) res.header('Content-Type', result.value.type);
+
+ if (result.value.content) {
+ res.status(200).send(result.value.content);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/bodyTypes',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: { type: 'string' },
+ },
+ },
+ handler: async function (req, res) {
+ const request: IBodyTypesRequest = {};
+
+ request.content = req.body as never;
+
+ const result = await api.bodyTypes(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.content) {
+ res.status(200).send(result.value.content);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+}
+
+const jsonSchemas = [
+ {
+ $id: '_error',
+ type: 'object',
+ properties: {
+ code: { type: 'string' },
+ message: { type: 'string' },
+ details: { type: 'object', additionalProperties: true },
+ innerError: { $ref: '_error' },
+ }
+ } as const,
+ {
+ $id: 'Widget',
+ type: 'object',
+ properties: {
+ id: { type: 'integer' },
+ name: { type: 'string' },
+ }
+ } as const,
+ {
+ $id: 'Any',
+ type: 'object',
+ properties: {
+ string: { type: 'string' },
+ boolean: { type: 'boolean' },
+ double: { type: 'number' },
+ int32: { type: 'integer' },
+ int64: { type: 'integer' },
+ decimal: { type: 'number' },
+ datetime: { type: 'string' },
+ bytes: { type: 'string' },
+ object: { type: 'object', additionalProperties: true },
+ error: { $ref: '_error' },
+ data: { $ref: 'Any' },
+ enum: { $ref: 'Answer' },
+ array: { $ref: 'AnyArray' },
+ map: { $ref: 'AnyMap' },
+ result: { $ref: 'AnyResult' },
+ nullable: { $ref: 'AnyNullable' },
+ }
+ } as const,
+ {
+ $id: 'AnyArray',
+ type: 'object',
+ properties: {
+ string: { type: 'array', items: { type: 'string' } },
+ boolean: { type: 'array', items: { type: 'boolean' } },
+ double: { type: 'array', items: { type: 'number' } },
+ int32: { type: 'array', items: { type: 'integer' } },
+ int64: { type: 'array', items: { type: 'integer' } },
+ decimal: { type: 'array', items: { type: 'number' } },
+ datetime: { type: 'array', items: { type: 'string' } },
+ bytes: { type: 'array', items: { type: 'string' } },
+ object: { type: 'array', items: { type: 'object', additionalProperties: true } },
+ error: { type: 'array', items: { $ref: '_error' } },
+ data: { type: 'array', items: { $ref: 'Any' } },
+ enum: { type: 'array', items: { $ref: 'Answer' } },
+ array: { type: 'array', items: { type: 'array', items: { type: 'integer' } } },
+ map: { type: 'array', items: { type: 'object', additionalProperties: { type: 'integer' } } },
+ result: { type: 'array', items: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } } },
+ nullable: { type: 'array', items: { oneOf: [ { type: 'integer' }, { type: 'null' } ] } },
+ }
+ } as const,
+ {
+ $id: 'AnyMap',
+ type: 'object',
+ properties: {
+ string: { type: 'object', additionalProperties: { type: 'string' } },
+ boolean: { type: 'object', additionalProperties: { type: 'boolean' } },
+ double: { type: 'object', additionalProperties: { type: 'number' } },
+ int32: { type: 'object', additionalProperties: { type: 'integer' } },
+ int64: { type: 'object', additionalProperties: { type: 'integer' } },
+ decimal: { type: 'object', additionalProperties: { type: 'number' } },
+ datetime: { type: 'object', additionalProperties: { type: 'string' } },
+ bytes: { type: 'object', additionalProperties: { type: 'string' } },
+ object: { type: 'object', additionalProperties: { type: 'object', additionalProperties: true } },
+ error: { type: 'object', additionalProperties: { $ref: '_error' } },
+ data: { type: 'object', additionalProperties: { $ref: 'Any' } },
+ enum: { type: 'object', additionalProperties: { $ref: 'Answer' } },
+ array: { type: 'object', additionalProperties: { type: 'array', items: { type: 'integer' } } },
+ map: { type: 'object', additionalProperties: { type: 'object', additionalProperties: { type: 'integer' } } },
+ result: { type: 'object', additionalProperties: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } } },
+ nullable: { type: 'object', additionalProperties: { oneOf: [ { type: 'integer' }, { type: 'null' } ] } },
+ }
+ } as const,
+ {
+ $id: 'AnyResult',
+ type: 'object',
+ properties: {
+ string: { type: 'object', properties: { value: { type: 'string' }, error: { $ref: '_error' } } },
+ boolean: { type: 'object', properties: { value: { type: 'boolean' }, error: { $ref: '_error' } } },
+ double: { type: 'object', properties: { value: { type: 'number' }, error: { $ref: '_error' } } },
+ int32: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } },
+ int64: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } },
+ decimal: { type: 'object', properties: { value: { type: 'number' }, error: { $ref: '_error' } } },
+ datetime: { type: 'object', properties: { value: { type: 'string' }, error: { $ref: '_error' } } },
+ bytes: { type: 'object', properties: { value: { type: 'string' }, error: { $ref: '_error' } } },
+ object: { type: 'object', properties: { value: { type: 'object', additionalProperties: true }, error: { $ref: '_error' } } },
+ error: { type: 'object', properties: { value: { $ref: '_error' }, error: { $ref: '_error' } } },
+ data: { type: 'object', properties: { value: { $ref: 'Any' }, error: { $ref: '_error' } } },
+ enum: { type: 'object', properties: { value: { $ref: 'Answer' }, error: { $ref: '_error' } } },
+ array: { type: 'object', properties: { value: { type: 'array', items: { type: 'integer' } }, error: { $ref: '_error' } } },
+ map: { type: 'object', properties: { value: { type: 'object', additionalProperties: { type: 'integer' } }, error: { $ref: '_error' } } },
+ result: { type: 'object', properties: { value: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } }, error: { $ref: '_error' } } },
+ nullable: { type: 'object', properties: { value: { oneOf: [ { type: 'integer' }, { type: 'null' } ] }, error: { $ref: '_error' } } },
+ }
+ } as const,
+ {
+ $id: 'AnyNullable',
+ type: 'object',
+ properties: {
+ string: { oneOf: [ { type: 'string' }, { type: 'null' } ] },
+ boolean: { oneOf: [ { type: 'boolean' }, { type: 'null' } ] },
+ double: { oneOf: [ { type: 'number' }, { type: 'null' } ] },
+ int32: { oneOf: [ { type: 'integer' }, { type: 'null' } ] },
+ int64: { oneOf: [ { type: 'integer' }, { type: 'null' } ] },
+ decimal: { oneOf: [ { type: 'number' }, { type: 'null' } ] },
+ datetime: { oneOf: [ { type: 'string' }, { type: 'null' } ] },
+ bytes: { oneOf: [ { type: 'string' }, { type: 'null' } ] },
+ object: { oneOf: [ { type: 'object', additionalProperties: true }, { type: 'null' } ] },
+ error: { oneOf: [ { $ref: '_error' }, { type: 'null' } ] },
+ data: { oneOf: [ { $ref: 'Any' }, { type: 'null' } ] },
+ enum: { oneOf: [ { $ref: 'Answer' }, { type: 'null' } ] },
+ array: { oneOf: [ { type: 'array', items: { type: 'integer' } }, { type: 'null' } ] },
+ map: { oneOf: [ { type: 'object', additionalProperties: { type: 'integer' } }, { type: 'null' } ] },
+ result: { oneOf: [ { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } }, { type: 'null' } ] },
+ }
+ } as const,
+ {
+ $id: 'HasWidget',
+ type: 'object',
+ properties: {
+ widget: { $ref: 'Widget' },
+ }
+ } as const,
+ {
+ $id: 'Answer',
+ type: 'string',
+ enum: [ 'yes', 'no', 'maybe' ],
+ } as const,
+] as const;
+
+/** API for a Facility test server. */
+export interface IConformanceApi {
+ /** Gets API information. */
+ getApiInfo(request: IGetApiInfoRequest, context?: unknown): Promise>;
+
+ /** Gets widgets. */
+ getWidgets(request: IGetWidgetsRequest, context?: unknown): Promise>;
+
+ /** Creates a new widget. */
+ createWidget(request: ICreateWidgetRequest, context?: unknown): Promise>;
+
+ /** Gets the specified widget. */
+ getWidget(request: IGetWidgetRequest, context?: unknown): Promise>;
+
+ /** Deletes the specified widget. */
+ deleteWidget(request: IDeleteWidgetRequest, context?: unknown): Promise>;
+
+ /** Gets the specified widgets. */
+ getWidgetBatch(request: IGetWidgetBatchRequest, context?: unknown): Promise>;
+
+ mirrorFields(request: IMirrorFieldsRequest, context?: unknown): Promise>;
+
+ checkQuery(request: ICheckQueryRequest, context?: unknown): Promise>;
+
+ checkPath(request: ICheckPathRequest, context?: unknown): Promise>;
+
+ mirrorHeaders(request: IMirrorHeadersRequest, context?: unknown): Promise>;
+
+ mixed(request: IMixedRequest, context?: unknown): Promise>;
+
+ required(request: IRequiredRequest, context?: unknown): Promise>;
+
+ mirrorBytes(request: IMirrorBytesRequest, context?: unknown): Promise>;
+
+ mirrorText(request: IMirrorTextRequest, context?: unknown): Promise>;
+
+ bodyTypes(request: IBodyTypesRequest, context?: unknown): Promise>;
+}
+
+/** Request for GetApiInfo. */
+export interface IGetApiInfoRequest {
+}
+
+/** Response for GetApiInfo. */
+export interface IGetApiInfoResponse {
+ /** The name of the service. */
+ service?: string;
+
+ /** The version of the service. */
+ version?: string;
+}
+
+/** Request for GetWidgets. */
+export interface IGetWidgetsRequest {
+ /** The query. */
+ query?: string;
+}
+
+/** Response for GetWidgets. */
+export interface IGetWidgetsResponse {
+ /** The widgets. */
+ widgets?: IWidget[];
+}
+
+/** Request for CreateWidget. */
+export interface ICreateWidgetRequest {
+ /** The widget to create. */
+ widget?: IWidget;
+}
+
+/** Response for CreateWidget. */
+export interface ICreateWidgetResponse {
+ /** The created widget. */
+ widget?: IWidget;
+
+ /** The URL of the created widget. */
+ url?: string;
+
+ /** The ETag of the created widget. */
+ eTag?: string;
+}
+
+/** Request for GetWidget. */
+export interface IGetWidgetRequest {
+ /** The widget ID. */
+ id: number;
+
+ /** Don't get the widget if it has this ETag. */
+ ifNotETag?: string;
+}
+
+/** Response for GetWidget. */
+export interface IGetWidgetResponse {
+ /** The requested widget. */
+ widget?: IWidget;
+
+ /** The ETag of the widget. */
+ eTag?: string;
+
+ /** The widget still has the specified ETag. */
+ notModified?: boolean;
+}
+
+/** Request for DeleteWidget. */
+export interface IDeleteWidgetRequest {
+ /** The widget ID. */
+ id?: number;
+
+ /** Don't delete the widget unless it has this ETag. */
+ ifETag?: string;
+}
+
+/** Response for DeleteWidget. */
+export interface IDeleteWidgetResponse {
+ /** The widget was not found. */
+ notFound?: boolean;
+
+ /** The widget no longer has the specified ETag. */
+ conflict?: boolean;
+}
+
+/** Request for GetWidgetBatch. */
+export interface IGetWidgetBatchRequest {
+ /** The IDs of the widgets to return. */
+ ids: number[];
+}
+
+/** Response for GetWidgetBatch. */
+export interface IGetWidgetBatchResponse {
+ /** The widget results. */
+ results?: IServiceResult[];
+}
+
+/** Request for MirrorFields. */
+export interface IMirrorFieldsRequest {
+ field?: IAny;
+
+ matrix?: number[][][];
+}
+
+/** Response for MirrorFields. */
+export interface IMirrorFieldsResponse {
+ field?: IAny;
+
+ matrix?: number[][][];
+}
+
+/** Request for CheckQuery. */
+export interface ICheckQueryRequest {
+ string?: string;
+
+ boolean?: boolean;
+
+ double?: number;
+
+ int32?: number;
+
+ int64?: number;
+
+ decimal?: number;
+
+ enum?: Answer;
+
+ datetime?: string;
+}
+
+/** Response for CheckQuery. */
+export interface ICheckQueryResponse {
+}
+
+/** Request for CheckPath. */
+export interface ICheckPathRequest {
+ string?: string;
+
+ boolean?: boolean;
+
+ double?: number;
+
+ int32?: number;
+
+ int64?: number;
+
+ decimal?: number;
+
+ enum?: Answer;
+
+ datetime?: string;
+}
+
+/** Response for CheckPath. */
+export interface ICheckPathResponse {
+}
+
+/** Request for MirrorHeaders. */
+export interface IMirrorHeadersRequest {
+ string?: string;
+
+ boolean?: boolean;
+
+ double?: number;
+
+ int32?: number;
+
+ int64?: number;
+
+ decimal?: number;
+
+ enum?: Answer;
+
+ datetime?: string;
+}
+
+/** Response for MirrorHeaders. */
+export interface IMirrorHeadersResponse {
+ string?: string;
+
+ boolean?: boolean;
+
+ double?: number;
+
+ int32?: number;
+
+ int64?: number;
+
+ decimal?: number;
+
+ enum?: Answer;
+
+ datetime?: string;
+}
+
+/** Request for Mixed. */
+export interface IMixedRequest {
+ path?: string;
+
+ query?: string;
+
+ header?: string;
+
+ normal?: string;
+}
+
+/** Response for Mixed. */
+export interface IMixedResponse {
+ header?: string;
+
+ normal?: string;
+
+ body?: { [name: string]: any };
+
+ empty?: boolean;
+}
+
+/** Request for Required. */
+export interface IRequiredRequest {
+ query: string;
+
+ normal: string;
+
+ widget?: IWidget;
+
+ widgets?: IWidget[];
+
+ widgetMatrix?: IWidget[][];
+
+ widgetResult?: IServiceResult;
+
+ widgetResults?: IServiceResult[];
+
+ widgetMap?: { [name: string]: IWidget };
+
+ hasWidget?: IHasWidget;
+
+ point?: number[];
+}
+
+/** Response for Required. */
+export interface IRequiredResponse {
+ normal: string;
+}
+
+/** Request for MirrorBytes. */
+export interface IMirrorBytesRequest {
+ content?: string;
+
+ type?: string;
+}
+
+/** Response for MirrorBytes. */
+export interface IMirrorBytesResponse {
+ content?: string;
+
+ type?: string;
+}
+
+/** Request for MirrorText. */
+export interface IMirrorTextRequest {
+ content?: string;
+
+ type?: string;
+}
+
+/** Response for MirrorText. */
+export interface IMirrorTextResponse {
+ content?: string;
+
+ type?: string;
+}
+
+/** Request for BodyTypes. */
+export interface IBodyTypesRequest {
+ content?: string;
+}
+
+/** Response for BodyTypes. */
+export interface IBodyTypesResponse {
+ content?: string;
+}
+
+/** A widget. */
+export interface IWidget {
+ /** A unique identifier for the widget. */
+ id?: number;
+
+ /** The name of the widget. */
+ name: string;
+}
+
+export interface IAny {
+ string?: string;
+
+ boolean?: boolean;
+
+ double?: number;
+
+ int32?: number;
+
+ int64?: number;
+
+ decimal?: number;
+
+ datetime?: string;
+
+ bytes?: string;
+
+ object?: { [name: string]: any };
+
+ error?: IServiceError;
+
+ data?: IAny;
+
+ enum?: Answer;
+
+ array?: IAnyArray;
+
+ map?: IAnyMap;
+
+ result?: IAnyResult;
+
+ nullable?: IAnyNullable;
+}
+
+export interface IAnyArray {
+ string?: string[];
+
+ boolean?: boolean[];
+
+ double?: number[];
+
+ int32?: number[];
+
+ int64?: number[];
+
+ decimal?: number[];
+
+ datetime?: string[];
+
+ bytes?: string[];
+
+ object?: { [name: string]: any }[];
+
+ error?: IServiceError[];
+
+ data?: IAny[];
+
+ enum?: Answer[];
+
+ array?: number[][];
+
+ map?: { [name: string]: number }[];
+
+ result?: IServiceResult[];
+
+ nullable?: (number | null)[];
+}
+
+export interface IAnyMap {
+ string?: { [name: string]: string };
+
+ boolean?: { [name: string]: boolean };
+
+ double?: { [name: string]: number };
+
+ int32?: { [name: string]: number };
+
+ int64?: { [name: string]: number };
+
+ decimal?: { [name: string]: number };
+
+ datetime?: { [name: string]: string };
+
+ bytes?: { [name: string]: string };
+
+ object?: { [name: string]: { [name: string]: any } };
+
+ error?: { [name: string]: IServiceError };
+
+ data?: { [name: string]: IAny };
+
+ enum?: { [name: string]: Answer };
+
+ array?: { [name: string]: number[] };
+
+ map?: { [name: string]: { [name: string]: number } };
+
+ result?: { [name: string]: IServiceResult };
+
+ nullable?: { [name: string]: (number | null) };
+}
+
+export interface IAnyResult {
+ string?: IServiceResult;
+
+ boolean?: IServiceResult;
+
+ double?: IServiceResult;
+
+ int32?: IServiceResult;
+
+ int64?: IServiceResult;
+
+ decimal?: IServiceResult;
+
+ datetime?: IServiceResult;
+
+ bytes?: IServiceResult;
+
+ object?: IServiceResult<{ [name: string]: any }>;
+
+ error?: IServiceResult;
+
+ data?: IServiceResult;
+
+ enum?: IServiceResult;
+
+ array?: IServiceResult;
+
+ map?: IServiceResult<{ [name: string]: number }>;
+
+ result?: IServiceResult>;
+
+ nullable?: IServiceResult<(number | null)>;
+}
+
+export interface IAnyNullable {
+ string?: (string | null);
+
+ boolean?: (boolean | null);
+
+ double?: (number | null);
+
+ int32?: (number | null);
+
+ int64?: (number | null);
+
+ decimal?: (number | null);
+
+ datetime?: (string | null);
+
+ bytes?: (string | null);
+
+ object?: ({ [name: string]: any } | null);
+
+ error?: (IServiceError | null);
+
+ data?: (IAny | null);
+
+ enum?: (Answer | null);
+
+ array?: (number[] | null);
+
+ map?: ({ [name: string]: number } | null);
+
+ result?: (IServiceResult | null);
+}
+
+export interface IHasWidget {
+ widget?: IWidget;
+}
+
+/** One of three answers. */
+export enum Answer {
+ /** Affirmative. */
+ yes = 'yes',
+
+ /** Negative. */
+ no = 'no',
+
+ /** Unknown. */
+ maybe = 'maybe',
+}
+
+/** Custom errors. */
+export enum ApiErrors {
+ /** The user is not an administrator. */
+ NotAdmin = 'NotAdmin',
+
+ /** I'm "too" 😄! */
+ TooHappy = 'TooHappy',
+}
diff --git a/conformance/src/fastify/jsConformanceApiPlugin.js b/conformance/src/fastify/jsConformanceApiPlugin.js
new file mode 100644
index 0000000..b377fab
--- /dev/null
+++ b/conformance/src/fastify/jsConformanceApiPlugin.js
@@ -0,0 +1,814 @@
+// DO NOT EDIT: generated by fsdgenjs
+/* eslint-disable */
+'use strict';
+
+
+const standardErrorCodes = {
+ 'NotModified': 304,
+ 'InvalidRequest': 400,
+ 'NotAuthenticated': 401,
+ 'NotAuthorized': 403,
+ 'NotFound': 404,
+ 'Conflict': 409,
+ 'RequestTooLarge': 413,
+ 'TooManyRequests': 429,
+ 'InternalError': 500,
+ 'ServiceUnavailable': 503,
+ 'NotAdmin': 403,
+ 'TooHappy': 500,
+};
+
+function parseBoolean(value) {
+ if (typeof value === 'string') {
+ const lowerValue = value.toLowerCase();
+ if (lowerValue === 'true') {
+ return true;
+ }
+ if (lowerValue === 'false') {
+ return false;
+ }
+ }
+ return undefined;
+}
+
+export const jsConformanceApiPlugin = async (fastify, opts) => {
+ const { api, caseInsenstiveQueryStringKeys, includeErrorDetails } = opts;
+
+ for (const jsonSchema of jsonSchemas) {
+ fastify.addSchema(jsonSchema);
+ }
+
+ fastify.setErrorHandler((error, req, res) => {
+ req.log.error(error);
+ if (includeErrorDetails) {
+ res.status(500).send({
+ code: 'InternalError',
+ message: error.message,
+ details: {
+ stack: error.stack?.split('\n').filter((x) => x.length > 0),
+ }
+ });
+ }
+ else {
+ res.status(500).send({
+ code: 'InternalError',
+ message: 'The service experienced an unexpected internal error.',
+ });
+ }
+ });
+
+ if (caseInsenstiveQueryStringKeys) {
+ fastify.addHook('onRequest', async (req, res) => {
+ const query = req.query;
+ for (const key of Object.keys(query)) {
+ const lowerKey = key.toLowerCase();
+ if (lowerKey !== key) {
+ query[lowerKey] = query[key];
+ delete query[key];
+ }
+ }
+ });
+ }
+
+ fastify.route({
+ url: '/',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ service: { type: 'string' },
+ version: { type: 'string' },
+ },
+ },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const result = await api.getApiInfo(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ widgets: { type: 'array', items: { $ref: 'Widget' } },
+ },
+ },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const query = req.query;
+ if (typeof query['q'] === 'string') request.query = query['q'];
+
+ const result = await api.getWidgets(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets',
+ method: 'POST',
+ schema: {
+ response: {
+ 201: { $ref: 'Widget' },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ request.widget = req.body;
+
+ const result = await api.createWidget(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.url != null) res.header('Location', result.value.url);
+ if (result.value.eTag != null) res.header('eTag', result.value.eTag);
+
+ if (result.value.widget) {
+ res.status(201).send(result.value.widget);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets/:id',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: { $ref: 'Widget' },
+ 304: { type: 'boolean' },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const params = req.params;
+ if (typeof params['id'] === 'string') request.id = parseInt(params['id'], 10);
+
+ const headers = req.headers;
+ if (typeof headers['if-none-match'] === 'string') request.ifNotETag = headers['if-none-match'];
+
+ const result = await api.getWidget(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.eTag != null) res.header('eTag', result.value.eTag);
+
+ if (result.value.widget) {
+ res.status(200).send(result.value.widget);
+ return;
+ }
+
+ if (result.value.notModified) {
+ res.status(304);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets/:id',
+ method: 'DELETE',
+ schema: {
+ response: {
+ 204: { type: 'object', additionalProperties: false },
+ 404: { type: 'boolean' },
+ 409: { type: 'boolean' },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const params = req.params;
+ if (typeof params['id'] === 'string') request.id = parseInt(params['id'], 10);
+
+ const headers = req.headers;
+ if (typeof headers['if-match'] === 'string') request.ifETag = headers['if-match'];
+
+ const result = await api.deleteWidget(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.notFound) {
+ res.status(404);
+ return;
+ }
+
+ if (result.value.conflict) {
+ res.status(409);
+ return;
+ }
+
+ res.status(204);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/widgets/get',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: { type: 'array', items: { type: 'object', properties: { value: { $ref: 'Widget' }, error: { $ref: '_error' } } } },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ request.ids = req.body;
+
+ const result = await api.getWidgetBatch(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.results) {
+ res.status(200).send(result.value.results);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mirrorFields',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ field: { $ref: 'Any' },
+ matrix: { type: 'array', items: { type: 'array', items: { type: 'array', items: { type: 'number' } } } },
+ },
+ },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const body = req.body;
+ request.field = body.field;
+ request.matrix = body.matrix;
+
+ const result = await api.mirrorFields(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/checkQuery',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: { type: 'object', additionalProperties: false },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const query = req.query;
+ if (typeof query['string'] === 'string') request.string = query['string'];
+ if (typeof query['boolean'] === 'string') request.boolean = parseBoolean(query['boolean']);
+ if (typeof query['double'] === 'string') request.double = parseFloat(query['double']);
+ if (typeof query['int32'] === 'string') request.int32 = parseInt(query['int32'], 10);
+ if (typeof query['int64'] === 'string') request.int64 = parseInt(query['int64'], 10);
+ if (typeof query['decimal'] === 'string') request.decimal = parseFloat(query['decimal']);
+ if (typeof query['enum'] === 'string') request.enum = query['enum'];
+ if (typeof query['datetime'] === 'string') request.datetime = query['datetime'];
+
+ const result = await api.checkQuery(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/checkPath/:string/:boolean/:double/:int32/:int64/:decimal/:enum/:datetime',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: { type: 'object', additionalProperties: false },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const params = req.params;
+ if (typeof params['string'] === 'string') request.string = params['string'];
+ if (typeof params['boolean'] === 'string') request.boolean = parseBoolean(params['boolean']);
+ if (typeof params['double'] === 'string') request.double = parseFloat(params['double']);
+ if (typeof params['int32'] === 'string') request.int32 = parseInt(params['int32'], 10);
+ if (typeof params['int64'] === 'string') request.int64 = parseInt(params['int64'], 10);
+ if (typeof params['decimal'] === 'string') request.decimal = parseFloat(params['decimal']);
+ if (typeof params['enum'] === 'string') request.enum = params['enum'];
+ if (typeof params['datetime'] === 'string') request.datetime = params['datetime'];
+
+ const result = await api.checkPath(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mirrorHeaders',
+ method: 'GET',
+ schema: {
+ response: {
+ 200: { type: 'object', additionalProperties: false },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const headers = req.headers;
+ if (typeof headers['string'] === 'string') request.string = headers['string'];
+ if (typeof headers['boolean'] === 'string') request.boolean = parseBoolean(headers['boolean']);
+ if (typeof headers['double'] === 'string') request.double = parseFloat(headers['double']);
+ if (typeof headers['int32'] === 'string') request.int32 = parseInt(headers['int32'], 10);
+ if (typeof headers['int64'] === 'string') request.int64 = parseInt(headers['int64'], 10);
+ if (typeof headers['decimal'] === 'string') request.decimal = parseFloat(headers['decimal']);
+ if (typeof headers['enum'] === 'string') request.enum = headers['enum'];
+ if (typeof headers['datetime'] === 'string') request.datetime = headers['datetime'];
+
+ const result = await api.mirrorHeaders(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.string != null) res.header('string', result.value.string);
+ if (result.value.boolean != null) res.header('boolean', result.value.boolean);
+ if (result.value.double != null) res.header('double', result.value.double);
+ if (result.value.int32 != null) res.header('int32', result.value.int32);
+ if (result.value.int64 != null) res.header('int64', result.value.int64);
+ if (result.value.decimal != null) res.header('decimal', result.value.decimal);
+ if (result.value.enum != null) res.header('enum', result.value.enum);
+ if (result.value.datetime != null) res.header('datetime', result.value.datetime);
+
+ res.status(200);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mixed/:path',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ normal: { type: 'string' },
+ },
+ },
+ 202: { type: 'object', additionalProperties: true },
+ 204: { type: 'boolean' },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const params = req.params;
+ if (typeof params['path'] === 'string') request.path = params['path'];
+
+ const query = req.query;
+ if (typeof query['query'] === 'string') request.query = query['query'];
+
+ const headers = req.headers;
+ if (typeof headers['header'] === 'string') request.header = headers['header'];
+
+ const body = req.body;
+ request.normal = body.normal;
+
+ const result = await api.mixed(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.header != null) res.header('header', result.value.header);
+
+ if (result.value.body) {
+ res.status(202).send(result.value.body);
+ return;
+ }
+
+ if (result.value.empty) {
+ res.status(204);
+ return;
+ }
+
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/required',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: {
+ type: 'object',
+ properties: {
+ normal: { type: 'string' },
+ },
+ },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const query = req.query;
+ if (typeof query['query'] === 'string') request.query = query['query'];
+
+ const body = req.body;
+ request.normal = body.normal;
+ request.widget = body.widget;
+ request.widgets = body.widgets;
+ request.widgetMatrix = body.widgetMatrix;
+ request.widgetResult = body.widgetResult;
+ request.widgetResults = body.widgetResults;
+ request.widgetMap = body.widgetMap;
+ request.hasWidget = body.hasWidget;
+ request.point = body.point;
+
+ const result = await api.required(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ res.status(200).send(result.value);
+ return;
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mirrorBytes',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: { type: 'string' },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const headers = req.headers;
+ if (typeof headers['content-type'] === 'string') request.type = headers['content-type'];
+
+ request.content = req.body;
+
+ const result = await api.mirrorBytes(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.type != null) res.header('Content-Type', result.value.type);
+
+ if (result.value.content) {
+ res.status(200).send(result.value.content);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/mirrorText',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: { type: 'string' },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ const headers = req.headers;
+ if (typeof headers['content-type'] === 'string') request.type = headers['content-type'];
+
+ request.content = req.body;
+
+ const result = await api.mirrorText(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.type != null) res.header('Content-Type', result.value.type);
+
+ if (result.value.content) {
+ res.status(200).send(result.value.content);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+
+ fastify.route({
+ url: '/bodyTypes',
+ method: 'POST',
+ schema: {
+ response: {
+ 200: { type: 'string' },
+ },
+ },
+ handler: async function (req, res) {
+ const request = {};
+
+ request.content = req.body;
+
+ const result = await api.bodyTypes(request);
+
+ if (result.error) {
+ const status = result.error.code && standardErrorCodes[result.error.code];
+ res.status(status || 500).send(result.error);
+ return;
+ }
+
+ if (result.value) {
+ if (result.value.content) {
+ res.status(200).send(result.value.content);
+ return;
+ }
+ }
+
+ throw new Error('Result must have an error or value.');
+ }
+ });
+}
+
+const jsonSchemas = [
+ {
+ $id: '_error',
+ type: 'object',
+ properties: {
+ code: { type: 'string' },
+ message: { type: 'string' },
+ details: { type: 'object', additionalProperties: true },
+ innerError: { $ref: '_error' },
+ }
+ },
+ {
+ $id: 'Widget',
+ type: 'object',
+ properties: {
+ id: { type: 'integer' },
+ name: { type: 'string' },
+ }
+ },
+ {
+ $id: 'Any',
+ type: 'object',
+ properties: {
+ string: { type: 'string' },
+ boolean: { type: 'boolean' },
+ double: { type: 'number' },
+ int32: { type: 'integer' },
+ int64: { type: 'integer' },
+ decimal: { type: 'number' },
+ datetime: { type: 'string' },
+ bytes: { type: 'string' },
+ object: { type: 'object', additionalProperties: true },
+ error: { $ref: '_error' },
+ data: { $ref: 'Any' },
+ enum: { $ref: 'Answer' },
+ array: { $ref: 'AnyArray' },
+ map: { $ref: 'AnyMap' },
+ result: { $ref: 'AnyResult' },
+ nullable: { $ref: 'AnyNullable' },
+ }
+ },
+ {
+ $id: 'AnyArray',
+ type: 'object',
+ properties: {
+ string: { type: 'array', items: { type: 'string' } },
+ boolean: { type: 'array', items: { type: 'boolean' } },
+ double: { type: 'array', items: { type: 'number' } },
+ int32: { type: 'array', items: { type: 'integer' } },
+ int64: { type: 'array', items: { type: 'integer' } },
+ decimal: { type: 'array', items: { type: 'number' } },
+ datetime: { type: 'array', items: { type: 'string' } },
+ bytes: { type: 'array', items: { type: 'string' } },
+ object: { type: 'array', items: { type: 'object', additionalProperties: true } },
+ error: { type: 'array', items: { $ref: '_error' } },
+ data: { type: 'array', items: { $ref: 'Any' } },
+ enum: { type: 'array', items: { $ref: 'Answer' } },
+ array: { type: 'array', items: { type: 'array', items: { type: 'integer' } } },
+ map: { type: 'array', items: { type: 'object', additionalProperties: { type: 'integer' } } },
+ result: { type: 'array', items: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } } },
+ nullable: { type: 'array', items: { oneOf: [ { type: 'integer' }, { type: 'null' } ] } },
+ }
+ },
+ {
+ $id: 'AnyMap',
+ type: 'object',
+ properties: {
+ string: { type: 'object', additionalProperties: { type: 'string' } },
+ boolean: { type: 'object', additionalProperties: { type: 'boolean' } },
+ double: { type: 'object', additionalProperties: { type: 'number' } },
+ int32: { type: 'object', additionalProperties: { type: 'integer' } },
+ int64: { type: 'object', additionalProperties: { type: 'integer' } },
+ decimal: { type: 'object', additionalProperties: { type: 'number' } },
+ datetime: { type: 'object', additionalProperties: { type: 'string' } },
+ bytes: { type: 'object', additionalProperties: { type: 'string' } },
+ object: { type: 'object', additionalProperties: { type: 'object', additionalProperties: true } },
+ error: { type: 'object', additionalProperties: { $ref: '_error' } },
+ data: { type: 'object', additionalProperties: { $ref: 'Any' } },
+ enum: { type: 'object', additionalProperties: { $ref: 'Answer' } },
+ array: { type: 'object', additionalProperties: { type: 'array', items: { type: 'integer' } } },
+ map: { type: 'object', additionalProperties: { type: 'object', additionalProperties: { type: 'integer' } } },
+ result: { type: 'object', additionalProperties: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } } },
+ nullable: { type: 'object', additionalProperties: { oneOf: [ { type: 'integer' }, { type: 'null' } ] } },
+ }
+ },
+ {
+ $id: 'AnyResult',
+ type: 'object',
+ properties: {
+ string: { type: 'object', properties: { value: { type: 'string' }, error: { $ref: '_error' } } },
+ boolean: { type: 'object', properties: { value: { type: 'boolean' }, error: { $ref: '_error' } } },
+ double: { type: 'object', properties: { value: { type: 'number' }, error: { $ref: '_error' } } },
+ int32: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } },
+ int64: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } },
+ decimal: { type: 'object', properties: { value: { type: 'number' }, error: { $ref: '_error' } } },
+ datetime: { type: 'object', properties: { value: { type: 'string' }, error: { $ref: '_error' } } },
+ bytes: { type: 'object', properties: { value: { type: 'string' }, error: { $ref: '_error' } } },
+ object: { type: 'object', properties: { value: { type: 'object', additionalProperties: true }, error: { $ref: '_error' } } },
+ error: { type: 'object', properties: { value: { $ref: '_error' }, error: { $ref: '_error' } } },
+ data: { type: 'object', properties: { value: { $ref: 'Any' }, error: { $ref: '_error' } } },
+ enum: { type: 'object', properties: { value: { $ref: 'Answer' }, error: { $ref: '_error' } } },
+ array: { type: 'object', properties: { value: { type: 'array', items: { type: 'integer' } }, error: { $ref: '_error' } } },
+ map: { type: 'object', properties: { value: { type: 'object', additionalProperties: { type: 'integer' } }, error: { $ref: '_error' } } },
+ result: { type: 'object', properties: { value: { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } }, error: { $ref: '_error' } } },
+ nullable: { type: 'object', properties: { value: { oneOf: [ { type: 'integer' }, { type: 'null' } ] }, error: { $ref: '_error' } } },
+ }
+ },
+ {
+ $id: 'AnyNullable',
+ type: 'object',
+ properties: {
+ string: { oneOf: [ { type: 'string' }, { type: 'null' } ] },
+ boolean: { oneOf: [ { type: 'boolean' }, { type: 'null' } ] },
+ double: { oneOf: [ { type: 'number' }, { type: 'null' } ] },
+ int32: { oneOf: [ { type: 'integer' }, { type: 'null' } ] },
+ int64: { oneOf: [ { type: 'integer' }, { type: 'null' } ] },
+ decimal: { oneOf: [ { type: 'number' }, { type: 'null' } ] },
+ datetime: { oneOf: [ { type: 'string' }, { type: 'null' } ] },
+ bytes: { oneOf: [ { type: 'string' }, { type: 'null' } ] },
+ object: { oneOf: [ { type: 'object', additionalProperties: true }, { type: 'null' } ] },
+ error: { oneOf: [ { $ref: '_error' }, { type: 'null' } ] },
+ data: { oneOf: [ { $ref: 'Any' }, { type: 'null' } ] },
+ enum: { oneOf: [ { $ref: 'Answer' }, { type: 'null' } ] },
+ array: { oneOf: [ { type: 'array', items: { type: 'integer' } }, { type: 'null' } ] },
+ map: { oneOf: [ { type: 'object', additionalProperties: { type: 'integer' } }, { type: 'null' } ] },
+ result: { oneOf: [ { type: 'object', properties: { value: { type: 'integer' }, error: { $ref: '_error' } } }, { type: 'null' } ] },
+ }
+ },
+ {
+ $id: 'HasWidget',
+ type: 'object',
+ properties: {
+ widget: { $ref: 'Widget' },
+ }
+ },
+ {
+ $id: 'Answer',
+ type: 'string',
+ enum: [ 'yes', 'no', 'maybe' ],
+ },
+];
diff --git a/conformance/src/jsConformanceApi.js b/conformance/src/jsConformanceApi.js
new file mode 100644
index 0000000..d1559ee
--- /dev/null
+++ b/conformance/src/jsConformanceApi.js
@@ -0,0 +1,589 @@
+// DO NOT EDIT: generated by fsdgenjs
+/* eslint-disable */
+'use strict';
+
+import { HttpClientUtility } from 'facility-core';
+
+/** Provides access to JsConformanceApi over HTTP via fetch. */
+export function createHttpClient({ fetch, baseUri }) {
+ return new JsConformanceApiHttpClient(fetch, baseUri);
+}
+
+const { fetchResponse, createResponseError, createRequiredRequestFieldError } = HttpClientUtility;
+
+function parseBoolean(value) {
+ if (typeof value === 'string') {
+ const lowerValue = value.toLowerCase();
+ if (lowerValue === 'true') {
+ return true;
+ }
+ if (lowerValue === 'false') {
+ return false;
+ }
+ }
+ return undefined;
+}
+
+class JsConformanceApiHttpClient {
+ constructor(fetch, baseUri) {
+ if (typeof fetch !== 'function') {
+ throw new TypeError('fetch must be a function.');
+ }
+ if (typeof baseUri === 'undefined') {
+ baseUri = '';
+ }
+ if (/[^\/]$/.test(baseUri)) {
+ baseUri += '/';
+ }
+ this._fetch = fetch;
+ this._baseUri = baseUri;
+ }
+
+ /** Gets API information. */
+ getApiInfo(request, context) {
+ const uri = '';
+ const fetchRequest = {
+ method: 'GET',
+ };
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = result.json;
+ }
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ return { value: value };
+ });
+ }
+
+ /** Gets widgets. */
+ getWidgets(request, context) {
+ let uri = 'widgets';
+ const query = [];
+ request.query == null || query.push('q=' + encodeURIComponent(request.query));
+ if (query.length) {
+ uri = uri + '?' + query.join('&');
+ }
+ const fetchRequest = {
+ method: 'GET',
+ };
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = result.json;
+ }
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ return { value: value };
+ });
+ }
+
+ /** Creates a new widget. */
+ createWidget(request, context) {
+ const uri = 'widgets';
+ if (!request.widget) {
+ return Promise.resolve(createRequiredRequestFieldError('widget'));
+ }
+ const fetchRequest = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(request.widget)
+ };
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 201) {
+ if (result.json) {
+ value = { widget: result.json };
+ }
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ let headerValue;
+ headerValue = result.response.headers.get('Location');
+ if (headerValue != null) {
+ value.url = headerValue;
+ }
+ headerValue = result.response.headers.get('eTag');
+ if (headerValue != null) {
+ value.eTag = headerValue;
+ }
+ return { value: value };
+ });
+ }
+
+ /** Gets the specified widget. */
+ getWidget(request, context) {
+ const uriPartId = request.id != null && request.id.toString();
+ if (!uriPartId) {
+ return Promise.resolve(createRequiredRequestFieldError('id'));
+ }
+ const uri = `widgets/${uriPartId}`;
+ const fetchRequest = {
+ method: 'GET',
+ headers: {},
+ };
+ if (request.ifNotETag != null) {
+ fetchRequest.headers['If-None-Match'] = request.ifNotETag;
+ }
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = { widget: result.json };
+ }
+ }
+ else if (status === 304) {
+ value = { notModified: true };
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ let headerValue;
+ headerValue = result.response.headers.get('eTag');
+ if (headerValue != null) {
+ value.eTag = headerValue;
+ }
+ return { value: value };
+ });
+ }
+
+ /** Deletes the specified widget. */
+ deleteWidget(request, context) {
+ const uriPartId = request.id != null && request.id.toString();
+ if (!uriPartId) {
+ return Promise.resolve(createRequiredRequestFieldError('id'));
+ }
+ const uri = `widgets/${uriPartId}`;
+ const fetchRequest = {
+ method: 'DELETE',
+ headers: {},
+ };
+ if (request.ifETag != null) {
+ fetchRequest.headers['If-Match'] = request.ifETag;
+ }
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 204) {
+ value = {};
+ }
+ else if (status === 404) {
+ value = { notFound: true };
+ }
+ else if (status === 409) {
+ value = { conflict: true };
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ return { value: value };
+ });
+ }
+
+ /** Gets the specified widgets. */
+ getWidgetBatch(request, context) {
+ const uri = 'widgets/get';
+ if (!request.ids) {
+ return Promise.resolve(createRequiredRequestFieldError('ids'));
+ }
+ const fetchRequest = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(request.ids)
+ };
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = { results: result.json };
+ }
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ return { value: value };
+ });
+ }
+
+ mirrorFields(request, context) {
+ const uri = 'mirrorFields';
+ const fetchRequest = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(request)
+ };
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = result.json;
+ }
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ return { value: value };
+ });
+ }
+
+ checkQuery(request, context) {
+ let uri = 'checkQuery';
+ const query = [];
+ request.string == null || query.push('string=' + encodeURIComponent(request.string));
+ request.boolean == null || query.push('boolean=' + request.boolean.toString());
+ request.double == null || query.push('double=' + encodeURIComponent(request.double.toString()));
+ request.int32 == null || query.push('int32=' + request.int32.toString());
+ request.int64 == null || query.push('int64=' + request.int64.toString());
+ request.decimal == null || query.push('decimal=' + request.decimal.toString());
+ request.enum == null || query.push('enum=' + request.enum);
+ request.datetime == null || query.push('datetime=' + encodeURIComponent(request.datetime));
+ if (query.length) {
+ uri = uri + '?' + query.join('&');
+ }
+ const fetchRequest = {
+ method: 'GET',
+ };
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ value = {};
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ return { value: value };
+ });
+ }
+
+ checkPath(request, context) {
+ const uriPartString = request.string != null && encodeURIComponent(request.string);
+ if (!uriPartString) {
+ return Promise.resolve(createRequiredRequestFieldError('string'));
+ }
+ const uriPartBoolean = request.boolean != null && request.boolean.toString();
+ if (!uriPartBoolean) {
+ return Promise.resolve(createRequiredRequestFieldError('boolean'));
+ }
+ const uriPartDouble = request.double != null && encodeURIComponent(request.double.toString());
+ if (!uriPartDouble) {
+ return Promise.resolve(createRequiredRequestFieldError('double'));
+ }
+ const uriPartInt32 = request.int32 != null && request.int32.toString();
+ if (!uriPartInt32) {
+ return Promise.resolve(createRequiredRequestFieldError('int32'));
+ }
+ const uriPartInt64 = request.int64 != null && request.int64.toString();
+ if (!uriPartInt64) {
+ return Promise.resolve(createRequiredRequestFieldError('int64'));
+ }
+ const uriPartDecimal = request.decimal != null && request.decimal.toString();
+ if (!uriPartDecimal) {
+ return Promise.resolve(createRequiredRequestFieldError('decimal'));
+ }
+ const uriPartEnum = request.enum != null && request.enum;
+ if (!uriPartEnum) {
+ return Promise.resolve(createRequiredRequestFieldError('enum'));
+ }
+ const uriPartDatetime = request.datetime != null && encodeURIComponent(request.datetime);
+ if (!uriPartDatetime) {
+ return Promise.resolve(createRequiredRequestFieldError('datetime'));
+ }
+ const uri = `checkPath/${uriPartString}/${uriPartBoolean}/${uriPartDouble}/${uriPartInt32}/${uriPartInt64}/${uriPartDecimal}/${uriPartEnum}/${uriPartDatetime}`;
+ const fetchRequest = {
+ method: 'GET',
+ };
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ value = {};
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ return { value: value };
+ });
+ }
+
+ mirrorHeaders(request, context) {
+ const uri = 'mirrorHeaders';
+ const fetchRequest = {
+ method: 'GET',
+ headers: {},
+ };
+ if (request.string != null) {
+ fetchRequest.headers['string'] = request.string;
+ }
+ if (request.boolean != null) {
+ fetchRequest.headers['boolean'] = request.boolean.toString();
+ }
+ if (request.double != null) {
+ fetchRequest.headers['double'] = request.double.toString();
+ }
+ if (request.int32 != null) {
+ fetchRequest.headers['int32'] = request.int32.toString();
+ }
+ if (request.int64 != null) {
+ fetchRequest.headers['int64'] = request.int64.toString();
+ }
+ if (request.decimal != null) {
+ fetchRequest.headers['decimal'] = request.decimal.toString();
+ }
+ if (request.enum != null) {
+ fetchRequest.headers['enum'] = request.enum;
+ }
+ if (request.datetime != null) {
+ fetchRequest.headers['datetime'] = request.datetime;
+ }
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ value = {};
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ let headerValue;
+ headerValue = result.response.headers.get('string');
+ if (headerValue != null) {
+ value.string = headerValue;
+ }
+ headerValue = result.response.headers.get('boolean');
+ if (headerValue != null) {
+ value.boolean = parseBoolean(headerValue);
+ }
+ headerValue = result.response.headers.get('double');
+ if (headerValue != null) {
+ value.double = parseFloat(headerValue);
+ }
+ headerValue = result.response.headers.get('int32');
+ if (headerValue != null) {
+ value.int32 = parseInt(headerValue, 10);
+ }
+ headerValue = result.response.headers.get('int64');
+ if (headerValue != null) {
+ value.int64 = parseInt(headerValue, 10);
+ }
+ headerValue = result.response.headers.get('decimal');
+ if (headerValue != null) {
+ value.decimal = parseFloat(headerValue);
+ }
+ headerValue = result.response.headers.get('enum');
+ if (headerValue != null) {
+ value.enum = headerValue;
+ }
+ headerValue = result.response.headers.get('datetime');
+ if (headerValue != null) {
+ value.datetime = headerValue;
+ }
+ return { value: value };
+ });
+ }
+
+ mixed(request, context) {
+ const uriPartPath = request.path != null && encodeURIComponent(request.path);
+ if (!uriPartPath) {
+ return Promise.resolve(createRequiredRequestFieldError('path'));
+ }
+ let uri = `mixed/${uriPartPath}`;
+ const query = [];
+ request.query == null || query.push('query=' + encodeURIComponent(request.query));
+ if (query.length) {
+ uri = uri + '?' + query.join('&');
+ }
+ const fetchRequest = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ normal: request.normal
+ })
+ };
+ if (request.header != null) {
+ fetchRequest.headers['header'] = request.header;
+ }
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = result.json;
+ }
+ }
+ else if (status === 202) {
+ if (result.json) {
+ value = { body: result.json };
+ }
+ }
+ else if (status === 204) {
+ value = { empty: true };
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ let headerValue;
+ headerValue = result.response.headers.get('header');
+ if (headerValue != null) {
+ value.header = headerValue;
+ }
+ return { value: value };
+ });
+ }
+
+ required(request, context) {
+ let uri = 'required';
+ const query = [];
+ request.query == null || query.push('query=' + encodeURIComponent(request.query));
+ if (query.length) {
+ uri = uri + '?' + query.join('&');
+ }
+ const fetchRequest = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ normal: request.normal,
+ widget: request.widget,
+ widgets: request.widgets,
+ widgetMatrix: request.widgetMatrix,
+ widgetResult: request.widgetResult,
+ widgetResults: request.widgetResults,
+ widgetMap: request.widgetMap,
+ hasWidget: request.hasWidget,
+ point: request.point
+ })
+ };
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = result.json;
+ }
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ return { value: value };
+ });
+ }
+
+ mirrorBytes(request, context) {
+ const uri = 'mirrorBytes';
+ if (!request.content) {
+ return Promise.resolve(createRequiredRequestFieldError('content'));
+ }
+ const fetchRequest = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(request.content)
+ };
+ if (request.type != null) {
+ fetchRequest.headers['Content-Type'] = request.type;
+ }
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = { content: result.json };
+ }
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ let headerValue;
+ headerValue = result.response.headers.get('Content-Type');
+ if (headerValue != null) {
+ value.type = headerValue;
+ }
+ return { value: value };
+ });
+ }
+
+ mirrorText(request, context) {
+ const uri = 'mirrorText';
+ if (!request.content) {
+ return Promise.resolve(createRequiredRequestFieldError('content'));
+ }
+ const fetchRequest = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(request.content)
+ };
+ if (request.type != null) {
+ fetchRequest.headers['Content-Type'] = request.type;
+ }
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = { content: result.json };
+ }
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ let headerValue;
+ headerValue = result.response.headers.get('Content-Type');
+ if (headerValue != null) {
+ value.type = headerValue;
+ }
+ return { value: value };
+ });
+ }
+
+ bodyTypes(request, context) {
+ const uri = 'bodyTypes';
+ if (!request.content) {
+ return Promise.resolve(createRequiredRequestFieldError('content'));
+ }
+ const fetchRequest = {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(request.content)
+ };
+ return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)
+ .then(result => {
+ const status = result.response.status;
+ let value = null;
+ if (status === 200) {
+ if (result.json) {
+ value = { content: result.json };
+ }
+ }
+ if (!value) {
+ return createResponseError(status, result.json);
+ }
+ return { value: value };
+ });
+ }
+}
diff --git a/conformance/test/conformanceApiTests.ts b/conformance/test/conformanceApiTests.ts
new file mode 100644
index 0000000..81b5212
--- /dev/null
+++ b/conformance/test/conformanceApiTests.ts
@@ -0,0 +1,70 @@
+import { createHttpClient } from "../src/conformanceApi";
+import { createHttpClient as jsCreateHttpClient } from "../src/jsConformanceApi";
+import { expect, should } from "chai";
+import fetch from "node-fetch";
+import conformanceTestsJson from "../ConformanceTests.json";
+import { isDeepStrictEqual } from "util";
+
+const tests = conformanceTestsJson.tests;
+
+validateTests();
+should();
+
+const clients = [
+ {
+ baseUri: "http://localhost:4117/",
+ createHttpClient: createHttpClient,
+ },
+ {
+ baseUri: "http://localhost:4117/js/",
+ createHttpClient: jsCreateHttpClient as never,
+ },
+];
+
+clients.forEach(({baseUri, createHttpClient}) => {
+ describe(`ConformanceApi (${baseUri})`, () => {
+ const httpClient = createHttpClient({ fetch, baseUri});
+ tests.forEach((data) => {
+ it(data.test, async () => {
+ if (data.httpRequest) {
+ const result = await fetch(baseUri + data.httpRequest.path.replace(/^\//, ""), { method: data.httpRequest.method });
+ if (result.status >= 300) {
+ throw new Error(`Raw http request failed with status code ${result.status}, ${JSON.stringify(await result.json())}`);
+ }
+ } else {
+ const result = await (httpClient as any)[data.method](data.request);
+ expect({
+ error: result.error ?? undefined,
+ value: result.value ?? undefined,
+ }).to.be.deep.equal({
+ error: data.error ?? undefined,
+ value: data.response ?? undefined,
+ });
+ }
+ });
+ });
+ });
+});
+
+function validateTests() {
+ tests.forEach((data) => {
+ if (!data.test) {
+ throw new Error(`Test is missing 'test'`);
+ }
+ if (!data.method) {
+ throw new Error(`'${data.test}' is missing 'method'`);
+ }
+ if (data.httpRequest && !data.httpRequest.method) {
+ throw new Error(`Test '${data.test}' is missintg 'httpRequest.method'`);
+ }
+ if (data.httpRequest && !data.httpRequest.path) {
+ throw new Error(`Test '${data.test}' is missintg 'httpRequest.path'`);
+ }
+ if (tests.filter((x) => x.test === data.test).length !== 1) {
+ throw new Error(`Multiple tests found with name '${data.test}'`);
+ }
+ if (tests.filter((x) => x.method === data.method && isDeepStrictEqual(x.request, data.request)).length !== 1) {
+ throw new Error(`Multiple tests found for with method '${data.method}' and request '${JSON.stringify(data.request)}'`);
+ }
+ });
+}
diff --git a/conformance/ts/.gitignore b/conformance/ts/.gitignore
deleted file mode 100644
index b196ce0..0000000
--- a/conformance/ts/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-node_modules/
-*.d.ts
-*.js
-*.js.map
diff --git a/conformance/ts/test/conformanceApiTests.ts b/conformance/ts/test/conformanceApiTests.ts
deleted file mode 100644
index 07fdef2..0000000
--- a/conformance/ts/test/conformanceApiTests.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { createHttpClient } from '../src/conformanceApi';
-import { expect, should } from 'chai';
-import fetch from 'node-fetch';
-import * as fs from 'fs';
-import * as path from 'path';
-
-should();
-
-const httpClient = createHttpClient({
- fetch: (uri, request) => {
- return fetch('http://localhost:4117/' + uri, request);
- }
-});
-
-const testData = JSON.parse(
- fs.readFileSync(path.resolve(__dirname, '../../conformanceTests.json'), 'utf8'));
-
-describe('tests', () => {
-
- testData.tests.forEach((data: any) => {
- it(data.test, () => {
- return ((httpClient as any)[data.method](data.request))
- .then((result: any) => {
- expect(result.value).to.deep.equal(data.response);
- expect(result.error).to.deep.equal(data.error);
- });
- });
- });
-
-});
diff --git a/conformance/ts/tsconfig.json b/conformance/ts/tsconfig.json
deleted file mode 100644
index 57266dd..0000000
--- a/conformance/ts/tsconfig.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "compilerOptions": {
- "module": "commonjs",
- "target": "ES2019",
- "strict": true,
- "newLine": "LF",
- "declaration": true,
- "sourceMap": true,
- "lib": [ "ES2019" ]
- },
- "include": [
- "src/*.ts",
- "test/*.ts"
- ]
-}
diff --git a/conformance/tsconfig.json b/conformance/tsconfig.json
new file mode 100644
index 0000000..ad750c0
--- /dev/null
+++ b/conformance/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "outDir": "dist",
+ "module": "commonjs",
+ "target": "ES2019",
+ "strict": true,
+ "newLine": "LF",
+ "declaration": true,
+ "sourceMap": true,
+ "lib": [ "ES2019", "DOM" ],
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "allowJs": true
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.js",
+ "test/**/*.ts"
+ ],
+}
diff --git a/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator.md b/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator.md
index aeb61b9..2b6e466 100644
--- a/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator.md
+++ b/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator.md
@@ -13,12 +13,14 @@ public sealed class JavaScriptGenerator : CodeGenerator
| [JavaScriptGenerator](JavaScriptGenerator/JavaScriptGenerator.md)() | The default constructor. |
| [DisableESLint](JavaScriptGenerator/DisableESLint.md) { get; set; } | True to disable ESLint via code comment. |
| [Express](JavaScriptGenerator/Express.md) { get; set; } | True to generate Express service. |
+| [Fastify](JavaScriptGenerator/Fastify.md) { get; set; } | True to generate Fastify plugin. |
+| [FileNameSuffix](JavaScriptGenerator/FileNameSuffix.md) { get; set; } | Suffix to append to generated file names before the extension. |
| [ModuleName](JavaScriptGenerator/ModuleName.md) { get; set; } | The name of the module (optional). |
| override [SupportsSingleOutput](JavaScriptGenerator/SupportsSingleOutput.md) { get; } | Supports writing output to a single file. |
| [TypeScript](JavaScriptGenerator/TypeScript.md) { get; set; } | True to generate TypeScript. |
| override [ApplySettings](JavaScriptGenerator/ApplySettings.md)(…) | Applies generator-specific settings. |
| override [GenerateOutput](JavaScriptGenerator/GenerateOutput.md)(…) | Generates the JavaScript/TypeScript output. |
-| static [GenerateJavaScript](JavaScriptGenerator/GenerateJavaScript.md)(…) | Generates JavaScript/TypeScript. |
+| static [GenerateCSharp](JavaScriptGenerator/GenerateCSharp.md)(…) | Generates JavaScript/TypeScript. |
## See Also
diff --git a/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/Fastify.md b/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/Fastify.md
new file mode 100644
index 0000000..ec4f7b1
--- /dev/null
+++ b/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/Fastify.md
@@ -0,0 +1,18 @@
+# JavaScriptGenerator.Fastify property
+
+True to generate Fastify plugin.
+
+```csharp
+public bool Fastify { get; set; }
+```
+
+## Remarks
+
+When specified, only the server plugin is generated, not the client.
+
+## See Also
+
+* class [JavaScriptGenerator](../JavaScriptGenerator.md)
+* namespace [Facility.CodeGen.JavaScript](../../Facility.CodeGen.JavaScript.md)
+
+
diff --git a/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/FileNameSuffix.md b/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/FileNameSuffix.md
new file mode 100644
index 0000000..ae7e67b
--- /dev/null
+++ b/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/FileNameSuffix.md
@@ -0,0 +1,14 @@
+# JavaScriptGenerator.FileNameSuffix property
+
+Suffix to append to generated file names before the extension.
+
+```csharp
+public string? FileNameSuffix { get; set; }
+```
+
+## See Also
+
+* class [JavaScriptGenerator](../JavaScriptGenerator.md)
+* namespace [Facility.CodeGen.JavaScript](../../Facility.CodeGen.JavaScript.md)
+
+
diff --git a/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/GenerateJavaScript.md b/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/GenerateCSharp.md
similarity index 74%
rename from docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/GenerateJavaScript.md
rename to docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/GenerateCSharp.md
index 2731ba5..687ee53 100644
--- a/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/GenerateJavaScript.md
+++ b/docs/Facility.CodeGen.JavaScript/JavaScriptGenerator/GenerateCSharp.md
@@ -1,13 +1,14 @@
-# JavaScriptGenerator.GenerateJavaScript method
+# JavaScriptGenerator.GenerateCSharp method
Generates JavaScript/TypeScript.
```csharp
-public static int GenerateJavaScript(JavaScriptGeneratorSettings settings)
+public static int GenerateCSharp(ServiceParser parser, JavaScriptGeneratorSettings settings)
```
| parameter | description |
| --- | --- |
+| parser | The parser. |
| settings | The settings. |
## Return Value
diff --git a/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings.md b/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings.md
index 7afc86f..203f80c 100644
--- a/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings.md
+++ b/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings.md
@@ -13,6 +13,8 @@ public sealed class JavaScriptGeneratorSettings : FileGeneratorSettings
| [JavaScriptGeneratorSettings](JavaScriptGeneratorSettings/JavaScriptGeneratorSettings.md)() | The default constructor. |
| [DisableESLint](JavaScriptGeneratorSettings/DisableESLint.md) { get; set; } | True to disable ESLint via code comment. |
| [Express](JavaScriptGeneratorSettings/Express.md) { get; set; } | True to generate Express service. |
+| [Fastify](JavaScriptGeneratorSettings/Fastify.md) { get; set; } | True to generate Fastify plugin. |
+| [FileNameSuffix](JavaScriptGeneratorSettings/FileNameSuffix.md) { get; set; } | Suffix to append to generated file names before the extension. |
| [ModuleName](JavaScriptGeneratorSettings/ModuleName.md) { get; set; } | The name of the module (optional). |
| [TypeScript](JavaScriptGeneratorSettings/TypeScript.md) { get; set; } | True to generate TypeScript. |
diff --git a/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings/Fastify.md b/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings/Fastify.md
new file mode 100644
index 0000000..43ec434
--- /dev/null
+++ b/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings/Fastify.md
@@ -0,0 +1,18 @@
+# JavaScriptGeneratorSettings.Fastify property
+
+True to generate Fastify plugin.
+
+```csharp
+public bool Fastify { get; set; }
+```
+
+## Remarks
+
+When specified, only the server plugin is generated, not the client.
+
+## See Also
+
+* class [JavaScriptGeneratorSettings](../JavaScriptGeneratorSettings.md)
+* namespace [Facility.CodeGen.JavaScript](../../Facility.CodeGen.JavaScript.md)
+
+
diff --git a/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings/FileNameSuffix.md b/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings/FileNameSuffix.md
new file mode 100644
index 0000000..291625d
--- /dev/null
+++ b/docs/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings/FileNameSuffix.md
@@ -0,0 +1,14 @@
+# JavaScriptGeneratorSettings.FileNameSuffix property
+
+Suffix to append to generated file names before the extension.
+
+```csharp
+public string? FileNameSuffix { get; set; }
+```
+
+## See Also
+
+* class [JavaScriptGeneratorSettings](../JavaScriptGeneratorSettings.md)
+* namespace [Facility.CodeGen.JavaScript](../../Facility.CodeGen.JavaScript.md)
+
+
diff --git a/dotnet-tools.json b/dotnet-tools.json
index 9562684..194ceef 100644
--- a/dotnet-tools.json
+++ b/dotnet-tools.json
@@ -3,10 +3,11 @@
"isRoot": true,
"tools": {
"facilityconformance": {
- "version": "2.21.0",
+ "version": "2.29.0",
"commands": [
"FacilityConformance"
- ]
+ ],
+ "rollForward": false
}
}
}
\ No newline at end of file
diff --git a/example/js/exampleApiServer.js b/example/js/exampleApiServer.js
index 5ba997a..2a13ea9 100644
--- a/example/js/exampleApiServer.js
+++ b/example/js/exampleApiServer.js
@@ -16,6 +16,7 @@ const standardErrorCodes = {
'TooManyRequests': 429,
'InternalError': 500,
'ServiceUnavailable': 503,
+ 'NotAdmin': 403,
};
function parseBoolean(value) {
@@ -46,7 +47,7 @@ export function createApp(service) {
request.limit = parseInt(req.query['limit'], 10);
}
if (typeof req.query['sort'] === 'string') {
- request.sort = req.query['sort'] as WidgetField;
+ request.sort = req.query['sort'];
}
if (typeof req.query['desc'] === 'string') {
request.desc = parseBoolean(req.query['desc']);
diff --git a/example/ts/src/exampleApi.ts b/example/ts/src/exampleApi.ts
index de5424a..fd6775b 100644
--- a/example/ts/src/exampleApi.ts
+++ b/example/ts/src/exampleApi.ts
@@ -2,7 +2,7 @@
/* eslint-disable */
import { HttpClientUtility, IServiceResult, IHttpClientOptions } from 'facility-core';
-import { IExampleApi, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IEditWidgetRequest, IEditWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IGetWidgetWeightRequest, IGetWidgetWeightResponse, IGetPreferenceRequest, IGetPreferenceResponse, ISetPreferenceRequest, ISetPreferenceResponse, IGetInfoRequest, IGetInfoResponse, INotRestfulRequest, INotRestfulResponse, IKitchenRequest, IKitchenResponse, IWidget, IWidgetJob, IPreference, IObsoleteData, IKitchenSink, WidgetField, ObsoleteEnum } from './exampleApiTypes';
+import { IExampleApi, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IEditWidgetRequest, IEditWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IGetWidgetWeightRequest, IGetWidgetWeightResponse, IGetPreferenceRequest, IGetPreferenceResponse, ISetPreferenceRequest, ISetPreferenceResponse, IGetInfoRequest, IGetInfoResponse, INotRestfulRequest, INotRestfulResponse, IKitchenRequest, IKitchenResponse, IWidget, IWidgetJob, IPreference, IObsoleteData, IKitchenSink, WidgetField, ObsoleteEnum, ExampleApiErrors } from './exampleApiTypes';
export * from './exampleApiTypes';
/** Provides access to ExampleApi over HTTP via fetch. */
diff --git a/example/ts/src/exampleApiServer.ts b/example/ts/src/exampleApiServer.ts
index 0609e3e..ac3ac2f 100644
--- a/example/ts/src/exampleApiServer.ts
+++ b/example/ts/src/exampleApiServer.ts
@@ -4,7 +4,7 @@
import * as bodyParser from 'body-parser';
import * as express from 'express';
import { IServiceResult } from 'facility-core';
-import { IExampleApi, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IEditWidgetRequest, IEditWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IGetWidgetWeightRequest, IGetWidgetWeightResponse, IGetPreferenceRequest, IGetPreferenceResponse, ISetPreferenceRequest, ISetPreferenceResponse, IGetInfoRequest, IGetInfoResponse, INotRestfulRequest, INotRestfulResponse, IKitchenRequest, IKitchenResponse, IWidget, IWidgetJob, IPreference, IObsoleteData, IKitchenSink, WidgetField, ObsoleteEnum } from './exampleApiTypes';
+import { IExampleApi, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IEditWidgetRequest, IEditWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IGetWidgetWeightRequest, IGetWidgetWeightResponse, IGetPreferenceRequest, IGetPreferenceResponse, ISetPreferenceRequest, ISetPreferenceResponse, IGetInfoRequest, IGetInfoResponse, INotRestfulRequest, INotRestfulResponse, IKitchenRequest, IKitchenResponse, IWidget, IWidgetJob, IPreference, IObsoleteData, IKitchenSink, WidgetField, ObsoleteEnum, ExampleApiErrors } from './exampleApiTypes';
export * from './exampleApiTypes';
const standardErrorCodes: { [code: string]: number } = {
@@ -18,6 +18,7 @@ const standardErrorCodes: { [code: string]: number } = {
'TooManyRequests': 429,
'InternalError': 500,
'ServiceUnavailable': 503,
+ 'NotAdmin': 403,
};
function parseBoolean(value: string | undefined) {
diff --git a/example/ts/src/exampleApiTypes.ts b/example/ts/src/exampleApiTypes.ts
index ef70dcb..45ce934 100644
--- a/example/ts/src/exampleApiTypes.ts
+++ b/example/ts/src/exampleApiTypes.ts
@@ -350,3 +350,9 @@ export enum ObsoleteEnum {
unused = 'unused',
}
+/** Custom errors. */
+export enum ExampleApiErrors {
+ /** The user is not an administrator. */
+ NotAdmin = 'NotAdmin',
+}
+
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..391ba3c
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "8.0.100",
+ "rollForward": "latestFeature"
+ }
+}
diff --git a/nuget.config b/nuget.config
index 3d2c236..6873eb9 100644
--- a/nuget.config
+++ b/nuget.config
@@ -1,4 +1,5 @@
-
+
+
diff --git a/src/Facility.CodeGen.JavaScript/Facility.CodeGen.JavaScript.csproj b/src/Facility.CodeGen.JavaScript/Facility.CodeGen.JavaScript.csproj
index 63e5c36..d33a1e2 100644
--- a/src/Facility.CodeGen.JavaScript/Facility.CodeGen.JavaScript.csproj
+++ b/src/Facility.CodeGen.JavaScript/Facility.CodeGen.JavaScript.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;net6.0
+ netstandard2.0;net6.0;net7.0;net8.0
A library that generates JavaScript or TypeScript for a Facility Service Definition.
Facility FSD JavaScript TypeScript CodeGen
true
@@ -9,7 +9,7 @@
-
+
diff --git a/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs b/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs
index 514608c..ca64b75 100644
--- a/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs
+++ b/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs
@@ -1,6 +1,7 @@
using System.Globalization;
using Facility.Definition;
using Facility.Definition.CodeGen;
+using Facility.Definition.Fsd;
using Facility.Definition.Http;
namespace Facility.CodeGen.JavaScript
@@ -13,8 +14,18 @@ public sealed class JavaScriptGenerator : CodeGenerator
///
/// Generates JavaScript/TypeScript.
///
+ /// The parser.
/// The settings.
/// The number of updated files.
+ public static int GenerateCSharp(ServiceParser parser, JavaScriptGeneratorSettings settings) =>
+ FileGenerator.GenerateFiles(parser, new JavaScriptGenerator { GeneratorName = nameof(JavaScriptGenerator) }, settings);
+
+ ///
+ /// Generates JavaScript/TypeScript.
+ ///
+ /// The settings.
+ /// The number of updated files.
+ [Obsolete("Use the overload that takes a parser.")]
public static int GenerateJavaScript(JavaScriptGeneratorSettings settings) =>
FileGenerator.GenerateFiles(new JavaScriptGenerator { GeneratorName = nameof(JavaScriptGenerator) }, settings);
@@ -33,23 +44,40 @@ public static int GenerateJavaScript(JavaScriptGeneratorSettings settings) =>
///
public bool Express { get; set; }
+ ///
+ /// True to generate Fastify plugin.
+ ///
+ ///
+ /// When specified, only the server plugin is generated, not the client.
+ ///
+ public bool Fastify { get; set; }
+
///
/// True to disable ESLint via code comment.
///
public bool DisableESLint { get; set; }
+ ///
+ /// Suffix to append to generated file names before the extension.
+ ///
+ public string? FileNameSuffix { get; set; }
+
///
/// Generates the JavaScript/TypeScript output.
///
public override CodeGenOutput GenerateOutput(ServiceInfo service)
{
+ if (Fastify)
+ return GenerateFastifyPluginOutput(service);
+
var httpServiceInfo = HttpServiceInfo.Create(service);
var moduleName = ModuleName ?? service.Name;
var capModuleName = CodeGenUtility.Capitalize(moduleName);
- var typesFileName = CodeGenUtility.Uncapitalize(moduleName) + "Types" + (TypeScript ? ".ts" : ".js");
- var clientFileName = CodeGenUtility.Uncapitalize(moduleName) + (TypeScript ? ".ts" : ".js");
- var serverFileName = CodeGenUtility.Uncapitalize(moduleName) + "Server" + (TypeScript ? ".ts" : ".js");
+ var typesFileNameNoExt = CodeGenUtility.Uncapitalize(moduleName) + "Types" + (FileNameSuffix ?? "");
+ var typesFileName = typesFileNameNoExt + (TypeScript ? ".ts" : ".js");
+ var clientFileName = CodeGenUtility.Uncapitalize(moduleName) + (FileNameSuffix ?? "") + (TypeScript ? ".ts" : ".js");
+ var serverFileName = CodeGenUtility.Uncapitalize(moduleName) + "Server" + (FileNameSuffix ?? "") + (TypeScript ? ".ts" : ".js");
var errors = new List();
var namedTexts = new List();
@@ -104,60 +132,7 @@ public override CodeGenOutput GenerateOutput(ServiceInfo service)
foreach (var import in externImports.GroupBy(x => x.Module))
WriteImports(code, import.Select(x => $"{x.Name}{(x.Name != x.Alias ? $" as {x.Alias}" : "")}").ToArray(), import.Key);
- code.WriteLine();
- WriteJsDoc(code, service);
- typeNames.Add($"I{capModuleName}");
- using (code.Block($"export interface I{capModuleName} {{", "}"))
- {
- foreach (var httpMethodInfo in httpServiceInfo.Methods)
- {
- var methodName = httpMethodInfo.ServiceMethod.Name;
- var capMethodName = CodeGenUtility.Capitalize(methodName);
- code.WriteLineSkipOnce();
- WriteJsDoc(code, httpMethodInfo.ServiceMethod);
- code.WriteLine($"{methodName}(request: I{capMethodName}Request, context?: unknown): Promise>;");
- }
- }
-
- foreach (var methodInfo in service.Methods)
- {
- var requestDtoName = $"{CodeGenUtility.Capitalize(methodInfo.Name)}Request";
- typeNames.Add($"I{requestDtoName}");
- WriteDto(code, new ServiceDtoInfo(
- name: requestDtoName,
- fields: methodInfo.RequestFields,
- summary: $"Request for {CodeGenUtility.Capitalize(methodInfo.Name)}."), service);
-
- var responseDtoName = $"{CodeGenUtility.Capitalize(methodInfo.Name)}Response";
- typeNames.Add($"I{responseDtoName}");
- WriteDto(code, new ServiceDtoInfo(
- name: responseDtoName,
- fields: methodInfo.ResponseFields,
- summary: $"Response for {CodeGenUtility.Capitalize(methodInfo.Name)}."), service);
- }
-
- foreach (var dtoInfo in service.Dtos)
- {
- typeNames.Add($"I{dtoInfo.Name}");
- WriteDto(code, dtoInfo, service);
- }
-
- foreach (var enumInfo in service.Enums)
- {
- typeNames.Add(enumInfo.Name);
- code.WriteLine();
- WriteJsDoc(code, enumInfo);
- using (code.Block($"export enum {enumInfo.Name} {{", "}"))
- {
- foreach (var value in enumInfo.Values)
- {
- code.WriteLineSkipOnce();
- WriteJsDoc(code, value);
- code.WriteLine($"{value.Name} = '{value.Name}',");
- }
- }
- }
-
+ typeNames.AddRange(WriteTypes(code, httpServiceInfo));
code.WriteLine();
}));
}
@@ -181,8 +156,8 @@ public override CodeGenOutput GenerateOutput(ServiceInfo service)
if (TypeScript)
{
- WriteImports(code, typeNames, $"./{CodeGenUtility.Uncapitalize(moduleName)}Types");
- code.WriteLine($"export * from './{CodeGenUtility.Uncapitalize(moduleName)}Types';");
+ WriteImports(code, typeNames, $"./{typesFileNameNoExt}");
+ code.WriteLine($"export * from './{typesFileNameNoExt}';");
}
code.WriteLine();
@@ -422,40 +397,15 @@ public override CodeGenOutput GenerateOutput(ServiceInfo service)
WriteImports(code, facilityImports, "facility-core");
if (TypeScript)
{
- WriteImports(code, typeNames, $"./{CodeGenUtility.Uncapitalize(moduleName)}Types");
- code.WriteLine($"export * from './{CodeGenUtility.Uncapitalize(moduleName)}Types';");
+ WriteImports(code, typeNames, $"./{typesFileNameNoExt}");
+ code.WriteLine($"export * from './{typesFileNameNoExt}';");
}
- // TODO: export this from facility-core
code.WriteLine();
- using (code.Block("const standardErrorCodes" + IfTypeScript(": { [code: string]: number }") + " = {", "};"))
- {
- code.WriteLine("'NotModified': 304,");
- code.WriteLine("'InvalidRequest': 400,");
- code.WriteLine("'NotAuthenticated': 401,");
- code.WriteLine("'NotAuthorized': 403,");
- code.WriteLine("'NotFound': 404,");
- code.WriteLine("'Conflict': 409,");
- code.WriteLine("'RequestTooLarge': 413,");
- code.WriteLine("'TooManyRequests': 429,");
- code.WriteLine("'InternalError': 500,");
- code.WriteLine("'ServiceUnavailable': 503,");
- }
+ WriteStandardErrorCodesVariable("standardErrorCodes", code, httpServiceInfo.ErrorSets);
- // TODO: export this from facility-core?
code.WriteLine();
- using (code.Block("function parseBoolean(value" + IfTypeScript(": string | undefined") + ") {", "}"))
- {
- using (code.Block("if (typeof value === 'string') {", "}"))
- {
- code.WriteLine("const lowerValue = value.toLowerCase();");
- using (code.Block("if (lowerValue === 'true') {", "}"))
- code.WriteLine("return true;");
- using (code.Block("if (lowerValue === 'false') {", "}"))
- code.WriteLine("return false;");
- }
- code.WriteLine("return undefined;");
- }
+ WriteParseBooleanFunction("parseBoolean", code);
code.WriteLine();
using (code.Block("export function createApp(service" + IfTypeScript($": I{capModuleName}") + ")" + IfTypeScript(": express.Application") + " {", "}"))
@@ -594,7 +544,9 @@ public override void ApplySettings(FileGeneratorSettings settings)
ModuleName = ourSettings.ModuleName;
TypeScript = ourSettings.TypeScript;
Express = ourSettings.Express;
+ Fastify = ourSettings.Fastify;
DisableESLint = ourSettings.DisableESLint;
+ FileNameSuffix = ourSettings.FileNameSuffix;
}
///
@@ -602,6 +554,365 @@ public override void ApplySettings(FileGeneratorSettings settings)
///
public override bool SupportsSingleOutput => true;
+ private CodeGenOutput GenerateFastifyPluginOutput(ServiceInfo service)
+ {
+ var httpServiceInfo = HttpServiceInfo.Create(service);
+ var moduleName = ModuleName ?? service.Name;
+ var capModuleName = CodeGenUtility.Capitalize(moduleName);
+ var camelCaseModuleName = CodeGenUtility.ToCamelCase(moduleName);
+ var pluginFileName = CodeGenUtility.Uncapitalize(moduleName) + "Plugin" + (TypeScript ? ".ts" : ".js");
+ var customTypes = new HashSet(service.Dtos.Select(x => x.Name).Concat(service.Enums.Select(x => x.Name)));
+
+ var file = CreateFile(pluginFileName, code =>
+ {
+ WriteFileHeader(code);
+
+ if (!TypeScript)
+ code.WriteLine("'use strict';");
+
+ code.WriteLine();
+
+ var fastifyImports = new List();
+ if (TypeScript)
+ {
+ fastifyImports.Add("FastifyPluginAsync");
+ fastifyImports.Add("RegisterOptions");
+ }
+
+ WriteImports(code, fastifyImports, "fastify");
+
+ var facilityImports = new List();
+ if (TypeScript)
+ {
+ facilityImports.Add("IServiceResult");
+ facilityImports.Add("IServiceError");
+ }
+ WriteImports(code, facilityImports, "facility-core");
+
+ code.WriteLine();
+ WriteStandardErrorCodesVariable("standardErrorCodes", code, httpServiceInfo.ErrorSets);
+
+ code.WriteLine();
+ WriteParseBooleanFunction("parseBoolean", code);
+
+ if (TypeScript)
+ {
+ code.WriteLine();
+ using (code.Block($"export type {capModuleName}PluginOptions = RegisterOptions & {{", "}"))
+ {
+ code.WriteLine($"api: I{capModuleName};");
+ code.WriteLine("caseInsenstiveQueryStringKeys?: boolean;");
+ code.WriteLine("includeErrorDetails?: boolean;");
+ }
+ }
+
+ code.WriteLine();
+ using (code.Block($"export const {camelCaseModuleName}Plugin" + IfTypeScript($": FastifyPluginAsync<{capModuleName}PluginOptions>") + " = async (fastify, opts) => {", "}"))
+ {
+ code.WriteLine("const { api, caseInsenstiveQueryStringKeys, includeErrorDetails } = opts;");
+
+ code.WriteLine();
+ using (code.Block("for (const jsonSchema of jsonSchemas) {", "}"))
+ code.WriteLine("fastify.addSchema(jsonSchema);");
+
+ code.WriteLine();
+ using (code.Block("fastify.setErrorHandler((error, req, res) => {", "});"))
+ {
+ code.WriteLine("req.log.error(error);");
+ using (code.Block("if (includeErrorDetails) {", "}"))
+ {
+ code.WriteLine("res.status(500).send({");
+ using (code.Indent())
+ {
+ code.WriteLine("code: 'InternalError',");
+ code.WriteLine("message: error.message,");
+ using (code.Block("details: {", "}"))
+ {
+ code.WriteLine("stack: error.stack?.split('\\n').filter((x) => x.length > 0),");
+ }
+ }
+ code.WriteLine("});");
+ }
+ using (code.Block("else {", "}"))
+ {
+ code.WriteLine("res.status(500).send({");
+ using (code.Indent())
+ {
+ code.WriteLine("code: 'InternalError',");
+ code.WriteLine("message: 'The service experienced an unexpected internal error.',");
+ }
+ code.WriteLine("});");
+ }
+ }
+
+ code.WriteLine();
+ using (code.Block("if (caseInsenstiveQueryStringKeys) {", "}"))
+ {
+ using (code.Block("fastify.addHook('onRequest', async (req, res) => {", "});"))
+ {
+ code.WriteLine($"const query = req.query{IfTypeScript(" as Record")};");
+ using (code.Block("for (const key of Object.keys(query)) {", "}"))
+ {
+ code.WriteLine("const lowerKey = key.toLowerCase();");
+ using (code.Block("if (lowerKey !== key) {", "}"))
+ {
+ code.WriteLine("query[lowerKey] = query[key];");
+ code.WriteLine("delete query[key];");
+ }
+ }
+ }
+ }
+
+ foreach (var httpMethodInfo in httpServiceInfo.Methods)
+ {
+ var methodName = httpMethodInfo.ServiceMethod.Name;
+ var capMethodName = CodeGenUtility.Capitalize(methodName);
+ var fastifyPath = httpMethodInfo.Path;
+ foreach (var httpPathField in httpMethodInfo.PathFields)
+ fastifyPath = ReplaceOrdinal(fastifyPath, "{" + httpPathField.Name + "}", $":{httpPathField.Name}");
+
+ code.WriteLine();
+ using (code.Block("fastify.route({", "});"))
+ {
+ code.WriteLine($"url: '{fastifyPath}',");
+ code.WriteLine($"method: '{httpMethodInfo.Method.ToUpperInvariant()}',");
+ using (code.Block("schema: {", "},"))
+ {
+ using (code.Block("response: {", "},"))
+ {
+ foreach (var response in httpMethodInfo.ValidResponses)
+ {
+ var statusCode = (int) response.StatusCode;
+ code.Write($"{statusCode}: ");
+
+ if (response.BodyField is not null)
+ {
+ code.WriteLine($"{GetJsonSchemaType(service.GetFieldType(response.BodyField.ServiceField)!)},");
+ }
+ else if (response.NormalFields?.Count > 0)
+ {
+ using (code.Block("{", "},"))
+ {
+ code.WriteLine("type: 'object',");
+ using (code.Block("properties: {", "},"))
+ {
+ foreach (var normalField in response.NormalFields)
+ code.WriteLine($"{normalField.ServiceField.Name}: {GetJsonSchemaType(service.GetFieldType(normalField.ServiceField)!)},");
+ }
+ }
+ }
+ else
+ {
+ code.WriteLine("{ type: 'object', additionalProperties: false },");
+ }
+ }
+ }
+ }
+ using (code.Block("handler: async function (req, res) {", "}"))
+ {
+ code.WriteLine("const request" + IfTypeScript($": I{capMethodName}Request") + " = {};");
+ if (httpMethodInfo.PathFields.Count != 0)
+ {
+ code.WriteLine();
+ code.WriteLine($"const params = req.params{IfTypeScript(" as Record")};");
+ foreach (var pathParam in httpMethodInfo.PathFields)
+ {
+ code.WriteLine($"if (typeof params['{pathParam.Name}'] === 'string') request.{pathParam.ServiceField.Name} = {ParseFieldValue(pathParam.ServiceField, service, $"params['{pathParam.Name}']")};");
+ }
+ }
+
+ if (httpMethodInfo.QueryFields.Count != 0)
+ {
+ code.WriteLine();
+ code.WriteLine($"const query = req.query{IfTypeScript(" as Record")};");
+ foreach (var queryParam in httpMethodInfo.QueryFields)
+ {
+ code.WriteLine($"if (typeof query['{queryParam.Name}'] === 'string') request.{queryParam.ServiceField.Name} = {ParseFieldValue(queryParam.ServiceField, service, $"query['{queryParam.Name}']")};");
+ }
+ }
+
+ if (httpMethodInfo.RequestHeaderFields.Count != 0)
+ {
+ code.WriteLine();
+ code.WriteLine($"const headers = req.headers{IfTypeScript(" as Record")};");
+ foreach (var header in httpMethodInfo.RequestHeaderFields)
+ {
+ string lowerHeaderName = header.Name.ToLowerInvariant();
+ code.WriteLine($"if (typeof headers['{lowerHeaderName}'] === 'string') request.{header.ServiceField.Name} = {ParseFieldValue(header.ServiceField, service, $"headers['{lowerHeaderName}']")};");
+ }
+ }
+
+ if (httpMethodInfo.RequestBodyField != null)
+ {
+ code.WriteLine();
+ code.WriteLine($"request.{httpMethodInfo.RequestBodyField.ServiceField.Name} = req.body{IfTypeScript(" as never")};");
+ }
+ else if (httpMethodInfo.RequestNormalFields.Count != 0)
+ {
+ code.WriteLine();
+ code.WriteLine($"const body = req.body{IfTypeScript(" as Record")};");
+ foreach (var field in httpMethodInfo.RequestNormalFields)
+ code.WriteLine($"request.{field.ServiceField.Name} = body.{field.ServiceField.Name};");
+ }
+
+ code.WriteLine();
+ code.WriteLine($"const result = await api.{methodName}(request);");
+
+ code.WriteLine();
+ using (code.Block("if (result.error) {", "}"))
+ {
+ code.WriteLine("const status = result.error.code && standardErrorCodes[result.error.code];");
+ code.WriteLine("res.status(status || 500).send(result.error);");
+ code.WriteLine("return;");
+ }
+
+ code.WriteLine();
+ using (code.Block("if (result.value) {", "}"))
+ {
+ if (httpMethodInfo.ResponseHeaderFields.Count != 0)
+ {
+ code.WriteLineSkipOnce();
+ foreach (var field in httpMethodInfo.ResponseHeaderFields)
+ code.WriteLine($"if (result.value.{field.ServiceField.Name} != null) res.header('{field.Name}', result.value.{field.ServiceField.Name});");
+ }
+
+ var handledResponses = httpMethodInfo.ValidResponses.ToList();
+ foreach (var validResponse in httpMethodInfo.ValidResponses.Where(x => x.BodyField is not null))
+ {
+ handledResponses.Remove(validResponse);
+ var bodyField = validResponse.BodyField!;
+ var statusCode = (int) validResponse.StatusCode;
+
+ code.WriteLineSkipOnce();
+ using (code.Block($"if (result.value.{bodyField.ServiceField.Name}) {{", "}"))
+ {
+ var bodyFieldType = service.GetFieldType(bodyField.ServiceField)!;
+ if (bodyFieldType.Kind == ServiceTypeKind.Boolean)
+ {
+ code.WriteLine($"res.status({statusCode});");
+ code.WriteLine("return;");
+ }
+ else
+ {
+ code.WriteLine($"res.status({statusCode}).send(result.value.{bodyField.ServiceField.Name});");
+ code.WriteLine("return;");
+ }
+ }
+ }
+
+ if (handledResponses.Count == 1)
+ {
+ var lastValidResponse = handledResponses[0];
+ var statusCode = (int) lastValidResponse.StatusCode;
+
+ if (lastValidResponse.NormalFields?.Count > 0)
+ {
+ code.WriteLineSkipOnce();
+ code.WriteLine($"res.status({statusCode}).send(result.value);");
+ }
+ else
+ {
+ code.WriteLineSkipOnce();
+ code.WriteLine($"res.status({statusCode});");
+ }
+ code.WriteLine("return;");
+ }
+ else if (handledResponses.Count > 1)
+ {
+ throw new InvalidOperationException("More than one response is left.");
+ }
+ }
+
+ code.WriteLine();
+ code.WriteLine("throw new Error('Result must have an error or value.');");
+ }
+ }
+ }
+ }
+
+ WriteJsonSchemaDtos(code, service);
+
+ if (TypeScript)
+ WriteTypes(code, httpServiceInfo);
+ });
+
+ return new CodeGenOutput(file);
+
+ string GetJsonSchemaType(ServiceTypeInfo serviceType) => serviceType.Kind switch
+ {
+ ServiceTypeKind.String or ServiceTypeKind.Bytes or ServiceTypeKind.DateTime or ServiceTypeKind.ExternalEnum => "{ type: 'string' }",
+ ServiceTypeKind.Boolean => "{ type: 'boolean' }",
+ ServiceTypeKind.Double or ServiceTypeKind.Decimal => "{ type: 'number' }",
+ ServiceTypeKind.Int32 or ServiceTypeKind.Int64 => "{ type: 'integer' }",
+ ServiceTypeKind.Object or ServiceTypeKind.ExternalDto => "{ type: 'object', additionalProperties: true }",
+ ServiceTypeKind.Error => "{ $ref: '_error' }",
+ ServiceTypeKind.Dto => $"{{ $ref: '{serviceType.Dto!.Name}' }}",
+ ServiceTypeKind.Enum => $"{{ $ref: '{serviceType.Enum!.Name}' }}",
+ ServiceTypeKind.Result => $"{{ type: 'object', properties: {{ value: {GetJsonSchemaType(serviceType.ValueType!)}, error: {{ $ref: '_error' }} }} }}",
+ ServiceTypeKind.Array => $"{{ type: 'array', items: {GetJsonSchemaType(serviceType.ValueType!)} }}",
+ ServiceTypeKind.Map => $"{{ type: 'object', additionalProperties: {GetJsonSchemaType(serviceType.ValueType!)} }}",
+ ServiceTypeKind.Nullable => $"{{ oneOf: [ {GetJsonSchemaType(serviceType.ValueType!)}, {{ type: 'null' }} ] }}",
+ _ => throw new NotSupportedException($"Unknown field type '{serviceType.Kind}'"),
+ };
+
+ void WriteJsonSchemaDtos(CodeWriter code, ServiceInfo service)
+ {
+ code.WriteLine();
+ using (code.Block("const jsonSchemas = [", $"]{IfTypeScript(" as const")};"))
+ {
+ using (code.Block("{", $"}}{IfTypeScript(" as const")},"))
+ {
+ code.WriteLine("$id: '_error',");
+ code.WriteLine("type: 'object',");
+ using (code.Block("properties: {", "}"))
+ {
+ code.WriteLine("code: { type: 'string' },");
+ code.WriteLine("message: { type: 'string' },");
+ code.WriteLine("details: { type: 'object', additionalProperties: true },");
+ code.WriteLine("innerError: { $ref: '_error' },");
+ }
+ }
+
+ foreach (var dto in service.Dtos)
+ {
+ using (code.Block("{", $"}}{IfTypeScript(" as const")},"))
+ {
+ code.WriteLine($"$id: '{dto.Name}',");
+ code.WriteLine("type: 'object',");
+ using (code.Block("properties: {", "}"))
+ {
+ foreach (var field in dto.Fields)
+ code.WriteLine($"{field.Name}: {GetJsonSchemaType(service.GetFieldType(field)!)},");
+ }
+ }
+ }
+
+ foreach (var enumInfo in service.Enums)
+ {
+ using (code.Block("{", $"}}{IfTypeScript(" as const")},"))
+ {
+ code.WriteLine($"$id: '{enumInfo.Name}',");
+ code.WriteLine("type: 'string',");
+ code.Write("enum: [ ");
+
+ var shouldWriteComma = false;
+ foreach (var enumValue in enumInfo.Values)
+ {
+ if (shouldWriteComma)
+ code.Write(", ");
+ else
+ shouldWriteComma = true;
+
+ code.Write($"'{enumValue.Name}'");
+ }
+
+ code.WriteLine(" ],");
+ }
+ }
+ }
+ }
+ }
+
private void WriteFileHeader(CodeWriter code)
{
code.WriteLine($"// DO NOT EDIT: generated by {GeneratorName}");
@@ -619,7 +930,7 @@ private static void WriteDto(CodeWriter code, ServiceDtoInfo dtoInfo, ServiceInf
{
code.WriteLineSkipOnce();
WriteJsDoc(code, fieldInfo);
- code.WriteLine($"{fieldInfo.Name}?: {RenderFieldType(service.GetFieldType(fieldInfo)!)};");
+ code.WriteLine($"{fieldInfo.Name}{(fieldInfo.IsRequired ? "" : "?")}: {RenderFieldType(service.GetFieldType(fieldInfo)!)};");
}
}
}
@@ -674,6 +985,7 @@ private static string RenderUriComponent(ServiceFieldInfo field, ServiceInfo ser
switch (fieldTypeKind)
{
case ServiceTypeKind.Enum:
+ case ServiceTypeKind.ExternalEnum:
return $"request.{fieldName}";
case ServiceTypeKind.String:
case ServiceTypeKind.Bytes:
@@ -687,6 +999,7 @@ private static string RenderUriComponent(ServiceFieldInfo field, ServiceInfo ser
case ServiceTypeKind.Double:
return $"encodeURIComponent(request.{fieldName}.toString())";
case ServiceTypeKind.Dto:
+ case ServiceTypeKind.ExternalDto:
case ServiceTypeKind.Error:
case ServiceTypeKind.Object:
throw new NotSupportedException("Field type not supported on path/query: " + fieldTypeKind);
@@ -695,14 +1008,15 @@ private static string RenderUriComponent(ServiceFieldInfo field, ServiceInfo ser
}
}
- private static string ParseFieldValue(ServiceFieldInfo field, ServiceInfo service, string value)
+ private string ParseFieldValue(ServiceFieldInfo field, ServiceInfo service, string value)
{
var fieldTypeKind = service.GetFieldType(field)!.Kind;
switch (fieldTypeKind)
{
case ServiceTypeKind.Enum:
- return $"{value} as {field.TypeName}";
+ case ServiceTypeKind.ExternalEnum:
+ return $"{value}{IfTypeScript($" as {field.TypeName}")}";
case ServiceTypeKind.String:
case ServiceTypeKind.Bytes:
case ServiceTypeKind.DateTime:
@@ -716,6 +1030,7 @@ private static string ParseFieldValue(ServiceFieldInfo field, ServiceInfo servic
case ServiceTypeKind.Double:
return $"parseFloat({value})";
case ServiceTypeKind.Dto:
+ case ServiceTypeKind.ExternalDto:
case ServiceTypeKind.Error:
case ServiceTypeKind.Object:
throw new NotSupportedException("Field type not supported on path/query/header: " + fieldTypeKind);
@@ -731,6 +1046,7 @@ private static string RenderFieldValue(ServiceFieldInfo field, ServiceInfo servi
switch (fieldTypeKind)
{
case ServiceTypeKind.Enum:
+ case ServiceTypeKind.ExternalEnum:
case ServiceTypeKind.String:
case ServiceTypeKind.Bytes:
case ServiceTypeKind.DateTime:
@@ -742,6 +1058,7 @@ private static string RenderFieldValue(ServiceFieldInfo field, ServiceInfo servi
case ServiceTypeKind.Double:
return $"{value}.toString()";
case ServiceTypeKind.Dto:
+ case ServiceTypeKind.ExternalDto:
case ServiceTypeKind.Error:
case ServiceTypeKind.Object:
throw new NotSupportedException("Field type not supported on path/query: " + fieldTypeKind);
@@ -784,6 +1101,131 @@ private static void WriteImports(CodeWriter code, IReadOnlyList imports,
code.WriteLine($"import {{ {string.Join(", ", imports)} }} from '{from}';");
}
+ private void WriteStandardErrorCodesVariable(string name, CodeWriter code, IEnumerable? errorSets)
+ {
+ // TODO: export this from facility-core
+ using (code.Block($"const {name}" + IfTypeScript(": { [code: string]: number }") + " = {", "};"))
+ {
+ code.WriteLine("'NotModified': 304,");
+ code.WriteLine("'InvalidRequest': 400,");
+ code.WriteLine("'NotAuthenticated': 401,");
+ code.WriteLine("'NotAuthorized': 403,");
+ code.WriteLine("'NotFound': 404,");
+ code.WriteLine("'Conflict': 409,");
+ code.WriteLine("'RequestTooLarge': 413,");
+ code.WriteLine("'TooManyRequests': 429,");
+ code.WriteLine("'InternalError': 500,");
+ code.WriteLine("'ServiceUnavailable': 503,");
+
+ if (errorSets is not null)
+ {
+ foreach (var errorSetInfo in errorSets)
+ {
+ foreach (var error in errorSetInfo.Errors)
+ {
+ code.WriteLine($"'{error.ServiceError.Name}': {(int) error.StatusCode},");
+ }
+ }
+ }
+ }
+ }
+
+ private void WriteParseBooleanFunction(string name, CodeWriter code)
+ {
+ // TODO: export this from facility-core
+ using (code.Block($"function {name}(value" + IfTypeScript(": string | undefined") + ") {", "}"))
+ {
+ using (code.Block("if (typeof value === 'string') {", "}"))
+ {
+ code.WriteLine("const lowerValue = value.toLowerCase();");
+ using (code.Block("if (lowerValue === 'true') {", "}"))
+ code.WriteLine("return true;");
+ using (code.Block("if (lowerValue === 'false') {", "}"))
+ code.WriteLine("return false;");
+ }
+ code.WriteLine("return undefined;");
+ }
+ }
+
+ private List WriteTypes(CodeWriter code, HttpServiceInfo httpServiceInfo)
+ {
+ var typeNames = new List();
+ var service = httpServiceInfo.Service;
+ code.WriteLine();
+ WriteJsDoc(code, service);
+
+ var capModuleName = CodeGenUtility.Capitalize(ModuleName ?? service.Name);
+ typeNames.Add($"I{capModuleName}");
+ using (code.Block($"export interface I{capModuleName} {{", "}"))
+ {
+ foreach (var httpMethodInfo in httpServiceInfo.Methods)
+ {
+ var methodName = httpMethodInfo.ServiceMethod.Name;
+ var capMethodName = CodeGenUtility.Capitalize(methodName);
+ code.WriteLineSkipOnce();
+ WriteJsDoc(code, httpMethodInfo.ServiceMethod);
+ code.WriteLine($"{methodName}(request: I{capMethodName}Request, context?: unknown): Promise>;");
+ }
+ }
+
+ foreach (var methodInfo in service.Methods)
+ {
+ var requestDtoName = $"{CodeGenUtility.Capitalize(methodInfo.Name)}Request";
+ typeNames.Add($"I{requestDtoName}");
+ WriteDto(code, new ServiceDtoInfo(
+ name: requestDtoName,
+ fields: methodInfo.RequestFields,
+ summary: $"Request for {CodeGenUtility.Capitalize(methodInfo.Name)}."), service);
+
+ var responseDtoName = $"{CodeGenUtility.Capitalize(methodInfo.Name)}Response";
+ typeNames.Add($"I{responseDtoName}");
+ WriteDto(code, new ServiceDtoInfo(
+ name: responseDtoName,
+ fields: methodInfo.ResponseFields,
+ summary: $"Response for {CodeGenUtility.Capitalize(methodInfo.Name)}."), service);
+ }
+
+ foreach (var dtoInfo in service.Dtos)
+ {
+ typeNames.Add($"I{dtoInfo.Name}");
+ WriteDto(code, dtoInfo, service);
+ }
+
+ foreach (var enumInfo in service.Enums)
+ {
+ typeNames.Add(enumInfo.Name);
+ code.WriteLine();
+ WriteJsDoc(code, enumInfo);
+ using (code.Block($"export enum {enumInfo.Name} {{", "}"))
+ {
+ foreach (var value in enumInfo.Values)
+ {
+ code.WriteLineSkipOnce();
+ WriteJsDoc(code, value);
+ code.WriteLine($"{value.Name} = '{value.Name}',");
+ }
+ }
+ }
+
+ foreach (var errorSetInfo in service.ErrorSets)
+ {
+ typeNames.Add(errorSetInfo.Name);
+ code.WriteLine();
+ WriteJsDoc(code, errorSetInfo);
+ using (code.Block($"export enum {errorSetInfo.Name} {{", "}"))
+ {
+ foreach (var error in errorSetInfo.Errors)
+ {
+ code.WriteLineSkipOnce();
+ WriteJsDoc(code, error);
+ code.WriteLine($"{error.Name} = '{error.Name}',");
+ }
+ }
+ }
+
+ return typeNames;
+ }
+
private static bool FieldUsesKind(ServiceInfo service, ServiceFieldInfo field, ServiceTypeKind kind)
{
var type = service.GetFieldType(field)!;
diff --git a/src/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings.cs b/src/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings.cs
index ce66619..6261b86 100644
--- a/src/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings.cs
+++ b/src/Facility.CodeGen.JavaScript/JavaScriptGeneratorSettings.cs
@@ -22,9 +22,22 @@ public sealed class JavaScriptGeneratorSettings : FileGeneratorSettings
///
public bool Express { get; set; }
+ ///
+ /// True to generate Fastify plugin.
+ ///
+ ///
+ /// When specified, only the server plugin is generated, not the client.
+ ///
+ public bool Fastify { get; set; }
+
///
/// True to disable ESLint via code comment.
///
public bool DisableESLint { get; set; }
+
+ ///
+ /// Suffix to append to generated file names before the extension.
+ ///
+ public string? FileNameSuffix { get; set; }
}
}
diff --git a/src/fsdgenjs/FsdGenJavaScriptApp.cs b/src/fsdgenjs/FsdGenJavaScriptApp.cs
index bd1dfdf..8d9c62f 100644
--- a/src/fsdgenjs/FsdGenJavaScriptApp.cs
+++ b/src/fsdgenjs/FsdGenJavaScriptApp.cs
@@ -2,6 +2,7 @@
using Facility.CodeGen.Console;
using Facility.CodeGen.JavaScript;
using Facility.Definition.CodeGen;
+using Facility.Definition.Fsd;
namespace fsdgenjs
{
@@ -9,22 +10,28 @@ public sealed class FsdGenJavaScriptApp : CodeGeneratorApp
{
public static int Main(string[] args) => new FsdGenJavaScriptApp().Run(args);
- protected override IReadOnlyList Description => new[]
- {
+ protected override IReadOnlyList Description =>
+ [
"Generates a JavaScript client for a Facility Service Definition.",
- };
+ ];
- protected override IReadOnlyList ExtraUsage => new[]
- {
+ protected override IReadOnlyList ExtraUsage =>
+ [
" --module ",
" The module name used by the generated JavaScript.",
" --typescript",
" Generates TypeScript.",
" --express",
" Generates Express service.",
+ " --fastify",
+ " Generates a Fastify plugin. When specified, only the server plugin is generated, not the client.",
" --disable-eslint",
" Disables ESLint via code comment.",
- };
+ " --file-name-suffix",
+ " Suffix to append to generated file names before the file extension.",
+ ];
+
+ protected override ServiceParser CreateParser() => new FsdParser(new FsdParserSettings { SupportsEvents = true });
protected override CodeGenerator CreateGenerator() => new JavaScriptGenerator();
@@ -34,7 +41,9 @@ protected override FileGeneratorSettings CreateSettings(ArgsReader args) =>
ModuleName = args.ReadOption("module"),
TypeScript = args.ReadFlag("typescript"),
Express = args.ReadFlag("express"),
+ Fastify = args.ReadFlag("fastify"),
DisableESLint = args.ReadFlag("disable-eslint"),
+ FileNameSuffix = args.ReadOption("file-name-suffix"),
};
}
}
diff --git a/src/fsdgenjs/fsdgenjs.csproj b/src/fsdgenjs/fsdgenjs.csproj
index 8514786..8242284 100644
--- a/src/fsdgenjs/fsdgenjs.csproj
+++ b/src/fsdgenjs/fsdgenjs.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0;net7.0
+ net6.0;net7.0;net8.0
A tool that generates JavaScript or TypeScript for a Facility Service Definition.
Facility FSD JavaScript TypeScript CodeGen
true
@@ -11,7 +11,7 @@
-
+
diff --git a/tests/Facility.CodeGen.JavaScript.UnitTests/Facility.CodeGen.JavaScript.UnitTests.csproj b/tests/Facility.CodeGen.JavaScript.UnitTests/Facility.CodeGen.JavaScript.UnitTests.csproj
index a209fbc..7562e1f 100644
--- a/tests/Facility.CodeGen.JavaScript.UnitTests/Facility.CodeGen.JavaScript.UnitTests.csproj
+++ b/tests/Facility.CodeGen.JavaScript.UnitTests/Facility.CodeGen.JavaScript.UnitTests.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
@@ -9,10 +9,10 @@
-
-
-
-
+
+
+
+
diff --git a/tests/Facility.CodeGen.JavaScript.UnitTests/JavaScriptGeneratorTests.cs b/tests/Facility.CodeGen.JavaScript.UnitTests/JavaScriptGeneratorTests.cs
index 1133267..c68c861 100644
--- a/tests/Facility.CodeGen.JavaScript.UnitTests/JavaScriptGeneratorTests.cs
+++ b/tests/Facility.CodeGen.JavaScript.UnitTests/JavaScriptGeneratorTests.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using Facility.Definition;
+using Facility.Definition.CodeGen;
using Facility.Definition.Fsd;
using FluentAssertions;
using NUnit.Framework;
@@ -14,9 +15,9 @@ public void GenerateExampleApiSuccess(bool typeScript)
{
ServiceInfo service;
const string fileName = "Facility.CodeGen.JavaScript.UnitTests.ExampleApi.fsd";
- var parser = new FsdParser();
+ var parser = CreateParser();
var stream = GetType().GetTypeInfo().Assembly.GetManifestResourceStream(fileName)!;
- Assert.IsNotNull(stream);
+ Assert.That(stream, Is.Not.Null);
using (var reader = new StreamReader(stream))
service = parser.ParseDefinition(new ServiceDefinitionText(Path.GetFileName(fileName), reader.ReadToEnd()));
@@ -26,7 +27,7 @@ public void GenerateExampleApiSuccess(bool typeScript)
TypeScript = typeScript,
};
var result = generator.GenerateOutput(service);
- Assert.IsNotNull(result);
+ Assert.That(result, Is.Not.Null);
}
[Test]
@@ -34,9 +35,9 @@ public void GenerateExampleApiTypeScript_IncludesEnums()
{
ServiceInfo service;
const string fileName = "Facility.CodeGen.JavaScript.UnitTests.ExampleApi.fsd";
- var parser = new FsdParser();
+ var parser = CreateParser();
var stream = GetType().GetTypeInfo().Assembly.GetManifestResourceStream(fileName)!;
- Assert.IsNotNull(stream);
+ Assert.That(stream, Is.Not.Null);
using (var reader = new StreamReader(stream))
service = parser.ParseDefinition(new ServiceDefinitionText(Path.GetFileName(fileName), reader.ReadToEnd()));
@@ -47,7 +48,7 @@ public void GenerateExampleApiTypeScript_IncludesEnums()
NewLine = "\n",
};
var result = generator.GenerateOutput(service);
- Assert.IsNotNull(result);
+ Assert.That(result, Is.Not.Null);
var typesFile = result.Files.Single(f => f.Name == "exampleApiTypes.ts");
const string expectedEnums = """
@@ -86,9 +87,9 @@ public void GenerateExampleApiTypeScript_DoesntRequireJsonWhenNoResponseBodyExpe
{
ServiceInfo service;
const string fileName = "Facility.CodeGen.JavaScript.UnitTests.ExampleApi.fsd";
- var parser = new FsdParser();
+ var parser = CreateParser();
var stream = GetType().GetTypeInfo().Assembly.GetManifestResourceStream(fileName)!;
- Assert.IsNotNull(stream);
+ Assert.That(stream, Is.Not.Null);
using (var reader = new StreamReader(stream))
service = parser.ParseDefinition(new ServiceDefinitionText(Path.GetFileName(fileName), reader.ReadToEnd()));
@@ -99,7 +100,7 @@ public void GenerateExampleApiTypeScript_DoesntRequireJsonWhenNoResponseBodyExpe
NewLine = "\n",
};
var result = generator.GenerateOutput(service);
- Assert.IsNotNull(result);
+ Assert.That(result, Is.Not.Null);
var apiFile = result.Files.Single(f => f.Name == "exampleApi.ts");
@@ -128,15 +129,15 @@ public void GenerateExampleApiTypeScript_DoesntRequireJsonWhenNoResponseBodyExpe
public void GenerateExampleApiTypeScript_ExternDataWithNameAndModuel()
{
const string definition = "[csharp] service TestApi { [js(name: \"SomeExternalDto\", module: \"extern-dto-module\")] extern data Thing; data Test { thing: Thing; } }";
- var parser = new FsdParser();
+ var parser = CreateParser();
var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true };
var result = generator.GenerateOutput(service);
- Assert.IsNotNull(result);
+ Assert.That(result, Is.Not.Null);
var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
- StringAssert.Contains("import { SomeExternalDto as IThing } from 'extern-dto-module';", typesFile.Text);
- StringAssert.Contains("thing?: IThing;", typesFile.Text);
+ Assert.That(typesFile.Text, Does.Contain("import { SomeExternalDto as IThing } from 'extern-dto-module';"));
+ Assert.That(typesFile.Text, Does.Contain("thing?: IThing;"));
}
[Test]
@@ -159,45 +160,81 @@ public void GenerateExampleApiTypeScript_ExternDataWithoutModule()
public void GenerateExampleApiTypeScript_ExternDataWithoutName()
{
const string definition = "[csharp] service TestApi { [js(module: \"extern-dto-module\")] extern data Thing; data Test { thing: Thing; } }";
- var parser = new FsdParser();
+ var parser = CreateParser();
var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true };
var result = generator.GenerateOutput(service);
- Assert.IsNotNull(result);
+ Assert.That(result, Is.Not.Null);
var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
- StringAssert.Contains("import { IThing } from 'extern-dto-module';", typesFile.Text);
- StringAssert.Contains("thing?: IThing;", typesFile.Text);
+ Assert.That(typesFile.Text, Does.Contain("import { IThing } from 'extern-dto-module';"));
+ Assert.That(typesFile.Text, Does.Contain("thing?: IThing;"));
}
[Test]
public void GenerateExampleApiTypeScript_ExternDataNameSameAsAlias()
{
const string definition = "[csharp] service TestApi { [js(name: \"IThing\", module: \"extern-dto-module\")] extern data Thing; data Test { thing: Thing; } }";
- var parser = new FsdParser();
+ var parser = CreateParser();
var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true };
var result = generator.GenerateOutput(service);
- Assert.IsNotNull(result);
+ Assert.That(result, Is.Not.Null);
var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
- StringAssert.Contains("import { IThing } from 'extern-dto-module';", typesFile.Text);
- StringAssert.Contains("thing?: IThing;", typesFile.Text);
+ Assert.That(typesFile.Text, Does.Contain("import { IThing } from 'extern-dto-module';"));
+ Assert.That(typesFile.Text, Does.Contain("thing?: IThing;"));
}
[Test]
public void GenerateExampleApiTypeScript_ExternEnumWithNameAndModule()
{
const string definition = "[csharp] service TestApi { [js(name: \"SomeExternalEnum\", module: \"extern-enum-module\")] extern enum Thing; data Test { thing: Thing; } }";
- var parser = new FsdParser();
+ var parser = CreateParser();
var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true };
var result = generator.GenerateOutput(service);
- Assert.IsNotNull(result);
+ Assert.That(result, Is.Not.Null);
var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
- StringAssert.Contains("import { SomeExternalEnum as Thing } from 'extern-enum-module';", typesFile.Text);
- StringAssert.Contains("thing?: Thing;", typesFile.Text);
+ Assert.That(typesFile.Text, Does.Contain("import { SomeExternalEnum as Thing } from 'extern-enum-module';"));
+ Assert.That(typesFile.Text, Does.Contain("thing?: Thing;"));
+ }
+
+ [Test]
+ public void GenerateExampleApiTypeScript_ExternEnumAsUriParam()
+ {
+ const string definition = "[csharp] service TestApi { [js(name: \"SomeExternalEnum\", module: \"extern-enum-module\")] extern enum Thing; [http(method: GET, path: \"/myMethod\")] method myMethod { e: Thing; }: {} }";
+ var parser = CreateParser();
+ var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
+ var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true, Express = true };
+ var result = generator.GenerateOutput(service);
+ Assert.That(result, Is.Not.Null);
+
+ var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
+ Assert.That(typesFile.Text, Does.Contain("export interface IMyMethodRequest"));
+ Assert.That(typesFile.Text, Does.Contain("e?: Thing;"));
+
+ var serverFile = result.Files.Single(f => f.Name == "testApiServer.ts");
+ Assert.That(serverFile.Text, Does.Contain("request.e = req.query['e'] as Thing;"));
+ }
+
+ [Test]
+ public void GenerateExampleApiTypeScript_ExternEnumAsHeader()
+ {
+ const string definition = "[csharp] service TestApi { [js(name: \"SomeExternalEnum\", module: \"extern-enum-module\")] extern enum Thing; [http(method: GET, path: \"/myMethod\")] method myMethod { [http(from: header, name: \"Thing-Header\")] e: Thing; }: {} }";
+ var parser = CreateParser();
+ var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
+ var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true, Express = true };
+ var result = generator.GenerateOutput(service);
+ Assert.That(result, Is.Not.Null);
+
+ var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
+ Assert.That(typesFile.Text, Does.Contain("export interface IMyMethodRequest"));
+ Assert.That(typesFile.Text, Does.Contain("e?: Thing;"));
+
+ var serverFile = result.Files.Single(f => f.Name == "testApiServer.ts");
+ Assert.That(serverFile.Text, Does.Contain("request.e = req.header('Thing-Header');"));
}
[Test]
@@ -220,39 +257,149 @@ public void GenerateExampleApiTypeScript_ExternEnumWithoutModule()
public void GenerateExampleApiTypeScript_ExternEnumWithoutName()
{
const string definition = "[csharp] service TestApi { [js(module: \"extern-enum-module\")] extern enum Thing; data Test { thing: Thing; } }";
- var parser = new FsdParser();
+ var parser = CreateParser();
var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true };
var result = generator.GenerateOutput(service);
- Assert.IsNotNull(result);
+ Assert.That(result, Is.Not.Null);
var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
- StringAssert.Contains("import { Thing } from 'extern-enum-module';", typesFile.Text);
- StringAssert.Contains("thing?: Thing;", typesFile.Text);
+ Assert.That(typesFile.Text, Does.Contain("import { Thing } from 'extern-enum-module';"));
+ Assert.That(typesFile.Text, Does.Contain("thing?: Thing;"));
}
[Test]
public void GenerateExampleApiTypeScript_ExternEnumNameIsSameAsAlias()
{
const string definition = "[csharp] service TestApi { [js(name: \"Thing\", module: \"extern-enum-module\")] extern enum Thing; data Test { thing: Thing; } }";
- var parser = new FsdParser();
+ var parser = CreateParser();
var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true };
var result = generator.GenerateOutput(service);
- Assert.IsNotNull(result);
+ Assert.That(result, Is.Not.Null);
var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
- StringAssert.Contains("import { Thing } from 'extern-enum-module';", typesFile.Text);
- StringAssert.Contains("thing?: Thing;", typesFile.Text);
+ Assert.That(typesFile.Text, Does.Contain("import { Thing } from 'extern-enum-module';"));
+ Assert.That(typesFile.Text, Does.Contain("thing?: Thing;"));
+ }
+
+ [Test]
+ public void GenerateExampleApiTypeScript_IncludesErrorSets()
+ {
+ ServiceInfo service;
+ const string fileName = "Facility.CodeGen.JavaScript.UnitTests.ExampleApi.fsd";
+ var parser = CreateParser();
+ var stream = GetType().GetTypeInfo().Assembly.GetManifestResourceStream(fileName)!;
+ Assert.That(stream, Is.Not.Null);
+ using (var reader = new StreamReader(stream))
+ service = parser.ParseDefinition(new ServiceDefinitionText(Path.GetFileName(fileName), reader.ReadToEnd()));
+
+ var generator = new JavaScriptGenerator
+ {
+ GeneratorName = "JavaScriptGeneratorTests",
+ TypeScript = true,
+ NewLine = "\n",
+ };
+ var result = generator.GenerateOutput(service);
+ Assert.That(result, Is.Not.Null);
+
+ var typesFile = result.Files.Single(f => f.Name == "exampleApiTypes.ts");
+ const string expectedErrorSet = """
+ /** Custom errors. */
+ export enum ExampleApiErrors {
+ /** The user is not an administrator. */
+ NotAdmin = 'NotAdmin',
+ }
+ """;
+ Assert.That(typesFile.Text, Contains.Substring(expectedErrorSet));
+ }
+
+ [Test]
+ public void GenerateExampleApiTypeScript_DataPropertiesOptional()
+ {
+ const string definition = "service TestApi { data Widget { id: string; name: string; price: decimal; } }";
+ var parser = CreateParser();
+ var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
+ var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true };
+ var result = generator.GenerateOutput(service);
+ Assert.That(result, Is.Not.Null);
+
+ var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
+ Assert.That(typesFile.Text, Does.Contain("export interface IWidget {"));
+ Assert.That(typesFile.Text, Does.Contain("id?: string;"));
+ Assert.That(typesFile.Text, Does.Contain("name?: string;"));
+ Assert.That(typesFile.Text, Does.Contain("price?: number;"));
+ Assert.That(typesFile.Text, Does.Contain("}"));
+ }
+
+ [Test]
+ public void GenerateExampleApiTypeScript_DataPropertiesRequired()
+ {
+ const string definition = "service TestApi { data Widget { [required] id: string; name: string!; price: decimal; } }";
+ var parser = CreateParser();
+ var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
+ var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true };
+ var result = generator.GenerateOutput(service);
+ Assert.That(result, Is.Not.Null);
+
+ var typesFile = result.Files.Single(f => f.Name == "testApiTypes.ts");
+ Assert.That(typesFile.Text, Does.Contain("export interface IWidget {"));
+ Assert.That(typesFile.Text, Does.Contain("id: string;"));
+ Assert.That(typesFile.Text, Does.Contain("name: string;"));
+ Assert.That(typesFile.Text, Does.Contain("price?: number;"));
+ Assert.That(typesFile.Text, Does.Contain("}"));
+ }
+
+ [TestCase("", true)]
+ [TestCase("", false)]
+ [TestCase("suffix", true)]
+ [TestCase("suffix", false)]
+ [TestCase(".g", true)]
+ [TestCase(".g", false)]
+ public void GenerateWithCustomFileNameSuffix(string suffix, bool isTypeScript)
+ {
+ const string definition = "service TestApi { }";
+ var parser = CreateParser();
+ var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
+ var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = isTypeScript, Express = true, FileNameSuffix = suffix };
+ var result = generator.GenerateOutput(service);
+ Assert.That(result, Is.Not.Null);
+
+ Assert.That(result.Files, Has.Count.EqualTo(isTypeScript ? 3 : 2));
+ var fullSuffix = suffix + (isTypeScript ? ".ts" : ".js");
+ Assert.That(result.Files, Has.One.Matches(f => f.Name == $"testApi{fullSuffix}"));
+ Assert.That(result.Files, Has.One.Matches(f => f.Name == $"testApiServer{fullSuffix}"));
+
+ if (isTypeScript)
+ Assert.That(result.Files.SingleOrDefault(f => f.Name == $"testApiTypes{fullSuffix}"), Is.Not.Null);
+ }
+
+ [Test]
+ public void GenerateWithCustomFileNameSuffix_TypeScriptFileNameReferencesCorrect()
+ {
+ const string definition = "service TestApi { }";
+ var parser = CreateParser();
+ var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
+ var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true, Express = true, FileNameSuffix = ".g" };
+ var result = generator.GenerateOutput(service);
+ Assert.That(result, Is.Not.Null);
+
+ var clientFile = result.Files.Single(x => x.Name == "testApi.g.ts");
+ Assert.That(clientFile.Text, Does.Contain("export * from './testApiTypes.g';"));
+
+ var serverFile = result.Files.Single(x => x.Name == "testApiServer.g.ts");
+ Assert.That(serverFile.Text, Does.Contain("export * from './testApiTypes.g';"));
}
private void ThrowsServiceDefinitionException(string definition, string message)
{
- var parser = new FsdParser();
+ var parser = CreateParser();
var service = parser.ParseDefinition(new ServiceDefinitionText("TestApi.fsd", definition));
var generator = new JavaScriptGenerator { GeneratorName = "JavaScriptGeneratorTests", TypeScript = true };
Action action = () => generator.GenerateOutput(service);
action.Should().Throw().WithMessage(message);
}
+
+ private static FsdParser CreateParser() => new FsdParser(new FsdParserSettings { SupportsEvents = true });
}
}
diff --git a/tools/Build/Build.cs b/tools/Build/Build.cs
index a947606..3e29038 100644
--- a/tools/Build/Build.cs
+++ b/tools/Build/Build.cs
@@ -46,7 +46,12 @@ void CodeGen(bool verify)
RunDotNet("FacilityConformance", "fsd", "--output", "conformance/ConformanceApi.fsd", verifyOption);
RunDotNet("FacilityConformance", "json", "--output", "conformance/ConformanceTests.json", verifyOption);
- RunCodeGen("conformance/ConformanceApi.fsd", "conformance/ts/src/", "--typescript", "--indent", "2", "--disable-eslint");
+
+ RunCodeGen("conformance/ConformanceApi.fsd", "conformance/src/", "--typescript", "--indent", "2", "--disable-eslint");
+ RunCodeGen("conformance/ConformanceApi.fsd", "conformance/src/fastify", "--typescript", "--fastify", "--indent", "2", "--disable-eslint");
+
+ RunCodeGen("conformance/ConformanceApi.fsd", "conformance/src/", "--indent", "2", "--disable-eslint", "--module", "jsConformanceApi");
+ RunCodeGen("conformance/ConformanceApi.fsd", "conformance/src/fastify/", "--fastify", "--indent", "2", "--disable-eslint", "--module", "jsConformanceApi");
void RunCodeGen(params string?[] args) =>
RunDotNet(new[] { "run", "--no-build", "--project", $"src/{codegen}", "-f", "net6.0", "-c", configuration, "--", "--newline", "lf", verifyOption }.Concat(args));
diff --git a/tools/Build/Build.csproj b/tools/Build/Build.csproj
index 3fda495..94c9978 100644
--- a/tools/Build/Build.csproj
+++ b/tools/Build/Build.csproj
@@ -2,11 +2,11 @@
Exe
- net7.0
+ net8.0
-
+
diff --git a/tools/XmlDocGen/XmlDocGen.csproj b/tools/XmlDocGen/XmlDocGen.csproj
index 0c7e5b3..0ac3cc2 100644
--- a/tools/XmlDocGen/XmlDocGen.csproj
+++ b/tools/XmlDocGen/XmlDocGen.csproj
@@ -2,7 +2,7 @@
Exe
- net7.0
+ net8.0
@@ -10,7 +10,7 @@
-
+