Data Modeling

Vendia automatically converts your JSON Schema-based data model into a production-grade, distributed application composed of serverless public cloud resources, complete with GraphQL APIs.

Much as a conventional database turns a CREATE_TABLE definition into a single, centralized database, Vendia turns your JSON Schema-based data model into a distributed, decentralized database...and all the platform services needed to make that data available and useful in the cloud.

This guide explains how JSON schema types are converted into the GraphQL types that each node in your Uni can access, update, and subscribe to.

Defining "Entities"

Each key/value pair in the top-level "properties" object of your JSON schema will be converted into a Vendia "Entity". Here's an example of a minimal JSON schema with a single entity called "Product":

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://vendia.net/schemas/sample.schema.json",
  "title": "Acme Uni Schema",
  "description": "Defines data model for Acme's Product Uni",
  "type": "object",
  "properties": {
    "Product": {   <------------------------------ This is an entity!
      "description": "Acme Product Line",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {   <--------------------------- This is a field on the Product entity
            "type" : "string"
          }
        }
      }
    }
  }
}   

Entities are special in Vendia Share. Entities receive their own GraphQL APIs for CRUD operations (e.g. addProduct, getProduct, listProductItems) and entities of type "array" can be indexed to enable efficient data retrieval (More on indexes below).

Only immediate children of your JSON schema's top-level "properties" object will be converted to Entities. In the example above, the Product entity is an "array" type with items of type "object" - the object definition, in turn, has a "name" field.

Entity fields can contain complex data types (e.g. "object", "array") in addition to scalar data types (e.g. "string", "number", "integer"), but arrays of data nested within entities will not have their own APIs and cannot be indexed. Data models that require large amounts of total storage or very high cardinality for nested arrays should be rewritten to make these appear as top-level Entity arrays in order to expose them to indexing and sharding in the database.

About scalar types

Strings, numbers, booleans... We often refer to these fundamental data types as "scalars". Your Uni's data model might be organized into any number of entities and these entities might consist of complex structures of nested arrays and objects, but ultimately you're going to need scalar types to store meaningful data.

Vendia Share supports any scalar type that can be defined in JSON schema.

Some notes on the particulars:

  • Strings, booleans, and integers convert directly to the corresponding GraphQL types.
  • JSON Schema number types will convert to GraphQL float types.
  • Floats have a precision of 16 decimal places and a range of 2.2250738585072014e-308 to 1.7976931348623157e+308.
  • The allowable range for integer types is -2147483648 through 2147483647.
  • JSON Schema date, time, and datetimes types will convert to system-specific scalars in GraphQL (e.g., AWS_DATE for GraphQL queries made on AWS).

Uni JSON schema example

The following is a sample data model for a product catalog and orders, written as JSON schema

Sample Uni schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://vendia.net/schemas/sample.schema.json",
  "title": "Acme Uni Schema",
  "description": "Defines data model for Acme's Product Uni",
  "type": "object",
  "properties": {
    "ContactInfo": {
      "type": "object",
      "description": "Global setting that records general purpose contact info for the chain as a whole",
      "properties": {
        "addressLine1": {
          "type": "string"
        },
        "addressLine2": {
          "type": "string"
        },
        "city": {
          "type": "string"
        },
        "state": {
          "type": "string"
        },
        "zipCode": {
          "type": "string"
        }
      }
    },
    "Participant": {
      "description": "Blockchain participant names",
      "type": "array",
      "items": {
        "type": "string"
      },
      "minItems": 1,
      "uniqueItems": true
    },
    "Order": {
      "description": "3b11 replenishment order",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "orderId": {
            "description": "The unique identifier for an order",
            "type": "string"
          },
          "owner": {
            "description": "Role who initially authorized order",
            "type": "string"
          },
          "creationTimestamp": {
            "description": "Timestamp when order was initially created",
            "type": "string"
          },
          "orderContent": {
            "description": "Product IDs in this order",
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "uniqueItems": true
          }
        },
        "required": [
          "orderId",
          "owner",
          "creationTimestamp",
          "orderContent"
        ]
      },
      "minItems": 0,
      "uniqueItems": true
    },
    "ShipmentMessage": {
      "description": "Update to shipping status of an order",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "orderId": {
            "description": "The unique identifier for all messages related to this shipment",
            "type": "string"
          },
          "carrier": {
            "description": "The carrier handling the shipment",
            "type": "string"
          },
          "timestamp": {
            "description": "The arrival time",
            "type": "string"
          },
          "fromAddress": {
            "description": "Origin for this shipment update",
            "type": "object",
            "properties": {
              "isInitial": {
                "type": "boolean"
              },
              "contact": {
                "type": "string"
              },
              "streetAddress": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "postalCode": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            },
            "required": [
              "streetAddress",
              "city",
              "country"
            ]
          },
          "toAddress": {
            "description": "Destination for this shipment update",
            "type": "object",
            "properties": {
              "isFinal": {
                "type": "boolean"
              },
              "contact": {
                "type": "string"
              },
              "streetAddress": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "postalCode": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            },
            "required": [
              "streetAddress",
              "city",
              "country"
            ]
          }
        },
        "required": [
          "orderId",
          "carrier",
          "timestamp",
          "fromAddress",
          "toAddress"
        ]
      },
      "minItems": 0,
      "uniqueItems": true
    },
    "Product": {
      "description": "Acme Product Line",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "productId": {
            "description": "The unique identifier for a product",
            "type": "integer"
          },
          "productName": {
            "description": "Name of the product",
            "type": "string"
          },
          "price": {
            "description": "The price of the product",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "tags": {
            "description": "Tags for the product",
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "uniqueItems": true
          },
          "dimensions": {
            "type": "object",
            "properties": {
              "length": {
                "type": "number"
              },
              "width": {
                "type": "number"
              },
              "height": {
                "type": "number"
              }
            },
            "required": [
              "length",
              "width"
            ]
          },
          "sales": {
            "description": "Sales for the product",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "start": {
                  "type": "string"
                },
                "end": {
                  "type": "string"
                },
                "discountPercent": {
                  "type": "number"
                }
              },
              "required": [
                "start",
                "end"
              ]
            }
          }
        },
        "required": [
          "productId",
          "productName",
          "price"
        ]
      },
      "minItems": 1,
      "uniqueItems": true
    }
  },
  "required": [
    "ContactInfo",
    "Participant",
    "Product"
  ]
}

