@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(): stringimport { 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 charactersimport { 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 failsimport { 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 | nullReturns 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 | nullReturns 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): ValidationResultvalidateId(id)
Validates a NanoID (platform ID or entity ID).
Must be exactly 10 characters, [a-z0-9] only.
function validateId(id: string): ValidationResultbuildHostname(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 typeimport { 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): stringbuildNnoHostname({ 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 | nullReturns 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.
| Type | Definition | Description |
|---|---|---|
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.
| Constant | Value | Description |
|---|---|---|
NNO_ID_LENGTH | 10 | Length of every NanoID (platform, tenant, stack IDs) |
CF_RESOURCE_NAME_MAX_LENGTH | 63 | Cloudflare 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
| Constant | Value | Description |
|---|---|---|
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 }): SpanContextimport { 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 UUIDbuildTracingHeaders(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 }
): NnoErrorEnvelopeimport { 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):
signatureis non-empty —missing_signaturetimestampis non-empty —missing_timestamptimestampis withinmaxAgeSecondsof now —timestamp_expired- 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.