ワンクリックで
add-namespace
Creates a new namespace package for a new API specification version in the ApiDOM monorepo
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Creates a new namespace package for a new API specification version in the ApiDOM monorepo
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Creates parser adapter packages for existing ApiDOM namespace packages and integrates them with apidom-reference
Updates apidom-ls configuration for a namespace package by analyzing the namespace structure and creating completion, documentation, and lint configurations
SOC 職業分類に基づく
| name | add-namespace |
| description | Creates a new namespace package for a new API specification version in the ApiDOM monorepo |
| disable-model-invocation | false |
| user-invocable | true |
Skill Name: add-namespace
Description: Creates a new namespace package for a new API specification version in the ApiDOM monorepo.
This skill guides you through creating a complete namespace package (apidom-ns-{spec}-{version}) for a new API specification version. It automates the creation of all required files and directories following ApiDOM's established patterns.
Use this skill when:
Recent Example: OpenAPI 3.2.0 support was added using these patterns, introducing features like:
Before running this skill:
READ THIS SECTION CAREFULLY before starting implementation. Refer to CLAUDE.md in the repository root for detailed guidelines.
When implementing a new specification version (especially when extending a parent version):
Check Parent Version First (⚠️ MOST COMMON MISTAKE):
Components.pathItems in OAS 3.2 if it already exists in OAS 3.1packages/apidom-ns-{parent}/src/elements/{ElementName}.tsVerify Field Existence in Specification:
components.webhooks if spec has webhooks at root level)Implement ALL New Fields:
Verify All URLs:
curl -I <url> before using ithttps://spec.openapis.org/oas/3.2/dialect/2025-09-17 (with date), not .../dialect/baseSearch Before Creating:
OpenApi3-1.ts when OpenApi3-2.ts should be used)Before writing ANY code:
Ask the user for the following information:
Specification Name: The name of the specification (e.g., 'openapi', 'asyncapi', 'arazzo')
Specification Version: The version string (e.g., '3-1', '2', '4-0')
Package Description: Short description for package.json
Parent Namespace (optional): If basing on existing namespace, which one? (e.g., '@swagger-api/apidom-ns-asyncapi-2')
Specification Elements: List of all element types in the specification with their properties
For each element, collect:
Create the following directory structure:
packages/apidom-ns-{spec}-{version}/
├── src/
│ ├── elements/
│ │ ├── nces/ # Named Collection Elements
│ │ └── {ElementName}.ts
│ ├── refractor/
│ │ ├── visitors/
│ │ │ ├── generics/
│ │ │ │ ├── FixedFieldsVisitor.ts
│ │ │ │ ├── PatternedFieldsVisitor.ts
│ │ │ │ └── MapVisitor.ts
│ │ │ ├── {spec}-{version}/
│ │ │ │ ├── {element-name}/
│ │ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ │ ├── FallbackVisitor.ts
│ │ │ ├── SpecificationExtensionVisitor.ts
│ │ │ ├── SpecificationVisitor.ts
│ │ │ └── Visitor.ts
│ │ ├── plugins/
│ │ ├── index.ts
│ │ ├── registration.ts
│ │ ├── specification.ts
│ │ ├── predicates.ts
│ │ └── toolbox.ts
│ ├── traversal/
│ │ └── visitor.ts
│ ├── index.ts
│ ├── namespace.ts
│ ├── predicates.ts
│ └── media-types.ts
├── test/
│ ├── refractor/
│ │ ├── elements/
│ │ │ └── {ElementName}/
│ │ │ ├── index.ts
│ │ │ └── __snapshots__/
│ │ └── plugins/
│ ├── fixtures/
│ ├── mocha-bootstrap.ts
│ ├── predicates.ts
│ └── tsconfig.json
├── config/
│ ├── api-extractor/
│ │ └── api-extractor.json
│ └── webpack/
│ └── browser.config.js
├── package.json
├── tsconfig.json
├── tsconfig.declaration.json
└── README.md
{
"name": "@swagger-api/apidom-ns-{spec}-{version}",
"version": "1.0.0",
"description": "{Specification Name} {Version} namespace for ApiDOM.",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"type": "module",
"sideEffects": [
"./src/refractor/registration.mjs",
"./src/refractor/registration.cjs"
],
"unpkg": "./dist/apidom-ns-{spec}-{version}.browser.min.js",
"main": "./src/index.cjs",
"exports": {
"types": "./types/apidom-ns-{spec}-{version}.d.ts",
"import": "./src/index.mjs",
"require": "./src/index.cjs"
},
"types": "./types/apidom-ns-{spec}-{version}.d.ts",
"scripts": {
"build": "npm run clean && run-p --max-parallel ${CPU_CORES:-2} typescript:declaration build:es build:cjs build:umd:browser",
"build:es": "cross-env BABEL_ENV=es babel src --out-dir src --extensions '.ts' --out-file-extension '.mjs' --root-mode 'upward'",
"build:cjs": "cross-env BABEL_ENV=cjs babel src --out-dir src --extensions '.ts' --out-file-extension '.cjs' --root-mode 'upward'",
"build:umd:browser": "cross-env BABEL_ENV=browser webpack --config config/webpack/browser.config.js --progress",
"lint": "eslint ./",
"lint:fix": "eslint ./ --fix",
"clean": "rimraf --glob 'src/**/*.mjs' 'test/**/*.mjs' ./dist ./types",
"test": "NODE_ENV=test ts-mocha --exit",
"test:update-snapshots": "cross-env UPDATE_SNAPSHOT=1 BABEL_ENV=cjs mocha",
"typescript:check-types": "tsc --noEmit && tsc -p ./test/tsconfig.json --noEmit",
"typescript:declaration": "tsc -p tsconfig.declaration.json && api-extractor run -l -c ./config/api-extractor/api-extractor.json",
"prepack": "copyfiles -u 3 ../../LICENSES/* LICENSES && copyfiles -u 2 ../../NOTICE .",
"postpack": "rimraf NOTICE LICENSES"
},
"repository": {
"type": "git",
"url": "git+https://github.com/swagger-api/apidom.git"
},
"author": "SmartBear",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime-corejs3": "^7.26.10",
"@swagger-api/apidom-core": "^1.2.2",
"@types/ramda": "~0.30.0",
"ramda": "~0.30.0",
"ramda-adjunct": "^5.0.0",
"ts-mixer": "^6.0.3"
},
"files": [
"src/**/*.mjs",
"src/**/*.cjs",
"dist/",
"types/apidom-ns-{spec}-{version}.d.ts",
"LICENSES",
"NOTICE",
"README.md",
"CHANGELOG.md"
]
}
Note: Add parent namespace to dependencies if extending an existing namespace.
For each element, create src/elements/{ElementName}.ts:
import {
ObjectElement,
ArrayElement,
StringElement,
NumberElement,
BooleanElement,
Attributes,
Meta,
} from '@swagger-api/apidom-core';
/**
* @public
*/
class {ElementName} extends {BaseElementType} {
constructor(content?: {ContentType}, meta?: Meta, attributes?: Attributes) {
super(content, meta, attributes);
this.element = '{elementName}';
// Add CSS classes if needed
// this.classes.push('{className}');
}
// Generate getter/setter pairs for each property
get {propertyName}(): {PropertyType} | undefined {
return this.get('{propertyName}');
}
set {propertyName}({propertyName}: {PropertyType} | undefined) {
this.set('{propertyName}', {propertyName});
}
}
export default {ElementName};
Element Type Mapping:
extends ObjectElement, content type: Record<string, unknown>extends ArrayElement, content type: Array<unknown>extends StringElement, content type: stringextends NumberElement, content type: numberextends BooleanElement, content type: booleanProperty Type Mapping:
StringElement for string propertiesNumberElement for number propertiesBooleanElement for boolean propertiesArrayElement for array propertiesObjectElement for generic object propertiesInfoElement, ServerElement)import { NamespacePluginOptions } from '@swagger-api/apidom-core';
import {ElementName}Element from './elements/{ElementName}.ts';
// Import all element classes...
/**
* @public
*/
const {specName}{version} = {
namespace: (options: NamespacePluginOptions) => {
const { base } = options;
// Register all elements
base.register('{elementName}', {ElementName}Element);
// Register remaining elements...
return base;
},
};
export default {specName}{version};
import { createPredicate } from '@swagger-api/apidom-core';
import {ElementName}Element from './elements/{ElementName}.ts';
// Import all element classes...
/**
* @public
*/
export const is{ElementName}Element = createPredicate(
({ hasBasicElementProps, isElementType, primitiveEq, hasClass }) => {
return (element: unknown): element is {ElementName}Element =>
element instanceof {ElementName}Element ||
(hasBasicElementProps(element) &&
isElementType('{elementName}', element) &&
primitiveEq('{primitiveType}', element));
// Add hasClass checks if element has CSS classes:
// && hasClass('{className}', element));
},
);
// Create predicates for all elements...
Primitive Type Mapping:
'object''array''string''number''boolean'/**
* @public
*/
export interface {SpecName}MediaTypes {
latest: (format: Format) => string;
generic: (format: Format) => string;
}
/**
* @public
*/
export type Format = 'json' | 'yaml';
const jsonMediaType = (version: string) =>
`application/vnd.{spec}.{version}+json`;
const yamlMediaType = (version: string) =>
`application/vnd.{spec}.{version}+yaml`;
const mediaTypes: {SpecName}MediaTypes = {
latest: (format = 'json') =>
format === 'json' ? jsonMediaType('{version}') : yamlMediaType('{version}'),
generic: (format = 'json') =>
format === 'json' ? 'application/json' : 'application/yaml',
};
export default mediaTypes;
Standard Fixed Fields Visitor (for most object elements):
For each element, create src/refractor/visitors/{spec}-{version}/{element-name}/index.ts:
import { Mixin } from 'ts-mixer';
import { always } from 'ramda';
import {ElementName}Element from '../../../../elements/{ElementName}.ts';
import FixedFieldsVisitor, {
FixedFieldsVisitorOptions,
SpecPath,
} from '../../generics/FixedFieldsVisitor.ts';
import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts';
/**
* @public
*/
export interface {ElementName}VisitorOptions
extends FixedFieldsVisitorOptions,
FallbackVisitorOptions {}
/**
* @public
*/
class {ElementName}Visitor extends Mixin(FixedFieldsVisitor, FallbackVisitor) {
declare public readonly element: {ElementName}Element;
declare protected readonly specPath: SpecPath<['document', 'objects', '{ElementName}']>;
declare protected readonly canSupportSpecificationExtensions: true;
constructor(options: {ElementName}VisitorOptions) {
super(options);
this.element = new {ElementName}Element();
this.specPath = always(['document', 'objects', '{ElementName}']);
this.canSupportSpecificationExtensions = true;
}
}
export default {ElementName}Visitor;
Note: Set canSupportSpecificationExtensions to true if the element supports extension fields (e.g., x-* properties).
Map Visitor (for dynamic key-value objects like components.mediaTypes, additionalOperations):
import { Mixin } from 'ts-mixer';
import { T as stubTrue, always } from 'ramda';
import { isStringElement } from '@swagger-api/apidom-core';
import MapVisitor, { MapVisitorOptions, SpecPath } from '../../generics/MapVisitor.ts';
import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts';
/**
* @public
*/
export interface {CollectionName}VisitorOptions extends MapVisitorOptions, FallbackVisitorOptions {}
/**
* @public
*/
class {CollectionName}Visitor extends Mixin(MapVisitor, FallbackVisitor) {
declare public readonly element: {CollectionElement};
declare protected readonly specPath: SpecPath<['document', 'objects', '{ParentObject}']>;
declare protected readonly canSupportSpecificationExtensions: false;
constructor(options: {CollectionName}VisitorOptions) {
super(options);
this.element = new {CollectionElement}();
this.specPath = always(['document', 'objects', '{ParentObject}']);
this.canSupportSpecificationExtensions = false;
}
ObjectElement(objectElement: ObjectElement) {
const result = MapVisitor.prototype.ObjectElement.call(this, objectElement);
// Decorate each child element if needed
// Example: Mark elements as references or operations
this.element.forEach((value: Element, key: Element) => {
// Add metadata to help identify element types
if (isStringElement(key)) {
value.setMetaProperty('{metadata-name}', key.toValue());
}
});
this.copyMetaAndAttributes(objectElement, result);
return result;
}
}
export default {CollectionName}Visitor;
Dynamic Type Detection Visitor (for fields that can be Reference OR specific type):
import { Mixin } from 'ts-mixer';
import { always } from 'ramda';
import { ObjectElement, isStringElement, isObjectElement, toValue } from '@swagger-api/apidom-core';
import MapVisitor, { MapVisitorOptions, SpecPath } from '../../generics/MapVisitor.ts';
import FallbackVisitor, { FallbackVisitorOptions } from '../../FallbackVisitor.ts';
import { isReferenceElement } from '../../../..';
/**
* Handles fields that can contain either Reference objects or specific element types.
* Decorates with metadata to help dereference strategies.
*
* @public
*/
class {CollectionName}Visitor extends Mixin(MapVisitor, FallbackVisitor) {
declare public readonly element: {CollectionElement};
declare protected readonly specPath: SpecPath<['document', 'objects', '{ParentObject}']>;
constructor(options: {CollectionName}VisitorOptions) {
super(options);
this.element = new {CollectionElement}();
this.specPath = always(['document', 'objects', '{ParentObject}']);
this.canSupportSpecificationExtensions = false;
}
ObjectElement(objectElement: ObjectElement) {
const result = MapVisitor.prototype.ObjectElement.call(this, objectElement);
// Detect and decorate elements based on their structure
this.element.forEach((value: Element, key: Element, memberElement: MemberElement) => {
// Determine if this is a Reference or actual element
if (isObjectElement(value)) {
const hasRef = value.hasKey('$ref');
if (hasRef) {
// It's a reference - decorate with referenced element type
memberElement.setMetaProperty('referenced-element', '{elementType}');
}
}
// Add identifying metadata
if (isStringElement(key)) {
value.setMetaProperty('{name-property}', key.toValue());
}
});
this.copyMetaAndAttributes(objectElement, result);
return result;
}
}
export default {CollectionName}Visitor;
Use Cases:
import FallbackVisitor from './visitors/FallbackVisitor.ts';
import {ElementName}Visitor from './visitors/{spec}-{version}/{element-name}/index.ts';
import {DynamicCollection}Visitor from './visitors/{spec}-{version}/{collection-name}/index.ts';
// Import all visitors...
/**
* Specification object allows us to have complete control over visitors
* when traversing the ApiDOM.
* Specification also allows us to create amended refractors from
* existing ones by manipulating it.
*
* Note: Specification object allows to use absolute internal JSON pointers.
*
* @public
*/
const specification = {
visitors: {
value: FallbackVisitor,
document: {
objects: {
{ElementName}: {
$visitor: {ElementName}Visitor,
fixedFields: {
// Simple value fields (string, number, boolean)
{propertyName}: { $ref: '#/visitors/value' },
// Nested object references
{nestedProperty}: { $ref: '#/visitors/document/objects/{NestedElement}' },
// Array fields with specific visitor
{arrayProperty}: {ArrayVisitor},
// Dynamic collections (maps with unknown keys)
{dynamicCollection}: {
$visitor: {DynamicCollection}Visitor,
// Define what visitor handles the values in the map
value: { $ref: '#/visitors/document/objects/{ValueElement}' },
},
// Collections that can be Reference OR specific element
{mixedCollection}: {
$visitor: {MixedCollection}Visitor,
value(element: Element) {
// Dynamically determine visitor based on element structure
if (isObjectElement(element) && element.hasKey('$ref')) {
// It's a reference
return { $ref: '#/visitors/value' };
}
// It's an actual element
return { $ref: '#/visitors/document/objects/{ActualElement}' };
},
},
},
},
// Define all element structures...
},
},
},
};
export default specification;
Advanced Specification Patterns:
import { specificationObj as parentSpec } from '@swagger-api/apidom-ns-{parent}';
const specification = {
visitors: {
...parentSpec.visitors,
document: {
objects: {
...parentSpec.visitors.document.objects,
// Override root element with new fields
{RootElement}: {
$visitor: {RootElement}Visitor,
fixedFields: {
...parentSpec.visitors.document.objects.{RootElement}.fixedFields,
// Add new fields
{newField}: { $ref: '#/visitors/value' },
},
},
// Add completely new elements
{NewElement}: {
$visitor: {NewElement}Visitor,
fixedFields: {
// ...
},
},
},
},
},
};
{
$visitor: PatternedFieldsVisitor,
patternedFields: {
// Keys starting with 'x-' are extensions
'^x-': { $ref: '#/visitors/value' },
},
}
{
value(element: Element) {
if (isConditionMet(element)) {
return { $ref: '#/visitors/document/objects/{ElementA}' };
}
return { $ref: '#/visitors/document/objects/{ElementB}' };
},
}
import {ElementName}Element from '../elements/{ElementName}.ts';
// Import all elements...
import { createRefractor } from './index.ts';
{ElementName}Element.refract = createRefractor([
'visitors',
'document',
'objects',
'{ElementName}',
'$visitor',
]);
// Register all elements...
export {
isRefElement,
isLinkElement as isLinkPrimitiveElement,
isMemberElement,
isObjectElement,
isArrayElement,
isBooleanElement,
isNullElement,
isElement,
isNumberElement,
isStringElement,
} from '@swagger-api/apidom-core';
export { default as mediaTypes, {SpecName}MediaTypes } from './media-types.ts';
export type { Format } from './media-types.ts';
// eslint-disable-next-line no-restricted-exports
export { default } from './namespace.ts';
export { default as refract, createRefractor } from './refractor/index.ts';
export { default as specificationObj } from './refractor/specification.ts';
// Export visitor base classes
export { default as FixedFieldsVisitor } from './refractor/visitors/generics/FixedFieldsVisitor.ts';
export type {
FixedFieldsVisitorOptions,
SpecPath,
} from './refractor/visitors/generics/FixedFieldsVisitor.ts';
export { default as FallbackVisitor } from './refractor/visitors/FallbackVisitor.ts';
export type { FallbackVisitorOptions } from './refractor/visitors/FallbackVisitor.ts';
// Export visitor types
export type {
default as {ElementName}Visitor,
{ElementName}VisitorOptions,
} from './refractor/visitors/{spec}-{version}/{element-name}/index.ts';
// Export all visitor types...
// Export predicates
export {
is{ElementName}Element,
// Export all predicates...
} from './predicates.ts';
// Export elements
export { {ElementName}Element } from './refractor/registration.ts';
// Export all elements...
{
"extends": "../../tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"composite": true
}
}
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"outDir": "./types"
}
}
Copy the following files from a similar namespace package (e.g., apidom-ns-arazzo-1):
src/refractor/visitors/generics/FixedFieldsVisitor.tssrc/refractor/visitors/generics/PatternedFieldsVisitor.tssrc/refractor/visitors/generics/MapVisitor.tssrc/refractor/visitors/FallbackVisitor.tssrc/refractor/visitors/SpecificationExtensionVisitor.tssrc/refractor/visitors/SpecificationVisitor.tssrc/refractor/visitors/Visitor.tssrc/refractor/index.ts (createRefractor function)src/refractor/toolbox.tssrc/refractor/predicates.tssrc/traversal/visitor.tstest/mocha-bootstrap.tsconfig/api-extractor/api-extractor.json (update package name)config/webpack/browser.config.js (update package name)For each element, create a test file test/refractor/elements/{ElementName}/index.ts:
import { expect } from 'chai';
import { sexprs } from '@swagger-api/apidom-core';
import { {ElementName}Element } from '../../../../src/index.ts';
describe('refractor', function () {
context('elements', function () {
context('{ElementName}Element', function () {
specify('should refract to semantic ApiDOM tree', function () {
const {elementName}Element = {ElementName}Element.refract({
{propertyName}: 'value',
// Add sample data...
});
expect(sexprs(elementName)).toMatchSnapshot();
});
});
});
});
Create test/predicates.ts:
import { expect } from 'chai';
import { {ElementName}Element, is{ElementName}Element } from '../src/index.ts';
describe('predicates', function () {
context('is{ElementName}Element', function () {
context('when element is {ElementName}Element', function () {
specify('should return true', function () {
const element = new {ElementName}Element();
expect(is{ElementName}Element(element)).to.be.true;
});
});
context('when element is not {ElementName}Element', function () {
specify('should return false', function () {
expect(is{ElementName}Element({})).to.be.false;
expect(is{ElementName}Element(null)).to.be.false;
expect(is{ElementName}Element(undefined)).to.be.false;
});
});
});
});
Create test/tsconfig.json:
{
"extends": "../../../tsconfig.test.json",
"compilerOptions": {
"types": ["mocha", "chai", "node"]
},
"include": ["./**/*"],
"references": [{ "path": ".." }]
}
Build the monorepo (required before testing):
npm run build
Run tests for the new package:
cd packages/apidom-ns-{spec}-{version}
npm run test
Run type checking:
npm run typescript:check-types
Run linting:
npm run lint
Add to root package.json workspaces (if not already included by glob pattern)
Update root tsconfig.json with project reference:
{
"references": [
{ "path": "./packages/apidom-ns-{spec}-{version}" }
]
}
Update CHANGELOG.md in the package root
Based on learnings from OpenAPI 3.2.0 and other implementations, watch for these common patterns:
New HTTP Methods or Operations:
query as a standard HTTP methodDynamic Operation Collections:
additionalOperations allows custom HTTP methodsreferenced-element metadata for referencesReusable Component Types:
components.mediaTypes for reusable media type definitionsGlobal Configuration Fields:
jsonSchemaDialect sets default JSON Schema dialectWebhook/Callback Patterns:
webhooks (inverted API calls)webhook-name)Self-Reference Fields:
$self for document URIExtended Metadata Fields:
Server.name and License.identifierWorkflowElement, InfoElement, JsonSchemaDialectElement)'workflow', 'info', 'jsonSchemaDialect')Workflow.ts, Info.ts, JsonSchemaDialect.ts){ElementName}Visitorsource-description/, success-action/)Add CSS classes to elements that need specialized identification:
constructor(content?: Record<string, unknown>, meta?: Meta, attributes?: Attributes) {
super(content, meta, attributes);
this.element = 'workflow';
this.classes.push('workflow-outputs');
}
For specialized array types, create NCE classes in src/elements/nces/:
import { ArrayElement, Attributes, Meta } from '@swagger-api/apidom-core';
class WorkflowSteps extends ArrayElement {
constructor(content?: Array<unknown>, meta?: Meta, attributes?: Attributes) {
super(content, meta, attributes);
this.element = 'array';
this.classes.push('workflow-steps');
}
}
export default WorkflowSteps;
For elements supporting x-* extension properties:
canSupportSpecificationExtensions = true in visitorFixedFieldsVisitor mixin (handles extensions automatically)When basing on existing namespace:
Example:
import { specificationObj as parentSpecification } from '@swagger-api/apidom-ns-{parent}';
const specification = {
visitors: {
...parentSpecification.visitors,
document: {
objects: {
...parentSpecification.visitors.document.objects,
// Override only changed elements
{ElementName}: {
$visitor: {ElementName}Visitor,
// ...
},
},
},
},
};
After completing the namespace package, you'll need to:
Create parser adapters for the specification (separate packages):
apidom-parser-adapter-{spec}-json-{version}apidom-parser-adapter-{spec}-yaml-{version}Add support to apidom-reference:
Add support to apidom-ls (Language Server):
Update swagger-editor (if applicable):
.ts extensionnpm run test:update-snapshots to update snapshots after changesapidom-ns-arazzo-1 (26 elements, workflow-focused)apidom-ns-openapi-3-1 (34 elements, JSON Schema integration)apidom-ns-asyncapi-3 (131 elements, protocol bindings)