Vendia Share will convert the schema above into a GraphQL representation similar to the one below. (Note that this is representative only; details of this translation may vary based on your registration, settings, etc.)

Representative generated GraphQL schema
enum ModelAttributeTypes {
    binary
    binarySet
    bool
    list
    map
    number
    numberSet
    string
    stringSet
    _null
}
input ModelBooleanInput {
    ne: Boolean
    eq: Boolean
    attributeExists: Boolean
    attributeType: ModelAttributeTypes
}
input ModelFloatInput {
    ne: Float
    eq: Float
    le: Float
    lt: Float
    ge: Float
    gt: Float
    between: [Float]
    attributeExists: Boolean
    attributeType: ModelAttributeTypes
}
input ModelIDInput {
    ne: ID
    eq: ID
    attributeExists: Boolean
    attributeType: ModelAttributeTypes
}
input ModelIntInput {
    ne: Int
    eq: Int
    le: Int
    lt: Int
    ge: Int
    gt: Int
    between: [Int]
    attributeExists: Boolean
    attributeType: ModelAttributeTypes
}
input ModelSizeInput {
    ne: Int
    eq: Int
    le: Int
    lt: Int
    ge: Int
    gt: Int
    between: [Int]
}
enum ModelSortDirection {
    ASC
    DESC
}
input ModelStringInput {
    ne: String
    eq: String
    le: String
    lt: String
    ge: String
    gt: String
    contains: String
    notContains: String
    between: [String]
    beginsWith: String
    attributeExists: Boolean
    attributeType: ModelAttributeTypes
    size: ModelSizeInput
}
type Transaction {
    tx_id: String!
    tx_version: String!
    submission_time: String!
    node_owner: String!
}

