npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@biyonik/zignal

v0.1.2

Published

Signal-first, schema-driven form library for Angular 17+. Powered by Zod validation.

Downloads

270

Readme

npm version License: MIT Angular Zod TypeScript


TR: Hakkında | EN: About

TR: Zignal, Angular Signals kullanarak reaktif form yönetimi sağlayan, Zod validasyonu ile type-safe bir form kütüphanesidir. Türkiye'ye özgü validatorlar (TCKN, VKN, IBAN) ve çoklu dil desteği içerir.

EN: Zignal is a type-safe form library that provides reactive form management using Angular Signals with Zod validation. It includes Turkey-specific validators (TCKN, VKN, IBAN) and multi-language support.


TR: Özellikler | EN: Features

| TR | EN | |----|----| | Signal-first - Angular Signals ile fine-grained reactivity | Signal-first - Fine-grained reactivity with Angular Signals | | Schema-driven - JSON'dan dinamik form oluşturma | Schema-driven - Dynamic form generation from JSON | | Type-safe - Zod entegrasyonu ile runtime + compile-time güvenlik | Type-safe - Runtime + compile-time safety with Zod integration | | Zoneless ready - Angular 18+ zoneless mode uyumlu | Zoneless ready - Compatible with Angular 18+ zoneless mode | | i18n - Çoklu dil desteği (TR, EN ve custom) | i18n - Multi-language support (TR, EN and custom) | | Form Persistence - localStorage/sessionStorage desteği | Form Persistence - localStorage/sessionStorage support | | Async Validation - API-based asenkron validasyon | Async Validation - API-based asynchronous validation | | TR Validators - TCKN, VKN, IBAN, Plaka validasyonu | TR Validators - Turkish ID, Tax ID, IBAN, License plate validation |


TR: Kurulum | EN: Installation

npm install @biyonik/zignal zod

TR: Hızlı Başlangıç | EN: Quick Start

1. Field Tanımlama | Define Fields

import { StringField, NumberField, BooleanField, PasswordField } from '@biyonik/zignal';

// TR: E-posta alanı
// EN: Email field
const emailField = new StringField('email', 'E-posta', {
  required: true,
  email: true
});

// TR: Yaş alanı
// EN: Age field
const ageField = new NumberField('age', 'Yaş', {
  required: true,
  min: 18,
  max: 100,
  integer: true
});

// TR: Şifre alanı (güçlü şifre kuralları)
// EN: Password field (strong password rules)
const passwordField = new PasswordField('password', 'Şifre', {
  required: true,
  minLength: 8,
  requireUppercase: true,
  requireNumber: true,
  requireSpecial: true
});

// TR: Şartları kabul checkbox
// EN: Terms acceptance checkbox
const acceptTerms = new BooleanField('acceptTerms', 'Şartları kabul ediyorum', {
  required: true  // true olması zorunlu
});

2. Form Schema Oluşturma | Create Form Schema

import { FormSchema } from '@biyonik/zignal';

interface UserForm {
  email: string;
  age: number;
  password: string;
  acceptTerms: boolean;
}

const userSchema = new FormSchema<UserForm>([
  emailField,
  ageField,
  passwordField,
  acceptTerms
]);

3. Component'te Kullanım | Use in Component

import { Component } from '@angular/core';

@Component({
  selector: 'app-user-form',
  template: `
    <form (ngSubmit)="onSubmit()">
      <!-- Email -->
      <div class="field">
        <label>{{ form.fields.email.label }}</label>
        <input
          type="email"
          [value]="form.fields.email.value()"
          (input)="form.setValue('email', $event.target.value)"
          (blur)="form.fields.email.touched.set(true)"
        />
        @if (form.fields.email.error()) {
          <span class="error">{{ form.fields.email.error() }}</span>
        }
      </div>

      <!-- Password -->
      <div class="field">
        <label>{{ form.fields.password.label }}</label>
        <input
          type="password"
          [value]="form.fields.password.value()"
          (input)="form.setValue('password', $event.target.value)"
        />
        @if (form.fields.password.error()) {
          <span class="error">{{ form.fields.password.error() }}</span>
        }
      </div>

      <button type="submit" [disabled]="!form.valid()">
        Kaydet / Save
      </button>
    </form>
  `
})
export class UserFormComponent {
  form = userSchema.createForm({
    email: '',
    age: null,
    password: '',
    acceptTerms: false
  });

