NNO Docs
SDK Reference

@neutrino-io/core

Neutrino platform core primitives — naming, types, constants, tracing, errors, and middleware.

Package: @neutrino-io/core · Version: 0.3.2

Core primitives shared across all Neutrino services and feature packages. Prefer subpath imports for tree-shaking.

// Subpath imports (recommended)
import { generateId } from '@neutrino-io/core/naming'
import type { Environment } from '@neutrino-io/core/types'
import { NNO_ID_LENGTH } from '@neutrino-io/core/constants'
import { X_TRACE_ID } from '@neutrino-io/core/tracing'
import { ErrorCode, makeErrorEnvelope } from '@neutrino-io/core/errors'
import { verifyNnoSignature } from '@neutrino-io/core/middleware'

// Barrel import (convenience — imports everything)
import { generateId, Environment, NNO_ID_LENGTH } from '@neutrino-io/core'

@neutrino-io/core/naming

Utilities for generating and validating Cloudflare-safe resource names, IDs, and hostnames. Never invent your own naming logic — always use these functions.

generateId()

Generates a Cloudflare-safe NanoID: 10 characters, [a-z0-9] only.

Use for platform IDs, tenant IDs, and sub-tenant IDs.

function generateId(): string
import { generateId } from '@neutrino-io/core/naming'

const platformId = generateId() // e.g. "k3m9p2xw7q"

buildResourceName(parts)

Builds a Cloudflare resource name for a client platform resource.

Format: {platformId}-{stackId}-{service}[-resourceType][-stg]

Production names carry no suffix. Staging names end with -stg.

interface ResourceNameParts {
  platformId: string     // 10-char nano-id
  stackId: string        // 10-char nano-id or "default"
  service: string        // service slug, e.g. "auth"
  resourceType?: string  // "db" | "storage" | "kv" | "queue"
  staging?: boolean
}

function buildResourceName(parts: ResourceNameParts): string
// throws Error if name exceeds 63 chars or contains invalid characters
import { buildResourceName } from '@neutrino-io/core/naming'

buildResourceName({ platformId: 'k3m9p2xw7q', stackId: 'ab1cd2ef3g', service: 'auth' })
// → "k3m9p2xw7q-ab1cd2ef3g-auth"

buildResourceName({ platformId: 'k3m9p2xw7q', stackId: 'ab1cd2ef3g', service: 'auth', resourceType: 'db', staging: true })
// → "k3m9p2xw7q-ab1cd2ef3g-auth-db-stg"

buildNnoResourceName(parts)

Builds a Cloudflare resource name for a Neutrino NNO-level resource.

Format: nno-{platformId}-{service}[-resourceType][-stg]

interface NnoResourceNameParts {
  platformId: string
  service: string
  resourceType?: string  // "db" | "storage" | "kv" | "queue"
  staging?: boolean
}

function buildNnoResourceName(parts: NnoResourceNameParts): string
// throws Error if validation fails
import { buildNnoResourceName } from '@neutrino-io/core/naming'

buildNnoResourceName({ platformId: 'k3m9p2xw7q', service: 'registry', resourceType: 'db' })
// → "nno-k3m9p2xw7q-registry-db"

parseResourceName(name)

Parses a client resource name back into its components.

interface ParsedResourceName {
  platformId: string
  stackId: string
  service: string
  resourceType?: string
  isStaging: boolean
}

function parseResourceName(name: string): ParsedResourceName | null

Returns null if the name does not match the expected format.


parseNnoResourceName(name)

Parses a Neutrino NNO resource name back into its components.

interface ParsedNnoResourceName {
  platformId: string
  service: string
  resourceType?: string
  isStaging: boolean
}

function parseNnoResourceName(name: string): ParsedNnoResourceName | null

Returns null if the name does not start with nno- or fails validation.


validateResourceName(name)

Validates a Cloudflare resource name against platform constraints.

Rules: max 63 chars, only [a-z0-9-], cannot start or end with a hyphen.

interface ValidationResult {
  valid: boolean
  error?: string
}

function validateResourceName(name: string): ValidationResult

validateId(id)

Validates a NanoID (platform ID or entity ID).

Must be exactly 10 characters, [a-z0-9] only.

function validateId(id: string): ValidationResult

buildHostname(opts)

Builds a fully-qualified *.nno.app hostname for a client platform service.