type Transaction_Result   {
    error: String
    result: Transaction
}
input ContactInfoConditionInput {
    addressLine1: ModelStringInput
    addressLine2: ModelStringInput
    city: ModelStringInput
    state: ModelStringInput
    zipCode: ModelStringInput
    and: [ContactInfoConditionInput]
    or: [ContactInfoConditionInput]
    not: ContactInfoConditionInput
}
input ContactInfoFilterInput {
    addressLine1: ModelStringInput
    addressLine2: ModelStringInput
    city: ModelStringInput
    state: ModelStringInput
    zipCode: ModelStringInput
    and: [ContactInfoFilterInput]
    or: [ContactInfoFilterInput]
    not: ContactInfoFilterInput
}
type ContactInfo   {
    addressLine1: String
    addressLine2: String
    city: String
    state: String
    zipCode: String
}
type ContactInfo_Result   {
    error: String
    result: ContactInfo
}
input ContactInfoInput {
    addressLine1: String
    addressLine2: String
    city: String
    state: String
    zipCode: String
}
input ParticipantConditionInput {
    Participant: ModelStringInput
    and: [ParticipantConditionInput]
    or: [ParticipantConditionInput]
    not: ParticipantConditionInput
}
input ParticipantFilterInput {
    id: ModelIDInput
    Participant: ModelStringInput
    and: [ParticipantFilterInput]
    or: [ParticipantFilterInput]
    not: ParticipantFilterInput
}
type Participant {
    id:ID!
    Participant: String!
}
type Participant_Result {
    error: String
    result: Participant
}
type ParticipantConnection {
    Participants: [Participant]
    nextToken: String
}
input OrderConditionInput {
    orderId: ModelStringInput
    owner: ModelStringInput
    creationTimestamp: ModelStringInput
    orderContent: ModelStringInput
    and: [OrderConditionInput]
    or: [OrderConditionInput]
    not: OrderConditionInput
}
input OrderFilterInput {
    id: ModelIDInput
    orderId: ModelStringInput
    owner: ModelStringInput
    creationTimestamp: ModelStringInput
    orderContent: ModelStringInput
    and: [OrderFilterInput]
    or: [OrderFilterInput]
    not: OrderFilterInput
}
type Order   {
    id:ID!
    orderId: String!
    owner: String!
    creationTimestamp: String!
    orderContent: [String]!
}
type Order_Result   {
    error: String
    result: Order
}
type OrderConnection   {
    Orders: [Order]
    nextToken: String
}
input OrderInput {
    orderId: String!
    owner: String!
    creationTimestamp: String!
    orderContent: [String]!
}
input ShipmentMessageConditionInput {
    orderId: ModelStringInput
    carrier: ModelStringInput
    timestamp: ModelStringInput
    fromAddress: fromAddressConditionInput
    toAddress: toAddressConditionInput
    and: [ShipmentMessageConditionInput]
    or: [ShipmentMessageConditionInput]
    not: ShipmentMessageConditionInput
}
input ShipmentMessageFilterInput {
    id: ModelIDInput
    orderId: ModelStringInput
    carrier: ModelStringInput
    timestamp: ModelStringInput
    fromAddress: fromAddressFilterInput
    toAddress: toAddressFilterInput
    and: [ShipmentMessageFilterInput]
    or: [ShipmentMessageFilterInput]
    not: ShipmentMessageFilterInput
}
type ShipmentMessage   {
    id:ID!
    orderId: String!
    carrier: String!
    timestamp: String!
    fromAddress: fromAddress
    toAddress: toAddress
}
type ShipmentMessage_Result   {
    error: String
    result: ShipmentMessage
}
type ShipmentMessageConnection   {
    ShipmentMessages: [ShipmentMessage]
    nextToken: String
}
input ShipmentMessageInput {
    orderId: String!
    carrier: String!
    timestamp: String!
    fromAddress: fromAddressInput
    toAddress: toAddressInput
}
input ProductConditionInput {
    productId: ModelIntInput
    productName: ModelStringInput
    price: ModelFloatInput
    tags: ModelStringInput
    dimensions: dimensionsConditionInput
    sales_element: sales_elementConditionInput
    and: [ProductConditionInput]
    or: [ProductConditionInput]
    not: ProductConditionInput
}
input ProductFilterInput {
    id: ModelIDInput
    productId: ModelIntInput
    productName: ModelStringInput
    price: ModelFloatInput
    tags: ModelStringInput
    dimensions: dimensionsFilterInput
    sales_element: sales_elementFilterInput
    and: [ProductFilterInput]
    or: [ProductFilterInput]
    not: ProductFilterInput
}
type Product   {
    id:ID!
    productId: Int!
    productName: String!
    price: Float!
    tags: [String]
    dimensions: dimensions
    sales: [sales_element]
}
type Product_Result   {
    error: String
    result: Product
}
type ProductConnection   {
    Products: [Product]
    nextToken: String
}
input ProductInput {
    productId: Int!
    productName: String!
    price: Float!
    tags: [String]
    dimensions: dimensionsInput
    sales: [sales_elementInput]
}
input _BlockConditionInput {
    BlockId: ModelStringInput
    RedactedBlockHash: ModelStringInput
    PreviousBlockId: ModelStringInput
    PreviousRedactedBlockHash: ModelStringInput
    PreconditionsMet: ModelStringInput
    CommitTime: ModelStringInput
    _TX_element: _TX_elementConditionInput
    and: [_BlockConditionInput]
    or: [_BlockConditionInput]
    not: _BlockConditionInput
}
input _BlockFilterInput {
    id: ModelIDInput
    BlockId: ModelStringInput
    RedactedBlockHash: ModelStringInput
    PreviousBlockId: ModelStringInput
    PreviousRedactedBlockHash: ModelStringInput
    PreconditionsMet: ModelStringInput
    CommitTime: ModelStringInput
    _TX_element: _TX_elementFilterInput
    and: [_BlockFilterInput]
    or: [_BlockFilterInput]
    not: _BlockFilterInput
}
type _Block   {
    id:ID!
    BlockId: String!
    RedactedBlockHash: String!
    PreviousBlockId: String
    PreviousRedactedBlockHash: String
    PreconditionsMet: String
    CommitTime: String!
    _TX: [_TX_element]!
}
type _Block_Result   {
    error: String
    result: _Block
}
type _BlockConnection   {
    _Blocks: [_Block]
    nextToken: String
}
input _BlockInput {
    BlockId: String!
    RedactedBlockHash: String!
    PreviousBlockId: String
    PreviousRedactedBlockHash: String
    PreconditionsMet: String
    CommitTime: String!
    _TX: [_TX_elementInput]!
}
input _SecureMessageConditionInput {
    Participants: ModelStringInput
    Message: ModelStringInput
    and: [_SecureMessageConditionInput]
    or: [_SecureMessageConditionInput]
    not: _SecureMessageConditionInput
}
input _SecureMessageFilterInput {
    id: ModelIDInput
    Participants: ModelStringInput
    Message: ModelStringInput
    and: [_SecureMessageFilterInput]
    or: [_SecureMessageFilterInput]
    not: _SecureMessageFilterInput
}
type _SecureMessage   {
    id:ID!
    Participants: [String]!
    Message: String!
}
type _SecureMessage_Result   {
    error: String
    result: _SecureMessage
}
type _SecureMessageConnection   {
    _SecureMessages: [_SecureMessage]
    nextToken: String
}
input _SecureMessageInput {
    Participants: [String]!
    Message: String!
}
input _ContractConditionInput {
    InvocationUID: ModelStringInput
    Function: ModelStringInput
    Arguments_element: Arguments_elementConditionInput
    Results_element: Results_elementConditionInput
    and: [_ContractConditionInput]
    or: [_ContractConditionInput]
    not: _ContractConditionInput
}
input _ContractFilterInput {
    id: ModelIDInput
    InvocationUID: ModelStringInput
    Function: ModelStringInput
    Arguments_element: Arguments_elementFilterInput
    Results_element: Results_elementFilterInput
    and: [_ContractFilterInput]
    or: [_ContractFilterInput]
    not: _ContractFilterInput
}
type _Contract   {
    id:ID!
    InvocationUID: String!
    Function: String!
    Arguments: [Arguments_element]
    Results: [Results_element]
}
type _Contract_Result   {
    error: String
    result: _Contract
}
type _ContractConnection   {
    _Contracts: [_Contract]
    nextToken: String
}
input _ContractInput {
    InvocationUID: String!
    Function: String!
    Arguments: [Arguments_elementInput]
    Results: [Results_elementInput]
}
input _SettingsConditionInput {
    blockReportWebhooks: ModelStringInput
    blockReportEmails: ModelStringInput
    aws_blockReportSQSQueues: ModelStringInput
    aws_blockReportLambdas: ModelStringInput
    aws_blockReportFirehoses: ModelStringInput
    aws_SQSIngressAccounts: ModelStringInput
    aws_S3ReadAccounts: ModelStringInput
    aws_LambdaIngressAccounts: ModelStringInput
    _ResourceMapKeys: ModelStringInput
    _ResourceMapValues: ModelStringInput
    graphqlAuth: graphqlAuthConditionInput
    aws_DataDogMonitoring: aws_DataDogMonitoringConditionInput
    and: [_SettingsConditionInput]
    or: [_SettingsConditionInput]
    not: _SettingsConditionInput
}
input _SettingsFilterInput {
    blockReportWebhooks: ModelStringInput
    blockReportEmails: ModelStringInput
    aws_blockReportSQSQueues: ModelStringInput
    aws_blockReportLambdas: ModelStringInput
    aws_blockReportFirehoses: ModelStringInput
    aws_SQSIngressAccounts: ModelStringInput
    aws_S3ReadAccounts: ModelStringInput
    aws_LambdaIngressAccounts: ModelStringInput
    _ResourceMapKeys: ModelStringInput
    _ResourceMapValues: ModelStringInput
    graphqlAuth: graphqlAuthFilterInput
    aws_DataDogMonitoring: aws_DataDogMonitoringFilterInput
    and: [_SettingsFilterInput]
    or: [_SettingsFilterInput]
    not: _SettingsFilterInput
}
type _Settings   {
    blockReportWebhooks: [String]
    blockReportEmails: [String]
    aws_blockReportSQSQueues: [String]
    aws_blockReportLambdas: [String]
    aws_blockReportFirehoses: [String]
    aws_SQSIngressAccounts: [String]
    aws_S3ReadAccounts: [String]
    aws_LambdaIngressAccounts: [String]
    _ResourceMapKeys: [String]
    _ResourceMapValues: [String]
    graphqlAuth: graphqlAuth
    aws_DataDogMonitoring: aws_DataDogMonitoring
}
type _Settings_Result   {
    error: String
    result: _Settings
}
input _SettingsInput {
    blockReportWebhooks: [String]
    blockReportEmails: [String]
    aws_blockReportSQSQueues: [String]
    aws_blockReportLambdas: [String]
    aws_blockReportFirehoses: [String]
    aws_SQSIngressAccounts: [String]
    aws_S3ReadAccounts: [String]
    aws_LambdaIngressAccounts: [String]
    _ResourceMapKeys: [String]
    _ResourceMapValues: [String]
    graphqlAuth: graphqlAuthInput
    aws_DataDogMonitoring: aws_DataDogMonitoringInput
}
input _UniInfoConditionInput {
    name: ModelStringInput
    schema: ModelStringInput
    status: ModelStringInput
    created: ModelStringInput
    nodes_element: nodes_elementConditionInput
    localNodeName: ModelStringInput
    and: [_UniInfoConditionInput]
    or: [_UniInfoConditionInput]
    not: _UniInfoConditionInput
}
input _UniInfoFilterInput {
    name: ModelStringInput
    schema: ModelStringInput
    status: ModelStringInput
    created: ModelStringInput
    nodes_element: nodes_elementFilterInput
    localNodeName: ModelStringInput
    and: [_UniInfoFilterInput]
    or: [_UniInfoFilterInput]
    not: _UniInfoFilterInput
}
type _UniInfo   {
    name: String!
    schema: String!
    status: String
    created: String!
    nodes: [nodes_element]!
    localNodeName: String
}
type _UniInfo_Result   {
    error: String
    result: _UniInfo
}
input _UniInfoInput {
    name: String!
    schema: String!
    status: String
    created: String!
    nodes: [nodes_elementInput]!
    localNodeName: String
}
input _DeploymentInfoConditionInput {
    DeploymentDate: ModelStringInput
    ConsensusDefinitionHash: ModelStringInput
    VersionTag: ModelStringInput
    and: [_DeploymentInfoConditionInput]
    or: [_DeploymentInfoConditionInput]
    not: _DeploymentInfoConditionInput
}
input _DeploymentInfoFilterInput {
    id: ModelIDInput
    DeploymentDate: ModelStringInput
    ConsensusDefinitionHash: ModelStringInput
    VersionTag: ModelStringInput
    and: [_DeploymentInfoFilterInput]
    or: [_DeploymentInfoFilterInput]
    not: _DeploymentInfoFilterInput
}
type _DeploymentInfo   {
    id:ID!
    DeploymentDate: String!
    ConsensusDefinitionHash: String!
    VersionTag: String!
}
type _DeploymentInfo_Result   {
    error: String
    result: _DeploymentInfo
}
type _DeploymentInfoConnection   {
    _DeploymentInfos: [_DeploymentInfo]
    nextToken: String
}
input _DeploymentInfoInput {
    DeploymentDate: String!
    ConsensusDefinitionHash: String!
    VersionTag: String!
}
input _ParticipantAddsAllowedConditionInput {
    _ParticipantAddsAllowed: ModelBooleanInput
    and: [_ParticipantAddsAllowedConditionInput]
    or: [_ParticipantAddsAllowedConditionInput]
    not: _ParticipantAddsAllowedConditionInput
}
input fromAddressConditionInput {
    isInitial: ModelBooleanInput
    contact: ModelStringInput
    streetAddress: ModelStringInput
    city: ModelStringInput
    postalCode: ModelStringInput
    country: ModelStringInput
    and: [fromAddressConditionInput]
    or: [fromAddressConditionInput]
    not: fromAddressConditionInput
}
input fromAddressFilterInput {
    isInitial: ModelBooleanInput
    contact: ModelStringInput
    streetAddress: ModelStringInput
    city: ModelStringInput
    postalCode: ModelStringInput
    country: ModelStringInput
    and: [fromAddressFilterInput]
    or: [fromAddressFilterInput]
    not: fromAddressFilterInput
}
type fromAddress   {
    isInitial: Boolean
    contact: String
    streetAddress: String!
    city: String!
    postalCode: String
    country: String!
}
type fromAddress_Result   {
    error: String
    result: fromAddress
}
input toAddressConditionInput {
    isFinal: ModelBooleanInput
    contact: ModelStringInput
    streetAddress: ModelStringInput
    city: ModelStringInput
    postalCode: ModelStringInput
    country: ModelStringInput
    and: [toAddressConditionInput]
    or: [toAddressConditionInput]
    not: toAddressConditionInput
}
input toAddressFilterInput {
    isFinal: ModelBooleanInput
    contact: ModelStringInput
    streetAddress: ModelStringInput
    city: ModelStringInput
    postalCode: ModelStringInput
    country: ModelStringInput
    and: [toAddressFilterInput]
    or: [toAddressFilterInput]
    not: toAddressFilterInput
}
type toAddress   {
    isFinal: Boolean
    contact: String
    streetAddress: String!
    city: String!
    postalCode: String
    country: String!
}
type toAddress_Result   {
    error: String
    result: toAddress
}
input dimensionsConditionInput {
    length: ModelFloatInput
    width: ModelFloatInput
    height: ModelFloatInput
    and: [dimensionsConditionInput]
    or: [dimensionsConditionInput]
    not: dimensionsConditionInput
}
input dimensionsFilterInput {
    length: ModelFloatInput
    width: ModelFloatInput
    height: ModelFloatInput
    and: [dimensionsFilterInput]
    or: [dimensionsFilterInput]
    not: dimensionsFilterInput
}
type dimensions   {
    length: Float!
    width: Float!
    height: Float
}
type dimensions_Result   {
    error: String
    result: dimensions
}
input sales_elementConditionInput {
    start: ModelStringInput
    end: ModelStringInput
    discountPercent: ModelFloatInput
    and: [sales_elementConditionInput]
    or: [sales_elementConditionInput]
    not: sales_elementConditionInput
}
input sales_elementFilterInput {
    start: ModelStringInput
    end: ModelStringInput
    discountPercent: ModelFloatInput
    and: [sales_elementFilterInput]
    or: [sales_elementFilterInput]
    not: sales_elementFilterInput
}
type sales_element   {
    start: String!
    end: String!
    discountPercent: Float
}
type sales_element_Result   {
    error: String
    result: sales_element
}
input _TX_elementConditionInput {
    TxId: ModelStringInput
    TxHash: ModelStringInput
    Owner: ModelStringInput
    Sig: ModelStringInput
    Version: ModelStringInput
    Mutations: ModelStringInput
    and: [_TX_elementConditionInput]
    or: [_TX_elementConditionInput]
    not: _TX_elementConditionInput
}
input _TX_elementFilterInput {
    TxId: ModelStringInput
    TxHash: ModelStringInput
    Owner: ModelStringInput
    Sig: ModelStringInput
    Version: ModelStringInput
    Mutations: ModelStringInput
    and: [_TX_elementFilterInput]
    or: [_TX_elementFilterInput]
    not: _TX_elementFilterInput
}
type _TX_element   {
    TxId: String!
    TxHash: String
    Owner: String
    Sig: String
    Version: String
    Mutations: String!
}
type _TX_element_Result   {
    error: String
    result: _TX_element
}
input Arguments_elementConditionInput {
    Name: ModelStringInput
    Query: ModelStringInput
    and: [Arguments_elementConditionInput]
    or: [Arguments_elementConditionInput]
    not: Arguments_elementConditionInput
}
input Arguments_elementFilterInput {
    Name: ModelStringInput
    Query: ModelStringInput
    and: [Arguments_elementFilterInput]
    or: [Arguments_elementFilterInput]
    not: Arguments_elementFilterInput
}
type Arguments_element   {
    Name: String!
    Query: String!
}
type Arguments_element_Result   {
    error: String
    result: Arguments_element
}
input Results_elementConditionInput {
    Mutation: ModelStringInput
    Arguments_element_1: Arguments_element_1ConditionInput
    and: [Results_elementConditionInput]
    or: [Results_elementConditionInput]
    not: Results_elementConditionInput
}
input Results_elementFilterInput {
    Mutation: ModelStringInput
    Arguments_element_1: Arguments_element_1FilterInput
    and: [Results_elementFilterInput]
    or: [Results_elementFilterInput]
    not: Results_elementFilterInput
}
type Results_element   {
    Mutation: String!
    Arguments: [Arguments_element_1]
}
type Results_element_Result   {
    error: String
    result: Results_element
}
input Arguments_element_1ConditionInput {
    Name: ModelStringInput
    Type: ModelStringInput
    ResultPath: ModelStringInput
    and: [Arguments_element_1ConditionInput]
    or: [Arguments_element_1ConditionInput]
    not: Arguments_element_1ConditionInput
}
input Arguments_element_1FilterInput {
    Name: ModelStringInput
    Type: ModelStringInput
    ResultPath: ModelStringInput
    and: [Arguments_element_1FilterInput]
    or: [Arguments_element_1FilterInput]
    not: Arguments_element_1FilterInput
}
type Arguments_element_1   {
    Name: String!
    Type: String!
    ResultPath: String
}
type Arguments_element_1_Result   {
    error: String
    result: Arguments_element_1
}
input graphqlAuthConditionInput {
    authorizerType: ModelStringInput
    authorizerArn: ModelStringInput
    and: [graphqlAuthConditionInput]
    or: [graphqlAuthConditionInput]
    not: graphqlAuthConditionInput
}
input graphqlAuthFilterInput {
    authorizerType: ModelStringInput
    authorizerArn: ModelStringInput
    and: [graphqlAuthFilterInput]
    or: [graphqlAuthFilterInput]
    not: graphqlAuthFilterInput
}
type graphqlAuth   {
    authorizerType: String
    authorizerArn: String
}
type graphqlAuth_Result   {
    error: String
    result: graphqlAuth
}
input aws_DataDogMonitoringConditionInput {
    ddExternalId: ModelStringInput
    ddApiKey: ModelStringInput
    ddLogEndpoint: ModelStringInput
    ddSendLogs: ModelBooleanInput
    and: [aws_DataDogMonitoringConditionInput]
    or: [aws_DataDogMonitoringConditionInput]
    not: aws_DataDogMonitoringConditionInput
}
input aws_DataDogMonitoringFilterInput {
    ddExternalId: ModelStringInput
    ddApiKey: ModelStringInput
    ddLogEndpoint: ModelStringInput
    ddSendLogs: ModelBooleanInput
    and: [aws_DataDogMonitoringFilterInput]
    or: [aws_DataDogMonitoringFilterInput]
    not: aws_DataDogMonitoringFilterInput
}
type aws_DataDogMonitoring   {
    ddExternalId: String
    ddApiKey: String
    ddLogEndpoint: String
    ddSendLogs: Boolean
}
type aws_DataDogMonitoring_Result   {
    error: String
    result: aws_DataDogMonitoring
}
input nodes_elementConditionInput {
    name: ModelStringInput
    userId: ModelStringInput
    userEmail: ModelStringInput
    description: ModelStringInput
    status: ModelStringInput
    region: ModelStringInput
    vendiaAccount: vendiaAccountConditionInput
    and: [nodes_elementConditionInput]
    or: [nodes_elementConditionInput]
    not: nodes_elementConditionInput
}
input nodes_elementFilterInput {
    name: ModelStringInput
    userId: ModelStringInput
    userEmail: ModelStringInput
    description: ModelStringInput
    status: ModelStringInput
    region: ModelStringInput
    vendiaAccount: vendiaAccountFilterInput
    and: [nodes_elementFilterInput]
    or: [nodes_elementFilterInput]
    not: nodes_elementFilterInput
}
type nodes_element   {
    name: String!
    userId: String!
    userEmail: String
    description: String
    status: String
    region: String!
    vendiaAccount: vendiaAccount
}
type nodes_element_Result   {
    error: String
    result: nodes_element
}
input vendiaAccountConditionInput {
    csp: ModelStringInput
    accountId: ModelStringInput
    userId: ModelStringInput
    org: ModelStringInput
    and: [vendiaAccountConditionInput]
    or: [vendiaAccountConditionInput]
    not: vendiaAccountConditionInput
}
input vendiaAccountFilterInput {
    csp: ModelStringInput
    accountId: ModelStringInput
    userId: ModelStringInput
    org: ModelStringInput
    and: [vendiaAccountFilterInput]
    or: [vendiaAccountFilterInput]
    not: vendiaAccountFilterInput
}
type vendiaAccount   {
    csp: String!
    accountId: String!
    userId: String
    org: String
}
type vendiaAccount_Result   {
    error: String
    result: vendiaAccount
}
type Query {
    getContactInfo: ContactInfo
    getParticipant(id: ID!): Participant
    listParticipants(filter: ParticipantFilterInput, limit: Int, nextToken: String): ParticipantConnection
    getOrder(id: ID!): Order
    listOrders(filter: OrderFilterInput, limit: Int, nextToken: String): OrderConnection
    getShipmentMessage(id: ID!): ShipmentMessage
    listShipmentMessages(filter: ShipmentMessageFilterInput, limit: Int, nextToken: String): ShipmentMessageConnection
    getProduct(id: ID!): Product
    listProducts(filter: ProductFilterInput, limit: Int, nextToken: String): ProductConnection
    get_Block(id: ID!): _Block
    list_Blocks(filter: _BlockFilterInput, limit: Int, nextToken: String): _BlockConnection
    get_SecureMessage(id: ID!): _SecureMessage
    list_SecureMessages(filter: _SecureMessageFilterInput, limit: Int, nextToken: String): _SecureMessageConnection
    get_Contract(id: ID!): _Contract
    list_Contracts(filter: _ContractFilterInput, limit: Int, nextToken: String): _ContractConnection
    get_Settings: _Settings
    get_UniInfo: _UniInfo
    get_DeploymentInfo(id: ID!): _DeploymentInfo
    list_DeploymentInfos(filter: _DeploymentInfoFilterInput, limit: Int, nextToken: String): _DeploymentInfoConnection
    get_ParticipantAddsAllowed: Boolean
}
type Mutation {
    createContactInfo_async(input: ContactInfoInput!): Transaction_Result
    updateContactInfo_async(input: ContactInfoInput!, condition: ContactInfoConditionInput): Transaction_Result
    addParticipant_async(input: String!): Transaction_Result
    updateParticipant_async(id: ID!, input: String!, condition: ParticipantConditionInput): Transaction_Result
    removeParticipant_async(id: ID!, condition: ParticipantConditionInput): Transaction_Result
    addOrder_async(input: OrderInput!): Transaction_Result
    updateOrder_async(id: ID!, input: OrderInput!, condition: OrderConditionInput): Transaction_Result
    removeOrder_async(id: ID!, condition: OrderConditionInput): Transaction_Result
    addShipmentMessage_async(input: ShipmentMessageInput!): Transaction_Result
    updateShipmentMessage_async(id: ID!, input: ShipmentMessageInput!, condition: ShipmentMessageConditionInput): Transaction_Result
    removeShipmentMessage_async(id: ID!, condition: ShipmentMessageConditionInput): Transaction_Result
    addProduct_async(input: ProductInput!): Transaction_Result
    updateProduct_async(id: ID!, input: ProductInput!, condition: ProductConditionInput): Transaction_Result
    removeProduct_async(id: ID!, condition: ProductConditionInput): Transaction_Result
    add_Block_async(input: _BlockInput!): Transaction_Result
    update_Block_async(id: ID!, input: _BlockInput!, condition: _BlockConditionInput): Transaction_Result
    remove_Block_async(id: ID!, condition: _BlockConditionInput): Transaction_Result
    add_SecureMessage_async(input: _SecureMessageInput!): Transaction_Result
    update_SecureMessage_async(id: ID!, input: _SecureMessageInput!, condition: _SecureMessageConditionInput): Transaction_Result
    remove_SecureMessage_async(id: ID!, condition: _SecureMessageConditionInput): Transaction_Result
    add_Contract_async(input: _ContractInput!): Transaction_Result
    update_Contract_async(id: ID!, input: _ContractInput!, condition: _ContractConditionInput): Transaction_Result
    remove_Contract_async(id: ID!, condition: _ContractConditionInput): Transaction_Result
    create_Settings_async(input: _SettingsInput!): Transaction_Result
    update_Settings_async(input: _SettingsInput!, condition: _SettingsConditionInput): Transaction_Result
    delete_Settings_async(condition: _SettingsConditionInput): Transaction_Result
    create_UniInfo_async(input: _UniInfoInput!): Transaction_Result
    update_UniInfo_async(input: _UniInfoInput!, condition: _UniInfoConditionInput): Transaction_Result
    delete_UniInfo_async(condition: _UniInfoConditionInput): Transaction_Result
    add_DeploymentInfo_async(input: _DeploymentInfoInput!): Transaction_Result
    update_DeploymentInfo_async(id: ID!, input: _DeploymentInfoInput!, condition: _DeploymentInfoConditionInput): Transaction_Result
    remove_DeploymentInfo_async(id: ID!, condition: _DeploymentInfoConditionInput): Transaction_Result
    create_ParticipantAddsAllowed_async(input: Boolean!): Transaction_Result
    update_ParticipantAddsAllowed_async(input: Boolean!, condition: _ParticipantAddsAllowedConditionInput): Transaction_Result
    delete_ParticipantAddsAllowed_async(condition: _ParticipantAddsAllowedConditionInput): Transaction_Result
}
type Schema {
    query: Query
    mutation: Mutation
}

