# Overview
The interfaces and schemas for each sub-packet can be found in `src/schemas`. The schemas are imported into `types.ts` and the overall JSONSchemaType as well as the sub-types are created, as are the validation methods. Validation methods are used to type-guard incoming messages, while the interfaces are used to type-guard at development time. This repository is configured as an `npm` package -- while under local development you can use `npm instal --save` along with the path to this repo to use it with the device agent.

# Naming Conventions
Below is an outline of the general interface structure.

const message: xxxxMessage = {
  ....,
  messageType: [`DeviceAgentMessageTypeValue` or `ClientMessageTypeValue`],
  payload: xxxxMessagePayload : {
    [type lowercase]: [type is the mirror of the key (uppercase)]
  }
}


## Upper Level
The upper level content has the suffix `Message`. This is in keeping with the convention of having the content delivered on MQTT be the 'message'.

## Message Payload
Each of the `Message` packets have a 'payload', and these sub-packets are one of the following for `DeviceAgentMessage`:
  - AppStateControlPayload
  - AppVersionControlPayload
  - LiveUpdatesTogglePayload
  - AppInstallCloudResponsePayload
  - ModelsInstallCloudResponsePayload

For `ClientMessage`, the 'payload' field must be one of the following:
  - AppLogsPayload
  - StatusResponsePayload
  - AppStatePayload
  - DeviceStatsPayload
  - SignedUrlsRequestPayload

For all packets, there is a `messageType` field, whose entry helps identify the contents. This field can be used to distinguish which message has been sent, such as in a conditional block. For `ClientMessage`packets, this field is one of `ClientMessageTypeValue` entries and for `DeviceAgentMessage` packets, it is one of `DeviceAgentMessageTypeValue` entries.

## Message Payload Contents
Additionally, each 'payload' object contains a key that corresponds to the actual useful contents of the 'payload'. The name of the key indicates what type of contents these are.

For instance, the `AppLogsPayload` type has a `messageType`, which is of type `app_logs`, as well as a key `appLogs`, which is of type `AppLogs`.

# To Create New Schemas and Validation Methods
To create a new message type, create a new `.ts` file in `src/schemas` and create the sub-schema and a corresponding `interface`. If you are using patterns or enums, you can add these to the `src/constants` file and import them as needed. You can them import the schemas into `types.ts` and modify the main schema's `payload` property's `oneOf` list to include the new type. You can then compile a new validation method against the new schema using your newly defined type. You may need to add the schema to the `ajv` instance, however this shouldn't be necessary if the main schema is referencing the new sub-schema. More notes on the development of this repo can be found here: https://alwaysai.atlassian.net/l/c/RdjVyG1d

# Development Practices
There are a few development practices we have been using to help keep the schema components modular and re-usable, as well as easy to change.
  1. Look in `src/constants.ts` and `src/schemas/common-schema.ts` to see if there are any components to re-use, such as the `projectId` schema or `releaseHash` schema. If you develop a component that could be used across multiple schemas, please put it in `src/schemas/common-schema.ts` and then use `$ref` to incorporate it into larger schemas.
  2. If you develop a schema that requires a pattern, you can define the pattern in `src/constants.ts`.
  2. If you develop a schema that uses a string, check that the string doesn't need to be more explicitly defined (e.g. with a pattern) and doesn't need to be a re-usable definition.
  3. Try to create Enum or KeyMirror variables when creating a schema that has a property that can take on one of a set of specific values. Examples of this can be seen in `src/constants.ts` -- the idea is that if the acceptable values change, we only need to change them in one place, and then we can use this set to make a list as well as create the development type guards.
  3. Create positive and negative examples of data for any new schemas you create, and add these to the tests in `src/types.test.ts`.

# To Run Tests
Run `npm run test:unit` to run the `types.test.ts` file. You can add valid and invalid data structures to the `validData` and `invalidData` variables and these will be checked as positive and negative tests, respectively.