Format: {name}.{type}[.stg].{stackId}.{platformId}.nno.app

interface BuildHostnameOpts {
  name: string       // DNS label, e.g. "api"
  type: 'app' | 'svc'
  stackId: string    // 10-char nano-id or "default"
  platformId: string // 10-char nano-id
  staging?: boolean
}

function buildHostname(opts: BuildHostnameOpts): string
// throws Error on invalid DNS label, stackId, platformId, or type
import { buildHostname } from '@neutrino-io/core/naming'

buildHostname({ name: 'api', type: 'svc', stackId: 'ab1cd2ef3g', platformId: 'k3m9p2xw7q' })
// → "api.svc.ab1cd2ef3g.k3m9p2xw7q.nno.app"

buildHostname({ name: 'api', type: 'svc', stackId: 'ab1cd2ef3g', platformId: 'k3m9p2xw7q', staging: true })
// → "api.svc.stg.ab1cd2ef3g.k3m9p2xw7q.nno.app"

buildNnoHostname(opts)

Builds a *.nno.app hostname for a Neutrino NNO core service.

Format: {name}.{type}[.stg].nno.app

interface BuildNnoHostnameOpts {
  name: string
  type: 'app' | 'svc'
  staging?: boolean
}

function buildNnoHostname(opts: BuildNnoHostnameOpts): string
buildNnoHostname({ name: 'registry', type: 'svc' })
// → "registry.svc.nno.app"

parseHostname(hostname)

Parses a *.nno.app hostname back into its components.

interface ParsedHostname {
  name: string
  type: 'app' | 'svc'
  stackId: string | null    // null for NNO core hostnames
  platformId: string | null // null for NNO core hostnames
  isStaging: boolean
  isNnoCore: boolean
}

function parseHostname(hostname: string): ParsedHostname | null

Returns null if the hostname does not end with .nno.app or has an unrecognised structure.


isValidUserStackId(id)

Returns true if id is a valid user-created stack ID (10-char nano-id). Returns false for "default" — the reserved built-in stack name.

function isValidUserStackId(id: string): boolean

@neutrino-io/core/types

Shared TypeScript types used across all Neutrino packages.

TypeDefinitionDescription
Environment"dev" | "stg" | "prod"Deployment environment
EntityType"platform" | "tenant" | "subtenant"Entity hierarchy level
ValidationResult{ valid: boolean; error?: string }Result from validators
import type { Environment, EntityType, ValidationResult } from '@neutrino-io/core/types'

@neutrino-io/core/constants

Shared platform constants.

ConstantValueDescription
NNO_ID_LENGTH10Length of every NanoID (platform, tenant, stack IDs)
CF_RESOURCE_NAME_MAX_LENGTH63Cloudflare resource name character limit
import { NNO_ID_LENGTH, CF_RESOURCE_NAME_MAX_LENGTH } from '@neutrino-io/core/constants'

@neutrino-io/core/tracing

Header name constants and helpers for distributed tracing. Import these everywhere — never hardcode header strings.

Constants

ConstantValueDescription
X_REQUEST_ID"X-Request-Id"Unique ID for a single HTTP request
X_TRACE_ID"X-Trace-Id"Distributed trace identifier, propagated across services
X_SPAN_ID"X-Span-Id"Identifier for a single span within a trace

Types

type TracingHeaderName = 'X-Request-Id' | 'X-Trace-Id' | 'X-Span-Id'

interface TracingHeaders {
  'X-Request-Id'?: string
  'X-Trace-Id'?: string
  'X-Span-Id'?: string
}

interface SpanContext {
  requestId: string
  traceId: string
  spanId: string
}

extractSpanContext(headers)

Extracts tracing context from incoming HTTP headers. Generates new UUIDs for any missing values. Always generates a fresh spanId per service hop.

function extractSpanContext(headers: { get(name: string): string | null }): SpanContext
import { extractSpanContext, X_TRACE_ID } from '@neutrino-io/core/tracing'

const ctx = extractSpanContext(request.headers)
// ctx.requestId — from X-Request-Id or new UUID
// ctx.traceId   — from X-Trace-Id or new UUID
// ctx.spanId    — always a new UUID

buildTracingHeaders(ctx)

Builds a headers record from a SpanContext for forwarding to upstream services.