  async onSubmit() {
    if (await this.form.validateAll()) {
      const data = this.form.getValues();
      console.log('Form data:', data);
    }
  }
}

TR: Dil Desteği (i18n) | EN: Language Support (i18n)

import { setLocale, addMessages, t, detectBrowserLocale, useAutoLocale } from '@biyonik/zignal';

// TR: Browser diline göre otomatik ayarla
// EN: Auto-detect browser language
const locale = useAutoLocale(); // Returns 'tr' or 'en'

// TR: Manuel dil değiştir
// EN: Change language manually
setLocale('en');

// TR: Custom mesajlar ekle (firma özel)
// EN: Add custom messages (company specific)
addMessages('tr', {
  required: 'Bu bilgi zorunludur',
  'string.email': 'Lütfen kurumsal e-posta adresinizi giriniz',
});

// TR: Yeni dil ekle (örn: Almanca)
// EN: Add new language (e.g., German)
addMessages('de', {
  required: 'Dieses Feld ist erforderlich',
  'string.min': 'Mindestens {min} Zeichen erforderlich',
});
setLocale('de');

// TR: Validation mesajını al
// EN: Get validation message
const msg = t('password.min', { min: 8 });
// TR: "Şifre en az 8 karakter olmalıdır"
// EN: "Password must be at least 8 characters"

TR: Form Persistence | EN: Form Persistence

import { createFormPersistence } from '@biyonik/zignal';

// TR: Form verilerini localStorage'a kaydet
// EN: Save form data to localStorage
const persistence = createFormPersistence<UserForm>('user-form', {
  storage: 'local',           // 'local' | 'session'
  debounceMs: 500,            // Auto-save debounce
  exclude: ['password'],      // Hassas alanları hariç tut
  ttl: 24 * 60 * 60 * 1000,   // 24 saat sonra expire
});

// TR: Form verilerini yükle
// EN: Load form data
const savedData = persistence.load();
if (savedData) {
  form.patchValues(savedData);
}

// TR: Auto-save aktifleştir
// EN: Enable auto-save
persistence.enableAutoSave(form.values);

// TR: Manuel kaydet
// EN: Manual save
persistence.save(form.getValues());

// TR: Temizle
// EN: Clear
persistence.clear();

TR: Asenkron Validasyon | EN: Async Validation

import { createEmailValidator, createUsernameValidator } from '@biyonik/zignal';

// TR: E-posta benzersizlik kontrolü
// EN: Email uniqueness check
const emailValidator = createEmailValidator(
  async (email) => {
    const response = await fetch(`/api/check-email?email=${email}`);
    const { exists } = await response.json();
    return !exists; // true = geçerli, false = zaten var
  },
  {
    debounceMs: 300,
    cacheSize: 50,
    errorMessage: 'Bu e-posta adresi zaten kullanımda'
  }
);

// TR: Component'te kullan
// EN: Use in component
@Component({...})
export class RegisterComponent {
  emailValidator = emailValidator;

  async checkEmail(email: string) {
    const error = await this.emailValidator.validate(email);
    if (error) {
      console.log('Validation error:', error);
    }
  }

  // TR: Reactive state
  isPending = this.emailValidator.pending;   // Signal<boolean>
  isValid = this.emailValidator.valid;       // Signal<boolean>
  errorMsg = this.emailValidator.error;      // Signal<string | null>
}

TR: Türkiye Validatorları | EN: Turkey Validators

import {
  tcknSchema,
  vknSchema,
  turkishIbanSchema,
  turkishPhoneSchema,
  turkishPlateSchema,
  isValidTCKN,
  isValidVKN
} from '@biyonik/zignal';

// TR: TCKN validasyonu
// EN: Turkish ID validation
const tckn = tcknSchema.safeParse('12345678901');
if (!tckn.success) {
  console.log('Geçersiz TCKN');
}

// TR: Direkt fonksiyon kullanımı
// EN: Direct function usage
if (isValidTCKN('12345678901')) {
  console.log('TCKN geçerli');
}

// TR: Form field olarak
// EN: As form field
const tcknField = new StringField('tckn', 'T.C. Kimlik No', {
  required: true,
  pattern: /^\d{11}$/,
  customValidator: (value) => isValidTCKN(value) ? null : 'Geçersiz TCKN'
});

