JSPM

input-spec

2.0.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 13
  • Score
    100M100P100Q58282F
  • License MIT

Zero-dependency TypeScript implementation of the Dynamic Input Field Specification Protocol with framework integration support

Package Exports

  • input-spec

Readme

input-spec (TypeScript SDK)

Declarative, backend-owned field & validation specifications for frontends.

FR: Définissez les champs et règles côté serveur et consommez-les côté client sans duplication.

Zero runtime dependencies • Fully typed • Framework agnostic

Docs · API · Intégration · Architecture


1. Install

npm install input-spec

Node >= 16. No runtime deps.


2. Frontend Quick Start

import { FieldValidator, InputFieldSpec } from 'input-spec';

const spec: InputFieldSpec = await fetch('/api/form-fields/email').then(r => r.json());
const validator = new FieldValidator();
const result = await validator.validate(spec, 'user@example.com', 'email');
if (!result.isValid) console.log(result.errors.map(e => e.message));

3. Backend Generation

import { InputFieldSpec, ConstraintDescriptor } from 'input-spec';

function buildEmail(tier: 'basic' | 'premium'): InputFieldSpec {
  const constraints: ConstraintDescriptor[] = [
    { name: 'email', pattern: '^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$', errorMessage: 'Invalid email' }
  ];
  constraints.push({
    name: 'maxLength',
    max: tier === 'premium' ? 200 : 50,
    errorMessage: `Email too long (max ${tier === 'premium' ? 200 : 50} chars)`
  });
  return {
    displayName: 'Email Address',
    dataType: 'STRING',
    expectMultipleValues: false,
    required: true,
    constraints
  };
}

4. Dynamic Values (Autocomplete)

import { ValuesResolver, FetchHttpClient, MemoryCacheProvider, createDefaultValuesEndpoint } from 'input-spec';
const resolver = new ValuesResolver(new FetchHttpClient(), new MemoryCacheProvider());
const endpoint = createDefaultValuesEndpoint('https://api.example.com/countries');
const result = await resolver.resolveValues(endpoint, { search: 'fr', page: 1, limit: 10 });
console.log(result.values);

5. Core Concepts

Name Description
InputFieldSpec Full declarative field specification
ConstraintDescriptor Ordered validation rule descriptor
ValuesEndpoint Dynamic values contract (search + pagination)
FieldValidator Executes validation (single constraint or all)
ValuesResolver Fetch + cache orchestration for dynamic values
MemoryCacheProvider In‑memory TTL cache

6. API Snapshot

interface InputFieldSpec {
  displayName: string;
  description?: string;
  dataType: 'STRING' | 'NUMBER' | 'DATE' | 'BOOLEAN';
  expectMultipleValues: boolean;
  required: boolean;
  constraints: ConstraintDescriptor[];
  valuesEndpoint?: ValuesEndpoint;
}

Full reference: ./docs/API.md.

Migration: A helper migrateV1Spec converts legacy (enumValues + composite min/max/pattern) to v2 atomic form; review output (see Migration section in API docs).

Coercion (optional library-only): Disabled by default; enable via new FieldValidator({ coercion: { coerce: true } }) or per-field coercion block to accept numeric / boolean strings or epoch dates. Protocol wire format remains unchanged.


7. Example Patterns

Validate specific constraint:

await validator.validate(spec, 'bad@', 'email');

Validate all:

await validator.validate(spec, 'good@example.com');

Array field:

await validator.validate(arraySpec, ['a','b']);

8. Design Principles

  1. Backend is source of truth
  2. Ordered constraint execution
  3. Zero runtime dependencies
  4. Extensible via injected HTTP/cache
  5. Serializable specs for testing

9. Project Layout

src/
  types/        # Interfaces & type guards
  validation/   # Validation engine
  client/       # HTTP + cache + resolver
  __tests__/    # Jest tests

10. Scripts

Task Command
Build npm run build
Test npm test
Lint npm run lint
Types npm run type-check

11. Publishing (Maintainers)

npm run build && npm test
npm publish --dry-run
npm publish --access public

12. Contributing

  1. Fork & branch
  2. Add tests
  3. Ensure green build
  4. Open PR

13. License

MIT (see LICENSE).


14. Integrity

Item Value
Package input-spec
Protocol constant PROTOCOL_VERSION
Library constant LIBRARY_VERSION
Runtime deps 0

15. Resources

  • Protocol Specification: ../../PROTOCOL_SPECIFICATION.md
  • Root README: ../../README.md
  • Docs: ./docs/

🌐 Real-World Scenarios

Scenario 1: Multi-Tenant SaaS Application

Frontend Team: Different validation rules per client, all handled automatically!

---

// Your component automatically adapts to each client's rules
const loadUserForm = async (clientId: string) => {
  const fields = await fetch(`/api/clients/${clientId}/form-fields/user`)
    .then(r => r.json());
  
  // Client A might require 2FA, Client B might not
  // Client A might have different email domains allowed
  // Your frontend doesn't care - it just renders what backend sends!
  return fields;
};

Backend Team: Complete control over client-specific business rules

// Your API dynamically generates rules based on client configuration
app.get('/api/clients/:clientId/form-fields/user', async (req, res) => {
  const client = await getClientConfig(req.params.clientId);
  
  const emailField: InputFieldSpec = {
    displayName: "Email Address",
    dataType: "STRING",
    required: true,
    constraints: [
      { name: "email", pattern: "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", errorMessage: "Invalid email format" }
    ]
  };

  // Client-specific email domain restrictions
  if (client.restrictEmailDomains) {
    emailField.constraints.push({
      name: "allowedDomains",
      pattern: `^[^@]+@(${client.allowedDomains.join('|')})$`,
      errorMessage: `Email must be from: ${client.allowedDomains.join(', ')}`
    });
  }

  res.json({ fields: [emailField] });
});

Scenario 2: Smart Product Search with Live Data

Frontend Team: Rich autocomplete without hardcoding product lists

// Product search that adapts to inventory and user permissions
const productField = await fetch(`/api/form-fields/product?userRole=${userRole}`)
  .then(r => r.json());

// Backend controls:
// - Which products user can see
// - Search parameters  
// - Pagination settings
// - Validation rules

const searchProducts = async (query: string) => {
  if (productField.valuesEndpoint) {
    const response = await fetch(
      `${productField.valuesEndpoint.url}?${productField.valuesEndpoint.searchParam}=${query}`
    );
    return response.json();
  }
};

Backend Team: Dynamic product visibility and search logic

app.get('/api/form-fields/product', async (req, res) => {
  const userRole = req.query.userRole;
  
  let searchEndpoint = '/api/products/search';
  let constraints: ConstraintDescriptor[] = [
    { name: "required", errorMessage: "Please select a product" }
  ];

  // Admin users can see all products including discontinued
  if (userRole === 'admin') {
    searchEndpoint += '?includeDiscontinued=true';
  }
  
  // Sales users have minimum quantity requirements
  if (userRole === 'sales') {
    constraints.push({
      name: "minQuantity",
      min: 10,
      errorMessage: "Sales orders minimum 10 units"
    });
  }

  const productFieldSpec: InputFieldSpec = {
    displayName: "Product",
    dataType: "STRING",
    required: true,
    constraints,
    valuesEndpoint: {
      url: searchEndpoint,
      searchParam: "q",
      pageParam: "page",
      minSearchLength: 2
    }
  };

  res.json(productFieldSpec);
});

Scenario 3: Dynamic Form Generation

Frontend Team: Build entire forms from backend configuration

// Generate complete registration form from backend
const buildRegistrationForm = async (country: string, userType: string) => {
  const formConfig = await fetch(`/api/forms/registration?country=${country}&userType=${userType}`)
    .then(r => r.json());
  
  // Backend controls:
  // - Which fields are required per country
  // - Validation rules (phone formats, postal codes)
  // - Field order and grouping
  // - Conditional field visibility
  
  const formElements = formConfig.fields.map(fieldSpec => 
    createFormField(fieldSpec) // Your UI component factory
  );
  
  return formElements;
};

Backend Team: Country and role-specific form logic

app.get('/api/forms/registration', async (req, res) => {
  const { country, userType } = req.query;
  const countryConfig = await getCountryConfig(country);
  
  const fields: InputFieldSpec[] = [
    // Email - universal
    {
      displayName: "Email",
      dataType: "STRING",
      required: true,
      constraints: [
        { name: "email", pattern: "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", errorMessage: "Invalid email format" },
        { name: "maxLength", max: 100, errorMessage: "Email too long (max 100 chars)" }
      ]
    }
  ];

  // Phone field with country-specific validation
  fields.push({
    displayName: "Phone Number",
    dataType: "STRING", 
    required: countryConfig.phoneRequired,
    constraints: [
      {
        name: "phoneFormat",
        pattern: countryConfig.phonePattern,
        errorMessage: `Invalid phone format for ${country}`
      }
    ]
  });

  // Business users get additional fields
  if (userType === 'business') {
    fields.push({
      displayName: "Tax ID",
      dataType: "STRING",
      required: true,
      constraints: [
        {
          name: "taxIdFormat", 
          pattern: countryConfig.taxIdPattern,
          errorMessage: `Invalid tax ID format for ${country}`
        }
      ]
    });
  }

  res.json({ fields });
});

🎯 Key Features

Zero Dependencies

  • Pure TypeScript implementation
  • No external runtime dependencies
  • Works in browser and Node.js
  • Small bundle size (34.7 KB)

🔧 Enhanced API Design

  • Required field at top-level: required: boolean moved from constraints for better ergonomics
  • Ordered constraint execution: Constraints execute in array order for predictable behavior
  • Named constraints: Each constraint has a name property for better identification and debugging

Framework Integration

  • Angular HttpClient: Seamless integration with interceptors and dependency injection
  • Axios Support: Custom Axios instances with existing configurations preserved
  • Custom HTTP Clients: Configurable fetch-based client with interceptors and error handling
  • Zero Breaking Changes: Existing HTTP infrastructure remains intact

📦 Feature Summary

  • Zero Runtime Dependencies: Pure TypeScript implementation
  • Framework Integration: Angular, React, Vue, Vanilla JS support
  • HTTP Client Injection: Preserves existing interceptors and configurations
  • Type Safety: Complete TypeScript type definitions
  • Validation Engine: Comprehensive field validation
  • HTTP Client: Pluggable HTTP client with caching
  • Dependency Injection: Clean architecture with IoC
  • Extensive Testing: 58 tests with 96%+ coverage

🏗️ Architecture

Separation of Concerns

src/
├── types/          # Pure TypeScript interfaces (zero dependencies)
├── validation/     # Business logic validation engine  
├── client/         # Infrastructure (HTTP, caching, resolution)
└── __tests__/      # Comprehensive test suite

Design Patterns

  • Dependency Injection: Constructor injection with interfaces
  • Strategy Pattern: Pluggable HTTP clients and cache providers
  • Factory Pattern: Simplified object creation
  • Template Method: Validation algorithms

Dynamic Values Resolution

import { 
  ValuesResolver, 
  FetchHttpClient, 
  MemoryCacheProvider,
  createDefaultValuesEndpoint 
} from './src';

// Setup with dependency injection
const resolver = new ValuesResolver(
  new FetchHttpClient(),
  new MemoryCacheProvider()
);

// Configure endpoint
const endpoint = createDefaultValuesEndpoint('https://api.example.com/countries');

// Resolve values with caching and pagination
const result = await resolver.resolveValues(endpoint, { 
  search: 'france',
  page: 1,
  limit: 10 
});

Zero-Dependency Architecture

// No external dependencies at runtime!
import { MemoryCacheProvider, FetchHttpClient } from './src';

const cache = new MemoryCacheProvider();    // Uses native Map
const client = new FetchHttpClient();       // Uses native fetch

🧪 Testing

Run Tests

# All tests
npm test

# Watch mode
npm run test:watch

# Coverage report
npm run test:coverage

Test Results

  • 58 tests pass with 100% success rate
  • Types Module: 100% coverage
  • Validation Module: 96% coverage
  • Client Module: 86% coverage
  • Integration Tests: End-to-end scenarios with new v2.0 structure

🔨 Build & Development

Available Scripts

npm run build         # Build distribution files (CJS, ESM, types)
npm run dev           # Build in watch mode
npm run test          # Run test suite
npm run lint          # ESLint checking
npm run format        # Prettier formatting
npm run type-check    # TypeScript type checking

Build Output

dist/
├── index.js          # CommonJS build
├── index.mjs         # ES Module build
├── index.d.ts        # TypeScript declarations (CJS)
└── index.d.mts       # TypeScript declarations (ESM)

📋 API Reference (Snapshot)

Core Types

  • InputFieldSpec - Complete field specification
  • ConstraintDescriptor - Validation rules
  • ValidationResult - Validation outcome
  • ValuesEndpoint - Dynamic values configuration

Classes

  • FieldValidator - Main validation engine
  • ValuesResolver - Value resolution orchestrator
  • FetchHttpClient - HTTP client implementation
  • MemoryCacheProvider - In-memory caching

Interfaces

  • HttpClient - HTTP client abstraction
  • CacheProvider - Cache provider abstraction

🎯 Design Principles

Zero Dependencies

  • Runtime: No external dependencies
  • Build: Only development dependencies (TypeScript, Jest, etc.)
  • Browser: Uses native fetch, Map, etc.

Type Safety

  • Compile-time: Full TypeScript strict mode
  • Runtime: Type guards for external data
  • API: Strongly typed interfaces

Testability

  • Dependency Injection: Easy mocking and testing
  • Interface Segregation: Focused, testable interfaces
  • Pure Functions: Predictable, testable logic

📚 Documentation


🤝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Add tests for your changes
  4. Ensure all tests pass (npm test)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

📄 License

MIT License - see LICENSE file for details.



📦 Publishing (Maintainers)

# Build & test
npm run build && npm test

# Dry run publish
npm publish --dry-run

# Publish (ensure version not already published)
npm publish --access public

✅ Integrity Notes

  • Package name: input-spec
  • Protocol version constant exported as PROTOCOL_VERSION
  • Library version exported as LIBRARY_VERSION
  • No runtime dependencies; only dev tooling (TypeScript, Jest, tsup)

If something is unclear or you need a French version complète du README, ouvrez une issue. 🙌