Skip to content

Pilet API

The Manager component provides a comprehensive Pilar pilet API for managing microfrontend modules (pilets) in the Productify Framework.

Overview

Pilets are packaged microfrontend modules that can be dynamically loaded by frontend applications. The pilet API provides:

  • Package Storage: Binary storage of pilet packages (typically gzipped tarballs)
  • Version Management: Support for multiple versions of the same pilet
  • Access Control: Project-scoped pilets with authentication
  • Integrity Verification: SHA256 hash validation
  • GraphQL & REST: Dual API interfaces for flexibility

Key Concepts

Project-based Storage

Pilets are stored at the project level, not the application level. This allows:

  • Sharing pilets across multiple applications within a project
  • Centralized version management
  • Simplified deployment workflows

FrontendConfig Integration

The FrontendConfig.EnabledModules array contains pilet names that should be loaded by the frontend:

json
{
  "module_config": {
    "enabled_modules": ["@myapp/dashboard", "@myapp/analytics"],
    "available_modules": [
      "@myapp/dashboard@1.0.0",
      "@myapp/analytics@2.1.0",
      "@myapp/settings@1.0.0"
    ]
  }
}

Frontend applications read this configuration and dynamically load the specified pilets via the pilet API.

Automatic AvailableModules Updates

When you upload or update a pilet, the system automatically:

  • Adds the pilet (with version) to AvailableModules in all frontend settings connected to the project
  • Updates across project, tenant, and application level settings
  • Removes the pilet from both AvailableModules and EnabledModules when deleted

This ensures all settings stay synchronized without manual updates.

REST API Endpoints

All endpoints require authentication (PAT or session-based) and are available under /pilets.

Piral Feed Service

Get enabled pilets in Piral feed format for dynamic loading:

http
GET /pilet-feed/:applicationId

Note: This endpoint does not require authentication - it's designed to be accessed by frontend applications through the proxy.

The endpoint automatically resolves the project ID from the application ID, so frontends don't need to know the project structure.

Response:

json
{
  "items": [
    {
      "name": "@myapp/dashboard",
      "version": "1.0.0",
      "link": "/pilets/550e8400-e29b-41d4-a716-446655440000/@myapp/dashboard/1.0.0",
      "hash": "abc123...",
      "spec": "v2"
    },
    {
      "name": "@myapp/analytics",
      "version": "2.1.0",
      "link": "/pilets/550e8400-e29b-41d4-a716-446655440000/@myapp/analytics/2.1.0",
      "hash": "def456...",
      "spec": "v2"
    }
  ]
}

Notes:

  • Returns only pilets that are:
    1. Enabled in the database (enabled=true)
    2. Listed in the application's EnabledModules (from merged frontend config)
  • No authentication required - accessible through proxy
  • Compatible with Piral v2 specification
  • Each item includes a relative download link (/pilets/{project_id}/{name}/{version})
  • Hash provided for integrity verification
  • Frontend can use this endpoint to discover and load available pilets
  • Settings are merged from project → tenant → application (application has highest priority)

Upload Package

Upload or update a pilet package:

http
POST /pilets/:projectId/:name/:version?description=...
Content-Type: application/gzip

[binary package content]

Response:

json
{
  "id": "uuid",
  "name": "@myapp/dashboard",
  "version": "1.0.0",
  "size": 12345,
  "hash": "abc123...",
  "message": "Package uploaded successfully"
}

Automatic Settings Sync:

When a pilet is uploaded or updated, the system automatically:

  • Adds {name}@{version} to AvailableModules in all frontend settings for the project
  • Updates existing entries if the same module name with a different version exists
  • Syncs across project-level, tenant-level, and application-level settings
  • This happens in the background and doesn't block the upload

Limits:

  • Maximum package size: 50MB
  • Name format: Alphanumeric, dashes, underscores, dots, and scoped packages (@scope/name)

Download Package

Download a specific pilet package version:

http
GET /pilets/:projectId/:name/:version

Response: Binary package with headers:

  • Content-Type: Package MIME type
  • Content-Disposition: Suggested filename
  • Content-Length: Package size
  • X-Pilet-Hash: SHA256 hash for verification

List Packages

List all pilets for a project:

http
GET /pilets/:projectId?enabled=true

Response:

json
{
  "packages": [
    {
      "id": "uuid",
      "name": "@myapp/dashboard",
      "version": "1.0.0",
      "description": "Dashboard module",
      "size": 12345,
      "hash": "abc123...",
      "enabled": true,
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-01-02T00:00:00Z"
    }
  ],
  "count": 1
}

Delete Package

Soft-delete a pilet package (disables it):

http
DELETE /pilets/:projectId/:name/:version

Response:

json
{
  "message": "Package disabled successfully"
}

Automatic Settings Sync:

When a pilet is deleted, the system automatically:

  • Removes the module from AvailableModules in all frontend settings
  • Removes the module from EnabledModules in all frontend settings
  • Applies to project-level, tenant-level, and application-level settings
  • Ensures no references to the deleted pilet remain in the configuration

GraphQL API

Queries

List Pilet Packages

graphql
query ListPilets($projectId: ID!, $enabled: Boolean) {
  piletPackages(projectId: $projectId, enabled: $enabled) {
    packages {
      id
      name
      version
      description
      size
      hash
      enabled
      createdAt
      updatedAt
    }
    count
  }
}

Get Single Pilet

graphql
query GetPilet($projectId: ID!, $name: String!, $version: String!) {
  piletPackage(projectId: $projectId, name: $name, version: $version) {
    id
    name
    version
    description
    size
    hash
    enabled
    createdAt
    updatedAt
  }
}

Mutations

Update Package Metadata

graphql
mutation UpdatePilet(
  $projectId: ID!
  $name: String!
  $version: String!
  $description: String
  $enabled: Boolean
) {
  updatePiletPackage(
    projectId: $projectId
    name: $name
    version: $version
    description: $description
    enabled: $enabled
  ) {
    id
    name
    version
    description
    enabled
  }
}

Enable Package

graphql
mutation EnablePilet($projectId: ID!, $name: String!, $version: String!) {
  enablePiletPackage(projectId: $projectId, name: $name, version: $version) {
    id
    enabled
  }
}

Disable Package

graphql
mutation DisablePilet($projectId: ID!, $name: String!, $version: String!) {
  disablePiletPackage(projectId: $projectId, name: $name, version: $version) {
    id
    enabled
  }
}

Frontend Integration

The simplest way to load pilets is using the Piral-compatible feed service:

javascript
import { createInstance } from "piral";

const instance = createInstance({
  requestPilets() {
    // No need for projectId - just use /pilet-feed
    // Proxy automatically adds applicationId
    return fetch("/pilet-feed")
      .then((response) => response.json())
      .then((data) => data.items);
  },
});

The feed service automatically:

  • Returns only pilets that are both enabled in database and listed in EnabledModules
  • Provides download links for each pilet
  • Includes hash for integrity verification
  • Follows Piral v2 specification format
  • Respects the same module enablement logic as the frontend config

Alternative: Manual Loading

If you need more control, you can manually load pilets:

  1. Fetch Frontend Configuration:

    javascript
    const config = await fetch(`/api/frontend-config/${applicationId}`).then(
      (r) => r.json()
    );
    const enabledModules = config.module_config.enabled_modules;
  2. Download Enabled Pilets:

    javascript
    for (const moduleName of enabledModules) {
      const [name, version] = parseModuleName(moduleName); // e.g., "@myapp/dashboard@1.0.0"
      const pilet = await fetch(`/pilets/${projectId}/${name}/${version}`, {
        headers: { Authorization: `Bearer ${token}` },
      }).then((r) => r.blob());
    
      // Load pilet into shell
      await loadPilet(pilet);
    }
  3. Register Pilets:

    javascript
    // Using Piral or similar microfrontend framework
    import { createInstance } from "piral";
    
    const instance = createInstance({
      // ... configuration
    });
    
    // Pilets are loaded dynamically based on EnabledModules

Version Specification

Modules in EnabledModules can specify versions:

  • Latest version: "@myapp/dashboard" (application resolves latest)
  • Specific version: "@myapp/dashboard@1.0.0"
  • Version range: "@myapp/dashboard@^1.0.0" (application resolves)

Security

Authentication

All pilet endpoints require authentication:

  • Personal Access Tokens (PAT): Recommended for CI/CD and automation
  • Session-based: For web UI interactions