TR: Field Tipleri | EN: Field Types

| Field | Type | TR: Açıklama | EN: Description | |-------|------|--------------|-----------------| | StringField | string | Tek satır metin | Single line text | | NumberField | number | Sayısal değer | Numeric value | | BooleanField | boolean | Evet/Hayır | Yes/No | | DateField | Date | Tarih seçici | Date picker | | PasswordField | string | Şifre (güç göstergeli) | Password (with strength) | | EmailField | string | E-posta | Email | | UrlField | string | URL | URL | | TextareaField | string | Çok satırlı metin | Multi-line text | | SelectField<T> | T | Dropdown seçimi | Dropdown selection | | MultiselectField<T> | T[] | Çoklu seçim | Multiple selection | | ArrayField | object[] | Tekrarlayan kayıtlar | Repeatable records | | JsonField | object | JSON/Object | JSON/Object | | FileField | FileInfo | Dosya yükleme | File upload |


TR: FormState API | EN: FormState API

const form = schema.createForm(initialValues);

// ===============================================
// TR: Signals (Reaktif)
// EN: Signals (Reactive)
// ===============================================
form.values();           // Signal<T> - Tüm değerler / All values
form.valid();            // Signal<boolean> - Geçerlilik / Validity
form.dirty();            // Signal<boolean> - Değişiklik var mı? / Has changes?
form.errors();           // Signal<Record<string, string | null>>

// ===============================================
// TR: Field Erişimi
// EN: Field Access
// ===============================================
form.fields.email.value();     // Signal<string>
form.fields.email.error();     // Signal<string | null>
form.fields.email.touched();   // Signal<boolean>
form.fields.email.valid();     // Signal<boolean>
form.fields.email.dirty();     // Signal<boolean>

// ===============================================
// TR: Aksiyonlar
// EN: Actions
// ===============================================
form.setValue('email', '[email protected]');
form.patchValues({ email: '[email protected]', age: 25 });
form.touchAll();               // Tüm hataları göster / Show all errors
form.reset();                  // Başlangıç değerlerine dön / Reset to initial
form.validateAll();            // Async validation

// ===============================================
// TR: Getter'lar
// EN: Getters
// ===============================================
form.getValues();              // Type-safe data (Zod parsed)
form.getDirtyValues();         // Sadece değişenler / Only changed fields

TR: JSON'dan Dinamik Form | EN: Dynamic Form from JSON

import { SchemaFactory } from '@biyonik/zignal';

@Component({...})
export class DynamicFormComponent {
  private factory = inject(SchemaFactory);

  form = this.factory.parse([
    {
      type: 'string',
      name: 'email',
      label: 'E-posta',
      config: { required: true, email: true }
    },
    {
      type: 'number',
      name: 'age',
      label: 'Yaş',
      config: { min: 18, max: 100 }
    },
    {
      type: 'select',
      name: 'country',
      label: 'Ülke',
      config: {
        required: true,
        options: [
          { value: 'TR', label: 'Türkiye' },
          { value: 'US', label: 'USA' }
        ]
      }
    }
  ]);
}

TR: Karşılaştırma | EN: Comparison

| TR: Özellik / EN: Feature | ngx-formly | Reactive Forms | Zignal | |---------------------------|------------|----------------|------------| | Reactivity | RxJS | RxJS | Angular Signals | | Zoneless Support | ❌ | ⚠️ | | | Type Safety | Limited | Limited | Full (Zod) | | i18n Support | Plugin | Manual | Built-in | | Form Persistence | ❌ | Manual | Built-in | | Async Validation | ✅ | ✅ | ✅ + Cache | | Bundle Size | ~50KB | ~0KB (Angular) | <20KB | | Learning Curve | High | Medium | Low |


TR: Gereksinimler | EN: Requirements

  • Angular 17.0.0+
  • Zod 3.22.0+
  • TypeScript 5.0+

TR: Lisans | EN: License

MIT License - Ahmet ALTUN


TR: Katkıda Bulunma | EN: Contributing

  1. Fork edin / Fork
  2. Feature branch oluşturun / Create feature branch (git checkout -b feature/amazing-feature)
  3. Commit edin / Commit (git commit -m 'feat: add amazing feature')
  4. Push edin / Push (git push origin feature/amazing-feature)
  5. Pull Request açın / Open Pull Request

TR: Destek | EN: Support