Identifier & entity types
Source: core/dsl/IdentifierBase.ts (the neutral data class), core/dsl/IdentifierType.ts (the non-name identifier parts), and lang-typescript/src/createIdentifier.ts +…
IdentifierBase (@skmtc/core) — neutral data
export class IdentifierBase {
name: string // the identifier name; toString() returns it
typeName?: string // opaque type annotation (lang-interpreted)
exported: boolean // neutral visibility fact; defaults to true
constructor(args: { name: string; typeName?: string; exported?: boolean })
toString(): string // -> name
declarationKey(): string // -> name (the file's dedup slot; languages override)
}IdentifierBase is engine machinery: it rides ContentSettings, the
(name, exportPath) cross-generator cache key, and DefinitionBase.
The engine reads only name:
name— the only field the engine interprets (the cache-key source).typeName— optional type annotation, opaque to the engine. TypeScript'sTsDefinitionrenders it asname: typeName.exported— a language-neutral visibility fact the engine never interprets. TypeScript emits/omitsexport; Go capitalizes the name (visibility via casing); Rust prefixespub.declarationKey()— the key a file dedups definitions under. The neutral default is the barename; a language with a richer declaration space overrides it (seeTsIdentifierbelow).
Crucially, IdentifierBase carries no declaration-type field —
no kind, no type. The per-language declaration vocabulary lives,
typed, on the language subclasses (TsIdentifier.type), and its
schema-derived form travels separately as IdentifierType.
IdentifierType (@skmtc/core) — the non-name parts
// core/dsl/IdentifierType.ts
export type IdentifierType = {
type: string // per-language declaration type — opaque-boundary string
typeName?: string
exported?: boolean
}This is what a projection config's toIdentifierType returns: the
non-name parts of the identifier, derived from the schema. The
engine assembles the full identifier as
lang.toIdentifier({ name: toIdentifierName(args), ...toIdentifierType(subject, context) })
and holds the result as IdentifierBase. Core never interprets
type; a lang veneer tightens the return to its own named form —
TypeScript's is TsIdentifierType
(IdentifierType & { type: TsEntityType }).
TsIdentifier (@skmtc/lang-typescript) — the concrete subclass
export class TsIdentifier extends IdentifierBase {
type: TsEntityType // typed declaration vocabulary
constructor(args: IdentifierBaseArgs & { type: TsEntityType })
override declarationKey(): string // -> `${toTsKeyword(type)} ${name}`
}The renderer reads type to pick the declaration keyword and the
import form. The declarationKey override folds the keyword into
the dedup slot, so a class Foo and a declare namespace Foo —
distinct declarations the compiler legitimately merges — get
distinct keys instead of colliding.
Generators normally never call the constructor — they use the factories below. Direct construction is the escape hatch for language-package authors.
The TypeScript factories (@skmtc/lang-typescript)
export type TsEntityType = 'variable' | 'type' | 'class' | 'interface' | 'namespace'
createVariable(name: string, args?: { typeName?: string; exported?: boolean }): TsIdentifier
createType(name: string, args?: { exported?: boolean }): TsIdentifier
createClass(name: string, args?: { exported?: boolean }): TsIdentifier
createInterface(name: string, args?: { exported?: boolean }): TsIdentifier
createNamespace(name: string, args?: { exported?: boolean }): TsIdentifier
toTsKeyword(type: string): string // 'variable' -> 'const', 'namespace' -> 'declare namespace'; throws otherwise
isBlockType(type: TsEntityType): boolean // class | interface | namespace
isTypeOnly(type: TsEntityType): boolean // type | interfaceimport { createVariable, createType } from '@skmtc/lang-typescript'
createVariable('userBody') // export const userBody = ...
createVariable('useCreateUser', { typeName: 'UseMutationResult<...>' })
// export const useCreateUser: UseMutationResult<...> = ...
createType('UserBody') // export type UserBody = ...
createVariable('helper', { exported: false }) // const helper = ... (no export)The five entity types split two ways:
- Declaration form —
'variable'and'type'are assignment-form (export <kw> Name = value;);'class','interface', and'namespace'are block-form (export <kw> Name <value>— the value carries heritage and the braced body, no= …, no trailing;). SeeisBlockType. - Import form —
'type'and'interface'import type-only (import { type X }), avoiding TS1484 underverbatimModuleSyntax;'variable','class', and'namespace'import as plain named imports. SeeisTypeOnly.
The declaration keyword mapping is a single exhaustive map
(tsDeclarationKeywords in createIdentifier.ts); toTsKeyword
throws on a type outside the vocabulary — the loud signal that an
identifier built for another language reached the TypeScript
renderer.
Removed API (F6 — for readers of older code)
| Old (core 0.8.0 and earlier) | Now |
|---|---|
Identifier class in core carrying a kind / entity-type field | IdentifierBase (name, typeName, exported — no declaration type) + the lang subclass TsIdentifier.type |
Identifier.createVariable / Identifier.createType statics | createVariable / createType (+ createClass / createInterface / createNamespace) functions from @skmtc/lang-typescript, returning TsIdentifier |
Identifier.entityType (an EntityType instance) | TsIdentifier.type (typed TsEntityType) |
EntityType class / EntityTypeValue (core) | TsEntityType + toTsKeyword in @skmtc/lang-typescript; core has no keyword mapping |
Identifier.toImport() | gone (no consumers). Drivers build imports through the lang package; hand-registered imports tag types explicitly: { name, type: 'type' } |
| Private constructor (factory-only construction) | public constructor — language packages construct directly |
Naming conventions (unchanged)
Derive names deterministically inside toIdentifierName — method +
path for operations (via camelCase / capitalize / toMethodVerb
from @skmtc/core), refName for models — and role-suffix them
(Form, Hook, Table) for cache-key uniqueness. See the
skmtc-generator skill section on bare-noun identifiers, and
sanitizePropertyName (from @skmtc/lang-typescript) for
schema-supplied object keys.
File classes: FileBase, CodeFileBase, TsFile, JsonFile
The in-memory file representations the Generate phase accumulates into and the Render phase serializes. Core defines the neutral abstract bases (FileBase, CodeFileBase); the concrete code file for…
Import (ImportBase & TsImport)
The import seam, split across the language boundary: core's neutral ImportBase (merge contract only) and @skmtc/lang-typescript's TsImport (the concrete class that renders import { X } from '...'…