1
1
mirror of https://github.com/leon-ai/leon.git synced 2024-12-25 17:54:43 +03:00

Merge branch 'feat/add-typebox-ajv' into develop

This commit is contained in:
louistiti 2022-11-14 23:56:08 +08:00
commit cad6d7f725
No known key found for this signature in database
GPG Key ID: 7ECA3DD523793FE6
14 changed files with 496 additions and 517 deletions

View File

@ -185,13 +185,6 @@ You'll find a write-up on this [blog post](https://blog.getleon.ai/the-story-beh
<br> <br>
<sub><sup>100 USD / month</sup></sub> <sub><sup>100 USD / month</sup></sub>
</td> </td>
<td align="center" valign="middle" width="128">
<a href="https://github.com/phareal">
<img src="https://github.com/phareal.png?size=128" />
phareal
</a><br>
<sub><sup>30 USD / month</sup></sub>
</td>
<td align="center" valign="middle" width="128"> <td align="center" valign="middle" width="128">
<a href="https://github.com/KeithIMyers"> <a href="https://github.com/KeithIMyers">
<img src="https://github.com/KeithIMyers.png?size=128" /> <img src="https://github.com/KeithIMyers.png?size=128" />

View File

@ -1,12 +1,12 @@
{ {
"type": "", "type": "service_account",
"project_id": "", "project_id": "",
"private_key_id": "", "private_key_id": "",
"private_key": "", "private_key": "",
"client_email": "", "client_email": "example@iam.gserviceaccount.com",
"client_id": "", "client_id": "",
"auth_uri": "", "auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "", "token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "" "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/"
} }

View File

@ -1,32 +0,0 @@
import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
const amazonVoiceConfiguration = Type.Object({
credentials: Type.Object({
accessKeyId: Type.String(),
secretAccessKey: Type.String()
}),
region: Type.String()
})
const googleCloudVoiceConfiguration = Type.Object({
type: Type.Literal('service_account'),
project_id: Type.String(),
private_key_id: Type.String(),
private_key: Type.String(),
client_email: Type.String({ format: 'email' }),
client_id: Type.String(),
auth_uri: Type.String({ format: 'uri' }),
token_uri: Type.String({ format: 'uri' }),
auth_provider_x509_cert_url: Type.String({ format: 'uri' }),
client_x509_cert_url: Type.String({ format: 'uri' })
})
const watsonVoiceConfiguration = Type.Object({
apikey: Type.String(),
url: Type.String({ format: 'uri' })
})
export type AmazonVoiceConfiguration = Static<typeof amazonVoiceConfiguration>
export type GoogleCloudVoiceConfiguration = Static<
typeof googleCloudVoiceConfiguration
>
export type WatsonVoiceConfiguration = Static<typeof watsonVoiceConfiguration>

View File

@ -1,4 +1,4 @@
{ {
"apikey": "", "apikey": "",
"url": "" "url": "https://stream.watsonplatform.net/speech-to-text/api"
} }

View File

@ -1,4 +1,4 @@
{ {
"apikey": "", "apikey": "",
"url": "" "url": "https://stream.watsonplatform.net/text-to-speech/api"
} }

View File

@ -1,45 +0,0 @@
import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
const globalEntity = {
options: Type.Record(
Type.String(),
Type.Object({
synonyms: Type.Array(Type.String()),
data: Type.Record(Type.String(), Type.Array(Type.String()))
})
)
}
const globalResolver = {
name: Type.String(),
intents: Type.Record(
Type.String(),
Type.Object({
utterance_samples: Type.Array(Type.String()),
value: Type.Unknown()
})
)
}
const answers = {
answers: Type.Record(
Type.String(),
Type.Union([
Type.Record(Type.String(), Type.String()),
Type.Array(Type.String())
])
)
}
const globalEntitySchemaObject = Type.Strict(
Type.Object(globalEntity, { additionalProperties: false })
)
const globalResolverSchemaObject = Type.Strict(
Type.Object(globalResolver, { additionalProperties: false })
)
const answersSchemaObject = Type.Strict(
Type.Object(answers, { additionalProperties: false })
)
export type GlobalEntity = Static<typeof globalEntitySchemaObject>
export type GlobalResolver = Static<typeof globalResolverSchemaObject>
export type Answers = Static<typeof answersSchemaObject>

264
package-lock.json generated
View File