input fromAddressInput {
    isInitial: Boolean
    contact: String
    streetAddress: String!
    city: String!
    postalCode: String
    country: String!
}
input toAddressInput {
    isFinal: Boolean
    contact: String
    streetAddress: String!
    city: String!
    postalCode: String
    country: String!
}
input dimensionsInput {
    length: Float!
    width: Float!
    height: Float
}
input sales_elementInput {
    start: String!
    end: String!
    discountPercent: Float
}
input _TX_elementInput {
    TxId: String!
    TxHash: String
    Owner: String
    Sig: String
    Version: String
    Mutations: String!
}
input Arguments_elementInput {
    Name: String!
    Query: String!
}
input Results_elementInput {
    Mutation: String!
    Arguments: [Arguments_element_1Input]
}
input Arguments_element_1Input {
    Name: String!
    Type: String!
    ResultPath: String
}
input graphqlAuthInput {
    authorizerType: String
    authorizerArn: String
}
input aws_DataDogMonitoringInput {
    ddExternalId: String
    ddApiKey: String
    ddLogEndpoint: String
    ddSendLogs: Boolean
}
input nodes_elementInput {
    name: String!
    userId: String!
    userEmail: String
    description: String
    status: String
    region: String!
    vendiaAccount: vendiaAccountInput
}
input vendiaAccountInput {
    csp: String!
    accountId: String!
    userId: String
    org: String
}

