diff --git a/packages/supergraph/README.md b/packages/supergraph/README.md
new file mode 100644
index 00000000..b349ceeb
--- /dev/null
+++ b/packages/supergraph/README.md
@@ -0,0 +1,154 @@
+# `@atlas/supergraph`
+
+Shared Relay client configuration for accessing the Diamond Light Source federated GraphQL supergraph from React applications.
+
+`@atlas/supergraph` provides:
+
+- A shared Relay environment configured to communicate with the supergraph.
+- The canonical supergraph GraphQL schema used by the Relay compiler.
+- Common HTTP configuration for communicating with the supergraph.
+
+Applications define their own GraphQL queries and fragments and compile them locally using Relay Compiler. Generated Relay artifacts remain application-specific.
+
+## Installation
+
+Install the Relay runtime and React bindings in your application (called `@atlas/myApp` in this example):
+
+```bash
+pnpm add -F @atlas/myApp react-relay relay-runtime
+```
+
+Then add `@atlas/supergraph` to the application from the monorepo workspace:
+
+```bash
+pnpm add @atlas/supergraph -F myApp --workspace
+```
+
+Relay Compiler is a development dependency of the application, since each application compiles its own GraphQL operations:
+
+```bash
+pnpm add -D relay-compiler -F @atlas/myApp
+```
+
+## Relay Compiler configuration
+
+Create `relay.config.json` in your application root:
+
+```json
+{
+ "src": "./src",
+ "language": "typescript",
+ "schema": "../../packages/supergraph/supergraph.graphql",
+ "exclude": ["**/node_modules/**", "**/__mocks__/**", "**/__generated__/**"],
+ "eagerEsModules": true
+}
+```
+
+Add a Relay compiler script to your application's `package.json`:
+
+```json
+{
+ "scripts": {
+ "relay": "relay-compiler"
+ }
+}
+```
+
+You can then compile the application's GraphQL operations with:
+
+```bash
+pnpm relay
+```
+
+## Using the Relay environment
+
+The shared Relay environment is exported by `@atlas/supergraph`:
+
+```ts
+import { RelayEnvironmentProvider } from "react-relay";
+import { relayEnvironment } from "@atlas/supergraph";
+
+export function App() {
+ return (
+
+ {/* Application */}
+
+ );
+}
+```
+
+The environment is configured to communicate with the supergraph at `/api/supergraph`.
+
+## Writing and using a query
+
+### 1. Write the query
+
+Define a GraphQL query using the `graphql` tag from `react-relay`. The query is validated against the shared supergraph schema when the Relay compiler runs.
+
+For example:
+
+```tsx
+import { graphql } from "react-relay";
+
+const instrumentSessionQuery = graphql`
+ query InstrumentSessionQuery($instrumentKey: String!) {
+ instrumentByKey(key: $instrumentKey) {
+ instrumentSessions(filterBy: { state: { eq: IN_PROGRESS } }) {
+ edges {
+ node {
+ instrumentSessionReference
+ }
+ }
+ }
+ }
+ }
+`;
+```
+
+The query can be placed wherever it is used by the application. Relay Compiler will find GraphQL operations in the application's configured src directory.
+
+### 2. Compile the query
+
+Run Relay Compiler from your app's directory:
+
+```bash
+cd apps/myApp
+pnpm relay
+```
+
+The compiler:
+
+- validates the query against the shared supergraph schema;
+- generates TypeScript types for the query variables and response data;
+- generates the Relay artifact required by the Relay runtime.
+
+For the example above, this produces:
+
+```
+./ # wherever the above query exists
+└── __generated__/
+ └── InstrumentSessionQuery.graphql.ts
+```
+
+The generated files should not be edited manually.
+
+### 3. Use the generated types with the query
+
+Import the generated query type and use it with `useLazyLoadQuery`:
+
+```tsx
+import { useLazyLoadQuery } from "react-relay";
+import type {
+ InstrumentSessionQuery,
+ InstrumentSessionQuery$data as InstrumentSessionQueryData,
+} from "./__generated__/InstrumentSessionQuery.graphql";
+
+const data: InstrumentSessionQueryData =
+ useLazyLoadQuery(instrumentSessionQuery, {
+ instrumentKey: "I14",
+ });
+```
+
+`InstrumentSessionQuery` is the generated type describing the query and its variables, while `InstrumentSessionQuery$data` describes the shape of the data returned by the query.
+
+The `instrumentSessionQuery` value itself is the original `graphql` expression. Relay uses the compiler-generated artifact associated with that expression when executing the query.
diff --git a/packages/supergraph/package.json b/packages/supergraph/package.json
new file mode 100644
index 00000000..dfc817cb
--- /dev/null
+++ b/packages/supergraph/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@atlas/supergraph",
+ "version": "0.0.0",
+ "private": true,
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "devDependencies": {
+ "@types/relay-runtime": "^20.1.1"
+ },
+ "peerDependencies": {
+ "relay-runtime": "^20.1.1"
+ }
+}
diff --git a/packages/supergraph/relay.config.json b/packages/supergraph/relay.config.json
new file mode 100644
index 00000000..cb1be380
--- /dev/null
+++ b/packages/supergraph/relay.config.json
@@ -0,0 +1,7 @@
+{
+ "src": "./src",
+ "language": "typescript",
+ "schema": "./supergraph.graphql",
+ "exclude": ["**/node_modules/**", "**/__mocks__/**", "**/__generated__/**"],
+ "eagerEsModules": true
+}
diff --git a/packages/supergraph/schema-version.txt b/packages/supergraph/schema-version.txt
new file mode 100644
index 00000000..871b3b4b
--- /dev/null
+++ b/packages/supergraph/schema-version.txt
@@ -0,0 +1 @@
+v3.4.0
\ No newline at end of file
diff --git a/packages/supergraph/src/environment.ts b/packages/supergraph/src/environment.ts
new file mode 100644
index 00000000..eed606f8
--- /dev/null
+++ b/packages/supergraph/src/environment.ts
@@ -0,0 +1,42 @@
+import {
+ Environment,
+ Network,
+ RecordSource,
+ Store,
+ type FetchFunction,
+} from "relay-runtime";
+
+const HTTP_DEFAULT_ENDPOINT = "/api/supergraph";
+
+const fetchFunction: FetchFunction = async (request, variables) => {
+ const resp = await fetch(HTTP_DEFAULT_ENDPOINT, {
+ method: "POST",
+ credentials: "include",
+ headers: {
+ Accept:
+ "application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ query: request.text,
+ variables,
+ }),
+ });
+
+ if (!resp.ok) {
+ throw new Error(
+ `Supergraph request failed: ${resp.status} ${resp.statusText}`,
+ );
+ }
+
+ return resp.json();
+};
+
+function createRelayEnvironment() {
+ return new Environment({
+ network: Network.create(fetchFunction),
+ store: new Store(new RecordSource()),
+ });
+}
+
+export const relayEnvironment = createRelayEnvironment();
diff --git a/packages/supergraph/src/index.ts b/packages/supergraph/src/index.ts
new file mode 100644
index 00000000..82f1fccd
--- /dev/null
+++ b/packages/supergraph/src/index.ts
@@ -0,0 +1 @@
+export * from "./environment";
diff --git a/packages/supergraph/supergraph.graphql b/packages/supergraph/supergraph.graphql
new file mode 100644
index 00000000..ae96cbe7
--- /dev/null
+++ b/packages/supergraph/supergraph.graphql
@@ -0,0 +1,1826 @@
+type Artifact {
+ """The file name of the artifact"""
+ name: String!
+
+ """The download URL for the artifact"""
+ url: Url!
+
+ """The MIME type of the artifact data"""
+ mimeType: String!
+}
+
+scalar Creator
+
+"""
+Implement the DateTime scalar
+
+The input/output is a string in RFC3339 format.
+"""
+scalar DateTime
+
+"""
+The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
+"""
+scalar JSON
+
+"""A scalar that can represent any JSON Object value."""
+scalar JSONObject
+
+"""Represents a label selector for filtering workflows based on labels"""
+input LabelSelector {
+ """The label key to filter on"""
+ key: String!
+
+ """The operator to use for the label selection"""
+ operator: WorkflowLabelSelectorOperator!
+
+ """The values to match against the label key (if applicable)"""
+ values: [String!]
+}
+
+"""A single log line streamed from a pod"""
+type LogEntry {
+ """The log line content"""
+ content: String!
+
+ """The name of the pod producing the log"""
+ podName: String!
+}
+
+"""The root mutation of the service"""
+type Mutation {
+ """submit specific workflow template"""
+ submitWorkflowTemplate(name: String!, visit: VisitInput!, parameters: JSON!): Workflow!
+
+ """
+ Submit a manifest (YAML) as a one-off Workflow within a visit's namespace.
+
+ The input is typically a `WorkflowTemplate` (the manifest developers author and
+ store in their repository). It is submitted via the Argo Server API so the graph
+ remains the single source of truth for submissions. To match the behaviour of the
+ workflows CLI, the manifest is coerced into a one-off Workflow: `kind` is set to
+ `Workflow`, and a fixed `metadata.name` is rewritten to a `metadata.generateName`
+ (suffixed with `-`) so repeated submissions yield fresh, uniquely named Workflows.
+ """
+ submitWorkflow(visit: VisitInput!, manifest: String!): Workflow!
+
+ """Create a Trigger from a template"""
+ createTrigger(templateRef: String!, name: String, visit: VisitInput): Trigger
+ createExperimentDefinition(input: CreateExperimentDefinitionInput!): CreateExperimentDefinitionResponse
+ experimentDefinition(id: UUID!): ExperimentDefinitionMutations
+ deleteExperiment(id: UUID!): DeleteExperimentResponse
+ restoreExperiment(id: UUID!): DeleteExperimentResponse
+ deleteExperimentDefinition(id: UUID!): DeletedExperimentDefinitionResponse
+ restoreExperimentDefinition(id: UUID!): DeletedExperimentDefinitionResponse
+ createInstrumentSession(input: CreateInstrumentSessionInput!): InstrumentSession!
+ instrumentSession(proposalNumber: Int!, instrumentSessionNumber: Int!): InstrumentSessionMutations
+ createOrValidateSamples(input: CreateOrValidateSampleInput!): CreateSamplesResponse!
+ sample(sampleId: UUID!): SampleMutations
+ deleteSample(sampleId: UUID!): DeleteSampleResponse!
+ restoreSample(sampleId: UUID!): RestoreSampleResponse!
+
+ """Create a new container type"""
+ createContainerType(input: CreateContainerTypeInput!): CreateContainerTypeResponse!
+
+ """Mutations relating to a specific container type"""
+ containerType(name: String!): ContainerTypeMutations!
+
+ """Create a new container"""
+ createContainer(input: CreateContainerInput!): CreateContainerResponse!
+
+ """Muatations relating to a specific container"""
+ container(id: UUID = null, barcode: String = null): ContainerMutations!
+}
+
+"""Represents Relay Node types"""
+union NodeValue = Workflow
+
+"""Information about pagination in a connection"""
+type PageInfo {
+ """When paginating backwards, are there more items?"""
+ hasPreviousPage: Boolean!
+
+ """When paginating forwards, are there more items?"""
+ hasNextPage: Boolean!
+
+ """When paginating backwards, the cursor to continue."""
+ startCursor: String
+
+ """When paginating forwards, the cursor to continue."""
+ endCursor: String
+}
+
+"""The root query of the service"""
+type Query {
+ node(id: ID!): NodeValue
+
+ """
+ Get a single [`Workflow`] by proposal, visit, and name.
+
+ In case of two workflows with the same name, returns the most recent.
+ """
+ workflow(visit: VisitInput!, name: String!): Workflow @deprecated(reason: "Use workflowById instead")
+
+ """Get a single [`Workflow`] by unique ID."""
+ workflowById(id: ID!): Workflow
+
+ """Find all workflows available for a given visit"""
+ workflows(visit: VisitInput!, cursor: String, limit: Int, filter: WorkflowFilter): WorkflowConnection!
+
+ """Retrieves a single cluster workflow template by name"""
+ workflowTemplate(name: String!): WorkflowTemplate!
+
+ """
+ Retrieves all cluster workflow templates with respective pagination data
+ """
+ workflowTemplates(cursor: String, limit: Int, filter: WorkflowTemplatesFilter): WorkflowTemplateConnection!
+
+ """Get a specific Trigger by name and visit"""
+ trigger(name: String!, visit: String): Trigger
+
+ """Get multiple Triggers across namespaces"""
+ triggers(cursor: String, limit: Int): TriggerConnection!
+ jsonSchema(url: String!): JSONSchema
+ jsonSchemas(type: String = null, instrument: String = null): [JSONSchema!]!
+ experimentDefinition(id: UUID!): ExperimentDefinition
+ experimentDefinitions(instrumentSessions: [InstrumentSessionInput!]!, filter: ExperimentDefinitionFilterInput = null, first: Int = null, last: Int = null, after: String = null, before: String = null): ExperimentDefinitionConnection
+ experiment(id: UUID!): Experiment
+ experiments(instrumentSessions: [InstrumentSessionInput!]!, first: Int = null, last: Int = null, after: String = null, before: String = null): ExperimentConnection
+
+ """Get a proposal by its number"""
+ proposal(proposalNumber: Int!): Proposal
+
+ """Get a list of proposals"""
+ proposals(first: Int = null, last: Int = null, after: String = null, before: String = null, sortBy: [ProposalSortInput!] = null, filterBy: ProposalFilterInput = null): ProposalConnection!
+
+ """
+ Get a proposal by its reference string e.g. 'MX12345'. The lookup is case-insensitive.
+ """
+ proposalByReference(reference: String!): Proposal
+
+ """Get a instrument session"""
+ instrumentSession(proposalNumber: Int!, instrumentSessionNumber: Int!): InstrumentSession
+
+ """
+ Get an instrument session by its reference string e.g. 'MX12345-1'. The lookup is case-insensitive.
+ """
+ instrumentSessionByReference(reference: String!): InstrumentSession
+
+ """Get a list of instrument sessions"""
+ instrumentSessions(proposalNumber: Int = null, proposalCategory: String = null, first: Int = null, last: Int = null, after: String = null, before: String = null, sortBy: [InstrumentSessionSortInput!] = null, filterBy: InstrumentSessionFilterInput = null): InstrumentSessionConnection!
+
+ """Get an instrument by name"""
+ instrumentByName(name: String!): Instrument
+
+ """Get an instrument by key"""
+ instrumentByKey(key: String!): Instrument
+
+ """Get a list of instruments"""
+ instruments(scienceGroup: String = null): [Instrument!]!
+
+ """Get an account"""
+ account(username: String!): Account
+
+ """Get a sample by its id"""
+ sample(sampleId: UUID!): Sample
+
+ """Get a list of samples associated with a given instrument session"""
+ samples(first: Int = null, instrumentSessions: [InstrumentSessionInput!] = null, filter: SampleFilterInput! = {schemaUrl: null, createdTime: null, updatedTime: null, name: null, data: null}, before: String = null, after: String = null, last: Int = null, orderBy: SampleOrder! = {name: null, createdTime: null, updatedTime: null}): SampleConnection!
+
+ """Fetch a single container type, by specifying its name"""
+ containerType(name: String!): ContainerType
+
+ """Fetch multiple container types, by specifying associated instruments"""
+ containerTypes(instrumentKeys: [String!]!, first: Int = 20, last: Int = null, before: String = null, after: String = null): ContainerTypeConnection!
+
+ """Fetch a single container, by specifying either its id or barcode"""
+ container(id: UUID = null, barcode: String = null): Container
+
+ "\n Fetch multiple containers, by specifying either its parent, a child, an\n associated instrument, or associated instrument sessions"
+ containers(instrumentSessions: [InstrumentSessionInput!] = null, instrumentKeys: [String!] = null, filter: ContainerFilterInput = null, first: Int = 20, last: Int = null, before: String = null, after: String = null): ContainerConnection!
+}
+
+"""Supported DLS science groups"""
+enum ScienceGroup {
+ """Macromolecular Crystallography"""
+ MX
+
+ """Workflows Examples"""
+ EXAMPLES
+
+ """Magnetic Materials"""
+ MAGNETIC_MATERIALS
+
+ """Soft Condensed Matter"""
+ CONDENSED_MATTER
+
+ """Imaging and Microscopy"""
+ IMAGING
+
+ """Biological Cryo-Imaging"""
+ BIO_CRYO_IMAGING
+
+ """Structures and Surfaces"""
+ SURFACES
+
+ """Crystallography"""
+ CRYSTALLOGRAPHY
+
+ """Spectroscopy"""
+ SPECTROSCOPY
+}
+
+"""The root mutation of the service"""
+type Subscription {
+ """Processing to subscribe to logs for a single pod of a workflow"""
+ logs(visit: VisitInput!, workflowName: String!, taskId: String!): LogEntry!
+
+ """Processing to subscribe to data for all workflows in a session"""
+ workflow(visit: VisitInput!, name: String!): Workflow!
+}
+
+type Task {
+ """Unique name of the task"""
+ id: String!
+
+ """Display name of the task"""
+ name: String!
+
+ """Current status of a task"""
+ status: TaskStatus!
+
+ """Parent of a task"""
+ depends: [String!]!
+
+ """Children of a task"""
+ dependencies: [String!]!
+
+ """Artifacts produced by a task"""
+ artifacts: [Artifact!]!
+
+ """Node type - Pod, DAG, etc"""
+ stepType: String!
+
+ """Start time for a task on a workflow"""
+ startTime: DateTime
+
+ """End time for a task on a workflow"""
+ endTime: DateTime
+
+ """
+ A human readable message indicating details about why this step is in this condition
+ """
+ message: String
+}
+
+enum TaskStatus {
+ PENDING
+ RUNNING
+ SUCCEEDED
+ SKIPPED
+ FAILED
+ ERROR
+ OMITTED
+}
+
+scalar Template
+
+"""Information about where the template is stored"""
+type TemplateSource {
+ """The URL of the GitHub repository"""
+ repositoryUrl: String!
+
+ """The path to the template within the repository"""
+ path: String!
+
+ """The current tracked branch of the repository"""
+ targetRevision: String!
+}
+
+"""A Trigger for creating automated workflows"""
+type Trigger {
+ """The name of the Trigger"""
+ name: String
+
+ """The name of a ClusterTriggerTemplate that the Trigger is created from"""
+ templateRef: String
+
+ """The beamline that the Trigger monitors"""
+ beamline: String
+}
+
+type TriggerConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [TriggerEdge!]!
+
+ """A list of nodes."""
+ nodes: [Trigger!]!
+}
+
+"""An edge in a connection."""
+type TriggerEdge {
+ """The item at the end of the edge"""
+ node: Trigger!
+
+ """A cursor for use in pagination"""
+ cursor: String!
+}
+
+"""
+URL is a String implementing the [URL Standard](http://url.spec.whatwg.org/)
+"""
+scalar Url
+
+"""A visit to an instrument as part of a session"""
+type Visit {
+ """Project Proposal Code"""
+ proposalCode: String!
+
+ """Project Proposal Number"""
+ proposalNumber: Int!
+
+ """Session visit Number"""
+ number: Int!
+}
+
+"""A visit to an instrument as part of a session"""
+input VisitInput {
+ """Project Proposal Code"""
+ proposalCode: String!
+
+ """Project Proposal Number"""
+ proposalNumber: Int!
+
+ """Session visit Number"""
+ number: Int!
+}
+
+type Workflow {
+ """The unique ID derived from the visit, name and uid"""
+ id: ID!
+
+ """The name given to the workflow, unique within a given visit"""
+ name: String!
+
+ """The visit the Workflow was run against"""
+ visit: Visit!
+
+ """The current status of the workflow"""
+ status: WorkflowStatus
+
+ """The top-level workflow parameters"""
+ parameters: JSONObject
+
+ """The name of the template used to run the workflow"""
+ templateRef: String
+
+ """The workflow creator"""
+ creator: WorkflowCreator!
+}
+
+type WorkflowConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [WorkflowEdge!]!
+
+ """A list of nodes."""
+ nodes: [Workflow!]!
+}
+
+"""Information about the creator of a workflow."""
+type WorkflowCreator {
+ """
+ An identifier unique to the creator of the workflow.
+ Typically this is the creator's Fed-ID.
+ """
+ creatorId: String!
+}
+
+"""An edge in a connection."""
+type WorkflowEdge {
+ """The item at the end of the edge"""
+ node: Workflow!
+
+ """A cursor for use in pagination"""
+ cursor: String!
+}
+
+"""All tasks in the workflow have errored"""
+type WorkflowErroredStatus {
+ """Time at which this workflow started"""
+ startTime: DateTime!
+
+ """Time at which this workflow completed"""
+ endTime: DateTime!
+
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+
+ """Tasks created by the workflow"""
+ tasks: [Task!]!
+}
+
+"""All tasks in the workflow have failed"""
+type WorkflowFailedStatus {
+ """Time at which this workflow started"""
+ startTime: DateTime!
+
+ """Time at which this workflow completed"""
+ endTime: DateTime!
+
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+
+ """Tasks created by the workflow"""
+ tasks: [Task!]!
+}
+
+"""All the supported Workflows filters"""
+input WorkflowFilter {
+ """
+ The status of the workflow (e.g., pending, running, succeeded, failed, error)
+ """
+ workflowStatusFilter: WorkflowStatusFilter
+
+ """The fedid of the user who created the workflow"""
+ creator: Creator
+
+ """The workflow template"""
+ template: Template
+
+ """Additional label selectors for filtering workflows"""
+ labelSelectors: [LabelSelector!]
+}
+
+"""Supported operators for label selection in workflows"""
+enum WorkflowLabelSelectorOperator {
+ """Match resources with an exact label value."""
+ EQ
+
+ """
+ Match resources with a label value that is not equal to a specified value.
+ """
+ NE
+
+ """Match resources with a label value in a set of values."""
+ IN
+
+ """Match resources with a label value not in a set of values."""
+ NOT_IN
+
+ """
+ Match resources that have a specific label key, regardless of its value.
+ """
+ EXISTS
+
+ """Match resources that do not have a specific label key."""
+ DOES_NOT_EXIST
+}
+
+type WorkflowPendingStatus {
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+}
+
+type WorkflowRunningStatus {
+ """Time at which this workflow started"""
+ startTime: DateTime!
+
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+
+ """Tasks created by the workflow"""
+ tasks: [Task!]!
+}
+
+"""The status of a workflow"""
+union WorkflowStatus = WorkflowPendingStatus | WorkflowRunningStatus | WorkflowSucceededStatus | WorkflowFailedStatus | WorkflowErroredStatus
+
+"""Represents workflow status filters"""
+input WorkflowStatusFilter {
+ pending: Boolean! = false
+ running: Boolean! = false
+ succeeded: Boolean! = false
+ failed: Boolean! = false
+ error: Boolean! = false
+}
+
+"""All tasks in the workflow have succeded"""
+type WorkflowSucceededStatus {
+ """Time at which this workflow started"""
+ startTime: DateTime!
+
+ """Time at which this workflow completed"""
+ endTime: DateTime!
+
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+
+ """Tasks created by the workflow"""
+ tasks: [Task!]!
+}
+
+type WorkflowTemplate {
+ """The name given to the workflow template, globally unique"""
+ name: String!
+
+ """The group who maintains the workflow template"""
+ maintainer: String!
+
+ """A human readable title for the workflow template"""
+ title: String
+
+ """A human readable description of the workflow which is created"""
+ description: String
+
+ """The repository storing the code associated with this template."""
+ repository: String
+
+ """A JSON Schema describing the arguments of a Workflow Template"""
+ arguments: JSON!
+
+ """
+ A JSON Forms UI Schema describing how to render the arguments of the Workflow Template
+ """
+ uiSchema: JSON
+
+ """Information about where the template is obtained from"""
+ templateSource: TemplateSource
+}
+
+type WorkflowTemplateConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [WorkflowTemplateEdge!]!
+
+ """A list of nodes."""
+ nodes: [WorkflowTemplate!]!
+}
+
+"""An edge in a connection."""
+type WorkflowTemplateEdge {
+ """The item at the end of the edge"""
+ node: WorkflowTemplate!
+
+ """A cursor for use in pagination"""
+ cursor: String!
+}
+
+"""Supported label filters for ClusterWorkflowTemplates"""
+input WorkflowTemplatesFilter {
+ """The science group owning the template eg imaging"""
+ scienceGroup: [ScienceGroup!]
+}
+
+"""A JSON schema"""
+type JSONSchema {
+ """The identifier of the schema"""
+ id: String!
+
+ """A URL from which the schema can be accessed"""
+ url: String!
+
+ """The type of object the shema describes (if known)"""
+ type: String
+
+ """The title of the schema"""
+ title: String
+
+ """The version of the schema"""
+ version: String
+
+ """The instrument the schema was created for"""
+ instrument: String
+
+ """The description og the schema"""
+ description: String
+}
+
+"""Values required to create an experiment definition"""
+input CreateExperimentDefinitionInput {
+ name: String!
+ data: JSON!
+ dataSchemaUrl: String!
+ proposalNumber: Int!
+ instrumentSessionNumber: Int!
+}
+
+"""Return type when creating an experiment definition"""
+type CreateExperimentDefinitionResponse {
+ """Whether the operation has succeeded without validation errors"""
+ success: Boolean!
+
+ """Experiment Definition that has been created"""
+ experimentDefinition: ExperimentDefinition
+
+ """Errors that occurred during validation"""
+ errors: [ExperimentDefinitionErrorDetails!]!
+}
+
+"""Values required for the createExperiments mutation"""
+input CreateExperimentsInput {
+ experiments: [ExperimentInput!]!
+}
+
+"""Return type when updating an experiment definition"""
+type CreateExperimentsResponse {
+ """Whether the operation has succeeded without errors"""
+ success: Boolean!
+
+ """Experiments created"""
+ experiments: [Experiment!]
+
+ """Errors that occurred during experiments creation"""
+ errors: [String!]!
+}
+
+input DatetimeFilterInputV2 {
+ """
+ Will filter to items where the `DateTime` field is greater than (i.e. after) the provided value
+ """
+ greaterThan: DateTime = null
+
+ """
+ Will filter to items where the `DateTime` field is less than (i.e. before) the provided value
+ """
+ lessThan: DateTime = null
+}
+
+"""The details of an deleted record validation error"""
+type DeleteErrorDetails {
+ """The type of error that occurred"""
+ type: String!
+
+ """
+ Tuple of strings identifying where in the record schema the error occurred.
+ """
+ location: [String!]!
+
+ """A human readable error message."""
+ message: String!
+}
+
+"""Return type when marking an record as deleted"""
+type DeleteExperimentResponse {
+ """Whether the operation has succeeded without validation errors"""
+ success: Boolean!
+
+ """Errors that occurred during validation"""
+ errors: [DeleteErrorDetails!]!
+}
+
+"""Return type when marking an record as deleted"""
+type DeletedExperimentDefinitionResponse {
+ """Whether the operation has succeeded without validation errors"""
+ success: Boolean!
+
+ """Errors that occurred during validation"""
+ errors: [DeleteErrorDetails!]!
+}
+
+type Experiment {
+ id: UUID!
+ name: String!
+ createdTime: DateTime!
+ updatedTime: DateTime!
+ deleted: Boolean!
+ experimentDefinition: ExperimentDefinition!
+ createdBy: String!
+ modifiedBy: String!
+
+ """The sample that this experiment is associated with"""
+ sample: Sample!
+}
+
+type ExperimentConnection {
+ edges: [ExperimentEdge!]!
+ pageInfo: PageInfo!
+}
+
+type ExperimentDefinition {
+ id: UUID!
+ name: String!
+ createdTime: DateTime!
+ updatedTime: DateTime!
+ data: JSON!
+ dataSchemaUrl: String!
+ proposalNumber: Int!
+ instrumentSessionNumber: Int!
+ deleted: Boolean!
+ createdBy: String!
+ modifiedBy: String!
+
+ """
+ The instrument session that this experiment definition is associated with
+ """
+ instrumentSession: InstrumentSession!
+
+ """Experiments associated with this experiment definition"""
+ experiments: [Experiment!]!
+}
+
+type ExperimentDefinitionConnection {
+ edges: [ExperimentDefinitionEdge!]!
+ pageInfo: PageInfo!
+}
+
+type ExperimentDefinitionEdge {
+ cursor: String!
+ node: ExperimentDefinition!
+}
+
+"""The details of an experiment definition validation error"""
+type ExperimentDefinitionErrorDetails {
+ """The type of error that occurred"""
+ type: String!
+
+ """
+ Tuple of strings identifying where in the experiment definition schema the error occurred.
+ """
+ location: [String!]!
+
+ """A human readable error message."""
+ message: String!
+}
+
+"""Values required to filter a list of experiment definitions"""
+input ExperimentDefinitionFilterInput {
+ """Filter by the name of experiment definitions"""
+ name: StringFilterInputV2 = null
+
+ """Filter by the created datetime of experiment definitions"""
+ createdTime: DatetimeFilterInputV2 = null
+
+ """Filter by the update datetime of experiment definitions"""
+ updatedTime: DatetimeFilterInputV2 = null
+
+ """Filter by the data schema URL of experiment definitions"""
+ dataSchemaUrl: StringFilterInputV2 = null
+
+ """Filter within the JSON data of experiment definitions"""
+ data: [JSONFilterInputV2!] = null
+}
+
+"""Mutations for a given experiment defintion"""
+type ExperimentDefinitionMutations {
+ updateExperimentDefinition(input: UpdateExperimentDefinitionInput!): UpdateExperimentDefinitionResponse!
+ createExperiments(input: CreateExperimentsInput!): CreateExperimentsResponse!
+}
+
+type ExperimentEdge {
+ cursor: String!
+ node: Experiment!
+}
+
+"""Values required to create an experiment"""
+input ExperimentInput {
+ name: String!
+ sampleId: UUID!
+}
+
+type InstrumentSession {
+ instrumentSessionNumber: Int!
+ proposal: Proposal
+
+ """Experiment Definitions"""
+ experimentDefinitions(first: Int = null, last: Int = null, after: String = null, before: String = null): ExperimentDefinitionConnection!
+
+ """Experiments associated with this session"""
+ experiments(first: Int = null, last: Int = null, after: String = null, before: String = null): ExperimentConnection!
+ instrumentSessionId: Int! @deprecated(reason: "instrument_session_id is deprecated and will be removed in a future version.")
+ instrumentSessionReference: String
+ startTime: DateTime
+ endTime: DateTime
+ type: String
+ state: String
+ riskRating: String
+ instrument: Instrument!
+ roles: [InstrumentSessionRole!]!
+
+ """Samples associated with a given instrument session"""
+ samples(first: Int = null, filter: SampleFilterInput! = {schemaUrl: null, createdTime: null, updatedTime: null, name: null, data: null}, before: String = null, after: String = null, last: Int = null, orderBy: SampleOrder! = {name: null, createdTime: null, updatedTime: null}): SampleConnection!
+}
+
+"""Values required to uniquely identify an instrument session"""
+input InstrumentSessionInput {
+ proposalNumber: Int!
+ instrumentSessionNumber: Int!
+}
+
+input JSONFilterConditionInput @oneOf {
+ stringFilter: StringFilterInputV2 = null
+ datetimeFilter: DatetimeFilterInputV2 = null
+ numericFilter: NumericFilterInputV2 = null
+}
+
+input JSONFilterInputV2 {
+ """The JSON path to the field to filter. Must start with '$.'"""
+ path: String!
+
+ """The filter operation to apply to the field"""
+ condition: JSONFilterConditionInput!
+}
+
+input NumericFilterInputV2 {
+ """
+ Will filter to items where the numeric field is greater than the provided value
+ """
+ greaterThan: Float = null
+
+ """
+ Will filter to items where the numeric field is less than the provided value
+ """
+ lessThan: Float = null
+}
+
+type Proposal {
+ proposalNumber: Int!
+ proposalCategory: String
+ title: String
+ summary: String
+ state: ProposalState!
+ proposalReference: String
+ instrumentSessions(first: Int = null, last: Int = null, after: String = null, before: String = null, sortBy: [InstrumentSessionSortInput!] = null, filterBy: InstrumentSessionFilterInput = null): InstrumentSessionConnection!
+ instruments: [Instrument!]!
+ roles: [ProposalAccount!]!
+}
+
+type Sample {
+ id: UUID!
+ experiments: [Experiment!]!
+ name: String!
+ data: JSON!
+ createdTime: DateTime!
+ updatedTime: DateTime!
+ dataSchemaUrl: String!
+ deleted: Boolean!
+
+ """Samples from which this sample is derived"""
+ parents(first: Int = null, before: String = null, after: String = null, last: Int = null): SampleConnection!
+
+ """Samples derived from this sample"""
+ children(first: Int = null, before: String = null, after: String = null, last: Int = null): SampleConnection!
+
+ """Events linked to this sample"""
+ events(first: Int = null, before: String = null, after: String = null, last: Int = null): SampleEventConnection!
+
+ """The JSON schema that the sample's `data` conforms to"""
+ dataSchema: JSON!
+
+ """The instrument sessions that this sample is associated with"""
+ instrumentSessions: [InstrumentSession!]!
+ images: [SampleImage!]!
+
+ """The container containing the sample"""
+ container: Container
+
+ """The sample's position within a container"""
+ positionInContainer: ContainerPosition
+}
+
+input StringFilterInputV2 {
+ """
+ Will filter to items where the `String` field is a member of the provided value
+ """
+ in: [String!] = null
+
+ """
+ Will filter to items where the `String` field is equal to the provided value
+ """
+ equalTo: String = null
+
+ """
+ Will filter to items where the `String` field is not equal to the provided value
+ """
+ notEqualTo: String = null
+
+ """
+ Will filter to items where the `String` field is not a member of the provided value
+ """
+ notIn: [String!] = null
+
+ """
+ Will filter to items where the `String` field is contains the provided value
+ """
+ contains: String = null
+}
+
+scalar UUID
+
+"""Values that can be updated on experiment definition"""
+input UpdateExperimentDefinitionInput {
+ name: String
+ data: JSON
+ dataSchemaUrl: String
+}
+
+"""Return type when updating an experiment definition"""
+type UpdateExperimentDefinitionResponse {
+ """Whether the operation has succeeded without validation errors"""
+ success: Boolean!
+
+ """Experiment Definition that has been updated"""
+ experimentDefinition: ExperimentDefinition
+
+ """Errors that occurred during validation"""
+ errors: [ExperimentDefinitionErrorDetails!]!
+}
+
+type Account {
+ username: String!
+ emailAddress: String
+ title: String
+ givenName: String
+ familyName: String
+ type: AccountType!
+ state: AccountState!
+ accountId: Int! @deprecated(reason: "account_id is deprecated and will be removed in a future version.")
+ proposalRoles: [ProposalAccount!]!
+ instrumentSessionRoles: [InstrumentSessionRole!]!
+}
+
+input AccountFilterInput {
+ emailAddress: StringFilterInput = null
+ givenName: StringFilterInput = null
+ familyName: StringFilterInput = null
+}
+
+enum AccountSortField {
+ family_name
+ given_name
+}
+
+input AccountSortInput {
+ field: AccountSortField!
+ orderBy: SortOrder!
+}
+
+enum AccountState {
+ enabled
+ disabled
+}
+
+enum AccountType {
+ user
+ staff
+ functional
+}
+
+input CreateInstrumentSessionInput {
+ """Number of the proposal the session is for"""
+ proposalNumber: Int!
+
+ """Name of the instrument the session is for"""
+ instrumentName: String!
+
+ """
+ Instrument Session information that isn't needed by the Session Service but should be passed through the UAS
+ """
+ additionalInfo: JSON = null
+}
+
+input DateTimeFilterInput {
+ eq: DateTime = null
+ neq: DateTime = null
+ gt: DateTime = null
+ lt: DateTime = null
+ gte: DateTime = null
+ lte: DateTime = null
+}
+
+type Instrument {
+ name: String!
+ key: String!
+ scienceGroup: String
+ description: String
+ proposals(first: Int = null, last: Int = null, after: String = null, before: String = null, sortBy: [ProposalSortInput!] = null, filterBy: ProposalFilterInput = null): ProposalConnection!
+ instrumentSessions(first: Int = null, last: Int = null, after: String = null, before: String = null, sortBy: [InstrumentSessionSortInput!] = null, filterBy: InstrumentSessionFilterInput = null): InstrumentSessionConnection!
+
+ """
+ Get paginated staff accounts for this instrument. Trusted users see all accounts; fedid users see only accounts linked to sessions or instruments they are associated with.
+ """
+ staff(first: Int = null, last: Int = null, after: String = null, before: String = null, sortBy: [AccountSortInput!] = null, filterBy: AccountFilterInput = null): StaffConnection!
+}
+
+type InstrumentSessionConnection {
+ edges: [InstrumentSessionEdge!]!
+ pageInfo: PageInfo!
+}
+
+type InstrumentSessionEdge {
+ cursor: String!
+ node: InstrumentSession!
+}
+
+input InstrumentSessionFilterInput {
+ instrumentSessionNumber: IntFilterInput = null
+ startTime: DateTimeFilterInput = null
+ endTime: DateTimeFilterInput = null
+ type: StringFilterInput = null
+ state: InstrumentSessionStateFilterInput = null
+ riskRating: StringFilterInput = null
+ proposalNumber: IntFilterInput = null
+}
+
+type InstrumentSessionMutations {
+ instrumentSessionNumber: Int!
+ proposalNumber: Int!
+
+ """Create or validate samples associated with this instrument session"""
+ createOrValidateSamples(input: CreateOrValidateSampleInputBase!): CreateSamplesResponse!
+}
+
+type InstrumentSessionRole {
+ instrumentSession: InstrumentSession!
+ account: Account!
+ role: String!
+ onSite: Boolean!
+}
+
+enum InstrumentSessionSortField {
+ instrumentSessionNumber
+ startTime
+ endTime
+ type
+ state
+ riskRating
+ proposalNumber
+}
+
+input InstrumentSessionSortInput {
+ field: InstrumentSessionSortField!
+ orderBy: SortOrder!
+}
+
+enum InstrumentSessionState {
+ CANCELLED
+ COMPLETED
+ FUTURE
+ IN_PROGRESS
+}
+
+input InstrumentSessionStateFilterInput {
+ eq: InstrumentSessionState = null
+ neq: InstrumentSessionState = null
+}
+
+input IntFilterInput {
+ eq: Int = null
+ neq: Int = null
+ gt: Int = null
+ lt: Int = null
+ gte: Int = null
+ lte: Int = null
+}
+
+type ProposalAccount {
+ proposal: Proposal!
+ account: Account!
+ role: String!
+}
+
+type ProposalConnection {
+ edges: [ProposalEdge!]!
+ pageInfo: PageInfo!
+}
+
+type ProposalEdge {
+ cursor: String!
+ node: Proposal!
+}
+
+input ProposalFilterInput {
+ proposalNumber: IntFilterInput = null
+ proposalCategory: StringFilterInput = null
+ title: StringFilterInput = null
+ state: ProposalStateFilterInput = null
+}
+
+enum ProposalSortField {
+ proposalNumber
+}
+
+input ProposalSortInput {
+ field: ProposalSortField!
+ orderBy: SortOrder!
+}
+
+enum ProposalState {
+ OPEN
+ CLOSED
+ CANCELLED
+}
+
+input ProposalStateFilterInput {
+ eq: ProposalState = null
+ neq: ProposalState = null
+}
+
+enum SortOrder {
+ ASC
+ DESC
+}
+
+type Staff {
+ username: String!
+ emailAddress: String
+ title: String
+ givenName: String
+ familyName: String
+ type: AccountType!
+ state: AccountState!
+}
+
+type StaffConnection {
+ edges: [StaffEdge!]!
+ pageInfo: PageInfo!
+}
+
+type StaffEdge {
+ cursor: String!
+ node: Staff!
+}
+
+input StringFilterInput {
+ eq: String = null
+ neq: String = null
+ contains: String = null
+ notContains: String = null
+ startsWith: String = null
+ endsWith: String = null
+}
+
+input AddSampleEventInput {
+ description: String!
+}
+
+input CreateOrValidateSampleInput {
+ """URL of the JSON schema the samples' `data` should be validated against"""
+ dataSchemaUrl: String!
+
+ """Samples to be created"""
+ samples: [SampleIn!]!
+
+ """
+ Whether or not the provided samples should only be validated and not created
+ """
+ validateOnly: Boolean! = false
+
+ """Number of the proposal the samples should be associated with"""
+ proposalNumber: Int!
+
+ """Number of the instrument session the samples should be associated with"""
+ instrumentSessionNumber: Int!
+}
+
+input CreateOrValidateSampleInputBase {
+ """URL of the JSON schema the samples' `data` should be validated against"""
+ dataSchemaUrl: String!
+
+ """Samples to be created"""
+ samples: [SampleIn!]!
+
+ """
+ Whether or not the provided samples should only be validated and not created
+ """
+ validateOnly: Boolean! = false
+}
+
+"""Return type when creating or validating samples"""
+type CreateSamplesResponse {
+ """Whether the operation has succeeded without validation errors"""
+ success: Boolean!
+
+ """Samples that have been created"""
+ samples: [Sample!]!
+
+ """Errors that occurred during sample validation"""
+ errors: [SampleValidationError!]!
+}
+
+input DatetimeOperatorInput {
+ """
+ Will filter to items where the `DateTime` field is greater than (i.e. after) the provided value
+ """
+ gt: DateTime = null
+
+ """
+ Will filter to items where the `DateTime` field is less than (i.e. before) the provided value
+ """
+ lt: DateTime = null
+}
+
+"""Return type when deleting a sample"""
+type DeleteSampleResponse {
+ """Whether the operation has succeeded"""
+ success: Boolean!
+
+ """Errors that occurred during the operation"""
+ errors: [String!]!
+}
+
+"""The details of sample validation error"""
+type ErrorDetails {
+ """The type of error that occurred"""
+ type: String!
+
+ """
+ Tuple of strings identifying where in the sample schema the error occurred.
+ """
+ location: [String!]!
+
+ """A human readable error message."""
+ message: String!
+}
+
+input JSONOperator @oneOf {
+ stringOperator: StringOperatorInput = null
+ datetimeOperator: DatetimeOperatorInput = null
+ numericOperator: NumericOperatorInput = null
+}
+
+input JSONOperatorInput {
+ """A JSON path specifying the value to filter. Must start with '$.'"""
+ path: String!
+
+ """The operator to apply to the JSON field"""
+ operator: JSONOperator!
+}
+
+input NumericOperatorInput {
+ """
+ Will filter to items where the numeric field is greater than the provided value
+ """
+ gt: Float = null
+
+ """
+ Will filter to items where the numeric field is less than the provided value
+ """
+ lt: Float = null
+}
+
+"""Return type when restoring a deleted sample"""
+type RestoreSampleResponse {
+ """Whether the operation has succeeded"""
+ success: Boolean!
+
+ """Errors that occurred during the operation"""
+ errors: [String!]!
+}
+
+type SampleConnection {
+ edges: [SampleEdge!]!
+ pageInfo: PageInfo!
+}
+
+type SampleEdge {
+ cursor: String!
+ node: Sample!
+}
+
+type SampleEvent {
+ id: UUID!
+ timestamp: DateTime!
+ description: String!
+}
+
+type SampleEventConnection {
+ edges: [SampleEventEdge!]!
+ pageInfo: PageInfo!
+}
+
+type SampleEventEdge {
+ cursor: String!
+ node: SampleEvent!
+}
+
+input SampleFilterInput {
+ """Filter on the `schemaUrl` field of `Sample`"""
+ schemaUrl: StringOperatorInput = null
+
+ """Filter on the `createdTime` field of `Sample`"""
+ createdTime: DatetimeOperatorInput = null
+
+ """Filter on the `createdTime` field of `Sample`"""
+ updatedTime: DatetimeOperatorInput = null
+
+ """Filter on the `name` field of `Sample`"""
+ name: StringOperatorInput = null
+
+ """Filter on the `data` field of `Sample`"""
+ data: [JSONOperatorInput!] = null
+}
+
+type SampleImage {
+ url: String!
+ filename: String!
+}
+
+input SampleIn {
+ """Name of the sample"""
+ name: String!
+
+ """Data of the sample"""
+ data: JSON!
+}
+
+type SampleMutations {
+ sampleId: UUID!
+ updateSample(input: UpdateSampleInput!): UpdateSampleResponse!
+ linkInstrumentSessionToSample(proposalNumber: Int!, instrumentSessionNumber: Int!): Void
+ addSampleEvent(sampleEvent: AddSampleEventInput!): SampleEvent!
+ createSampleImageUploadUrl(filename: String!, contentType: String!, contentLength: Int!): String!
+
+ """Assign this sample to a container"""
+ addSampleToContainer(input: AddSampleToContainerInput!): AddSampleToContainerResponse!
+}
+
+input SampleOrder {
+ name: SortingOrder = null
+ createdTime: SortingOrder = null
+ updatedTime: SortingOrder = null
+}
+
+"""The details of errors occurred when validating a sample"""
+type SampleValidationError {
+ """
+ The index of the sample in CreateSampleInput.samples for which the error occurred
+ """
+ index: Int!
+
+ """Errors that occurred when validating the sample"""
+ errors: [ErrorDetails!]!
+}
+
+enum SortingOrder {
+ ASC
+ DESC
+}
+
+"""Conditions used to filter results based on the value of a String field"""
+input StringOperatorInput {
+ """
+ Will filter to items where the `String` field is equal to the provided value
+ """
+ eq: String = null
+
+ """
+ Will filter to items where the `String` field is not equal to the provided value
+ """
+ ne: String = null
+
+ """
+ Will filter to items where the `String` field is a member of the provided value
+ """
+ in: [String!] = null
+
+ """
+ Will filter to items where the `String` field is not a member of the provided value
+ """
+ nin: [String!] = null
+
+ """
+ Will filter to items where the `String` field is contains the provided value
+ """
+ contains: String = null
+}
+
+input UpdateSampleInput {
+ """Name of the sample"""
+ name: String
+
+ """Data of the sample"""
+ data: JSON
+
+ """URL of the JSON schema the samples' `data` should be validated against"""
+ dataSchemaUrl: String
+}
+
+"""Return type when updating a sample"""
+type UpdateSampleResponse {
+ """Whether the operation has succeeded"""
+ success: Boolean!
+
+ """Sample that has been updated"""
+ sample: Sample
+
+ """Errors that occurred during the operation"""
+ errors: [SampleValidationError!]!
+}
+
+"""Represents NULL values"""
+scalar Void
+
+"""Values required to set a container's children"""
+input AddContainersToContainerInput {
+ containerPositions: [ChildContainerPositionInput!]!
+}
+
+type AddContainersToContainerResponse {
+ success: Boolean!
+ containerPositions: [ContainerPosition!]
+ errors: [String!]!
+}
+
+input AddSampleToContainerInput {
+ containerPosition: ContainerPositionInput!
+}
+
+type AddSampleToContainerResponse {
+ success: Boolean!
+ samplePosition: SamplePostion
+ errors: [String!]!
+}
+
+input AddSamplesToContainerInput {
+ samplePositions: [SamplePositionInput!]!
+}
+
+type AddSamplesToContainerResponse {
+ success: Boolean!
+ samplePositions: [SamplePostion!]
+ errors: [String!]!
+}
+
+"""Values required to assign a contianer to an instrument"""
+input AssignContainerToInstrumentInput {
+ """The unique key of the instrument this container should be assigned to"""
+ instrumentKey: String!
+}
+
+type AssignContainerToInstrumentResponse {
+ success: Boolean!
+ errors: [String!]!
+}
+
+"""Values required to assign a contianer to an instrument session"""
+input AssignContainerToInstrumentSessionInput {
+ """The instrument session this container should be assigned to"""
+ instrumentSession: InstrumentSessionInput!
+}
+
+type AssignContainerToInstrumentSessionResponse {
+ success: Boolean!
+ errors: [String!]!
+}
+
+"""Values required to specify a container's position within a container"""
+input ChildContainerPositionInput {
+ childContainerId: UUID!
+ position: Int = null
+}
+
+"""A container that can store samples and/or other containers"""
+type Container {
+ """The unique identifier of this container"""
+ id: UUID!
+
+ """When the container was created in the service"""
+ createdTime: DateTime!
+
+ """When the container was last updated in the service"""
+ updatedTime: DateTime!
+
+ """The name of the container"""
+ name: String!
+
+ """The type of this container"""
+ type: ContainerType!
+
+ """The manufacturer's serial number of the container"""
+ serialNumber: String
+
+ """The facility barcode for the container"""
+ barcode: String
+
+ """Whether or not the container has been marked as discarded"""
+ deleted: Boolean!
+
+ """The user that created the container"""
+ createdBy: String!
+
+ """The user that last modified the container"""
+ modifiedBy: String!
+
+ """
+ The instrument sessions that this container is currently associated with
+ """
+ instrumentSessions: [InstrumentSession!]!
+
+ """Sample positions within this container"""
+ samplePositions: [SamplePostion!]!
+
+ """The top-level fixed location where this container is stored"""
+ location: Container
+ parent: Container
+
+ """The container position occupied by this container"""
+ positionInParent: ContainerPosition
+ children: [Container!]!
+
+ """Container positions within this container"""
+ containerPositions: [ContainerPosition!]!
+}
+
+type ContainerConnection {
+ edges: [ContainerEdge!]!
+ pageInfo: PageInfo!
+}
+
+type ContainerEdge {
+ cursor: String!
+ node: Container!
+}
+
+"""Values required to filter a list of containers"""
+input ContainerFilterInput {
+ """Filter using the id of the container's parent"""
+ parentId: UUIDFilterInputV2 = null
+
+ """Filter using the of the container's children"""
+ childId: UUIDFilterInputV2 = null
+
+ """Include/exclude 'deleted' containers"""
+ includeDeleted: Boolean! = false
+
+ """Filter to containers with/without child containers"""
+ hasChildren: Boolean = null
+
+ """Filter to containers with/without a parent"""
+ hasParent: Boolean = null
+}
+
+type ContainerMutations {
+ """Update this container"""
+ updateContainer(input: UpdateContainerInput!): UpdateContainerResponse!
+
+ """Add containers to this container"""
+ addContainersToContainer(input: AddContainersToContainerInput!): AddContainersToContainerResponse!
+
+ """Remove containers from this container"""
+ removeContainersFromContainer(input: RemoveContainersFromContainerInput!): RemoveContainersFromContainerResponse!
+
+ """Set the container that this container is contained within"""
+ setParentContainer(input: SetParentContainerInput!): SetParentContainerResponse!
+
+ """Add samples to this container"""
+ addSamplesToContainer(input: AddSamplesToContainerInput!): AddSamplesToContainerResponse!
+
+ """Remove samples from this container"""
+ removeSamplesFromContainer(input: RemoveSamplesFromContainerInput!): RemoveSamplesFromContainerResponse!
+
+ """Associate this container with an instrument session"""
+ assignContainerToInstrumentSession(input: AssignContainerToInstrumentSessionInput!): AssignContainerToInstrumentSessionResponse!
+
+ """Unassign this container from an instrument session"""
+ unassignContainerFromInstrumentSession(input: UnassignContainerFromInstrumentSessionInput!): UnassignContainerFromInstrumentSessionResponse!
+
+ """Associate this container with an instrument"""
+ assignContainerToInstrument(input: AssignContainerToInstrumentInput!): AssignContainerToInstrumentResponse!
+
+ """Unassign this container from an instrument"""
+ unassignContainerFromInstrument(input: UnassignContainerFromInstrumentInput!): UnassignContainerFromInstrumentResponse!
+}
+
+type ContainerPosition {
+ position: Int
+ container: Container
+}
+
+"""Values required to specify a position within a container"""
+input ContainerPositionInput {
+ parentContainerId: UUID!
+ position: Int = null
+}
+
+"""A type of container"""
+type ContainerType {
+ """The unique name of the container type"""
+ name: String!
+
+ """A description of this type of container"""
+ description: String!
+
+ """The number of containers the container can hold"""
+ numberOfContainerPositions: Int
+
+ """The number of samples the container can hold"""
+ numberOfSamplePositions: Int
+
+ """Used to distingush storage locations from moveable containers"""
+ isFixedLocation: Boolean!
+
+ """The user that created the container type"""
+ createdBy: String!
+
+ """The user user that last modified the container type"""
+ modifiedBy: String!
+}
+
+type ContainerTypeConnection {
+ edges: [ContainerTypeEdge!]!
+ pageInfo: PageInfo!
+}
+
+type ContainerTypeEdge {
+ cursor: String!
+ node: ContainerType!
+}
+
+type ContainerTypeMutations {
+ """Update this container type"""
+ updateContainerType(input: UpdateContainerTypeInput!): UpdateContainerTypeResponse!
+}
+
+"""Values required to create a container"""
+input CreateContainerInput {
+ """The unique key of an instrument associated with this container"""
+ instrumentKey: String = null
+
+ """An instrument session associated with this container"""
+ instrumentSession: InstrumentSessionInput = null
+
+ """The name of the type of this container"""
+ type: String!
+
+ """The name of the container"""
+ name: String!
+
+ """The serial number of this container"""
+ serialNumber: String = null
+
+ """The facility barcode of the container"""
+ barcode: String = null
+}
+
+type CreateContainerResponse {
+ success: Boolean!
+ container: Container
+ errors: [String!]!
+}
+
+"""Values required to create a container type"""
+input CreateContainerTypeInput {
+ """The unique key of an instrument associated with this container type"""
+ instrumentKey: String!
+
+ """The unique name of this container type"""
+ name: String!
+
+ """A description of this container type"""
+ description: String!
+
+ """Whether or not this container type is a fixed storage location"""
+ isFixedLocation: Boolean!
+
+ """The number of container positions within this container"""
+ numberOfContainerPositions: Int = 0
+
+ """The number of sample positions within this container"""
+ numberOfSamplePositions: Int = 0
+}
+
+type CreateContainerTypeResponse {
+ success: Boolean!
+ containerType: ContainerType
+ errors: [String!]!
+}
+
+"""Values required to remove containers from a container"""
+input RemoveContainersFromContainerInput {
+ containerIds: [UUID!]!
+}
+
+type RemoveContainersFromContainerResponse {
+ success: Boolean!
+}
+
+"""Values required to remove samples from a container"""
+input RemoveSamplesFromContainerInput {
+ sampleIds: [UUID!]!
+}
+
+type RemoveSamplesFromContainerResponse {
+ success: Boolean!
+}
+
+"""Values required to specify a sample's position within a container"""
+input SamplePositionInput {
+ sampleId: UUID!
+ position: Int = null
+}
+
+type SamplePostion {
+ position: Int
+ sample: Sample
+}
+
+"""Values required to set a container's parent"""
+input SetParentContainerInput {
+ containerPosition: ContainerPositionInput!
+}
+
+type SetParentContainerResponse {
+ success: Boolean!
+ containerPosition: ContainerPosition
+ errors: [String!]!
+}
+
+input UUIDFilterInputV2 {
+ """
+ Will filter to items where the `UUID` field is a member of the provided value
+ """
+ in: [UUID!] = null
+
+ """
+ Will filter to items where the `UUID` field is equal to the provided value
+ """
+ equalTo: UUID = null
+
+ """
+ Will filter to items where the `UUID` field is not equal to the provided value
+ """
+ notEqualTo: UUID = null
+
+ """
+ Will filter to items where the `UUID` field is not a member of the provided value
+ """
+ notIn: [UUID!] = null
+}
+
+"""Values required to unassign a contianer from an instrument"""
+input UnassignContainerFromInstrumentInput {
+ """
+ The unique key of the instrument this container should be unassigned from
+ """
+ instrumentKey: String!
+}
+
+type UnassignContainerFromInstrumentResponse {
+ success: Boolean!
+ errors: [String!]!
+}
+
+"""Values required to unassign a contianer from an instrument session"""
+input UnassignContainerFromInstrumentSessionInput {
+ """The instrument session this container should be unassigned from"""
+ instrumentSession: InstrumentSessionInput!
+}
+
+type UnassignContainerFromInstrumentSessionResponse {
+ success: Boolean!
+ errors: [String!]!
+}
+
+"""Values required to update a container"""
+input UpdateContainerInput {
+ """The name of the container"""
+ name: String
+
+ """The serial number of this container"""
+ serialNumber: String
+
+ """The facility barcode of the container"""
+ barcode: String
+}
+
+type UpdateContainerResponse {
+ success: Boolean!
+ container: Container
+ errors: [String!]!
+}
+
+"""Values required to update a container type"""
+input UpdateContainerTypeInput {
+ """The unique name of the container type"""
+ name: String
+
+ """A description of this container type"""
+ description: String
+
+ """Used to distingush storage locations from moveable containers"""
+ isFixedLocation: Boolean
+}
+
+type UpdateContainerTypeResponse {
+ success: Boolean!
+ containerType: ContainerType
+ errors: [String!]!
+}
\ No newline at end of file
diff --git a/packages/supergraph/tsconfig.json b/packages/supergraph/tsconfig.json
new file mode 100644
index 00000000..7bfeac26
--- /dev/null
+++ b/packages/supergraph/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "allowImportingTsExtensions": false,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 16a1376d..65837e7b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -510,6 +510,16 @@ importers:
specifier: '*'
version: 4.0.18(@types/node@25.9.2)(jiti@2.7.0)(jsdom@26.1.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))(msw@2.14.6(@types/node@25.9.2)(typescript@7.0.2))(terser@5.48.0)(yaml@2.9.0)
+ packages/supergraph:
+ dependencies:
+ relay-runtime:
+ specifier: ^20.1.1
+ version: 20.1.1
+ devDependencies:
+ '@types/relay-runtime':
+ specifier: ^20.1.1
+ version: 20.1.1
+
packages/vitest-conf:
dependencies:
vitest:
@@ -2307,6 +2317,9 @@ packages:
'@types/relay-runtime@19.0.3':
resolution: {integrity: sha512-pvpWWQq5e9KeESF8klQaP2igLLhr2bRd3XxVCxNpGElsPQiP6Mejr59RT9/OGY3O3i8jAGGQsshVe0QCQDbxNg==}
+ '@types/relay-runtime@20.1.1':
+ resolution: {integrity: sha512-loM2iJteknnJcsxrynmBVb7pIDkSkJUuCMnAa/oDFcrvaxkDvq8iy0J2UshMdDy14iJJocDblXeAu7c6Q1+Ucw==}
+
'@types/relay-test-utils@19.0.0':
resolution: {integrity: sha512-yC/wVgDetV+88HrHLKlesIfju3RGsp32vLs0IiwBPKD0npQSTrFddcP3FNPe5Kkk4/Y/kgY3oR/DA8g8Bmmppw==}
@@ -7734,6 +7747,8 @@ snapshots:
'@types/relay-runtime@19.0.3': {}
+ '@types/relay-runtime@20.1.1': {}
+
'@types/relay-test-utils@19.0.0':
dependencies:
'@types/react': 18.3.28
@@ -10227,7 +10242,7 @@ snapshots:
react-relay@20.1.1(react@18.3.1):
dependencies:
- '@babel/runtime': 7.28.6
+ '@babel/runtime': 7.29.7
fbjs: 3.0.5
invariant: 2.2.4
nullthrows: 1.1.1
@@ -10382,7 +10397,7 @@ snapshots:
relay-runtime@20.1.1:
dependencies:
- '@babel/runtime': 7.28.6
+ '@babel/runtime': 7.29.7
fbjs: 3.0.5
invariant: 2.2.4
transitivePeerDependencies: