GraphQL Schema Designer
Run ID: 69cb05d758b35c7ea758c2962026-03-30Development
PantheraHive BOS
BOS Dashboard

GraphQL Schema Designer: Architecture Plan

This document outlines the architectural plan for designing a comprehensive GraphQL schema, encompassing types, queries, mutations, subscriptions, resolvers, and integration examples. This plan ensures a robust, scalable, and maintainable GraphQL API.


Important Clarification Regarding "Study Plan" Request

The request included a directive to "Create a detailed study plan with: weekly schedule, learning objectives, recommended resources, milestones, and assessment strategies." Given the workflow step "plan_architecture" for a "GraphQL Schema Designer," the primary focus is on designing the architecture of a GraphQL schema itself, not a study plan for learning.

We have prioritized the architectural design of the GraphQL schema as per the workflow description. If the "study plan" request was intended for learning how to use a "GraphQL Schema Designer tool" or for general GraphQL development, please clarify, and we can generate that as a separate deliverable. For this step, we are presenting the architectural blueprint for the GraphQL schema.


1. Introduction: GraphQL Schema Architecture Overview

The goal is to design a GraphQL schema that acts as a unified interface to various backend services and data sources. This architecture plan focuses on creating a flexible, performant, and secure API layer that simplifies data fetching and manipulation for client applications.

2. Core Architectural Principles

Before diving into specifics, the following principles will guide the schema design:

3. Schema Structure and Organization

The schema will be organized using a "schema-first" or "code-first" approach, depending on the chosen implementation framework, but always with a strong emphasis on logical grouping.

3.1. Root Types

* Structure: Each field in the Query type will correspond to a top-level resource or collection clients can request.

* Examples: user(id: ID!): User, users(limit: Int, offset: Int): [User!]!, product(sku: String!): Product, searchProducts(query: String!): [Product!]!.

* Structure: Fields will typically take an Input object type as an argument and return a Payload object type containing the affected resource and a success status/errors.

* Examples: createUser(input: CreateUserInput!): CreateUserPayload, updateProduct(input: UpdateProductInput!): UpdateProductPayload, deleteOrder(id: ID!): DeleteOrderPayload.

* Structure: Fields will typically return a specific object type that represents the event being subscribed to.

* Examples: orderUpdated(id: ID!): Order, productPriceChanged(sku: String!): ProductPriceUpdate.

3.2. Object Types

Represent specific data entities within the system.

text • 313 chars
### 3.4. Payload Types (for Mutations)

Standardized return types for mutations, typically including the affected object and status information.

*   **Definition:** Encapsulates the result of a mutation, often including the created/updated object and an array of errors or a success flag.
*   **Examples:**
    
Sandboxed live preview

3.5. Scalar Types

Built-in (String, Int, Float, Boolean, ID) and custom scalar types for specific data formats.

  • Custom Scalars:

* DateTime: For ISO 8601 formatted date-time strings.

* JSON: For arbitrary JSON data (use sparingly).

* EmailAddress: For validated email strings.

* URL: For validated URL strings.

3.6. Enum Types

Represent a fixed set of allowed values for a field.

  • Examples: OrderStatus (PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED), ProductCategory (ELECTRONICS, CLOTHING, BOOKS).

3.7. Interface and Union Types

For polymorphic data structures.

  • Interfaces: Define a set of fields that multiple object types must implement.

* Example: Node interface with an id: ID! field, implemented by User, Product, Order.

  • Unions: Represent a type that can be one of several object types.

* Example: SearchResult union (User | Product | Order) for a universal search query.

4. Resolver Architecture

Resolvers are the core logic that fetches the data for each field in the schema.

4.1. Resolver Structure

  • Modular Organization: Resolvers will be organized by domain or feature. Each Type in the schema will have its own resolver file or directory (e.g., UserResolvers.js, ProductResolvers.js).
  • Parent-Child Resolution: Resolvers will naturally chain, with child field resolvers receiving the parent object as an argument.
  • Asynchronous Operations: All data fetching will be asynchronous, using async/await patterns.

4.2. Data Fetching Strategies

  • Direct Database Access: For primary data sources, resolvers will directly interact with ORMs/ODMs (e.g., Prisma, Sequelize, Mongoose) or raw SQL clients.
  • REST API Integration: For integrating with existing microservices or third-party APIs, resolvers will make HTTP requests.
  • Microservice Communication: Utilize internal communication mechanisms (e.g., gRPC, message queues) for service-to-service data fetching.
  • DataLoader for Batching and Caching:

* Purpose: To solve the N+1 problem by batching requests to backend services/databases and caching results within a single request.

* Implementation: Each data source (e.g., users, products) will have a corresponding DataLoader instance, typically instantiated per-request in the GraphQL context.

Example: A DataLoader for fetching users by ID will collect all requested user IDs within a tick of the event loop and make a single database query SELECT FROM users WHERE id IN (...).

4.3. Context Object

A context object will be passed to every resolver, containing request-scoped information and shared resources.

  • Contents:

* Authenticated user information.

* DataLoaders instances.

* Database connections/ORMs.

* Logger instances.

* API clients for external services.

* Request-specific metadata (e.g., correlation IDs).

5. Data Source Integration Examples

The GraphQL API will act as an aggregation layer, integrating various backend systems.

5.1. Relational Database (e.g., PostgreSQL with Prisma/Sequelize)

  • Integration: ORM models will map directly to GraphQL object types where appropriate.
  • Resolvers:

* Query.user: Calls Prisma.user.findUnique({ where: { id } }).

* User.orders: Calls Prisma.order.findMany({ where: { userId: parent.id } }) (optimized with DataLoader).

* Mutation.createUser: Calls Prisma.user.create({ data: input }).

5.2. NoSQL Database (e.g., MongoDB with Mongoose)

  • Integration: Mongoose schemas will inform GraphQL object types.
  • Resolvers:

* Query.product: Calls ProductModel.findById(id).

* Product.reviews: Calls ReviewModel.find({ productId: parent.id }) (optimized with DataLoader).

* Mutation.updateProduct: Calls ProductModel.findByIdAndUpdate(id, input, { new: true }).

5.3. External REST APIs (e.g., Payment Gateway, Weather Service)

  • Integration: Custom API clients will encapsulate HTTP requests.
  • Resolvers:

* Query.paymentStatus(orderId: ID!): PaymentStatus: Calls paymentGatewayClient.getPaymentStatus(orderId).

* User.shippingInfo: Calls shippingService.getShippingInfo(parent.addressId).

5.4. Internal Microservices (e.g., Inventory Service, Notification Service)

  • Integration: gRPC clients or message queue publishers/subscribers.
  • Resolvers:

* Product.inventory: Calls inventoryServiceClient.getInventory(parent.sku).

* Mutation.placeOrder: Calls orderService.createOrder(input) which then publishes to notificationService.

6. Error Handling and Validation

Robust error management is crucial for a stable API.

  • GraphQL Errors: Utilize GraphQL's built-in error handling for syntax errors, validation errors, and resolver errors.

* errors array: Return a list of errors with message, path, and extensions (e.g., code, details).

  • Custom Error Types: Define custom error types in the schema (e.g., AuthenticationError, PermissionDeniedError, NotFoundError, ValidationError) for specific business logic failures.
  • Input Validation:

* Schema-level: Use non-null fields (!) to enforce required inputs.

* Resolver-level: Implement explicit validation checks within resolvers or use validation libraries (e.g., Joi, Yup) before interacting with data sources.

* Return errors in Payloads: For mutations, return specific validation errors within the errors field of the Payload type.

7. Security Considerations

Security will be integrated at multiple layers.

  • Authentication:

* Mechanism: Typically JWT (JSON Web Tokens) passed in the Authorization header.

* Implementation: Middleware will validate the JWT and populate the context object with the authenticated user's details.

  • Authorization (Access Control):

* Role-Based Access Control (RBAC) / Attribute-Based Access Control (ABAC):

* Schema-level Directives: Custom directives (e.g., @auth(roles: [ADMIN, USER]), @hasPermission(action: "read:product")) can be used to mark fields or types requiring specific permissions.

* Resolver-level Checks: Explicit checks within resolvers based on the context.user object. This is the most granular and robust approach.

