Skip to content

Custom Primitives

You can extend the built-in primitive types with your own semantic extensions via atscript.config.ts. Custom primitives work exactly like built-in ones — they appear in IntelliSense, carry validation constraints, and generate appropriate type tags. For the full primitive system (complex shapes, container primitives, semantic tags, phantom design), see Custom Primitives — Plugin Development.

Quick Example

Add custom extensions under the primitives key in your config:

javascript
import { defineConfig } from '@atscript/core'
import ts from '@atscript/typescript'

export default defineConfig({
  rootDir: 'src',
  plugins: [ts()],
  primitives: {
    string: {
      extensions: {
        url: {
          type: 'string',
          documentation: 'URL format',
          annotations: {
            'expect.pattern': {
              pattern: '^https?://.+$',
              message: 'Invalid URL',
            },
          },
        },
        slug: {
          type: 'string',
          documentation: 'URL-safe slug',
          annotations: {
            'expect.pattern': {
              pattern: '^[a-z0-9-]+$',
              message: 'Invalid slug',
            },
          },
        },
      },
    },
    number: {
      extensions: {
        percentage: {
          type: 'number',
          documentation: 'Percentage value (0–100)',
          annotations: {
            'expect.min': 0,
            'expect.max': 100,
          },
        },
      },
    },
  },
})

Then use them in .as files with dot notation:

atscript
export interface Page {
    url: string.url
    slug: string.slug
    completeness: number.percentage
}

The validator will automatically enforce the constraints — no @expect.* annotations needed.

What You Can Define

Each primitive extension supports:

FieldDescription
typeThe base type ('string', 'number', 'boolean', 'phantom', etc.) — inherited from parent if omitted
documentationDescription shown in IntelliSense — inherited from parent if omitted
extensionsNested sub-extensions (e.g., number.int.positive)
isContainerIf true, the primitive cannot be used directly — one of its extensions must be chosen
tagsArray of semantic tags (e.g., ['created']) — inherited from parent, used by DB adapters and runtime tools
annotationsImplicit annotations applied to any field using this primitive (e.g., { 'expect.int': true, 'expect.min': 0 }) — merged with parent's annotations

isContainer

When isContainer: true, the primitive itself cannot be used as a type — only its extensions are valid:

atscript
field: ui             // ✗ Error — ui is a container, must use an extension
field: ui.action      // ✓ Correct — uses the extension

Inheritance

Extensions automatically inherit type, documentation, annotations, and tags from their parent primitive. You only need to specify fields you want to override or add. This is how built-in extensions like string.email work — they inherit type: 'string' from string and only add their own expect.pattern annotation.

Phantom Namespaces

You can define entirely new primitive namespaces with type: 'phantom' to create families of non-data UI elements. These are omitted from TypeScript types and skipped by validation, but discoverable at runtime — perfect for form renderers.

javascript
primitives: {
  ui: {
    type: 'phantom',
    isContainer: true,
    documentation: 'Non-data UI elements for form rendering',
    extensions: {
      action:    { documentation: 'An action element (button, link)' },
      divider:   { documentation: 'A visual divider between form sections' },
      paragraph: { documentation: 'A block of informational text' },
    },
  },
}
atscript
export interface CheckoutForm {
    @meta.label 'Email'
    email: string.email

    @meta.label 'Shipping Address'
    shippingHeader: ui.divider

    @meta.label 'Street'
    street: string
}

Annotations are always namespaced — use @meta.label (built-in), not bare @label. Custom annotations follow the same rule (@grid.column, @form.section, etc.).

Next Steps

Released under the MIT License.