一键导入
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 页面并帮你完成安装。
基于 SOC 职业分类
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
| 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)