Authorization

  • Users must have access to the project to manage pilets
  • Creator and editor tracking for audit purposes

Validation

  • Size limits: 50MB maximum package size
  • Name validation: Prevents injection attacks
  • Hash verification: SHA256 for integrity checking
  • Content-type validation: Ensures proper MIME types

Soft Deletes

Pilets are disabled rather than deleted to:

  • Maintain audit trails
  • Allow rollback if needed
  • Preserve historical data

Development Workflow

1. Build Pilet

bash
# Using npm/pnpm
cd pilets/my-dashboard
pnpm build
pnpm pack

2. Upload to Manager

bash
curl -X POST \
  -H "Authorization: Bearer $PAT_TOKEN" \
  -H "Content-Type: application/gzip" \
  --data-binary @myapp-dashboard-1.0.0.tgz \
  "https://manager.example.com/pilets/$PROJECT_ID/@myapp/dashboard/1.0.0?description=Dashboard+module"

3. Enable in FrontendConfig

graphql
mutation {
  updateFrontendSettings(
    id: "SETTINGS_ID"
    moduleConfig: {
      EnabledModules: ["@myapp/dashboard@1.0.0", "@myapp/analytics@2.1.0"]
    }
  ) {
    moduleConfig {
      EnabledModules
    }
  }
}

4. Frontend Loads Pilet

The frontend application automatically fetches and loads pilets based on the configuration.

Database Schema

PiletPackage Entity

FieldTypeDescription
idUUIDPrimary key
nameStringPackage name (supports scoped packages)
versionStringSemantic version
descriptionString (optional)Package description
contentBytesBinary package data
content_typeStringMIME type (default: application/gzip)
sizeIntegerPackage size in bytes
hashStringSHA256 hash
metadataJSON (optional)Additional metadata
enabledBooleanEnable/disable flag
created_atDateTimeCreation timestamp
updated_atDateTime (optional)Last update timestamp

Relationships

  • Project: Each pilet belongs to one project
  • Creator: User who uploaded the pilet
  • Editor: User who last modified the pilet

Examples

Fetch Pilets via Feed Service

bash
curl "$APP_URL/pilet-feed" | jq '.items[] | {name, version, link}'

Output:

json
{
  "name": "@myapp/dashboard",
  "version": "1.0.0",
  "link": "/pilets/550e8400-e29b-41d4-a716-446655440000/@myapp/dashboard/1.0.0"
}
{
  "name": "@myapp/analytics",
  "version": "2.1.0",
  "link": "/pilets/550e8400-e29b-41d4-a716-446655440000/@myapp/analytics/2.1.0"
}

Piral Shell Integration

typescript
// app-shell.tsx
import { createInstance, Piral } from "piral";

const instance = createInstance({
  requestPilets() {
    // Simple! No project_id needed
    return fetch("/pilet-feed")
      .then((response) => response.json())
      .then((data) => data.items);
  },
});

export default () => <Piral instance={instance}>{/* Your app layout */}</Piral>;

Complete Upload/Enable Workflow

bash
#!/bin/bash

PROJECT_ID="550e8400-e29b-41d4-a716-446655440000"
PAT_TOKEN="your-personal-access-token"
MANAGER_URL="https://manager.example.com"

# Upload pilet
echo "Uploading pilet..."
curl -X POST \
  -H "Authorization: Bearer $PAT_TOKEN" \
  -H "Content-Type: application/gzip" \
  --data-binary @dashboard-1.0.0.tgz \
  "$MANAGER_URL/pilets/$PROJECT_ID/@myapp/dashboard/1.0.0?description=Dashboard+v1.0.0"

# Verify upload
echo "Verifying upload..."
curl -H "Authorization: Bearer $PAT_TOKEN" \
  "$MANAGER_URL/pilets/$PROJECT_ID/@myapp/dashboard/1.0.0" \
  -o /tmp/verify.tgz

# Compare hashes
sha256sum dashboard-1.0.0.tgz /tmp/verify.tgz

echo "Pilet uploaded and verified successfully!"

List All Enabled Pilets

bash
curl -H "Authorization: Bearer $PAT_TOKEN" \
  "$MANAGER_URL/pilets/$PROJECT_ID?enabled=true" | jq '.packages[] | {name, version, size}'

See Also