Lastly, here is an example of an initial state (Uni starting point) file that conforms to the schema above:

NOTE: defining an initial state for your Uni is entirely optional! Initial state is only supported in the Share CLI and is only intended for small sets of data.

Sample Uni initial state
{
  "ContactInfo": {
    "addressLine1": "170 West Tasman Drive",
    "city": "San Jose",
    "state": "CA",
    "zipCode": "95134"
  },
  "Participant": [
    "Acme Corporation",
    "RoadRunner, Inc."
  ],
  "Product": [{
      "productId": 123,
      "productName": "Server rack",
      "price": 99.99,
      "tags": [
        "discount",
        "requires_installation"
      ],
      "dimensions": {
        "length": 48,
        "width": 36,
        "height": 51
      }
    },
    {
      "productId": 456,
      "productName": "Server_xyz_config",
      "price": 555.99
    },
    {
      "productId": 789,
      "productName": "Server_abc_config",
      "price": 799.99
    }
  ]
}

Runtime validation of GraphQL mutations

Your Uni's JSON schema can be used to express data restrictions that extend beyond basic types. Strings, for example, support contraints such as:

These constraints will be used to validate incoming GraphQL mutations and violations will return descriptive error messages.

For example, here is a sample of JSON schema defining a "Shipment" entity with a "created" field that must adhere to a "date-time" format":