function buildTracingHeaders(ctx: SpanContext): Record<string, string>

@neutrino-io/core/errors

Canonical NNO API error envelope and error codes.

ErrorCode

Common error codes used across all NNO services.

const ErrorCode = {
  UNAUTHORIZED: 'UNAUTHORIZED',
  FORBIDDEN: 'FORBIDDEN',
  VALIDATION_ERROR: 'VALIDATION_ERROR',
  INVALID_REQUEST: 'INVALID_REQUEST',
  NOT_FOUND: 'NOT_FOUND',
  CONFLICT: 'CONFLICT',
  INTERNAL_ERROR: 'INTERNAL_ERROR',
  SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
  TIMEOUT: 'TIMEOUT',
  RATE_LIMITED: 'RATE_LIMITED',
} as const

type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode]

NnoErrorEnvelopeSchema / NnoErrorEnvelope

Zod schema and inferred type for the canonical NNO API error response shape. All NNO services return errors in this envelope.

// Wire shape
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Platform not found",
    "details": { "platformId": "k3m9p2xw7q" }, // optional
    "requestId": "req_abc123"                   // optional
  }
}
import { NnoErrorEnvelopeSchema } from '@neutrino-io/core/errors'
import type { NnoErrorEnvelope } from '@neutrino-io/core/errors'

// Validate an unknown response body
const result = NnoErrorEnvelopeSchema.safeParse(body)

makeErrorEnvelope(code, message, options?)

Constructs a well-formed NnoErrorEnvelope object.

function makeErrorEnvelope(
  code: string,
  message: string,
  options?: { details?: Record<string, unknown>; requestId?: string }
): NnoErrorEnvelope
import { makeErrorEnvelope, ErrorCode } from '@neutrino-io/core/errors'

return c.json(
  makeErrorEnvelope(ErrorCode.NOT_FOUND, 'Platform not found', {
    details: { platformId },
    requestId: c.get('requestId'),
  }),
  404
)

@neutrino-io/core/middleware

HMAC-SHA256 signature verification for upstream service-to-service calls.

The gateway injects an X-NNO-Signature header on every proxied request. Upstream services use verifyNnoSignature to confirm the request originated from the gateway and has not been tampered with.

verifyNnoSignature(opts)

Verifies an X-NNO-Signature HMAC header. Uses constant-time comparison to prevent timing attacks.

Checks (in order):

  1. signature is non-empty — missing_signature
  2. timestamp is non-empty — missing_timestamp
  3. timestamp is within maxAgeSeconds of now — timestamp_expired
  4. Reconstructed HMAC matches provided signature — invalid_signature
interface VerifySignatureOptions {
  secret: string          // NNO_INTERNAL_API_KEY shared secret
  signature: string       // X-NNO-Signature header value
  timestamp: string       // X-NNO-Timestamp header value (unix seconds)
  nnoUserId: string
  nnoRole: string
  nnoPlatformId: string
  requestId: string
  maxAgeSeconds?: number  // default: 300 (5 minutes)
}

interface VerifyResult {
  valid: boolean
  reason?: 'missing_signature' | 'missing_timestamp' | 'timestamp_expired' | 'invalid_signature'
}

function verifyNnoSignature(opts: VerifySignatureOptions): Promise<VerifyResult>
import { verifyNnoSignature } from '@neutrino-io/core/middleware'

const result = await verifyNnoSignature({
  secret: env.NNO_INTERNAL_API_KEY,
  signature: c.req.header('X-NNO-Signature') ?? '',
  timestamp: c.req.header('X-NNO-Timestamp') ?? '',
  nnoUserId: c.req.header('X-NNO-User-Id') ?? '',
  nnoRole: c.req.header('X-NNO-Role') ?? '',
  nnoPlatformId: c.req.header('X-NNO-Platform-Id') ?? '',
  requestId: c.get('requestId'),
})

if (!result.valid) {
  return c.json(makeErrorEnvelope('UNAUTHORIZED', 'Invalid gateway signature'), 401)
}

computeNnoSignature(key, payload)

Computes an HMAC-SHA256 signature over payload using key. Returns a lowercase hex-encoded 64-character string.

function computeNnoSignature(key: string, payload: string): Promise<string>

This is the same algorithm used by services/gateway. Exposed for testing and debugging.

On this page