* Field-level Security: Ensure users can only access fields they are authorized to see (e.g., an ADMIN can see User.email, but a regular USER cannot see other Users' emails).

  • Rate Limiting: Implement rate limiting at the API gateway or GraphQL server level to prevent abuse.
  • Query Depth/Complexity Limiting: Protect against expensive and malicious queries that could overload the server.
  • Input Sanitization: Ensure all user inputs are properly sanitized to prevent injection attacks.

8. Scalability and Performance

Architectural choices to ensure the API can handle increasing load.

  • DataLoader: As mentioned, crucial for batching and caching.
  • Caching:

* Resolver-level: Cache frequently accessed data from external services.

* Response Caching: Consider HTTP caching for queries (using Cache-Control headers) or dedicated GraphQL caching solutions (e.g., using a CDN or in-memory cache).

  • Database Optimization: Proper indexing, efficient queries, connection pooling.
  • Asynchronous Operations: Leverage Node.js's non-blocking I/O model.
  • Horizontal Scaling: The GraphQL server should be stateless (aside from DataLoader caches per request) to allow easy horizontal scaling across multiple instances.
  • Monitoring and Tracing: Implement robust monitoring to identify performance bottlenecks (see Section 9).

9. Deployment and Monitoring (High-Level)

*

gemini Output

GraphQL Schema Design: Blogging Platform

This document outlines a comprehensive GraphQL schema for a blogging platform, encompassing types, queries, mutations, subscriptions, resolver conceptualization, and client-side integration examples. The design prioritizes clarity, scalability, and adherence to GraphQL best practices.


1. Schema Overview & Core Concepts

A GraphQL schema is the core of any GraphQL service. It defines what data can be queried, mutated, and subscribed to by clients.

  • Type System: GraphQL has a strong type system that defines the shape of data.
  • Queries: Read-only operations to fetch data.
  • Mutations: Write operations to create, update, or delete data.
  • Subscriptions: Real-time operations to receive data updates pushed from the server.
  • Resolvers: Functions that populate data for each field in the schema.

2. GraphQL Schema Definition Language (SDL)

Below is the complete GraphQL Schema Definition Language (SDL) for our Blogging Platform.


# --- Custom Scalar Types ---
scalar DateTime

# --- Enum Types ---
enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

# --- Interface Types ---
# An interface for entities that have creation and update timestamps.
interface Auditable {
  createdAt: DateTime!
  updatedAt: DateTime!
}

# --- Object Types ---

# Represents a user in the blogging platform.
type User implements Auditable {
  id: ID!
  username: String!
  email: String!
  firstName: String
  lastName: String
  bio: String
  posts(limit: Int = 10, offset: Int = 0): [Post!]!
  comments(limit: Int = 10, offset: Int = 0): [Comment!]!
  createdAt: DateTime!
  updatedAt: DateTime!
}

# Represents a blog post.
type Post implements Auditable {
  id: ID!
  title: String!
  content: String!
  status: PostStatus!
  author: User!
  tags: [Tag!]!
  comments(limit: Int = 10, offset: Int = 0): [Comment!]!
  createdAt: DateTime!
  updatedAt: DateTime!
}

# Represents a comment on a blog post.
type Comment implements Auditable {
  id: ID!
  content: String!
  author: User!
  post: Post!
  createdAt: DateTime!
  updatedAt: DateTime!
}

# Represents a tag for blog posts.
type Tag {
  id: ID!
  name: String!
  posts(limit: Int = 10, offset: Int = 0): [Post!]!
}

# --- Union Types ---
# Represents a search result, which can be either a Post or a User.
union SearchResult = Post | User

# --- Input Types for Mutations ---

# Input for creating a new user.
input CreateUserInput {
  username: String!
  email: String!
  firstName: String
  lastName: String
  bio: String
}

# Input for updating an existing user.
input UpdateUserInput {
  firstName: String
  lastName: String
  bio: String
}

# Input for creating a new post.
input CreatePostInput {
  title: String!
  content: String!
  authorId: ID!
  tagIds: [ID!]
  status: PostStatus = DRAFT # Default status
}

# Input for updating an existing post.
input UpdatePostInput {
  title: String
  content: String
  status: PostStatus
  tagIds: [ID!]
}

# Input for creating a new comment.
input CreateCommentInput {
  content: String!
  authorId: ID!
  postId: ID!
}

# Input for updating an existing comment.
input UpdateCommentInput {
  content: String!
}

# --- Root Query Type ---
type Query {
  # Fetch a single user by ID.
  user(id: ID!): User

  # Fetch multiple users with pagination.
  users(limit: Int = 10, offset: Int = 0): [User!]!

  # Fetch a single post by ID.
  post(id: ID!): Post

  # Fetch multiple posts with filtering and pagination.
  posts(
    status: PostStatus
    tagId: ID
    authorId: ID
    limit: Int = 10
    offset: Int = 0
  ): [Post!]!

  # Fetch a single comment by ID.
  comment(id: ID!): Comment

  # Fetch multiple comments for a specific post or user with pagination.
  comments(
    postId: ID
    authorId: ID
    limit: Int = 10
    offset: Int = 0
  ): [Comment!]!

  # Fetch all available tags.
  tags: [Tag!]!

  # Perform a global search across posts and users.
  search(query: String!): [SearchResult!]!
}

# --- Root Mutation Type ---
type Mutation {
  # User Mutations
  registerUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User
  deleteUser(id: ID!): Boolean! # Returns true if deletion was successful

  # Post Mutations
  createPost(input: CreatePostInput!): Post!
  updatePost(id: ID!, input: UpdatePostInput!): Post
  deletePost(id: ID!): Boolean!

  # Comment Mutations
  createComment(input: CreateCommentInput!): Comment!
  updateComment(id: ID!, input: UpdateCommentInput!): Comment
  deleteComment(id: ID!): Boolean!
}

# --- Root Subscription Type ---
type Subscription {
  # Notifies when a new post is created.
  postAdded: Post!

  # Notifies when an existing post is updated.
  postUpdated(id: ID!): Post!

  # Notifies when a new comment is added to a specific post.
  commentAdded(postId: ID!): Comment!
}

3. Detailed Schema Components Explanation

3.1. Scalar Types

  • scalar DateTime: A custom scalar type to represent date and time values. This typically maps to a JavaScript Date object or similar in the backend.

3.2. Enum Types

  • enum PostStatus: Defines a set of allowed values for a post's status (DRAFT, PUBLISHED, ARCHIVED). Enums ensure data consistency and provide clear options.

3.3. Interface Types

  • interface Auditable: Defines a contract that any implementing type must fulfill. Here, it ensures that User, Post, and Comment objects all have createdAt and updatedAt fields. This promotes consistency and reusability.

3.4. Object Types

These are the fundamental data structures of our blogging platform.

  • User:

* id: Unique identifier.

* username, email: Core user credentials.

* firstName, lastName, bio: Optional profile details.

* posts, comments: Relational fields to fetch posts and comments authored by this user, with pagination.

* createdAt, updatedAt: Timestamps (from Auditable interface).

  • Post:

* id, title, content: Core post details.

* status: Current state of the post (using PostStatus enum).

* author: The User who wrote the post. This is a nested object.

* tags: A list of Tag objects associated with the post.

* comments: A list of Comment objects on this post, with pagination.

* createdAt, updatedAt: Timestamps.

  • Comment:

* id, content: Comment details.

* author: The User who wrote the comment.

* post: The Post the comment belongs to.

* createdAt, updatedAt: Timestamps.

  • Tag:

* id, name: Tag identifier and name.

* posts: A list of Post objects associated with this tag, with pagination.

3.5. Union Types

  • union SearchResult = Post | User: Allows a field to return one of several object types. The search query can return either a Post or a User object, enabling flexible search results.

3.6. Input Types

Input types are special object types used as arguments for mutations. They prevent argument bloat and make mutation signatures cleaner.

  • CreateUserInput, UpdateUserInput: For registering and modifying user profiles.
  • CreatePostInput, UpdatePostInput: For creating and modifying blog posts.
  • CreateCommentInput, UpdateCommentInput: For adding and modifying comments.

3.7. Root Query Type

The entry point for all read operations.

  • user(id: ID!): Fetches a single user by their ID.
  • users(limit: Int, offset: Int): Fetches a list of users, supporting pagination.
  • post(id: ID!): Fetches a single post by its ID.
  • posts(...): Fetches a list of posts, supporting filtering by status, tagId, authorId, and pagination.
  • comment(id: ID!): Fetches a single comment by its ID.
  • comments(...): Fetches a list of comments, supporting filtering by postId, authorId, and pagination.
  • tags: Fetches all available tags.
  • search(query: String!): Performs a full-text search across relevant entities (e.g., posts and users) and returns a list of SearchResult union types.

3.8. Root Mutation Type

The entry point for all write operations.

  • User Mutations: registerUser, updateUser, deleteUser.
  • Post Mutations: createPost, updatePost, deletePost.
  • Comment Mutations: createComment, updateComment, deleteComment.

* Notice the ! on return types for create operations, indicating they will always return the created entity. Update and delete operations might return null if the entity is not found or Boolean for success/failure.

3.9. Root Subscription Type

The entry point for real-time data updates.

  • postAdded: Clients subscribed to this will receive a Post object whenever a new post is created.
  • postUpdated(id: ID!): Clients subscribed to this will receive the updated Post object when a specific post is modified.
  • commentAdded(postId: ID!): Clients can subscribe to new comments for a specific post, receiving the Comment object when it's added.

4. Resolver Concepts and Examples

Resolvers are functions that tell the GraphQL server how to fetch the data for a particular field. Every field in the schema has a corresponding resolver function.

General Resolver Structure:

A resolver function typically takes four arguments: (parent, args, context, info).

  • parent (or root): The result of the parent field's resolver.
  • args: An object containing all arguments provided to the field.
  • context: An object shared across all resolvers for a single request, often containing database connections, authentication info, or other utilities.
  • info: An object containing information about the execution state of the query, including the field's AST (Abstract Syntax Tree).

Example Resolver Implementation (Conceptual - JavaScript/Node.js with a hypothetical ORM db):


// A conceptual implementation of resolvers for a GraphQL server

const resolvers = {
  // Custom Scalar Resolver
  DateTime: new GraphQLScalarType({
    name: 'DateTime',
    description: 'DateTime custom scalar type',
    serialize(value) {
      return value.toISOString(); // Convert Date object to ISO string for client
    },
    parseValue(value) {
      return new Date(value); // Convert client string to Date object
    },
    parseLiteral(ast) {
      if (ast.kind === Kind.STRING) {
        return new Date(ast.value);
      }
      return null;
    },
  }),

  // Type Resolvers for interfaces/unions (if needed for type disambiguation)
  SearchResult: {
    __resolveType(obj, context, info) {
      if (obj.title) { // Assuming Posts have a 'title' field
        return 'Post';
      }
      if (obj.username) { // Assuming Users have a 'username' field
        return 'User';
      }
      return null; // Or throw an error
    },
  },

  // Field Resolvers for Object Types
  User: {
    posts: async (parent, { limit, offset }, context) => {
      // parent here is the User object returned by the parent resolver (e.g., query.user)
      return await context.db.Post.findMany({
        where: { authorId: parent.id },
        take: limit,
        skip: offset,
      });
    },
    comments: async (parent, { limit, offset }, context) => {
      return await context.db.Comment.findMany({
        where: { authorId: parent.id },
        take: limit,
        skip: offset,
      });
    },
    // createdAt and updatedAt would be direct fields on the User object,
    // so no explicit resolver is needed unless transformation is required.
  },

  Post:
gemini Output

GraphQL Schema Design: Project Management System

This document outlines a comprehensive GraphQL schema design for a Project Management System. It covers data types, query operations, mutation operations, real-time subscriptions, conceptual resolver structures, and essential considerations for integration, security, and performance.


1. Introduction and Overview

This GraphQL schema provides a unified and strongly-typed API for managing projects, tasks, users, and teams within a project management application. GraphQL allows clients to request exactly the data they need, improving efficiency and reducing over-fetching or under-fetching of data compared to traditional REST APIs.

Key Benefits of this GraphQL Design:

  • Strongly Typed: Ensures data consistency and provides clear API contracts.
  • Flexible Queries: Clients can retrieve specific data shapes, reducing multiple round trips.
  • Real-time Capabilities: Subscriptions enable instant updates for critical events.
  • Extensible: Easily adaptable to future feature additions without breaking existing clients.
  • Self-Documenting: The schema itself serves as a comprehensive documentation of the API.

2. Core Principles and Design Decisions

The following principles guided the design of this schema:

  • Domain-Driven Design: The schema reflects the core entities and relationships of a typical project management system.
  • Clear Naming Conventions: Consistent and descriptive names for types, fields, arguments, and operations (e.g., createProject, updateTask).
  • Input Types for Mutations: All mutation arguments are encapsulated within dedicated Input types for better organization and future extensibility.
  • Payload Types for Mutations: Mutations return a dedicated Payload type, typically containing the affected entity and a success status, allowing for consistent error handling and client-side updates.
  • Global Node Interface (Optional but Recommended for Larger Systems): For very large systems, implementing a Node interface with a global id field can simplify caching and data fetching patterns (not explicitly included in this basic example but noted as a best practice).
  • Custom Scalar for Dates: Using a DateTime custom scalar ensures consistent date/time handling across the API.
  • Enums for Statuses/Roles: Provides a finite set of allowed values, improving data integrity.

3. Schema Definition Language (SDL)

The following sections define the GraphQL schema using the Schema Definition Language (SDL).

3.1. Custom Scalars


scalar DateTime
  • DateTime: Represents a date and time string, typically in ISO 8601 format (e.g., "2023-10-27T10:00:00Z").

3.2. Enums


enum UserRole {
  ADMIN
  MANAGER
  MEMBER
  GUEST
}

enum ProjectStatus {
  NOT_STARTED
  IN_PROGRESS
  ON_HOLD
  COMPLETED
  ARCHIVED
}

enum TaskStatus {
  TODO
  IN_PROGRESS
  BLOCKED
  DONE
}

enum TaskPriority {
  LOW
  MEDIUM
  HIGH
  URGENT
}

3.3. Object Types


type User {
  id: ID!
  name: String!
  email: String!
  role: UserRole!
  profilePictureUrl: String
  createdAt: DateTime!
  updatedAt: DateTime!
  
  # Relationships
  projectsCreated: [Project!]!
  assignedTasks: [Task!]!
  teams: [Team!]! # Teams the user is a member of
}

type Team {
  id: ID!
  name: String!
  description: String
  createdAt: DateTime!
  updatedAt: DateTime!

  # Relationships
  members: [User!]!
  projects: [Project!]!
}

type Project {
  id: ID!
  name: String!
  description: String
  status: ProjectStatus!
  startDate: DateTime
  endDate: DateTime
  createdAt: DateTime!
  updatedAt: DateTime!

  # Relationships
  owner: User! # The user who created/manages the project
  team: Team # The team associated with the project
  tasks: [Task!]!
  comments(first: Int, after: String): CommentConnection! # Paginated comments
}

type Task {
  id: ID!
  title: String!
  description: String
  status: TaskStatus!
  priority: TaskPriority!
  dueDate: DateTime
  createdAt: DateTime!
  updatedAt: DateTime!

  # Relationships
  project: Project!
  assignedTo: User # The user assigned to this task
  creator: User! # The user who created this task
  comments(first: Int, after: String): CommentConnection! # Paginated comments
}

type Comment {
  id: ID!
  content: String!
  createdAt: DateTime!
  updatedAt: DateTime!

  # Relationships
  author: User!
  # Polymorphic relationship: a comment can belong to a Project or a Task
  # In implementation, this might be handled by an interface or by separate fields (e.g., projectId, taskId)
  # For simplicity, we'll assume a single 'parent' reference that resolvers will handle.
  # A common pattern is to use an interface:
  # parent: Commentable!
  # For this example, let's keep it simpler and assume the client knows the context
  # or we'd have separate mutations for ProjectComment and TaskComment.
  # Let's simplify and make it linked directly to a Project or Task in the backend,
  # but for the schema, we'll assume the query context provides the parent.
  # Or, to be explicit, we can add parentId and parentType if we want to retrieve comments independently.
  # Let's use a simpler approach for the schema, assuming comments are always fetched via Project or Task.
  # If fetching a comment directly, we'd need its parent context.
}

3.4. Connection Types (for Pagination)


type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}

type CommentEdge {
  node: Comment!
  cursor: String!
}

type CommentConnection {
  edges: [CommentEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

3.5. Input Types


# User Inputs
input CreateUserInput {
  name: String!
  email: String!
  password: String! # Should be hashed server-side
  role: UserRole = MEMBER # Default role
}

input UpdateUserInput {
  id: ID!
  name: String
  email: String
  role: UserRole
  profilePictureUrl: String
}

# Team Inputs
input CreateTeamInput {
  name: String!
  description: String
  memberIds: [ID!] # Optional: initial members
}

input UpdateTeamInput {
  id: ID!
  name: String
  description: String
}

input AddTeamMembersInput {
  teamId: ID!
  memberIds: [ID!]!
}

input RemoveTeamMembersInput {
  teamId: ID!
  memberIds: [ID!]!
}

# Project Inputs
input CreateProjectInput {
  name: String!
  description: String
  status: ProjectStatus = NOT_STARTED # Default status
  startDate: DateTime
  endDate: DateTime
  ownerId: ID! # The ID of the user creating the project
  teamId: ID # Optional: associate with a team
}

input UpdateProjectInput {
  id: ID!
  name: String
  description: String
  status: ProjectStatus
  startDate: DateTime
  endDate: DateTime
  ownerId: ID
  teamId: ID
}

# Task Inputs
input CreateTaskInput {
  projectId: ID!
  title: String!
  description: String
  status: TaskStatus = TODO # Default status
  priority: TaskPriority = MEDIUM # Default priority
  dueDate: DateTime
  assignedToId: ID # Optional: ID of the user to assign
  creatorId: ID! # ID of the user creating the task
}

input UpdateTaskInput {
  id: ID!
  title: String
  description: String
  status: TaskStatus
  priority: TaskPriority
  dueDate: DateTime
  assignedToId: ID
}

# Comment Inputs
input CreateCommentInput {
  content: String!
  authorId: ID!
  # Either projectId or taskId must be provided
  projectId: ID
  taskId: ID
}

3.6. Payload Types (for Mutations)


type UserPayload {
  success: Boolean!
  message: String
  user: User
}

type TeamPayload {
  success: Boolean!
  message: String
  team: Team
}

type ProjectPayload {
  success: Boolean!
  message: String
  project: Project
}

type TaskPayload {
  success: Boolean!
  message: String
  task: Task
}

type CommentPayload {
  success: Boolean!
  message: String
  comment: Comment
}

type DeletePayload {
  success: Boolean!
  message: String
  id: ID # ID of the deleted resource
}

3.7. Root Query Type


type Query {
  # User Queries
  me: User # Get the currently authenticated user
  user(id: ID!): User
  users(
    first: Int = 10, 
    after: String, 
    role: UserRole, 
    search: String
  ): [User!]! # Search/filter users

  # Team Queries
  team(id: ID!): Team
  teams(
    first: Int = 10, 
    after: String, 
    search: String
  ): [Team!]! # Search/filter teams

  # Project Queries
  project(id: ID!): Project
  projects(
    first: Int = 10, 
    after: String, 
    status: ProjectStatus, 
    ownerId: ID, 
    teamId: ID, 
    search: String
  ): [Project!]! # Search/filter projects

  # Task Queries
  task(id: ID!): Task
  tasks(
    first: Int = 10, 
    after: String, 
    projectId: ID, 
    assignedToId: ID, 
    status: TaskStatus, 
    priority: TaskPriority, 
    dueDateBefore: DateTime, 
    search: String
  ): [Task!]! # Search/filter tasks
  
  # Comment Queries (typically fetched via Project/Task, but direct access might be needed for specific cases)
  comment(id: ID!): Comment
}

3.8. Root Mutation Type


type Mutation {
  # User Mutations
  createUser(input: CreateUserInput!): UserPayload!
  updateUser(input: UpdateUserInput!): UserPayload!
  deleteUser(id: ID!): DeletePayload!

  # Team Mutations
  createTeam(input: CreateTeamInput!): TeamPayload!
  updateTeam(input: UpdateTeamInput!): TeamPayload!
  deleteTeam(id: ID!): DeletePayload!
  addMembersToTeam(input: AddTeamMembersInput!): TeamPayload!
  removeMembersFromTeam(input: RemoveTeamMembersInput!): TeamPayload!

  # Project Mutations
  createProject(input: CreateProjectInput!): ProjectPayload!
  updateProject(input: UpdateProjectInput!): ProjectPayload!
  deleteProject(id: ID!): DeletePayload!

  # Task Mutations
  createTask(input: CreateTaskInput!): TaskPayload!
  updateTask(input: UpdateTaskInput!): TaskPayload!
  deleteTask(id: ID!): DeletePayload!

  # Comment Mutations
  createComment(input: CreateCommentInput!): CommentPayload!
  # updateComment(input: UpdateCommentInput!): CommentPayload! # If comments are editable
  deleteComment(id: ID!): DeletePayload!
}

3.9. Root Subscription Type


type Subscription {
  # Real-time updates for tasks within a specific project
  taskUpdated(projectId: ID!): Task!

  # Real-time updates for new comments on a specific project or task
  newComment(parentId: ID!): Comment! # parentId can be Project ID or Task ID
}

4. Resolver Structure (Conceptual)

Resolvers are functions that tell the GraphQL server how to fetch the data for a particular field in the schema. Each field in the schema (e.g., User.name, Query.project, Mutation.createTask) has a corresponding resolver.


// Conceptual Resolver Map (JavaScript/TypeScript-like pseudocode)
const resolvers = {
  DateTime: new GraphQLScalarType({ /* ... implementation for date parsing/serialization ... */ }),
  
  Query: {
    me: (parent, args, context) => {
      // context will contain authenticated user info
      return context.dataSources.usersAPI.getCurrentUser(context.userId);
    },
    user: (parent, { id }, context) => {
      return context.dataSources.usersAPI.getUserById(id);
    },
    users: (parent, args, context) => {
      // args will contain first, after, role, search for filtering/pagination
      return context.dataSources.usersAPI.getUsers(args);
    },
    project: (parent, { id }, context) => {
      return context.dataSources.projectsAPI.getProjectById(id);
    },
    projects: (parent, args, context) => {
      return context
graphql_schema_designer.txt
Download source file
Copy all content
Full output as text
Download ZIP
IDE-ready project ZIP
Copy share link
Permanent URL for this run
Get Embed Code
Embed this result on any website
Print / Save PDF
Use browser print dialog
\n\n\n"); var hasSrcMain=Object.keys(extracted).some(function(k){return k.indexOf("src/main")>=0;}); if(!hasSrcMain) zip.file(folder+"src/main."+ext,"import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n \n \n \n)\n"); var hasSrcApp=Object.keys(extracted).some(function(k){return k==="src/App."+ext||k==="App."+ext;}); if(!hasSrcApp) zip.file(folder+"src/App."+ext,"import React from 'react'\nimport './App.css'\n\nfunction App(){\n return(\n
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n
\n )\n}\nexport default App\n"); zip.file(folder+"src/index.css","*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#f0f2f5;color:#1a1a2e}\n.app{min-height:100vh;display:flex;flex-direction:column}\n.app-header{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:40px}\nh1{font-size:2.5rem;font-weight:700}\n"); zip.file(folder+"src/App.css",""); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/pages/.gitkeep",""); zip.file(folder+"src/hooks/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\n## Open in IDE\nOpen the project folder in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Vue (Vite + Composition API + TypeScript) --- */ function buildVue(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "type": "module",\n "scripts": {\n "dev": "vite",\n "build": "vue-tsc -b && vite build",\n "preview": "vite preview"\n },\n "dependencies": {\n "vue": "^3.5.13",\n "vue-router": "^4.4.5",\n "pinia": "^2.3.0",\n "axios": "^1.7.9"\n },\n "devDependencies": {\n "@vitejs/plugin-vue": "^5.2.1",\n "typescript": "~5.7.3",\n "vite": "^6.0.5",\n "vue-tsc": "^2.2.0"\n }\n}\n'); zip.file(folder+"vite.config.ts","import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport { resolve } from 'path'\n\nexport default defineConfig({\n plugins: [vue()],\n resolve: { alias: { '@': resolve(__dirname,'src') } }\n})\n"); zip.file(folder+"tsconfig.json",'{"files":[],"references":[{"path":"./tsconfig.app.json"},{"path":"./tsconfig.node.json"}]}\n'); zip.file(folder+"tsconfig.app.json",'{\n "compilerOptions":{\n "target":"ES2020","useDefineForClassFields":true,"module":"ESNext","lib":["ES2020","DOM","DOM.Iterable"],\n "skipLibCheck":true,"moduleResolution":"bundler","allowImportingTsExtensions":true,\n "isolatedModules":true,"moduleDetection":"force","noEmit":true,"jsxImportSource":"vue",\n "strict":true,"paths":{"@/*":["./src/*"]}\n },\n "include":["src/**/*.ts","src/**/*.d.ts","src/**/*.tsx","src/**/*.vue"]\n}\n'); zip.file(folder+"env.d.ts","/// \n"); zip.file(folder+"index.html","\n\n\n \n \n "+slugTitle(pn)+"\n\n\n
\n \n\n\n"); var hasMain=Object.keys(extracted).some(function(k){return k==="src/main.ts"||k==="main.ts";}); if(!hasMain) zip.file(folder+"src/main.ts","import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from './App.vue'\nimport './assets/main.css'\n\nconst app = createApp(App)\napp.use(createPinia())\napp.mount('#app')\n"); var hasApp=Object.keys(extracted).some(function(k){return k.indexOf("App.vue")>=0;}); if(!hasApp) zip.file(folder+"src/App.vue","\n\n\n\n\n"); zip.file(folder+"src/assets/main.css","*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,sans-serif;background:#fff;color:#213547}\n"); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/views/.gitkeep",""); zip.file(folder+"src/stores/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\nOpen in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Angular (v19 standalone) --- */ function buildAngular(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var sel=pn.replace(/_/g,"-"); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "scripts": {\n "ng": "ng",\n "start": "ng serve",\n "build": "ng build",\n "test": "ng test"\n },\n "dependencies": {\n "@angular/animations": "^19.0.0",\n "@angular/common": "^19.0.0",\n "@angular/compiler": "^19.0.0",\n "@angular/core": "^19.0.0",\n "@angular/forms": "^19.0.0",\n "@angular/platform-browser": "^19.0.0",\n "@angular/platform-browser-dynamic": "^19.0.0",\n "@angular/router": "^19.0.0",\n "rxjs": "~7.8.0",\n "tslib": "^2.3.0",\n "zone.js": "~0.15.0"\n },\n "devDependencies": {\n "@angular-devkit/build-angular": "^19.0.0",\n "@angular/cli": "^19.0.0",\n "@angular/compiler-cli": "^19.0.0",\n "typescript": "~5.6.0"\n }\n}\n'); zip.file(folder+"angular.json",'{\n "$schema": "./node_modules/@angular/cli/lib/config/schema.json",\n "version": 1,\n "newProjectRoot": "projects",\n "projects": {\n "'+pn+'": {\n "projectType": "application",\n "root": "",\n "sourceRoot": "src",\n "prefix": "app",\n "architect": {\n "build": {\n "builder": "@angular-devkit/build-angular:application",\n "options": {\n "outputPath": "dist/'+pn+'",\n "index": "src/index.html",\n "browser": "src/main.ts",\n "tsConfig": "tsconfig.app.json",\n "styles": ["src/styles.css"],\n "scripts": []\n }\n },\n "serve": {"builder":"@angular-devkit/build-angular:dev-server","configurations":{"production":{"buildTarget":"'+pn+':build:production"},"development":{"buildTarget":"'+pn+':build:development"}},"defaultConfiguration":"development"}\n }\n }\n }\n}\n'); zip.file(folder+"tsconfig.json",'{\n "compileOnSave": false,\n "compilerOptions": {"baseUrl":"./","outDir":"./dist/out-tsc","forceConsistentCasingInFileNames":true,"strict":true,"noImplicitOverride":true,"noPropertyAccessFromIndexSignature":true,"noImplicitReturns":true,"noFallthroughCasesInSwitch":true,"paths":{"@/*":["src/*"]},"skipLibCheck":true,"esModuleInterop":true,"sourceMap":true,"declaration":false,"experimentalDecorators":true,"moduleResolution":"bundler","importHelpers":true,"target":"ES2022","module":"ES2022","useDefineForClassFields":false,"lib":["ES2022","dom"]},\n "references":[{"path":"./tsconfig.app.json"}]\n}\n'); zip.file(folder+"tsconfig.app.json",'{\n "extends":"./tsconfig.json",\n "compilerOptions":{"outDir":"./dist/out-tsc","types":[]},\n "files":["src/main.ts"],\n "include":["src/**/*.d.ts"]\n}\n'); zip.file(folder+"src/index.html","\n\n\n \n "+slugTitle(pn)+"\n \n \n \n\n\n \n\n\n"); zip.file(folder+"src/main.ts","import { bootstrapApplication } from '@angular/platform-browser';\nimport { appConfig } from './app/app.config';\nimport { AppComponent } from './app/app.component';\n\nbootstrapApplication(AppComponent, appConfig)\n .catch(err => console.error(err));\n"); zip.file(folder+"src/styles.css","* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { font-family: system-ui, -apple-system, sans-serif; background: #f9fafb; color: #111827; }\n"); var hasComp=Object.keys(extracted).some(function(k){return k.indexOf("app.component")>=0;}); if(!hasComp){ zip.file(folder+"src/app/app.component.ts","import { Component } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\n\n@Component({\n selector: 'app-root',\n standalone: true,\n imports: [RouterOutlet],\n templateUrl: './app.component.html',\n styleUrl: './app.component.css'\n})\nexport class AppComponent {\n title = '"+pn+"';\n}\n"); zip.file(folder+"src/app/app.component.html","
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n \n
\n"); zip.file(folder+"src/app/app.component.css",".app-header{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;gap:16px}h1{font-size:2.5rem;font-weight:700;color:#6366f1}\n"); } zip.file(folder+"src/app/app.config.ts","import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';\nimport { provideRouter } from '@angular/router';\nimport { routes } from './app.routes';\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true }),\n provideRouter(routes)\n ]\n};\n"); zip.file(folder+"src/app/app.routes.ts","import { Routes } from '@angular/router';\n\nexport const routes: Routes = [];\n"); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nng serve\n# or: npm start\n\`\`\`\n\n## Build\n\`\`\`bash\nng build\n\`\`\`\n\nOpen in VS Code with Angular Language Service extension.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n.angular/\n"); } /* --- Python --- */ function buildPython(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var reqMap={"numpy":"numpy","pandas":"pandas","sklearn":"scikit-learn","tensorflow":"tensorflow","torch":"torch","flask":"flask","fastapi":"fastapi","uvicorn":"uvicorn","requests":"requests","sqlalchemy":"sqlalchemy","pydantic":"pydantic","dotenv":"python-dotenv","PIL":"Pillow","cv2":"opencv-python","matplotlib":"matplotlib","seaborn":"seaborn","scipy":"scipy"}; var reqs=[]; Object.keys(reqMap).forEach(function(k){if(src.indexOf("import "+k)>=0||src.indexOf("from "+k)>=0)reqs.push(reqMap[k]);}); var reqsTxt=reqs.length?reqs.join("\n"):"# add dependencies here\n"; zip.file(folder+"main.py",src||"# "+title+"\n# Generated by PantheraHive BOS\n\nprint(title+\" loaded\")\n"); zip.file(folder+"requirements.txt",reqsTxt); zip.file(folder+".env.example","# Environment variables\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n\`\`\`\n\n## Run\n\`\`\`bash\npython main.py\n\`\`\`\n"); zip.file(folder+".gitignore",".venv/\n__pycache__/\n*.pyc\n.env\n.DS_Store\n"); } /* --- Node.js --- */ function buildNode(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var depMap={"mongoose":"^8.0.0","dotenv":"^16.4.5","axios":"^1.7.9","cors":"^2.8.5","bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2","socket.io":"^4.7.4","uuid":"^9.0.1","zod":"^3.22.4","express":"^4.18.2"}; var deps={}; Object.keys(depMap).forEach(function(k){if(src.indexOf(k)>=0)deps[k]=depMap[k];}); if(!deps["express"])deps["express"]="^4.18.2"; var pkgJson=JSON.stringify({"name":pn,"version":"1.0.0","main":"src/index.js","scripts":{"start":"node src/index.js","dev":"nodemon src/index.js"},"dependencies":deps,"devDependencies":{"nodemon":"^3.0.3"}},null,2)+"\n"; zip.file(folder+"package.json",pkgJson); var fallback="const express=require(\"express\");\nconst app=express();\napp.use(express.json());\n\napp.get(\"/\",(req,res)=>{\n res.json({message:\""+title+" API\"});\n});\n\nconst PORT=process.env.PORT||3000;\napp.listen(PORT,()=>console.log(\"Server on port \"+PORT));\n"; zip.file(folder+"src/index.js",src||fallback); zip.file(folder+".env.example","PORT=3000\n"); zip.file(folder+".gitignore","node_modules/\n.env\n.DS_Store\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\n\`\`\`\n\n## Run\n\`\`\`bash\nnpm run dev\n\`\`\`\n"); } /* --- Vanilla HTML --- */ function buildVanillaHtml(zip,folder,app,code){ var title=slugTitle(app); var isFullDoc=code.trim().toLowerCase().indexOf("=0||code.trim().toLowerCase().indexOf("=0; var indexHtml=isFullDoc?code:"\n\n\n\n\n"+title+"\n\n\n\n"+code+"\n\n\n\n"; zip.file(folder+"index.html",indexHtml); zip.file(folder+"style.css","/* "+title+" — styles */\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#fff;color:#1a1a2e}\n"); zip.file(folder+"script.js","/* "+title+" — scripts */\n"); zip.file(folder+"assets/.gitkeep",""); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Open\nDouble-click \`index.html\` in your browser.\n\nOr serve locally:\n\`\`\`bash\nnpx serve .\n# or\npython3 -m http.server 3000\n\`\`\`\n"); zip.file(folder+".gitignore",".DS_Store\nnode_modules/\n.env\n"); } /* ===== MAIN ===== */ var sc=document.createElement("script"); sc.src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"; sc.onerror=function(){ if(lbl)lbl.textContent="Download ZIP"; alert("JSZip load failed — check connection."); }; sc.onload=function(){ var zip=new JSZip(); var base=(_phFname||"output").replace(/\.[^.]+$/,""); var app=base.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"my_app"; var folder=app+"/"; var vc=document.getElementById("panel-content"); var panelTxt=vc?(vc.innerText||vc.textContent||""):""; var lang=detectLang(_phCode,panelTxt); if(_phIsHtml){ buildVanillaHtml(zip,folder,app,_phCode); } else if(lang==="flutter"){ buildFlutter(zip,folder,app,_phCode,panelTxt); } else if(lang==="react-native"){ buildReactNative(zip,folder,app,_phCode,panelTxt); } else if(lang==="swift"){ buildSwift(zip,folder,app,_phCode,panelTxt); } else if(lang==="kotlin"){ buildKotlin(zip,folder,app,_phCode,panelTxt); } else if(lang==="react"){ buildReact(zip,folder,app,_phCode,panelTxt); } else if(lang==="vue"){ buildVue(zip,folder,app,_phCode,panelTxt); } else if(lang==="angular"){ buildAngular(zip,folder,app,_phCode,panelTxt); } else if(lang==="python"){ buildPython(zip,folder,app,_phCode); } else if(lang==="node"){ buildNode(zip,folder,app,_phCode); } else { /* Document/content workflow */ var title=app.replace(/_/g," "); var md=_phAll||_phCode||panelTxt||"No content"; zip.file(folder+app+".md",md); var h=""+title+""; h+="

"+title+"

"; var hc=md.replace(/&/g,"&").replace(//g,">"); hc=hc.replace(/^### (.+)$/gm,"

$1

"); hc=hc.replace(/^## (.+)$/gm,"

$1

"); hc=hc.replace(/^# (.+)$/gm,"

$1

"); hc=hc.replace(/\*\*(.+?)\*\*/g,"$1"); hc=hc.replace(/\n{2,}/g,"

"); h+="

"+hc+"

Generated by PantheraHive BOS
"; zip.file(folder+app+".html",h); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\nFiles:\n- "+app+".md (Markdown)\n- "+app+".html (styled HTML)\n"); } zip.generateAsync({type:"blob"}).then(function(blob){ var a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=app+".zip"; a.click(); URL.revokeObjectURL(a.href); if(lbl)lbl.textContent="Download ZIP"; }); }; document.head.appendChild(sc); } function phShare(){navigator.clipboard.writeText(window.location.href).then(function(){var el=document.getElementById("ph-share-lbl");if(el){el.textContent="Link copied!";setTimeout(function(){el.textContent="Copy share link";},2500);}});}function phEmbed(){var runId=window.location.pathname.split("/").pop().replace(".html","");var embedUrl="https://pantherahive.com/embed/"+runId;var code='';navigator.clipboard.writeText(code).then(function(){var el=document.getElementById("ph-embed-lbl");if(el){el.textContent="Embed code copied!";setTimeout(function(){el.textContent="Get Embed Code";},2500);}});}