"Shipment": {
  "description": "Shipment information",
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "created": {
        "type": "string",
        "format": "date-time" <---- Values for this field must adhere to "date-time" format!
      },

The following GraphQL mutation attempts to add a new shipment object with a malformed value for the "created" field:

mutation addShipment {
  add_Shipment(input: {created: "yesterday"}) {
    result {
      _id
      created
    }
  }
}

The following GraphQL error will be returned describing the violation:

'yesterday' is not a 'date-time'

Failed validating 'format' in schema['properties']['Shipment']['items']['properties']['created']:
    {'format': 'date-time',
     'type': 'string'}

On instance['Shipment'][0]['created']:
    'yesterday'

You can learn more about the string constraints mentioned above here - Vendia Share currently supports all constraints available in JSON schema draft 7.

Indexes

Indexes can be defined in the JSON schema to support efficient queries on arbitrary attributes via the GraphQL filter argument. Indexes can also be used to sort the results of list queries via the GraphQL order argument.

Indexes can only be added to entities of type "array" and are restricted to top-level scalar fields (e.g. "string", "number", "integer") on these entities.

The top-level directive "x-vendia-indexes" is used to define indexes on attributes. For example, to support an efficient list query of Orders by owner, an index can be defined in the schema referencing the owner property of the existing Order type.

{
 "$schema": "http://json-schema.org/draft-07/schema#",
 "$id": "http://vendia.net/schemas/sample.schema.json",
 "title": "Acme Uni Schema",
 "description": "Defines data model for Acme's Product Uni",
 "x-vendia-indexes": {
    "OrderOwnerIndex": [
      {
        "type": "Order",
        "property": "owner"
      }
    ]
  }
  ...
}

Filtering on an indexed field

Using the index defined above we can now list Orders, filtering on the owner property, and make use of our new index:

list_OrderItems(filter: {owner: {eq: "bob@acme.com"}}) {
  nextToken        
  _OrderItems {
    _id
    owner
    orderContent
  }
}

Indexes can be used with the following GraphQL filter operators:

  • eq
  • gt
  • lt
  • le
  • ge
  • between
  • beginsWith

The remaining filter operators can be used, but your list query will no longer take advatage of the index.

Sorting list results by an indexed field

In addition to filtering, list query results can now be sorted by the owner property in ascending or descending order:

list_OrderItems(order: { owner: DESC }) {
  nextToken        
  _OrderItems {
    _id
    owner
    orderContent
  }
}

Note that the filter and order arguments can be used in the same query with the following restrictions:

  1. Only one index can be used per query - if order and filter make use of two different indexes, the index used for order will take precedence.
  2. If order and filter are both used with the same index, then filter must be restricted to the supported operators listed above.

Vendia-specific JSON schema restrictions

Vendia Share endeavors to support the widest possible range of data models allowable in standard JSON schema. That said, translating JSON schema into strongly-typed GraphQL APIs requires us to enforce some minor restrictions as GraphQL itself is simply more strict about what can and cannot be supported.

While you may never bump into the following restrictions, they are listed here for clarity and transparency. If your JSON schema happens to violate any of the following rules, you will receive an error message explaining the problem and can update your schema accordingly.

  1. "Empty" objects aren't allowed in GraphQL schema. All "object" types must have "properties" and "properties", in turn, must contain at least one property definition.
  2. Similarly, all "array" types must contain an "items" definition.
  3. Any fields marked as required via the "required" array property must be defined on the corresponding "object" definition.
  4. JSON schema uses "additionalProperties" to determine whether an "object" type can include additional properties not defined explicitly in your JSON schema. The value of this property will always be set to false implicitly by Vendia Share. GraphQL is strongly-typed and does not allow storing/retrieving arbitrary additional fields on objects.
  5. JSON schema "combining" functionality (e.g. allOf, anyOf, oneOf, not) is not supported at this time.
  6. JSON schema definitions must use the "definitions" keyword rather than "$defs" (consistent with JSON schema draft 7).

Limits

A Uni is limited to a total of 12 indexes. Indexes can be defined at Uni registration time or added later via schema evolution. Only one index change is allowed per schema evolution. Learn more about Schema evolution and adding/removing indexes here.