@ -20,7 +20,10 @@
"@nlpjs/core-loader": "^4.22.7", "@nlpjs/core-loader": "^4.22.7",
"@nlpjs/lang-all": "^4.22.12", "@nlpjs/lang-all": "^4.22.12",
"@nlpjs/nlp": "^4.22.17", "@nlpjs/nlp": "^4.22.17",
"@sinclair/typebox": "0.24.49", "@segment/ajv-human-errors": "2.1.2",
"@sinclair/typebox": "0.25.8",
"ajv": "8.11.0",
"ajv-formats": "2.1.1",
"archiver": "^5.3.1", "archiver": "^5.3.1",
"async": "^3.2.0", "async": "^3.2.0",
"axios": "1.1.2", "axios": "1.1.2",
@ -1644,6 +1647,28 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/@eslint/eslintrc/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/@fast-csv/format": { "node_modules/@fast-csv/format": {
"version": "4.3.5", "version": "4.3.5",
"dev": true, "dev": true,
@ -1700,24 +1725,6 @@
"fast-uri": "^2.0.0" "fast-uri": "^2.0.0"
} }
}, },
"node_modules/@fastify/ajv-compiler/node_modules/ajv": {
"version": "8.11.0",
"license": "MIT",
"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/@fastify/ajv-compiler/node_modules/json-schema-traverse": {
"version": "1.0.0",
"license": "MIT"
},
"node_modules/@fastify/deepmerge": { "node_modules/@fastify/deepmerge": {
"version": "1.1.0", "version": "1.1.0",
"license": "MIT" "license": "MIT"
@ -3311,10 +3318,18 @@
"version": "1.1.0", "version": "1.1.0",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@segment/ajv-human-errors": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@segment/ajv-human-errors/-/ajv-human-errors-2.1.2.tgz",
"integrity": "sha512-d1uQndRFLRO01+xW1y5m+joxDgHf5SLJ70YCY2ArLoo2FJ50o6AoX2mEbuGvnKz/IdwnvDugm9Ti3mZQkW1OoA==",
"peerDependencies": {
"ajv": "^8.0.0"
}
},
"node_modules/@sinclair/typebox": { "node_modules/@sinclair/typebox": {
"version": "0.24.49", "version": "0.25.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.49.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.8.tgz",
"integrity": "sha512-qWgJVeCThMWTJSAZHyHDlHBkJY0LARFoq/96RH4oIFAwJptMwB3Isq62c4zRVRIAF2r4RMOc2WOhtOLj5C4InA==" "integrity": "sha512-/7GXgMOCfnxpIov52cPetqQ7bxRBaTBJAgp04Se2UQB1J0vUfEOIMpn63cLc3S5JXDUflCWxELKDV8eiPpmUTQ=="
}, },
"node_modules/@sinonjs/commons": { "node_modules/@sinonjs/commons": {
"version": "1.8.5", "version": "1.8.5",
@ -4107,38 +4122,9 @@
} }
}, },
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "2.1.1",
"license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.11.0", "version": "8.11.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0", "json-schema-traverse": "^1.0.0",
@ -4150,9 +4136,21 @@
"url": "https://github.com/sponsors/epoberezkin" "url": "https://github.com/sponsors/epoberezkin"
} }
}, },
"node_modules/ajv-formats/node_modules/json-schema-traverse": { "node_modules/ajv-formats": {
"version": "1.0.0", "version": "2.1.1",
"license": "MIT" "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": { "node_modules/ansi-colors": {
"version": "4.1.3", "version": "4.1.3",
@ -6607,6 +6605,22 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/eslint/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/eslint/node_modules/eslint-utils": { "node_modules/eslint/node_modules/eslint-utils": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
@ -6634,6 +6648,12 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/espree": { "node_modules/espree": {
"version": "9.4.1", "version": "9.4.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
@ -6952,24 +6972,6 @@
"rfdc": "^1.2.0" "rfdc": "^1.2.0"
} }
}, },
"node_modules/fast-json-stringify/node_modules/ajv": {
"version": "8.11.0",
"license": "MIT",
"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/fast-json-stringify/node_modules/json-schema-traverse": {
"version": "1.0.0",
"license": "MIT"
},
"node_modules/fast-levenshtein": { "node_modules/fast-levenshtein": {
"version": "2.0.6", "version": "2.0.6",
"dev": true, "dev": true,
@ -10080,9 +10082,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "1.0.0",
"dev": true, "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"license": "MIT" "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
}, },
"node_modules/json-stable-stringify-without-jsonify": { "node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1", "version": "1.0.1",
@ -15544,6 +15546,26 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"minimatch": "^3.1.2", "minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1" "strip-json-comments": "^3.1.1"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
}
} }
}, },
"@fast-csv/format": { "@fast-csv/format": {
@ -15598,20 +15620,6 @@
"ajv": "^8.11.0", "ajv": "^8.11.0",
"ajv-formats": "^2.1.1", "ajv-formats": "^2.1.1",
"fast-uri": "^2.0.0" "fast-uri": "^2.0.0"
},
"dependencies": {
"ajv": {
"version": "8.11.0",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "1.0.0"
}
} }
}, },
"@fastify/deepmerge": { "@fastify/deepmerge": {
@ -16839,10 +16847,16 @@
"@protobufjs/utf8": { "@protobufjs/utf8": {
"version": "1.1.0" "version": "1.1.0"
}, },
"@segment/ajv-human-errors": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@segment/ajv-human-errors/-/ajv-human-errors-2.1.2.tgz",
"integrity": "sha512-d1uQndRFLRO01+xW1y5m+joxDgHf5SLJ70YCY2ArLoo2FJ50o6AoX2mEbuGvnKz/IdwnvDugm9Ti3mZQkW1OoA==",
"requires": {}
},
"@sinclair/typebox": { "@sinclair/typebox": {
"version": "0.24.49", "version": "0.25.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.49.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.8.tgz",
"integrity": "sha512-qWgJVeCThMWTJSAZHyHDlHBkJY0LARFoq/96RH4oIFAwJptMwB3Isq62c4zRVRIAF2r4RMOc2WOhtOLj5C4InA==" "integrity": "sha512-/7GXgMOCfnxpIov52cPetqQ7bxRBaTBJAgp04Se2UQB1J0vUfEOIMpn63cLc3S5JXDUflCWxELKDV8eiPpmUTQ=="
}, },
"@sinonjs/commons": { "@sinonjs/commons": {
"version": "1.8.5", "version": "1.8.5",
@ -17338,33 +17352,22 @@
} }
}, },
"ajv": { "ajv": {
"version": "6.12.6", "version": "8.11.0",
"dev": true, "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^1.0.0",
"json-schema-traverse": "^0.4.1", "require-from-string": "^2.0.2",
"uri-js": "^4.2.2" "uri-js": "^4.2.2"
} }
}, },
"ajv-formats": { "ajv-formats": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"requires": { "requires": {
"ajv": "^8.0.0" "ajv": "^8.0.0"
},
"dependencies": {
"ajv": {
"version": "8.11.0",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "1.0.0"
}
} }
}, },
"ansi-colors": { "ansi-colors": {
@ -18715,6 +18718,18 @@
"v8-compile-cache": "^2.0.3" "v8-compile-cache": "^2.0.3"
}, },
"dependencies": { "dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"eslint-utils": { "eslint-utils": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
@ -18731,6 +18746,12 @@
"dev": true "dev": true
} }
} }
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
} }
} }
}, },
@ -19139,20 +19160,6 @@
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"fast-uri": "^2.1.0", "fast-uri": "^2.1.0",
"rfdc": "^1.2.0" "rfdc": "^1.2.0"
},
"dependencies": {
"ajv": {
"version": "8.11.0",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "1.0.0"
}
} }
}, },
"fast-levenshtein": { "fast-levenshtein": {
@ -21282,8 +21289,9 @@
"dev": true "dev": true
}, },
"json-schema-traverse": { "json-schema-traverse": {
"version": "0.4.1", "version": "1.0.0",
"dev": true "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
}, },
"json-stable-stringify-without-jsonify": { "json-stable-stringify-without-jsonify": {
"version": "1.0.1", "version": "1.0.1",

View File

@ -75,7 +75,10 @@
"@nlpjs/core-loader": "^4.22.7", "@nlpjs/core-loader": "^4.22.7",
"@nlpjs/lang-all": "^4.22.12", "@nlpjs/lang-all": "^4.22.12",
"@nlpjs/nlp": "^4.22.17", "@nlpjs/nlp": "^4.22.17",
"@sinclair/typebox": "0.24.49", "@segment/ajv-human-errors": "2.1.2",
"@sinclair/typebox": "0.25.8",
"ajv": "8.11.0",
"ajv-formats": "2.1.1",
"archiver": "^5.3.1", "archiver": "^5.3.1",
"async": "^3.2.0", "async": "^3.2.0",
"axios": "1.1.2", "axios": "1.1.2",

25
server/src/ajv.ts Normal file
View File

@ -0,0 +1,25 @@
import addFormats from 'ajv-formats'
import Ajv from 'ajv'
export const ajv = addFormats(
new Ajv({
allErrors: true,
verbose: true
}),
[
'date-time',
'time',
'date',
'email',
'hostname',
'ipv4',
'ipv6',
'uri',
'uri-reference',
'uuid',
'uri-template',
'json-pointer',
'relative-json-pointer',
'regex'
]
)

View File

@ -1,8 +1,9 @@
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import { TypeCompiler } from '@sinclair/typebox/compiler' import { AggregateAjvError } from '@segment/ajv-human-errors'
import { ajv } from '@/ajv'
import { import {
amazonVoiceConfiguration, amazonVoiceConfiguration,
googleCloudVoiceConfiguration, googleCloudVoiceConfiguration,
@ -31,6 +32,27 @@ import { SkillDomainHelper } from '@/helpers/skill-domain-helper'
import { VOICE_CONFIG_PATH, GLOBAL_DATA_PATH } from '@/constants' import { VOICE_CONFIG_PATH, GLOBAL_DATA_PATH } from '@/constants'
import { getGlobalEntitiesPath, getGlobalResolversPath } from '@/utilities' import { getGlobalEntitiesPath, getGlobalResolversPath } from '@/utilities'
interface ObjectUnknown {
[key: string]: unknown
}
const validateSchema = (
schema: ObjectUnknown,
contentToValidate: ObjectUnknown,
customErrorMesage: string
): void => {
const validate = ajv.compile(schema)
const isValid = validate(contentToValidate)
if (!isValid) {
LogHelper.error(customErrorMesage)
const errors = new AggregateAjvError(validate.errors ?? [])
for (const error of errors) {
LogHelper.error(error.message)
}
process.exit(1)
}
}
/** /**
* Pre-checking * Pre-checking
* Ensure JSON files are correctly formatted * Ensure JSON files are correctly formatted
@ -57,21 +79,19 @@ const GLOBAL_DATA_SCHEMAS = {
const voiceConfigFiles = ( const voiceConfigFiles = (
await fs.promises.readdir(VOICE_CONFIG_PATH) await fs.promises.readdir(VOICE_CONFIG_PATH)
).filter((file) => file.endsWith('.json') && !file.includes('.sample.')) ).filter((file) => file.endsWith('.json'))
for (const file of voiceConfigFiles) { for (const file of voiceConfigFiles) {
const voiceConfigPath = path.join(VOICE_CONFIG_PATH, file)
const config: VoiceConfiguration = JSON.parse( const config: VoiceConfiguration = JSON.parse(
await fs.promises.readFile(path.join(VOICE_CONFIG_PATH, file), 'utf8') await fs.promises.readFile(voiceConfigPath, 'utf8')
) )
const [configName] = file.split('.') as [keyof typeof VOICE_CONFIG_SCHEMAS] const [configName] = file.split('.') as [keyof typeof VOICE_CONFIG_SCHEMAS]
const result = TypeCompiler.Compile(VOICE_CONFIG_SCHEMAS[configName]) validateSchema(
const errors = [...result.Errors(config)] VOICE_CONFIG_SCHEMAS[configName],
config,
if (errors.length > 0) { `The voice configuration schema "${voiceConfigPath}" is not valid:`
LogHelper.error(`The voice configuration schema "${file}" is not valid:`) )
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
} }
LogHelper.success('Voice configuration schemas checked') LogHelper.success('Voice configuration schemas checked')
@ -91,17 +111,15 @@ const GLOBAL_DATA_SCHEMAS = {
).filter((file) => file.endsWith('.json')) ).filter((file) => file.endsWith('.json'))
for (const file of globalEntityFiles) { for (const file of globalEntityFiles) {
const globalEntityPath = path.join(globalEntitiesPath, file)
const globalEntity: GlobalEntity = JSON.parse( const globalEntity: GlobalEntity = JSON.parse(
await fs.promises.readFile(path.join(globalEntitiesPath, file), 'utf8') await fs.promises.readFile(globalEntityPath, 'utf8')
)
validateSchema(
globalEntitySchemaObject,
globalEntity,
`The global entity schema "${globalEntityPath}" is not valid:`
) )
const result = TypeCompiler.Compile(globalEntitySchemaObject)
const errors = [...result.Errors(globalEntity)]
if (errors.length > 0) {
LogHelper.error(`The global entity schema "${file}" is not valid:`)
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
} }
/** /**
@ -113,37 +131,32 @@ const GLOBAL_DATA_SCHEMAS = {
).filter((file) => file.endsWith('.json')) ).filter((file) => file.endsWith('.json'))
for (const file of globalResolverFiles) { for (const file of globalResolverFiles) {
const globalResolverPath = path.join(globalResolversPath, file)
const globalResolver: GlobalResolver = JSON.parse( const globalResolver: GlobalResolver = JSON.parse(
await fs.promises.readFile(path.join(globalResolversPath, file), 'utf8') await fs.promises.readFile(globalResolverPath, 'utf8')
)
validateSchema(
globalResolverSchemaObject,
globalResolver,
`The global resolver schema "${globalResolverPath}" is not valid:`
) )
const result = TypeCompiler.Compile(globalResolverSchemaObject)
const errors = [...result.Errors(globalResolver)]
if (errors.length > 0) {
LogHelper.error(`The global resolver schema "${file}" is not valid:`)
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
} }
/** /**
* Global answers checking * Global answers checking
*/ */
const globalAnswersPath = path.join(GLOBAL_DATA_PATH, lang, 'answers.json')
const answers: GlobalAnswers = JSON.parse( const answers: GlobalAnswers = JSON.parse(
await fs.promises.readFile( await fs.promises.readFile(
path.join(GLOBAL_DATA_PATH, lang, 'answers.json'), globalAnswersPath,
'utf8' 'utf8'
) )
) )
const result = TypeCompiler.Compile(GLOBAL_DATA_SCHEMAS.answers) validateSchema(
GLOBAL_DATA_SCHEMAS.answers,
const errors = [...result.Errors(answers)] answers,
`The global answers schema "${globalAnswersPath}" is not valid:`
if (errors.length > 0) { )
LogHelper.error(`The global answers schema "answers.json" is not valid:`)
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
} }
LogHelper.success('Global data schemas checked') LogHelper.success('Global data schemas checked')
@ -162,14 +175,11 @@ const GLOBAL_DATA_SCHEMAS = {
const domainObject: Domain = JSON.parse( const domainObject: Domain = JSON.parse(
await fs.promises.readFile(pathToDomain, 'utf8') await fs.promises.readFile(pathToDomain, 'utf8')
) )
const domainResult = TypeCompiler.Compile(domainSchemaObject) validateSchema(
const domainErrors = [...domainResult.Errors(domainObject)] domainSchemaObject,
domainObject,
if (domainErrors.length > 0) { `The domain schema "${pathToDomain}" is not valid:`
LogHelper.error(`The domain schema "${pathToDomain}" is not valid:`) )
LogHelper.error(JSON.stringify(domainErrors, null, 2))
process.exit(1)
}
const skillKeys = Object.keys(currentDomain.skills) const skillKeys = Object.keys(currentDomain.skills)
@ -184,14 +194,11 @@ const GLOBAL_DATA_SCHEMAS = {
const skillObject: Skill = JSON.parse( const skillObject: Skill = JSON.parse(
await fs.promises.readFile(pathToSkill, 'utf8') await fs.promises.readFile(pathToSkill, 'utf8')
) )
const skillResult = TypeCompiler.Compile(skillSchemaObject) validateSchema(
const skillErrors = [...skillResult.Errors(skillObject)] skillSchemaObject,
skillObject,
if (skillErrors.length > 0) { `The skill schema "${pathToSkill}" is not valid:`
LogHelper.error(`The skill schema "${pathToSkill}" is not valid:`) )
LogHelper.error(JSON.stringify(skillErrors, null, 2))
process.exit(1)
}
/** /**
* Skills config checking * Skills config checking
@ -206,16 +213,11 @@ const GLOBAL_DATA_SCHEMAS = {
const skillConfig: SkillConfig = JSON.parse( const skillConfig: SkillConfig = JSON.parse(
await fs.promises.readFile(skillConfigPath, 'utf8') await fs.promises.readFile(skillConfigPath, 'utf8')
) )
const result = TypeCompiler.Compile(skillConfigSchemaObject) validateSchema(
const errors = [...result.Errors(skillConfig)] skillConfigSchemaObject,
skillConfig,
if (errors.length > 0) { `The skill config schema "${skillConfigPath}" is not valid:`
LogHelper.error( )
`The skill config schema "${skillConfigPath}" is not valid:`
)
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
} }
} }
} }

View File

@ -1,34 +1,57 @@
import type { Static } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
export const globalEntitySchemaObject = Type.Object({ export const globalEntitySchemaObject = Type.Strict(
options: Type.Record( Type.Object(
Type.String(), {
Type.Object({ options: Type.Record(
synonyms: Type.Array(Type.String()), Type.String(),
data: Type.Optional(Type.Record(Type.String(), Type.Array(Type.String()))) Type.Object(
}) {
synonyms: Type.Array(Type.String()),
data: Type.Optional(
Type.Record(Type.String(), Type.Array(Type.String()))
)
},
{ additionalProperties: false }
)
)
},
{ additionalProperties: false }
) )
}) )
export const globalResolverSchemaObject = Type.Object({ export const globalResolverSchemaObject = Type.Strict(
name: Type.String(), Type.Object(
intents: Type.Record( {
Type.String(), name: Type.String(),
Type.Object({ intents: Type.Record(
utterance_samples: Type.Array(Type.String()), Type.String(),
value: Type.Unknown() Type.Object(
}) {
utterance_samples: Type.Array(Type.String()),
value: Type.Unknown()
},
{ additionalProperties: false }
)
)
},
{ additionalProperties: false }
) )
}) )
export const globalAnswersSchemaObject = Type.Object({ export const globalAnswersSchemaObject = Type.Strict(
answers: Type.Record( Type.Object(
Type.String(), {
Type.Union([ answers: Type.Record(
Type.Record(Type.String(), Type.String()), Type.String(),
Type.Array(Type.String()) Type.Union([
]) Type.Record(Type.String(), Type.String()),
Type.Array(Type.String())
])
)
},
{ additionalProperties: false }
) )
}) )
export type GlobalEntity = Static<typeof globalEntitySchemaObject> export type GlobalEntity = Static<typeof globalEntitySchemaObject>
export type GlobalResolver = Static<typeof globalResolverSchemaObject> export type GlobalResolver = Static<typeof globalResolverSchemaObject>

View File

@ -1,6 +1,8 @@
import type { Static } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
import { globalResolverSchemaObject } from '@/schemas/global-data-schemas'
const skillBridges = [Type.Literal('python'), Type.Null()] const skillBridges = [Type.Literal('python'), Type.Null()]
const skillActionTypes = [Type.Literal('logic'), Type.Literal('dialog')] const skillActionTypes = [Type.Literal('logic'), Type.Literal('dialog')]
const skillDataTypes = [ const skillDataTypes = [
@ -10,109 +12,184 @@ const skillDataTypes = [
] ]
const skillCustomEntityTypes = [ const skillCustomEntityTypes = [
Type.Array( Type.Array(
Type.Object({ Type.Object(
type: Type.Literal('trim'), {
name: Type.String({ minLength: 1 }), type: Type.Literal('trim'),
conditions: Type.Array( name: Type.String({ minLength: 1 }),
Type.Object({ conditions: Type.Array(
type: Type.Union([ Type.Object(
Type.Literal('between'), {
Type.Literal('after'), type: Type.Union([
Type.Literal('after_first'), Type.Literal('between'),
Type.Literal('after_last'), Type.Literal('after'),
Type.Literal('before'), Type.Literal('after_first'),
Type.Literal('before_first'), Type.Literal('after_last'),
Type.Literal('before_last') Type.Literal('before'),
]), Type.Literal('before_first'),
from: Type.Optional( Type.Literal('before_last')
Type.Union([ ]),
Type.Array(Type.String({ minLength: 1 })), from: Type.Optional(
Type.String({ minLength: 1 }) Type.Union([
]) Type.Array(Type.String({ minLength: 1 })),
), Type.String({ minLength: 1 })
to: Type.Optional( ])
Type.Union([ ),
Type.Array(Type.String({ minLength: 1 })), to: Type.Optional(
Type.String({ minLength: 1 }) Type.Union([
]) Type.Array(Type.String({ minLength: 1 })),
Type.String({ minLength: 1 })
])
)
},
{ additionalProperties: false }
) )
}) )
) },
}) { additionalProperties: false }
)
), ),
Type.Array( Type.Array(
Type.Object({ Type.Object(
type: Type.Literal('regex'), {
name: Type.String({ minLength: 1 }), type: Type.Literal('regex'),
regex: Type.String({ minLength: 1 }) name: Type.String({ minLength: 1 }),
}) regex: Type.String({ minLength: 1 })
},
{ additionalProperties: false }
)
), ),
Type.Array( Type.Array(
Type.Object({ Type.Object(
type: Type.Literal('enum'), {
name: Type.String(), type: Type.Literal('enum'),
options: Type.Record( name: Type.String(),
Type.String({ minLength: 1 }), options: Type.Record(
Type.Object({ Type.String({ minLength: 1 }),
synonyms: Type.Array(Type.String({ minLength: 1 })) Type.Object({
}) synonyms: Type.Array(Type.String({ minLength: 1 }))
) })
}) )
},
{ additionalProperties: false }
)
) )
] ]
export const domainSchemaObject = Type.Object({ export const domainSchemaObject = Type.Strict(
name: Type.String({ minLength: 1 }) Type.Object(
}) {
export const skillSchemaObject = Type.Object({ name: Type.String({ minLength: 1 })
name: Type.String({ minLength: 1 }), },
bridge: Type.Union(skillBridges), { additionalProperties: false }
version: Type.String({ minLength: 1 }), )
description: Type.String({ minLength: 1 }), )
author: Type.Object({ export const skillSchemaObject = Type.Strict(
name: Type.String({ minLength: 1 }), Type.Object(
email: Type.Optional(Type.String({ minLength: 1, maxLength: 254 })), {
url: Type.Optional(Type.String({ minLength: 1, maxLength: 255 })) name: Type.String({ minLength: 1 }),
}) bridge: Type.Union(skillBridges),
}) version: Type.String({ minLength: 1 }),
export const skillConfigSchemaObject = Type.Object({ description: Type.String({ minLength: 1 }),
variables: Type.Optional(Type.Record(Type.String(), Type.String())), author: Type.Object(
actions: Type.Record( {
Type.String(), name: Type.String({ minLength: 1 }),
Type.Object({ email: Type.Optional(
type: Type.Union(skillActionTypes), Type.String({ minLength: 1, maxLength: 254, format: 'email' })
loop: Type.Optional( ),
Type.Object({ url: Type.Optional(
expected_item: Type.Object({ Type.String({ minLength: 1, maxLength: 255, format: 'uri' })
type: Type.Union(skillDataTypes), )
name: Type.String() },
}) { additionalProperties: false }
}) )
), },
utterance_samples: Type.Optional(Type.Array(Type.String())), { additionalProperties: false }
answers: Type.Optional(Type.Array(Type.String())), )
unknown_answers: Type.Optional(Type.Array(Type.String())), )
suggestions: Type.Optional(Type.Array(Type.String())), export const skillConfigSchemaObject = Type.Strict(
slots: Type.Optional( Type.Object(
Type.Array( {
Type.Object({ variables: Type.Optional(Type.Record(Type.String(), Type.String())),
name: Type.String(), actions: Type.Record(
item: Type.Object({ Type.String(),
type: Type.Union(skillDataTypes), Type.Object(
name: Type.String() {
}), type: Type.Union(skillActionTypes),
questions: Type.Array(Type.String()), loop: Type.Optional(
suggestions: Type.Optional(Type.Array(Type.String())) Type.Object(
}) {
expected_item: Type.Object({
type: Type.Union(skillDataTypes),
name: Type.String()
})
},
{ additionalProperties: false }
)
),
http_api: Type.Optional(
Type.Object(
{
entities: Type.Array(
Type.Object(
{
entity: Type.String(),
resolution: Type.Array(Type.String())
},
{ additionalProperties: false }
)
)
},
{ additionalProperties: false }
)
),
utterance_samples: Type.Optional(Type.Array(Type.String())),
answers: Type.Optional(Type.Array(Type.String())),
unknown_answers: Type.Optional(Type.Array(Type.String())),
suggestions: Type.Optional(Type.Array(Type.String())),
slots: Type.Optional(
Type.Array(
Type.Object(
{
name: Type.String(),
item: Type.Object(
{
type: Type.Union(skillDataTypes),
name: Type.String()
},
{ additionalProperties: false }
),
questions: Type.Array(Type.String()),
suggestions: Type.Optional(Type.Array(Type.String()))
},
{ additionalProperties: false }
)
)
),
entities: Type.Optional(Type.Union(skillCustomEntityTypes)),
next_action: Type.Optional(Type.String())
},
{ additionalProperties: false }
) )
), ),
entities: Type.Optional(Type.Union(skillCustomEntityTypes)), answers: Type.Optional(
next_action: Type.Optional(Type.String()) Type.Record(Type.String(), Type.Array(Type.String()))
}) ),
), entities: Type.Optional(Type.Record(Type.String(), Type.String())),
answers: Type.Optional(Type.Record(Type.String(), Type.Array(Type.String()))), resolvers: Type.Optional(
entities: Type.Optional(Type.Record(Type.String(), Type.String())) Type.Record(
}) Type.String(),
Type.Object(
{
intents: globalResolverSchemaObject.properties.intents
},
{ additionalProperties: false }
)
)
)
},
{ additionalProperties: false }
)
)
export type Domain = Static<typeof domainSchemaObject> export type Domain = Static<typeof domainSchemaObject>
export type Skill = Static<typeof skillSchemaObject> export type Skill = Static<typeof skillSchemaObject>

View File

@ -1,29 +1,44 @@
import type { Static } from '@sinclair/typebox' import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox' import { Type } from '@sinclair/typebox'
export const amazonVoiceConfiguration = Type.Object({ export const amazonVoiceConfiguration = Type.Strict(
credentials: Type.Object({ Type.Object(
accessKeyId: Type.String(), {
secretAccessKey: Type.String() credentials: Type.Object({
}), accessKeyId: Type.String(),
region: Type.String() secretAccessKey: Type.String()
}) }),
export const googleCloudVoiceConfiguration = Type.Object({ region: Type.String()
type: Type.Literal('service_account'), },
project_id: Type.String(), { additionalProperties: false }
private_key_id: Type.String(), )
private_key: Type.String(), )
client_email: Type.String(), export const googleCloudVoiceConfiguration = Type.Strict(
client_id: Type.String(), Type.Object(
auth_uri: Type.String(), {
token_uri: Type.String(), type: Type.Literal('service_account'),
auth_provider_x509_cert_url: Type.String(), project_id: Type.String(),
client_x509_cert_url: Type.String() private_key_id: Type.String(),
}) private_key: Type.String(),
export const watsonVoiceConfiguration = Type.Object({ client_email: Type.String({ format: 'email' }),
apikey: Type.String(), client_id: Type.String(),
url: Type.String() auth_uri: Type.String({ format: 'uri' }),
}) token_uri: Type.String({ format: 'uri' }),
auth_provider_x509_cert_url: Type.String({ format: 'uri' }),
client_x509_cert_url: Type.String({ format: 'uri' })
},
{ additionalProperties: false }
)
)
export const watsonVoiceConfiguration = Type.Strict(
Type.Object(
{
apikey: Type.String(),
url: Type.String({ format: 'uri' })
},
{ additionalProperties: false }
)
)
export type AmazonVoiceConfiguration = Static<typeof amazonVoiceConfiguration> export type AmazonVoiceConfiguration = Static<typeof amazonVoiceConfiguration>
export type GoogleCloudVoiceConfiguration = Static< export type GoogleCloudVoiceConfiguration = Static<

View File

@ -1,90 +0,0 @@
import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
const domainSchema = {
name: Type.String({ minLength: 1 })
}
const skillBridges = [Type.Literal('python')]
const skillSchema = {
name: Type.String({ minLength: 1 }),
bridge: Type.Union(skillBridges),
version: Type.String({ minLength: 1 }),
description: Type.String({ minLength: 1 }),
author: Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ minLength: 1, maxLength: 254, format: 'email' }),
url: Type.String({ minLength: 1, maxLength: 255, format: 'uri' })
})
}
const skillActionTypes = [Type.Literal('logic'), Type.Literal('dialog')]
const skillDataTypes = [
Type.Literal('skill_resolver'),
Type.Literal('global_resolver'),
Type.Literal('entity')
]
const skillConfigSchema = {
variables: Type.Optional(Type.Record(Type.String(), Type.String())),
actions: Type.Record(
Type.String(),
Type.Object({
type: Type.Union(skillActionTypes),
loop: Type.Optional(
Type.Object({
expected_item: Type.Object({
type: Type.Union(skillDataTypes),
name: Type.String()
})
})
),
utterance_samples: Type.Optional(Type.Array(Type.String())),
answers: Type.Optional(Type.Array(Type.String())),
unknown_answers: Type.Optional(Type.Array(Type.String())),
suggestions: Type.Optional(Type.Array(Type.String())),
slots: Type.Optional(
Type.Array(
Type.Object({
name: Type.String(),
item: Type.Object({
type: Type.Union(skillDataTypes),
name: Type.String()
}),
questions: Type.Array(Type.String()),
suggestions: Type.Optional(Type.Array(Type.String()))
})
)
),
entities: Type.Optional(
Type.Array(
Type.Object({
type: Type.Literal('enum'),
name: Type.String(),
options: Type.Record(
Type.String(),
Type.Object({
synonyms: Type.Array(Type.String())
})
)
})
)
),
next_action: Type.Optional(Type.String())
})
),
answers: Type.Optional(Type.Record(Type.String(), Type.Array(Type.String()))),
entities: Type.Optional(Type.Record(Type.String(), Type.String()))
}
const domainSchemaObject = Type.Strict(
Type.Object(domainSchema, { additionalProperties: false })
)
const skillSchemaObject = Type.Strict(
Type.Object(skillSchema, { additionalProperties: false })
)
const skillConfigSchemaObject = Type.Strict(
Type.Object(skillConfigSchema, { additionalProperties: false })
)
export type Domain = Static<typeof domainSchemaObject>
export type Skill = Static<typeof skillSchemaObject>
export type SkillConfig = Static<typeof skillConfigSchemaObject>
export type SkillBridge = Static<typeof skillSchema.bridge>