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.com/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 GraphQLfloat
types. - Floats have a precision of 16 decimal places and a range of
2.2250738585072014e-308
to1.7976931348623157e+308
. - The allowable range for
integer
types is -2147483648 through 2147483647. - JSON Schema
date
,time
, anddatetimes
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.com/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
input ModelBooleanInput { ne: Boolean eq: Boolean}input ModelFloatInput { ne: Float eq: Float le: Float lt: Float ge: Float gt: Float between: [Float]}input ModelIDInput { ne: ID eq: ID}input ModelIntInput { 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}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}
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 constraints such as:
- “minLength”
- “maxLength”
- “pattern” (regular expressions)
- “format” (supports predefined values such as “date-time”)
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.com/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 advantage 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:
- Only one index can be used per query - if
order
andfilter
make use of two different indexes, the index used fororder
will take precedence. - If
order
andfilter
are both used with the same index, thenfilter
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.
- “Empty” objects aren’t allowed in GraphQL schema. All
object
types must have"properties
. In turn,properties
must contain at least one property definition. - Similarly, all
array
types must contain anitems
definition. - Any fields marked as required via the
required"\
array property must be defined on the correspondingobject
definition. - JSON schema uses
additionalProperties
to determine whether anobject
type can include additional properties not defined explicitly in your JSON schema. The value of this property will always be set tofalse
implicitly by Vendia Share. GraphQL is strongly-typed and does not allow storing/retrieving arbitrary additional fields on objects. - JSON schema “combining” functionality (e.g.
allOf
,anyOf
,oneOf
,not
) is not supported at this time. - 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.