@lssm/lib.contracts-transformers 0.0.0-canary-20251219202229 → 0.0.0-canary-20251220015515

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"differ.js","names":["changeType: DiffChangeType","changes: DiffChange[]","diffs: SpecDiff[]","existing: AnyContractSpec | undefined","lines: string[]"],"sources":["../../src/openapi/differ.ts"],"sourcesContent":["/**\n * Diff ContractSpec specs against OpenAPI operations.\n * Used for sync operations to detect changes.\n */\n\nimport type { AnyContractSpec } from '@lssm/lib.contracts';\nimport type { ParsedOperation } from './types';\nimport type {\n SpecDiff,\n DiffChange,\n DiffChangeType,\n ImportedSpec,\n} from '../common/types';\nimport { deepEqual, getByPath } from '../common/utils';\n\n/**\n * Options for diffing specs.\n */\nexport interface DiffOptions {\n /** Ignore description changes */\n ignoreDescriptions?: boolean;\n /** Ignore tag changes */\n ignoreTags?: boolean;\n /** Ignore transport changes (path, method) */\n ignoreTransport?: boolean;\n /** Custom paths to ignore */\n ignorePaths?: string[];\n}\n\n/**\n * Compare two values and generate a diff change if different.\n */\nfunction compareValues(\n path: string,\n oldValue: unknown,\n newValue: unknown,\n description: string\n): DiffChange | null {\n if (deepEqual(oldValue, newValue)) {\n return null;\n }\n\n let changeType: DiffChangeType = 'modified';\n if (oldValue === undefined || oldValue === null) {\n changeType = 'added';\n } else if (newValue === undefined || newValue === null) {\n changeType = 'removed';\n } else if (typeof oldValue !== typeof newValue) {\n changeType = 'type_changed';\n }\n\n return {\n path,\n type: changeType,\n oldValue,\n newValue,\n description,\n };\n}\n\n/**\n * Diff two objects recursively.\n */\nfunction diffObjects(\n path: string,\n oldObj: Record<string, unknown> | undefined,\n newObj: Record<string, unknown> | undefined,\n options: DiffOptions\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n if (!oldObj && !newObj) return changes;\n if (!oldObj) {\n changes.push({\n path,\n type: 'added',\n newValue: newObj,\n description: `Added ${path}`,\n });\n return changes;\n }\n if (!newObj) {\n changes.push({\n path,\n type: 'removed',\n oldValue: oldObj,\n description: `Removed ${path}`,\n });\n return changes;\n }\n\n const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);\n\n for (const key of allKeys) {\n const keyPath = path ? `${path}.${key}` : key;\n\n // Skip ignored paths\n if (options.ignorePaths?.some((p) => keyPath.startsWith(p))) {\n continue;\n }\n\n const oldVal = oldObj[key];\n const newVal = newObj[key];\n\n if (typeof oldVal === 'object' && typeof newVal === 'object') {\n changes.push(\n ...diffObjects(\n keyPath,\n oldVal as Record<string, unknown>,\n newVal as Record<string, unknown>,\n options\n )\n );\n } else {\n const change = compareValues(\n keyPath,\n oldVal,\n newVal,\n `Changed ${keyPath}`\n );\n if (change) {\n changes.push(change);\n }\n }\n }\n\n return changes;\n}\n\n/**\n * Diff a ContractSpec against an OpenAPI operation.\n */\nexport function diffSpecVsOperation(\n spec: AnyContractSpec,\n operation: ParsedOperation,\n options: DiffOptions = {}\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n // Compare basic metadata\n if (!options.ignoreDescriptions) {\n const descChange = compareValues(\n 'meta.description',\n spec.meta.description,\n operation.summary ?? operation.description,\n 'Description changed'\n );\n if (descChange) changes.push(descChange);\n }\n\n if (!options.ignoreTags) {\n const oldTags = [...(spec.meta.tags ?? [])].sort();\n const newTags = [...operation.tags].sort();\n if (!deepEqual(oldTags, newTags)) {\n changes.push({\n path: 'meta.tags',\n type: 'modified',\n oldValue: oldTags,\n newValue: newTags,\n description: 'Tags changed',\n });\n }\n }\n\n // Compare transport\n if (!options.ignoreTransport) {\n const specMethod =\n spec.transport?.rest?.method ??\n (spec.meta.kind === 'query' ? 'GET' : 'POST');\n const opMethod = operation.method.toUpperCase();\n\n if (specMethod !== opMethod) {\n changes.push({\n path: 'transport.rest.method',\n type: 'modified',\n oldValue: specMethod,\n newValue: opMethod,\n description: 'HTTP method changed',\n });\n }\n\n const specPath = spec.transport?.rest?.path;\n if (specPath && specPath !== operation.path) {\n changes.push({\n path: 'transport.rest.path',\n type: 'modified',\n oldValue: specPath,\n newValue: operation.path,\n description: 'Path changed',\n });\n }\n }\n\n // Compare deprecation status\n const specDeprecated = spec.meta.stability === 'deprecated';\n if (specDeprecated !== operation.deprecated) {\n changes.push({\n path: 'meta.stability',\n type: 'modified',\n oldValue: spec.meta.stability,\n newValue: operation.deprecated ? 'deprecated' : 'stable',\n description: 'Deprecation status changed',\n });\n }\n\n return changes;\n}\n\n/**\n * Diff two ContractSpecs.\n */\nexport function diffSpecs(\n oldSpec: AnyContractSpec,\n newSpec: AnyContractSpec,\n options: DiffOptions = {}\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n // Compare meta\n const metaChanges = diffObjects(\n 'meta',\n oldSpec.meta as unknown as Record<string, unknown>,\n newSpec.meta as unknown as Record<string, unknown>,\n {\n ...options,\n ignorePaths: [\n ...(options.ignorePaths ?? []),\n ...(options.ignoreDescriptions\n ? ['meta.description', 'meta.goal', 'meta.context']\n : []),\n ...(options.ignoreTags ? ['meta.tags'] : []),\n ],\n }\n );\n changes.push(...metaChanges);\n\n // Compare transport\n if (!options.ignoreTransport) {\n const transportChanges = diffObjects(\n 'transport',\n oldSpec.transport as unknown as Record<string, unknown>,\n newSpec.transport as unknown as Record<string, unknown>,\n options\n );\n changes.push(...transportChanges);\n }\n\n // Compare policy\n const policyChanges = diffObjects(\n 'policy',\n oldSpec.policy as unknown as Record<string, unknown>,\n newSpec.policy as unknown as Record<string, unknown>,\n options\n );\n changes.push(...policyChanges);\n\n return changes;\n}\n\n/**\n * Create a SpecDiff from an existing spec and an imported spec.\n */\nexport function createSpecDiff(\n operationId: string,\n existing: AnyContractSpec | undefined,\n incoming: ImportedSpec,\n options: DiffOptions = {}\n): SpecDiff {\n let changes: DiffChange[] = [];\n let isEquivalent = false;\n\n if (existing) {\n // Compare existing vs incoming\n changes = diffSpecs(existing, incoming.spec, options);\n isEquivalent = changes.length === 0;\n } else {\n // New spec - mark as added\n changes = [\n {\n path: '',\n type: 'added',\n newValue: incoming.spec,\n description: 'New spec imported from OpenAPI',\n },\n ];\n }\n\n return {\n operationId,\n existing,\n incoming,\n changes,\n isEquivalent,\n };\n}\n\n/**\n * Batch diff multiple specs against OpenAPI operations.\n */\nexport function diffAll(\n existingSpecs: Map<string, AnyContractSpec>,\n importedSpecs: ImportedSpec[],\n options: DiffOptions = {}\n): SpecDiff[] {\n const diffs: SpecDiff[] = [];\n\n // Track which existing specs have been matched\n const matchedExisting = new Set<string>();\n\n for (const imported of importedSpecs) {\n const operationId = imported.source.sourceId;\n\n // Try to find matching existing spec\n // Match by operationId in x-contractspec extension or by name\n let existing: AnyContractSpec | undefined;\n\n for (const [key, spec] of existingSpecs) {\n // Check x-contractspec match or name match\n const specName = spec.meta.name;\n if (key === operationId || specName.includes(operationId)) {\n existing = spec;\n matchedExisting.add(key);\n break;\n }\n }\n\n diffs.push(createSpecDiff(operationId, existing, imported, options));\n }\n\n // Add diffs for existing specs that weren't matched (removed from OpenAPI)\n for (const [key, spec] of existingSpecs) {\n if (!matchedExisting.has(key)) {\n diffs.push({\n operationId: key,\n existing: spec,\n incoming: undefined as unknown as ImportedSpec,\n changes: [\n {\n path: '',\n type: 'removed',\n oldValue: spec,\n description: 'Spec no longer exists in OpenAPI source',\n },\n ],\n isEquivalent: false,\n });\n }\n }\n\n return diffs;\n}\n\n/**\n * Format diff changes for display.\n */\nexport function formatDiffChanges(changes: DiffChange[]): string {\n if (changes.length === 0) {\n return 'No changes detected';\n }\n\n const lines: string[] = [];\n\n for (const change of changes) {\n const prefix = {\n added: '+',\n removed: '-',\n modified: '~',\n type_changed: '!',\n required_changed: '?',\n }[change.type];\n\n lines.push(`${prefix} ${change.path}: ${change.description}`);\n\n if (change.type === 'modified' || change.type === 'type_changed') {\n lines.push(` old: ${JSON.stringify(change.oldValue)}`);\n lines.push(` new: ${JSON.stringify(change.newValue)}`);\n } else if (change.type === 'added') {\n lines.push(` value: ${JSON.stringify(change.newValue)}`);\n } else if (change.type === 'removed') {\n lines.push(` was: ${JSON.stringify(change.oldValue)}`);\n }\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"differ.js","names":["changeType: DiffChangeType","changes: DiffChange[]","diffs: SpecDiff[]","existing: AnyContractSpec | undefined","lines: string[]"],"sources":["../../src/openapi/differ.ts"],"sourcesContent":["/**\n * Diff ContractSpec specs against OpenAPI operations.\n * Used for sync operations to detect changes.\n */\n\nimport type { AnyContractSpec } from '@lssm/lib.contracts';\nimport type { ParsedOperation } from './types';\nimport type {\n SpecDiff,\n DiffChange,\n DiffChangeType,\n ImportedSpec,\n} from '../common/types';\nimport { deepEqual } from '../common/utils';\n\n/**\n * Options for diffing specs.\n */\nexport interface DiffOptions {\n /** Ignore description changes */\n ignoreDescriptions?: boolean;\n /** Ignore tag changes */\n ignoreTags?: boolean;\n /** Ignore transport changes (path, method) */\n ignoreTransport?: boolean;\n /** Custom paths to ignore */\n ignorePaths?: string[];\n}\n\n/**\n * Compare two values and generate a diff change if different.\n */\nfunction compareValues(\n path: string,\n oldValue: unknown,\n newValue: unknown,\n description: string\n): DiffChange | null {\n if (deepEqual(oldValue, newValue)) {\n return null;\n }\n\n let changeType: DiffChangeType = 'modified';\n if (oldValue === undefined || oldValue === null) {\n changeType = 'added';\n } else if (newValue === undefined || newValue === null) {\n changeType = 'removed';\n } else if (typeof oldValue !== typeof newValue) {\n changeType = 'type_changed';\n }\n\n return {\n path,\n type: changeType,\n oldValue,\n newValue,\n description,\n };\n}\n\n/**\n * Diff two objects recursively.\n */\nfunction diffObjects(\n path: string,\n oldObj: Record<string, unknown> | undefined,\n newObj: Record<string, unknown> | undefined,\n options: DiffOptions\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n if (!oldObj && !newObj) return changes;\n if (!oldObj) {\n changes.push({\n path,\n type: 'added',\n newValue: newObj,\n description: `Added ${path}`,\n });\n return changes;\n }\n if (!newObj) {\n changes.push({\n path,\n type: 'removed',\n oldValue: oldObj,\n description: `Removed ${path}`,\n });\n return changes;\n }\n\n const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);\n\n for (const key of allKeys) {\n const keyPath = path ? `${path}.${key}` : key;\n\n // Skip ignored paths\n if (options.ignorePaths?.some((p) => keyPath.startsWith(p))) {\n continue;\n }\n\n const oldVal = oldObj[key];\n const newVal = newObj[key];\n\n if (typeof oldVal === 'object' && typeof newVal === 'object') {\n changes.push(\n ...diffObjects(\n keyPath,\n oldVal as Record<string, unknown>,\n newVal as Record<string, unknown>,\n options\n )\n );\n } else {\n const change = compareValues(\n keyPath,\n oldVal,\n newVal,\n `Changed ${keyPath}`\n );\n if (change) {\n changes.push(change);\n }\n }\n }\n\n return changes;\n}\n\n/**\n * Diff a ContractSpec against an OpenAPI operation.\n */\nexport function diffSpecVsOperation(\n spec: AnyContractSpec,\n operation: ParsedOperation,\n options: DiffOptions = {}\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n // Compare basic metadata\n if (!options.ignoreDescriptions) {\n const descChange = compareValues(\n 'meta.description',\n spec.meta.description,\n operation.summary ?? operation.description,\n 'Description changed'\n );\n if (descChange) changes.push(descChange);\n }\n\n if (!options.ignoreTags) {\n const oldTags = [...(spec.meta.tags ?? [])].sort();\n const newTags = [...operation.tags].sort();\n if (!deepEqual(oldTags, newTags)) {\n changes.push({\n path: 'meta.tags',\n type: 'modified',\n oldValue: oldTags,\n newValue: newTags,\n description: 'Tags changed',\n });\n }\n }\n\n // Compare transport\n if (!options.ignoreTransport) {\n const specMethod =\n spec.transport?.rest?.method ??\n (spec.meta.kind === 'query' ? 'GET' : 'POST');\n const opMethod = operation.method.toUpperCase();\n\n if (specMethod !== opMethod) {\n changes.push({\n path: 'transport.rest.method',\n type: 'modified',\n oldValue: specMethod,\n newValue: opMethod,\n description: 'HTTP method changed',\n });\n }\n\n const specPath = spec.transport?.rest?.path;\n if (specPath && specPath !== operation.path) {\n changes.push({\n path: 'transport.rest.path',\n type: 'modified',\n oldValue: specPath,\n newValue: operation.path,\n description: 'Path changed',\n });\n }\n }\n\n // Compare deprecation status\n const specDeprecated = spec.meta.stability === 'deprecated';\n if (specDeprecated !== operation.deprecated) {\n changes.push({\n path: 'meta.stability',\n type: 'modified',\n oldValue: spec.meta.stability,\n newValue: operation.deprecated ? 'deprecated' : 'stable',\n description: 'Deprecation status changed',\n });\n }\n\n return changes;\n}\n\n/**\n * Diff two ContractSpecs.\n */\nexport function diffSpecs(\n oldSpec: AnyContractSpec,\n newSpec: AnyContractSpec,\n options: DiffOptions = {}\n): DiffChange[] {\n const changes: DiffChange[] = [];\n\n // Compare meta\n const metaChanges = diffObjects(\n 'meta',\n oldSpec.meta as unknown as Record<string, unknown>,\n newSpec.meta as unknown as Record<string, unknown>,\n {\n ...options,\n ignorePaths: [\n ...(options.ignorePaths ?? []),\n ...(options.ignoreDescriptions\n ? ['meta.description', 'meta.goal', 'meta.context']\n : []),\n ...(options.ignoreTags ? ['meta.tags'] : []),\n ],\n }\n );\n changes.push(...metaChanges);\n\n // Compare transport\n if (!options.ignoreTransport) {\n const transportChanges = diffObjects(\n 'transport',\n oldSpec.transport as unknown as Record<string, unknown>,\n newSpec.transport as unknown as Record<string, unknown>,\n options\n );\n changes.push(...transportChanges);\n }\n\n // Compare policy\n const policyChanges = diffObjects(\n 'policy',\n oldSpec.policy as unknown as Record<string, unknown>,\n newSpec.policy as unknown as Record<string, unknown>,\n options\n );\n changes.push(...policyChanges);\n\n return changes;\n}\n\n/**\n * Create a SpecDiff from an existing spec and an imported spec.\n */\nexport function createSpecDiff(\n operationId: string,\n existing: AnyContractSpec | undefined,\n incoming: ImportedSpec,\n options: DiffOptions = {}\n): SpecDiff {\n let changes: DiffChange[] = [];\n let isEquivalent = false;\n\n if (existing) {\n // Compare existing vs incoming\n changes = diffSpecs(existing, incoming.spec, options);\n isEquivalent = changes.length === 0;\n } else {\n // New spec - mark as added\n changes = [\n {\n path: '',\n type: 'added',\n newValue: incoming.spec,\n description: 'New spec imported from OpenAPI',\n },\n ];\n }\n\n return {\n operationId,\n existing,\n incoming,\n changes,\n isEquivalent,\n };\n}\n\n/**\n * Batch diff multiple specs against OpenAPI operations.\n */\nexport function diffAll(\n existingSpecs: Map<string, AnyContractSpec>,\n importedSpecs: ImportedSpec[],\n options: DiffOptions = {}\n): SpecDiff[] {\n const diffs: SpecDiff[] = [];\n\n // Track which existing specs have been matched\n const matchedExisting = new Set<string>();\n\n for (const imported of importedSpecs) {\n const operationId = imported.source.sourceId;\n\n // Try to find matching existing spec\n // Match by operationId in x-contractspec extension or by name\n let existing: AnyContractSpec | undefined;\n\n for (const [key, spec] of existingSpecs) {\n // Check x-contractspec match or name match\n const specName = spec.meta.name;\n if (key === operationId || specName.includes(operationId)) {\n existing = spec;\n matchedExisting.add(key);\n break;\n }\n }\n\n diffs.push(createSpecDiff(operationId, existing, imported, options));\n }\n\n // Add diffs for existing specs that weren't matched (removed from OpenAPI)\n for (const [key, spec] of existingSpecs) {\n if (!matchedExisting.has(key)) {\n diffs.push({\n operationId: key,\n existing: spec,\n incoming: undefined as unknown as ImportedSpec,\n changes: [\n {\n path: '',\n type: 'removed',\n oldValue: spec,\n description: 'Spec no longer exists in OpenAPI source',\n },\n ],\n isEquivalent: false,\n });\n }\n }\n\n return diffs;\n}\n\n/**\n * Format diff changes for display.\n */\nexport function formatDiffChanges(changes: DiffChange[]): string {\n if (changes.length === 0) {\n return 'No changes detected';\n }\n\n const lines: string[] = [];\n\n for (const change of changes) {\n const prefix = {\n added: '+',\n removed: '-',\n modified: '~',\n type_changed: '!',\n required_changed: '?',\n }[change.type];\n\n lines.push(`${prefix} ${change.path}: ${change.description}`);\n\n if (change.type === 'modified' || change.type === 'type_changed') {\n lines.push(` old: ${JSON.stringify(change.oldValue)}`);\n lines.push(` new: ${JSON.stringify(change.newValue)}`);\n } else if (change.type === 'added') {\n lines.push(` value: ${JSON.stringify(change.newValue)}`);\n } else if (change.type === 'removed') {\n lines.push(` was: ${JSON.stringify(change.oldValue)}`);\n }\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","names":[],"sources":["../../src/openapi/parser.ts"],"sourcesContent":[],"mappings":";;;;AA0DA;AAmNA;;AAEY,iBA7OI,kBAAA,CA6OJ,OAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,MAAA,CAAA,EA1OT,eA0OS;;;AA2EZ;AAEW,iBA7SK,YAAA,CA6SL,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,MAAA;;;;AAIR,iBAtSa,aAAA,CAsSb,GAAA,EAtSgC,eAsShC,CAAA,EAtSkD,cAsSlD;;;;iBAnFa,oBAAA,MACT,4BACK,sBACT;;;;;;iBA0EmB,YAAA,2BAEX;iBACQ,UAAA,CAAW;+BACG;IAE9B,QAAQ"}
1
+ {"version":3,"file":"parser.d.ts","names":[],"sources":["../../src/openapi/parser.ts"],"sourcesContent":[],"mappings":";;;;AA0DA;AAqNA;;AAEY,iBA/OI,kBAAA,CA+OJ,OAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,MAAA,CAAA,EA5OT,eA4OS;;;AA2EZ;AAEW,iBA/SK,YAAA,CA+SL,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA,GAAA,MAAA;;;;AAIR,iBAxSa,aAAA,CAwSb,GAAA,EAxSgC,eAwShC,CAAA,EAxSkD,cAwSlD;;;;iBAnFa,oBAAA,MACT,4BACK,sBACT;;;;;;iBA0EmB,YAAA,2BAEX;iBACQ,UAAA,CAAW;+BACG;IAE9B,QAAQ"}
@@ -117,7 +117,7 @@ function parseOperation(doc, method, path, operation, pathParams) {
117
117
  const content = body.content?.[contentType];
118
118
  if (content?.schema) requestBody = {
119
119
  required: body.required ?? false,
120
- schema: resolveSchema(doc, content.schema),
120
+ schema: resolveSchema(doc, content.schema) ?? {},
121
121
  contentType
122
122
  };
123
123
  }
@@ -1 +1 @@
1
- {"version":3,"file":"parser.js","names":["HTTP_METHODS: HttpMethod[]","parseYaml","current: unknown","resolved: OpenAPIV3.ParameterObject | OpenAPIV3_1.ParameterObject","parsed: ParsedParameter","requestBody: ParsedOperation['requestBody']","responses: ParsedOperation['responses']","warnings: string[]","operations: ParsedOperation[]","schemas: Record<string, OpenApiSchema>","servers: OpenApiServer[]","content: string","format: 'json' | 'yaml'"],"sources":["../../src/openapi/parser.ts"],"sourcesContent":["/**\n * OpenAPI document parser.\n * Parses OpenAPI 3.x documents from JSON/YAML files or URLs.\n */\n\nimport { parse as parseYaml } from 'yaml';\nimport type {\n OpenApiDocument,\n OpenApiParseOptions,\n ParseResult,\n ParsedOperation,\n ParsedParameter,\n HttpMethod,\n OpenApiVersion,\n OpenApiSchema,\n OpenApiParameter,\n OpenApiServer,\n} from './types';\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\nconst HTTP_METHODS: HttpMethod[] = [\n 'get',\n 'post',\n 'put',\n 'delete',\n 'patch',\n 'head',\n 'options',\n 'trace',\n];\n\n/**\n * Parse an OpenAPI document from a string (JSON or YAML).\n */\nexport function parseOpenApiString(\n content: string,\n format: 'json' | 'yaml' = 'json'\n): OpenApiDocument {\n if (format === 'yaml') {\n return parseYaml(content) as OpenApiDocument;\n }\n return JSON.parse(content) as OpenApiDocument;\n}\n\n/**\n * Detect the format of content (JSON or YAML).\n */\nexport function detectFormat(content: string): 'json' | 'yaml' {\n const trimmed = content.trim();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n return 'json';\n }\n return 'yaml';\n}\n\n/**\n * Detect OpenAPI version from document.\n */\nexport function detectVersion(doc: OpenApiDocument): OpenApiVersion {\n const version = doc.openapi;\n if (version.startsWith('3.1')) {\n return '3.1';\n }\n return '3.0';\n}\n\n/**\n * Check if a value is a reference object.\n */\nfunction isReference(\n obj: unknown\n): obj is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject {\n return typeof obj === 'object' && obj !== null && '$ref' in obj;\n}\n\n/**\n * Resolve a $ref reference in the document.\n */\nfunction resolveRef<T>(doc: OpenApiDocument, ref: string): T | undefined {\n // Only support local refs for now\n if (!ref.startsWith('#/')) {\n return undefined;\n }\n\n const path = ref.slice(2).split('/');\n let current: unknown = doc;\n\n for (const part of path) {\n if (current === null || current === undefined) return undefined;\n if (typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n\n return current as T;\n}\n\n/**\n * Resolve a schema, following $ref if needed.\n */\nfunction resolveSchema(\n doc: OpenApiDocument,\n schema: OpenApiSchema | undefined\n): OpenApiSchema | undefined {\n if (!schema) return undefined;\n if (isReference(schema)) {\n return resolveRef<OpenApiSchema>(doc, schema.$ref) ?? schema;\n }\n return schema;\n}\n\n/**\n * Parse parameters from an operation.\n */\nfunction parseParameters(\n doc: OpenApiDocument,\n params: OpenApiParameter[] | undefined\n): {\n path: ParsedParameter[];\n query: ParsedParameter[];\n header: ParsedParameter[];\n cookie: ParsedParameter[];\n} {\n const result = {\n path: [] as ParsedParameter[],\n query: [] as ParsedParameter[],\n header: [] as ParsedParameter[],\n cookie: [] as ParsedParameter[],\n };\n\n if (!params) return result;\n\n for (const param of params) {\n let resolved: OpenAPIV3.ParameterObject | OpenAPIV3_1.ParameterObject;\n\n if (isReference(param)) {\n const ref = resolveRef<\n OpenAPIV3.ParameterObject | OpenAPIV3_1.ParameterObject\n >(doc, param.$ref);\n if (!ref) continue;\n resolved = ref;\n } else {\n resolved = param;\n }\n\n const parsed: ParsedParameter = {\n name: resolved.name,\n in: resolved.in as ParsedParameter['in'],\n required: resolved.required ?? resolved.in === 'path',\n description: resolved.description,\n schema: resolved.schema as OpenApiSchema,\n deprecated: resolved.deprecated ?? false,\n };\n\n result[resolved.in as keyof typeof result]?.push(parsed);\n }\n\n return result;\n}\n\n/**\n * Generate an operationId if not present.\n */\nfunction generateOperationId(method: HttpMethod, path: string): string {\n // Convert path to camelCase operationId\n const pathParts = path\n .split('/')\n .filter(Boolean)\n .map((part) => {\n // Remove path parameters\n if (part.startsWith('{') && part.endsWith('}')) {\n return (\n 'By' + part.slice(1, -1).charAt(0).toUpperCase() + part.slice(2, -1)\n );\n }\n return part.charAt(0).toUpperCase() + part.slice(1);\n });\n\n return method + pathParts.join('');\n}\n\n/**\n * Parse a single operation.\n */\nfunction parseOperation(\n doc: OpenApiDocument,\n method: HttpMethod,\n path: string,\n operation: OpenAPIV3.OperationObject | OpenAPIV3_1.OperationObject,\n pathParams: OpenApiParameter[] | undefined\n): ParsedOperation {\n // Merge path-level and operation-level parameters\n const allParams = [...(pathParams ?? []), ...(operation.parameters ?? [])];\n const params = parseParameters(doc, allParams as OpenApiParameter[]);\n\n // Parse request body\n let requestBody: ParsedOperation['requestBody'];\n if (operation.requestBody) {\n const body = isReference(operation.requestBody)\n ? resolveRef<OpenAPIV3.RequestBodyObject | OpenAPIV3_1.RequestBodyObject>(\n doc,\n operation.requestBody.$ref\n )\n : operation.requestBody;\n\n if (body) {\n const contentType =\n Object.keys(body.content ?? {})[0] ?? 'application/json';\n const content = body.content?.[contentType];\n if (content?.schema) {\n requestBody = {\n required: body.required ?? false,\n schema: resolveSchema(doc, content.schema as OpenApiSchema)!,\n contentType,\n };\n }\n }\n }\n\n // Parse responses\n const responses: ParsedOperation['responses'] = {};\n for (const [status, response] of Object.entries(operation.responses ?? {})) {\n const resolved = isReference(response)\n ? resolveRef<OpenAPIV3.ResponseObject | OpenAPIV3_1.ResponseObject>(\n doc,\n response.$ref\n )\n : response;\n\n if (resolved) {\n const contentType = Object.keys(resolved.content ?? {})[0];\n const content = contentType ? resolved.content?.[contentType] : undefined;\n\n responses[status] = {\n description: resolved.description,\n schema: content?.schema\n ? resolveSchema(doc, content.schema as OpenApiSchema)\n : undefined,\n contentType,\n };\n }\n }\n\n // Check for x-contractspec extension\n const contractSpecMeta = (operation as Record<string, unknown>)?.[\n 'x-contractspec'\n ] as ParsedOperation['contractSpecMeta'];\n\n return {\n operationId: operation.operationId ?? generateOperationId(method, path),\n method,\n path,\n summary: operation.summary,\n description: operation.description,\n tags: operation.tags ?? [],\n pathParams: params.path,\n queryParams: params.query,\n headerParams: params.header,\n cookieParams: params.cookie,\n requestBody,\n responses,\n deprecated: operation.deprecated ?? false,\n security: operation.security as ParsedOperation['security'],\n contractSpecMeta,\n };\n}\n\n/**\n * Parse an OpenAPI document into a structured result.\n */\nexport function parseOpenApiDocument(\n doc: OpenApiDocument,\n _options: OpenApiParseOptions = {}\n): ParseResult {\n const version = detectVersion(doc);\n const warnings: string[] = [];\n const operations: ParsedOperation[] = [];\n\n // Parse operations from paths\n for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {\n if (!pathItem) continue;\n\n // Get path-level parameters\n const pathParams = (pathItem as OpenAPIV3.PathItemObject).parameters;\n\n for (const method of HTTP_METHODS) {\n const operation = (pathItem as Record<string, unknown>)[method] as\n | OpenAPIV3.OperationObject\n | OpenAPIV3_1.OperationObject\n | undefined;\n\n if (operation) {\n try {\n operations.push(\n parseOperation(\n doc,\n method,\n path,\n operation,\n pathParams as OpenApiParameter[]\n )\n );\n } catch (error) {\n warnings.push(\n `Failed to parse ${method.toUpperCase()} ${path}: ${error}`\n );\n }\n }\n }\n }\n\n // Extract component schemas\n const schemas: Record<string, OpenApiSchema> = {};\n const components = doc.components;\n if (components?.schemas) {\n for (const [name, schema] of Object.entries(components.schemas)) {\n schemas[name] = schema as OpenApiSchema;\n }\n }\n\n // Parse servers\n const servers: OpenApiServer[] = (doc.servers ?? []).map((s) => ({\n url: s.url,\n description: s.description,\n variables: s.variables as OpenApiServer['variables'],\n }));\n\n return {\n document: doc,\n version,\n info: {\n title: doc.info.title,\n version: doc.info.version,\n description: doc.info.description,\n },\n operations,\n schemas,\n servers,\n warnings,\n };\n}\n\n/**\n * Parse OpenAPI from a file path or URL.\n * Note: This is an async function that requires I/O adapters.\n * For pure parsing, use parseOpenApiString or parseOpenApiDocument.\n */\nexport async function parseOpenApi(\n source: string,\n options: OpenApiParseOptions & {\n fetch?: typeof globalThis.fetch;\n readFile?: (path: string) => Promise<string>;\n } = {}\n): Promise<ParseResult> {\n const {\n fetch: fetchFn = globalThis.fetch,\n readFile,\n timeout = 30000,\n } = options;\n\n let content: string;\n let format: 'json' | 'yaml';\n\n if (source.startsWith('http://') || source.startsWith('https://')) {\n // Fetch from URL\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetchFn(source, { signal: controller.signal });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n content = await response.text();\n } finally {\n clearTimeout(timeoutId);\n }\n\n // Detect format from URL or content\n if (source.endsWith('.yaml') || source.endsWith('.yml')) {\n format = 'yaml';\n } else if (source.endsWith('.json')) {\n format = 'json';\n } else {\n format = detectFormat(content);\n }\n } else {\n // Read from file\n if (!readFile) {\n throw new Error('readFile adapter required for file paths');\n }\n content = await readFile(source);\n\n // Detect format from extension or content\n if (source.endsWith('.yaml') || source.endsWith('.yml')) {\n format = 'yaml';\n } else if (source.endsWith('.json')) {\n format = 'json';\n } else {\n format = detectFormat(content);\n }\n }\n\n const doc = parseOpenApiString(content, format);\n return parseOpenApiDocument(doc, options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"parser.js","names":["HTTP_METHODS: HttpMethod[]","parseYaml","current: unknown","resolved: OpenAPIV3.ParameterObject | OpenAPIV3_1.ParameterObject","parsed: ParsedParameter","requestBody: ParsedOperation['requestBody']","responses: ParsedOperation['responses']","warnings: string[]","operations: ParsedOperation[]","schemas: Record<string, OpenApiSchema>","servers: OpenApiServer[]","content: string","format: 'json' | 'yaml'"],"sources":["../../src/openapi/parser.ts"],"sourcesContent":["/**\n * OpenAPI document parser.\n * Parses OpenAPI 3.x documents from JSON/YAML files or URLs.\n */\n\nimport { parse as parseYaml } from 'yaml';\nimport type {\n OpenApiDocument,\n OpenApiParseOptions,\n ParseResult,\n ParsedOperation,\n ParsedParameter,\n HttpMethod,\n OpenApiVersion,\n OpenApiSchema,\n OpenApiParameter,\n OpenApiServer,\n} from './types';\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\nconst HTTP_METHODS: HttpMethod[] = [\n 'get',\n 'post',\n 'put',\n 'delete',\n 'patch',\n 'head',\n 'options',\n 'trace',\n];\n\n/**\n * Parse an OpenAPI document from a string (JSON or YAML).\n */\nexport function parseOpenApiString(\n content: string,\n format: 'json' | 'yaml' = 'json'\n): OpenApiDocument {\n if (format === 'yaml') {\n return parseYaml(content) as OpenApiDocument;\n }\n return JSON.parse(content) as OpenApiDocument;\n}\n\n/**\n * Detect the format of content (JSON or YAML).\n */\nexport function detectFormat(content: string): 'json' | 'yaml' {\n const trimmed = content.trim();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n return 'json';\n }\n return 'yaml';\n}\n\n/**\n * Detect OpenAPI version from document.\n */\nexport function detectVersion(doc: OpenApiDocument): OpenApiVersion {\n const version = doc.openapi;\n if (version.startsWith('3.1')) {\n return '3.1';\n }\n return '3.0';\n}\n\n/**\n * Check if a value is a reference object.\n */\nfunction isReference(\n obj: unknown\n): obj is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject {\n return typeof obj === 'object' && obj !== null && '$ref' in obj;\n}\n\n/**\n * Resolve a $ref reference in the document.\n */\nfunction resolveRef<T>(doc: OpenApiDocument, ref: string): T | undefined {\n // Only support local refs for now\n if (!ref.startsWith('#/')) {\n return undefined;\n }\n\n const path = ref.slice(2).split('/');\n let current: unknown = doc;\n\n for (const part of path) {\n if (current === null || current === undefined) return undefined;\n if (typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n\n return current as T;\n}\n\n/**\n * Resolve a schema, following $ref if needed.\n */\nfunction resolveSchema(\n doc: OpenApiDocument,\n schema: OpenApiSchema | undefined\n): OpenApiSchema | undefined {\n if (!schema) return undefined;\n if (isReference(schema)) {\n return resolveRef<OpenApiSchema>(doc, schema.$ref) ?? schema;\n }\n return schema;\n}\n\n/**\n * Parse parameters from an operation.\n */\nfunction parseParameters(\n doc: OpenApiDocument,\n params: OpenApiParameter[] | undefined\n): {\n path: ParsedParameter[];\n query: ParsedParameter[];\n header: ParsedParameter[];\n cookie: ParsedParameter[];\n} {\n const result = {\n path: [] as ParsedParameter[],\n query: [] as ParsedParameter[],\n header: [] as ParsedParameter[],\n cookie: [] as ParsedParameter[],\n };\n\n if (!params) return result;\n\n for (const param of params) {\n let resolved: OpenAPIV3.ParameterObject | OpenAPIV3_1.ParameterObject;\n\n if (isReference(param)) {\n const ref = resolveRef<\n OpenAPIV3.ParameterObject | OpenAPIV3_1.ParameterObject\n >(doc, param.$ref);\n if (!ref) continue;\n resolved = ref;\n } else {\n resolved = param;\n }\n\n const parsed: ParsedParameter = {\n name: resolved.name,\n in: resolved.in as ParsedParameter['in'],\n required: resolved.required ?? resolved.in === 'path',\n description: resolved.description,\n schema: resolved.schema as OpenApiSchema,\n deprecated: resolved.deprecated ?? false,\n };\n\n result[resolved.in as keyof typeof result]?.push(parsed);\n }\n\n return result;\n}\n\n/**\n * Generate an operationId if not present.\n */\nfunction generateOperationId(method: HttpMethod, path: string): string {\n // Convert path to camelCase operationId\n const pathParts = path\n .split('/')\n .filter(Boolean)\n .map((part) => {\n // Remove path parameters\n if (part.startsWith('{') && part.endsWith('}')) {\n return (\n 'By' + part.slice(1, -1).charAt(0).toUpperCase() + part.slice(2, -1)\n );\n }\n return part.charAt(0).toUpperCase() + part.slice(1);\n });\n\n return method + pathParts.join('');\n}\n\n/**\n * Parse a single operation.\n */\nfunction parseOperation(\n doc: OpenApiDocument,\n method: HttpMethod,\n path: string,\n operation: OpenAPIV3.OperationObject | OpenAPIV3_1.OperationObject,\n pathParams: OpenApiParameter[] | undefined\n): ParsedOperation {\n // Merge path-level and operation-level parameters\n const allParams = [...(pathParams ?? []), ...(operation.parameters ?? [])];\n const params = parseParameters(doc, allParams as OpenApiParameter[]);\n\n // Parse request body\n let requestBody: ParsedOperation['requestBody'];\n if (operation.requestBody) {\n const body = isReference(operation.requestBody)\n ? resolveRef<OpenAPIV3.RequestBodyObject | OpenAPIV3_1.RequestBodyObject>(\n doc,\n operation.requestBody.$ref\n )\n : operation.requestBody;\n\n if (body) {\n const contentType =\n Object.keys(body.content ?? {})[0] ?? 'application/json';\n const content = body.content?.[contentType];\n if (content?.schema) {\n requestBody = {\n required: body.required ?? false,\n schema:\n resolveSchema(doc, content.schema as OpenApiSchema) ??\n ({} as OpenApiSchema),\n contentType,\n };\n }\n }\n }\n\n // Parse responses\n const responses: ParsedOperation['responses'] = {};\n for (const [status, response] of Object.entries(operation.responses ?? {})) {\n const resolved = isReference(response)\n ? resolveRef<OpenAPIV3.ResponseObject | OpenAPIV3_1.ResponseObject>(\n doc,\n response.$ref\n )\n : response;\n\n if (resolved) {\n const contentType = Object.keys(resolved.content ?? {})[0];\n const content = contentType ? resolved.content?.[contentType] : undefined;\n\n responses[status] = {\n description: resolved.description,\n schema: content?.schema\n ? resolveSchema(doc, content.schema as OpenApiSchema)\n : undefined,\n contentType,\n };\n }\n }\n\n // Check for x-contractspec extension\n const contractSpecMeta = (operation as Record<string, unknown>)?.[\n 'x-contractspec'\n ] as ParsedOperation['contractSpecMeta'];\n\n return {\n operationId: operation.operationId ?? generateOperationId(method, path),\n method,\n path,\n summary: operation.summary,\n description: operation.description,\n tags: operation.tags ?? [],\n pathParams: params.path,\n queryParams: params.query,\n headerParams: params.header,\n cookieParams: params.cookie,\n requestBody,\n responses,\n deprecated: operation.deprecated ?? false,\n security: operation.security as ParsedOperation['security'],\n contractSpecMeta,\n };\n}\n\n/**\n * Parse an OpenAPI document into a structured result.\n */\nexport function parseOpenApiDocument(\n doc: OpenApiDocument,\n _options: OpenApiParseOptions = {}\n): ParseResult {\n const version = detectVersion(doc);\n const warnings: string[] = [];\n const operations: ParsedOperation[] = [];\n\n // Parse operations from paths\n for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {\n if (!pathItem) continue;\n\n // Get path-level parameters\n const pathParams = (pathItem as OpenAPIV3.PathItemObject).parameters;\n\n for (const method of HTTP_METHODS) {\n const operation = (pathItem as Record<string, unknown>)[method] as\n | OpenAPIV3.OperationObject\n | OpenAPIV3_1.OperationObject\n | undefined;\n\n if (operation) {\n try {\n operations.push(\n parseOperation(\n doc,\n method,\n path,\n operation,\n pathParams as OpenApiParameter[]\n )\n );\n } catch (error) {\n warnings.push(\n `Failed to parse ${method.toUpperCase()} ${path}: ${error}`\n );\n }\n }\n }\n }\n\n // Extract component schemas\n const schemas: Record<string, OpenApiSchema> = {};\n const components = doc.components;\n if (components?.schemas) {\n for (const [name, schema] of Object.entries(components.schemas)) {\n schemas[name] = schema as OpenApiSchema;\n }\n }\n\n // Parse servers\n const servers: OpenApiServer[] = (doc.servers ?? []).map((s) => ({\n url: s.url,\n description: s.description,\n variables: s.variables as OpenApiServer['variables'],\n }));\n\n return {\n document: doc,\n version,\n info: {\n title: doc.info.title,\n version: doc.info.version,\n description: doc.info.description,\n },\n operations,\n schemas,\n servers,\n warnings,\n };\n}\n\n/**\n * Parse OpenAPI from a file path or URL.\n * Note: This is an async function that requires I/O adapters.\n * For pure parsing, use parseOpenApiString or parseOpenApiDocument.\n */\nexport async function parseOpenApi(\n source: string,\n options: OpenApiParseOptions & {\n fetch?: typeof globalThis.fetch;\n readFile?: (path: string) => Promise<string>;\n } = {}\n): Promise<ParseResult> {\n const {\n fetch: fetchFn = globalThis.fetch,\n readFile,\n timeout = 30000,\n } = options;\n\n let content: string;\n let format: 'json' | 'yaml';\n\n if (source.startsWith('http://') || source.startsWith('https://')) {\n // Fetch from URL\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetchFn(source, { signal: controller.signal });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n content = await response.text();\n } finally {\n clearTimeout(timeoutId);\n }\n\n // Detect format from URL or content\n if (source.endsWith('.yaml') || source.endsWith('.yml')) {\n format = 'yaml';\n } else if (source.endsWith('.json')) {\n format = 'json';\n } else {\n format = detectFormat(content);\n }\n } else {\n // Read from file\n if (!readFile) {\n throw new Error('readFile adapter required for file paths');\n }\n content = await readFile(source);\n\n // Detect format from extension or content\n if (source.endsWith('.yaml') || source.endsWith('.yml')) {\n format = 'yaml';\n } else if (source.endsWith('.json')) {\n format = 'json';\n } else {\n format = detectFormat(content);\n }\n }\n\n const doc = parseOpenApiString(content, format);\n return parseOpenApiDocument(doc, options);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema-converter.d.ts","names":[],"sources":["../../src/openapi/schema-converter.ts"],"sourcesContent":[],"mappings":";;;;AA2CA;AA6CA;AAgGA;AAkBgB,UA/LC,cAAA,CA+LgB;EAmCjB;EAgHA,IAAA,EAAA,MAAA;;;;;;;;;;;;;UAlUC,WAAA;;;;QAIT;;;;;;gBAMQ;;;;;UAMC,cAAA;;;;;;UAMP;;;;;;;iBAuCM,gBAAA,SACN,+BAEP;;;;iBA6Fa,aAAA,SAAsB;;;;iBAkBtB,iBAAA,SACN,sDAGP;;;;iBA+Ba,uBAAA,SACN,oDAGP;;;;iBA4Ga,eAAA,SAAwB"}
1
+ {"version":3,"file":"schema-converter.d.ts","names":[],"sources":["../../src/openapi/schema-converter.ts"],"sourcesContent":[],"mappings":";;;;AA2CA;AA6CA;AAgGA;AAkBgB,UA/LC,cAAA,CA+LgB;EAmCjB;EA+GA,IAAA,EAAA,MAAA;;;;;;;;;;;;;UAjUC,WAAA;;;;QAIT;;;;;;gBAMQ;;;;;UAMC,cAAA;;;;;;UAMP;;;;;;;iBAuCM,gBAAA,SACN,+BAEP;;;;iBA6Fa,aAAA,SAAsB;;;;iBAkBtB,iBAAA,SACN,sDAGP;;;;iBA+Ba,uBAAA,SACN,oDAGP;;;;iBA2Ga,eAAA,SAAwB"}
@@ -68,20 +68,20 @@ function jsonSchemaToType(schema, name) {
68
68
  array: false,
69
69
  primitive: false
70
70
  };
71
- format && `${type}${format}`;
72
- if (type === "string") return {
71
+ const scalarKey = format ? `${type}:${format}` : type;
72
+ if (scalarKey === "string") return {
73
73
  type: "string",
74
74
  optional: nullable ?? false,
75
75
  array: false,
76
76
  primitive: true
77
77
  };
78
- if (type === "integer" || type === "number") return {
78
+ if (scalarKey === "integer" || type === "number") return {
79
79
  type: "number",
80
80
  optional: nullable ?? false,
81
81
  array: false,
82
82
  primitive: true
83
83
  };
84
- if (type === "boolean") return {
84
+ if (scalarKey === "boolean") return {
85
85
  type: "boolean",
86
86
  optional: nullable ?? false,
87
87
  array: false,
@@ -133,14 +133,13 @@ function jsonSchemaToField(schema, fieldName, required) {
133
133
  function generateSchemaModelCode(schema, modelName, indent = 0) {
134
134
  const spaces = " ".repeat(indent);
135
135
  const fields = [];
136
- let description;
137
136
  if (isReference(schema)) return {
138
137
  name: toPascalCase(typeNameFromRef(schema.$ref)),
139
138
  fields: [],
140
139
  code: `// Reference to ${schema.$ref}`
141
140
  };
142
141
  const schemaObj = schema;
143
- description = schemaObj["description"];
142
+ const description = schemaObj["description"];
144
143
  const properties = schemaObj["properties"];
145
144
  const required = schemaObj["required"] ?? [];
146
145
  if (!properties) return {
@@ -1 +1 @@
1
- {"version":3,"file":"schema-converter.js","names":["JSON_SCHEMA_TO_SCALAR: Record<string, string>","enumValues: string[] | undefined","fields: SchemaField[]","description: string | undefined","lines: string[]"],"sources":["../../src/openapi/schema-converter.ts"],"sourcesContent":["/**\n * JSON Schema to SchemaModel conversion utilities.\n * Converts OpenAPI JSON Schema to ContractSpec SchemaModel definitions.\n */\n\nimport type { OpenApiSchema } from './types';\nimport { toPascalCase, toCamelCase, toValidIdentifier } from '../common/utils';\n\n/**\n * TypeScript type representation for code generation.\n */\nexport interface TypescriptType {\n /** The type expression (e.g., \"string\", \"number\", \"MyModel\") */\n type: string;\n /** Whether the type is optional */\n optional: boolean;\n /** Whether the type is an array */\n array: boolean;\n /** Whether this is a primitive type */\n primitive: boolean;\n /** Description for documentation */\n description?: string;\n}\n\n/**\n * SchemaModel field representation for code generation.\n */\nexport interface SchemaField {\n /** Field name */\n name: string;\n /** Field type */\n type: TypescriptType;\n /** Scalar type enum value (for FieldType) */\n scalarType?: string;\n /** Enum values if this is an enum type */\n enumValues?: string[];\n /** Nested model if this is an object type */\n nestedModel?: GeneratedModel;\n}\n\n/**\n * Generated model representation.\n */\nexport interface GeneratedModel {\n /** Model name (PascalCase) */\n name: string;\n /** Model description */\n description?: string;\n /** Fields */\n fields: SchemaField[];\n /** Generated TypeScript code */\n code: string;\n}\n\n/**\n * Map JSON Schema types to ContractSpec ScalarTypeEnum values.\n */\nconst JSON_SCHEMA_TO_SCALAR: Record<string, string> = {\n string: 'ScalarTypeEnum.STRING',\n integer: 'ScalarTypeEnum.INT',\n number: 'ScalarTypeEnum.FLOAT',\n boolean: 'ScalarTypeEnum.BOOLEAN',\n // Special formats\n 'string:date': 'ScalarTypeEnum.DATE',\n 'string:date-time': 'ScalarTypeEnum.DATE_TIME',\n 'string:email': 'ScalarTypeEnum.EMAIL',\n 'string:uri': 'ScalarTypeEnum.URL',\n 'string:uuid': 'ScalarTypeEnum.ID',\n};\n\n/**\n * Check if a schema is a reference object.\n */\nfunction isReference(schema: OpenApiSchema): schema is { $ref: string } {\n return '$ref' in schema;\n}\n\n/**\n * Extract type name from a $ref.\n */\nfunction typeNameFromRef(ref: string): string {\n const parts = ref.split('/');\n return parts[parts.length - 1] ?? 'Unknown';\n}\n\n/**\n * Convert a JSON Schema to a TypeScript type representation.\n */\nexport function jsonSchemaToType(\n schema: OpenApiSchema,\n name?: string\n): TypescriptType {\n if (isReference(schema)) {\n return {\n type: toPascalCase(typeNameFromRef(schema.$ref)),\n optional: false,\n array: false,\n primitive: false,\n };\n }\n\n const schemaObj = schema as Record<string, unknown>;\n const type = schemaObj['type'] as string | undefined;\n const format = schemaObj['format'] as string | undefined;\n const nullable = schemaObj['nullable'] as boolean | undefined;\n\n // Handle arrays\n if (type === 'array') {\n const items = schemaObj['items'] as OpenApiSchema | undefined;\n if (items) {\n const itemType = jsonSchemaToType(items, name);\n return {\n ...itemType,\n array: true,\n optional: nullable ?? false,\n };\n }\n return {\n type: 'unknown',\n optional: nullable ?? false,\n array: true,\n primitive: false,\n };\n }\n\n // Handle objects\n if (type === 'object' || schemaObj['properties']) {\n return {\n type: name ? toPascalCase(name) : 'Record<string, unknown>',\n optional: nullable ?? false,\n array: false,\n primitive: false,\n };\n }\n\n // Handle enums\n if (schemaObj['enum']) {\n return {\n type: name ? toPascalCase(name) : 'string',\n optional: nullable ?? false,\n array: false,\n primitive: false,\n };\n }\n\n // Handle primitives\n const scalarKey = format ? `${type}:${format}` : type;\n if (type === 'string') {\n return {\n type: 'string',\n optional: nullable ?? false,\n array: false,\n primitive: true,\n };\n }\n if (type === 'integer' || type === 'number') {\n return {\n type: 'number',\n optional: nullable ?? false,\n array: false,\n primitive: true,\n };\n }\n if (type === 'boolean') {\n return {\n type: 'boolean',\n optional: nullable ?? false,\n array: false,\n primitive: true,\n };\n }\n\n // Default to unknown\n return {\n type: 'unknown',\n optional: nullable ?? false,\n array: false,\n primitive: false,\n };\n}\n\n/**\n * Get the ScalarTypeEnum value for a JSON Schema type.\n */\nexport function getScalarType(schema: OpenApiSchema): string | undefined {\n if (isReference(schema)) {\n return undefined;\n }\n\n const schemaObj = schema as Record<string, unknown>;\n const type = schemaObj['type'] as string | undefined;\n const format = schemaObj['format'] as string | undefined;\n\n if (!type) return undefined;\n\n const key = format ? `${type}:${format}` : type;\n return JSON_SCHEMA_TO_SCALAR[key] ?? JSON_SCHEMA_TO_SCALAR[type];\n}\n\n/**\n * Convert a JSON Schema to a SchemaModel field definition.\n */\nexport function jsonSchemaToField(\n schema: OpenApiSchema,\n fieldName: string,\n required: boolean\n): SchemaField {\n const type = jsonSchemaToType(schema, fieldName);\n const scalarType = getScalarType(schema);\n\n // Handle enums\n let enumValues: string[] | undefined;\n if (!isReference(schema)) {\n const schemaObj = schema as Record<string, unknown>;\n const enumArr = schemaObj['enum'] as unknown[] | undefined;\n if (enumArr) {\n enumValues = enumArr.map(String);\n }\n }\n\n return {\n name: toValidIdentifier(toCamelCase(fieldName)),\n type: {\n ...type,\n optional: !required || type.optional,\n description: !isReference(schema)\n ? ((schema as Record<string, unknown>)['description'] as string)\n : undefined,\n },\n scalarType,\n enumValues,\n };\n}\n\n/**\n * Generate SchemaModel TypeScript code for a JSON Schema object.\n */\nexport function generateSchemaModelCode(\n schema: OpenApiSchema,\n modelName: string,\n indent = 0\n): GeneratedModel {\n const spaces = ' '.repeat(indent);\n const fields: SchemaField[] = [];\n let description: string | undefined;\n\n if (isReference(schema)) {\n // Reference type - just use the referenced type\n return {\n name: toPascalCase(typeNameFromRef(schema.$ref)),\n fields: [],\n code: `// Reference to ${schema.$ref}`,\n };\n }\n\n const schemaObj = schema as Record<string, unknown>;\n description = schemaObj['description'] as string | undefined;\n const properties = schemaObj['properties'] as\n | Record<string, OpenApiSchema>\n | undefined;\n const required = (schemaObj['required'] as string[]) ?? [];\n\n if (!properties) {\n // No properties - generate an empty model or type alias\n return {\n name: toPascalCase(modelName),\n description,\n fields: [],\n code: `${spaces}// Empty schema for ${modelName}`,\n };\n }\n\n // Generate fields\n for (const [propName, propSchema] of Object.entries(properties)) {\n const isRequired = required.includes(propName);\n fields.push(jsonSchemaToField(propSchema, propName, isRequired));\n }\n\n // Generate code\n const lines: string[] = [];\n\n // Model definition\n const safeModelName = toPascalCase(toValidIdentifier(modelName));\n lines.push(`${spaces}export const ${safeModelName} = defineSchemaModel({`);\n lines.push(`${spaces} name: '${safeModelName}',`);\n if (description) {\n lines.push(`${spaces} description: ${JSON.stringify(description)},`);\n }\n lines.push(`${spaces} fields: {`);\n\n for (const field of fields) {\n const fieldLines = generateFieldCode(field, indent + 2);\n lines.push(fieldLines);\n }\n\n lines.push(`${spaces} },`);\n lines.push(`${spaces}});`);\n\n return {\n name: safeModelName,\n description,\n fields,\n code: lines.join('\\n'),\n };\n}\n\n/**\n * Generate TypeScript code for a single field.\n */\nfunction generateFieldCode(field: SchemaField, indent: number): string {\n const spaces = ' '.repeat(indent);\n const lines: string[] = [];\n\n lines.push(`${spaces}${field.name}: {`);\n\n // Type\n if (field.enumValues) {\n // Enum type\n lines.push(\n `${spaces} type: new EnumType([${field.enumValues.map((v) => `'${v}'`).join(', ')}]),`\n );\n } else if (field.scalarType) {\n // Scalar type\n lines.push(`${spaces} type: ${field.scalarType},`);\n } else {\n // Nested model or reference\n lines.push(\n `${spaces} type: ${field.type.type}, // TODO: Define or import this type`\n );\n }\n\n // Optional\n if (field.type.optional) {\n lines.push(`${spaces} isOptional: true,`);\n }\n\n // Array\n if (field.type.array) {\n lines.push(`${spaces} isArray: true,`);\n }\n\n lines.push(`${spaces}},`);\n\n return lines.join('\\n');\n}\n\n/**\n * Generate import statements for a SchemaModel.\n */\nexport function generateImports(fields: SchemaField[]): string {\n const imports = new Set<string>();\n\n imports.add(\n \"import { defineSchemaModel, ScalarTypeEnum, EnumType } from '@lssm/lib.schema';\"\n );\n\n // Check if we need any custom type imports\n for (const field of fields) {\n if (!field.type.primitive && !field.enumValues && !field.scalarType) {\n // This is a reference to another model - would need import\n // imports.add(`import { ${field.type.type} } from './${toKebabCase(field.type.type)}';`);\n }\n }\n\n return Array.from(imports).join('\\n');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"schema-converter.js","names":["JSON_SCHEMA_TO_SCALAR: Record<string, string>","enumValues: string[] | undefined","fields: SchemaField[]","lines: string[]"],"sources":["../../src/openapi/schema-converter.ts"],"sourcesContent":["/**\n * JSON Schema to SchemaModel conversion utilities.\n * Converts OpenAPI JSON Schema to ContractSpec SchemaModel definitions.\n */\n\nimport type { OpenApiSchema } from './types';\nimport { toCamelCase, toPascalCase, toValidIdentifier } from '../common/utils';\n\n/**\n * TypeScript type representation for code generation.\n */\nexport interface TypescriptType {\n /** The type expression (e.g., \"string\", \"number\", \"MyModel\") */\n type: string;\n /** Whether the type is optional */\n optional: boolean;\n /** Whether the type is an array */\n array: boolean;\n /** Whether this is a primitive type */\n primitive: boolean;\n /** Description for documentation */\n description?: string;\n}\n\n/**\n * SchemaModel field representation for code generation.\n */\nexport interface SchemaField {\n /** Field name */\n name: string;\n /** Field type */\n type: TypescriptType;\n /** Scalar type enum value (for FieldType) */\n scalarType?: string;\n /** Enum values if this is an enum type */\n enumValues?: string[];\n /** Nested model if this is an object type */\n nestedModel?: GeneratedModel;\n}\n\n/**\n * Generated model representation.\n */\nexport interface GeneratedModel {\n /** Model name (PascalCase) */\n name: string;\n /** Model description */\n description?: string;\n /** Fields */\n fields: SchemaField[];\n /** Generated TypeScript code */\n code: string;\n}\n\n/**\n * Map JSON Schema types to ContractSpec ScalarTypeEnum values.\n */\nconst JSON_SCHEMA_TO_SCALAR: Record<string, string> = {\n string: 'ScalarTypeEnum.STRING',\n integer: 'ScalarTypeEnum.INT',\n number: 'ScalarTypeEnum.FLOAT',\n boolean: 'ScalarTypeEnum.BOOLEAN',\n // Special formats\n 'string:date': 'ScalarTypeEnum.DATE',\n 'string:date-time': 'ScalarTypeEnum.DATE_TIME',\n 'string:email': 'ScalarTypeEnum.EMAIL',\n 'string:uri': 'ScalarTypeEnum.URL',\n 'string:uuid': 'ScalarTypeEnum.ID',\n};\n\n/**\n * Check if a schema is a reference object.\n */\nfunction isReference(schema: OpenApiSchema): schema is { $ref: string } {\n return '$ref' in schema;\n}\n\n/**\n * Extract type name from a $ref.\n */\nfunction typeNameFromRef(ref: string): string {\n const parts = ref.split('/');\n return parts[parts.length - 1] ?? 'Unknown';\n}\n\n/**\n * Convert a JSON Schema to a TypeScript type representation.\n */\nexport function jsonSchemaToType(\n schema: OpenApiSchema,\n name?: string\n): TypescriptType {\n if (isReference(schema)) {\n return {\n type: toPascalCase(typeNameFromRef(schema.$ref)),\n optional: false,\n array: false,\n primitive: false,\n };\n }\n\n const schemaObj = schema as Record<string, unknown>;\n const type = schemaObj['type'] as string | undefined;\n const format = schemaObj['format'] as string | undefined;\n const nullable = schemaObj['nullable'] as boolean | undefined;\n\n // Handle arrays\n if (type === 'array') {\n const items = schemaObj['items'] as OpenApiSchema | undefined;\n if (items) {\n const itemType = jsonSchemaToType(items, name);\n return {\n ...itemType,\n array: true,\n optional: nullable ?? false,\n };\n }\n return {\n type: 'unknown',\n optional: nullable ?? false,\n array: true,\n primitive: false,\n };\n }\n\n // Handle objects\n if (type === 'object' || schemaObj['properties']) {\n return {\n type: name ? toPascalCase(name) : 'Record<string, unknown>',\n optional: nullable ?? false,\n array: false,\n primitive: false,\n };\n }\n\n // Handle enums\n if (schemaObj['enum']) {\n return {\n type: name ? toPascalCase(name) : 'string',\n optional: nullable ?? false,\n array: false,\n primitive: false,\n };\n }\n\n // Handle primitives\n const scalarKey = format ? `${type}:${format}` : type;\n if (scalarKey === 'string') {\n return {\n type: 'string',\n optional: nullable ?? false,\n array: false,\n primitive: true,\n };\n }\n if (scalarKey === 'integer' || type === 'number') {\n return {\n type: 'number',\n optional: nullable ?? false,\n array: false,\n primitive: true,\n };\n }\n if (scalarKey === 'boolean') {\n return {\n type: 'boolean',\n optional: nullable ?? false,\n array: false,\n primitive: true,\n };\n }\n\n // Default to unknown\n return {\n type: 'unknown',\n optional: nullable ?? false,\n array: false,\n primitive: false,\n };\n}\n\n/**\n * Get the ScalarTypeEnum value for a JSON Schema type.\n */\nexport function getScalarType(schema: OpenApiSchema): string | undefined {\n if (isReference(schema)) {\n return undefined;\n }\n\n const schemaObj = schema as Record<string, unknown>;\n const type = schemaObj['type'] as string | undefined;\n const format = schemaObj['format'] as string | undefined;\n\n if (!type) return undefined;\n\n const key = format ? `${type}:${format}` : type;\n return JSON_SCHEMA_TO_SCALAR[key] ?? JSON_SCHEMA_TO_SCALAR[type];\n}\n\n/**\n * Convert a JSON Schema to a SchemaModel field definition.\n */\nexport function jsonSchemaToField(\n schema: OpenApiSchema,\n fieldName: string,\n required: boolean\n): SchemaField {\n const type = jsonSchemaToType(schema, fieldName);\n const scalarType = getScalarType(schema);\n\n // Handle enums\n let enumValues: string[] | undefined;\n if (!isReference(schema)) {\n const schemaObj = schema as Record<string, unknown>;\n const enumArr = schemaObj['enum'] as unknown[] | undefined;\n if (enumArr) {\n enumValues = enumArr.map(String);\n }\n }\n\n return {\n name: toValidIdentifier(toCamelCase(fieldName)),\n type: {\n ...type,\n optional: !required || type.optional,\n description: !isReference(schema)\n ? ((schema as Record<string, unknown>)['description'] as string)\n : undefined,\n },\n scalarType,\n enumValues,\n };\n}\n\n/**\n * Generate SchemaModel TypeScript code for a JSON Schema object.\n */\nexport function generateSchemaModelCode(\n schema: OpenApiSchema,\n modelName: string,\n indent = 0\n): GeneratedModel {\n const spaces = ' '.repeat(indent);\n const fields: SchemaField[] = [];\n\n if (isReference(schema)) {\n // Reference type - just use the referenced type\n return {\n name: toPascalCase(typeNameFromRef(schema.$ref)),\n fields: [],\n code: `// Reference to ${schema.$ref}`,\n };\n }\n\n const schemaObj = schema as Record<string, unknown>;\n const description = schemaObj['description'] as string | undefined;\n const properties = schemaObj['properties'] as\n | Record<string, OpenApiSchema>\n | undefined;\n const required = (schemaObj['required'] as string[]) ?? [];\n\n if (!properties) {\n // No properties - generate an empty model or type alias\n return {\n name: toPascalCase(modelName),\n description,\n fields: [],\n code: `${spaces}// Empty schema for ${modelName}`,\n };\n }\n\n // Generate fields\n for (const [propName, propSchema] of Object.entries(properties)) {\n const isRequired = required.includes(propName);\n fields.push(jsonSchemaToField(propSchema, propName, isRequired));\n }\n\n // Generate code\n const lines: string[] = [];\n\n // Model definition\n const safeModelName = toPascalCase(toValidIdentifier(modelName));\n lines.push(`${spaces}export const ${safeModelName} = defineSchemaModel({`);\n lines.push(`${spaces} name: '${safeModelName}',`);\n if (description) {\n lines.push(`${spaces} description: ${JSON.stringify(description)},`);\n }\n lines.push(`${spaces} fields: {`);\n\n for (const field of fields) {\n const fieldLines = generateFieldCode(field, indent + 2);\n lines.push(fieldLines);\n }\n\n lines.push(`${spaces} },`);\n lines.push(`${spaces}});`);\n\n return {\n name: safeModelName,\n description,\n fields,\n code: lines.join('\\n'),\n };\n}\n\n/**\n * Generate TypeScript code for a single field.\n */\nfunction generateFieldCode(field: SchemaField, indent: number): string {\n const spaces = ' '.repeat(indent);\n const lines: string[] = [];\n\n lines.push(`${spaces}${field.name}: {`);\n\n // Type\n if (field.enumValues) {\n // Enum type\n lines.push(\n `${spaces} type: new EnumType([${field.enumValues.map((v) => `'${v}'`).join(', ')}]),`\n );\n } else if (field.scalarType) {\n // Scalar type\n lines.push(`${spaces} type: ${field.scalarType},`);\n } else {\n // Nested model or reference\n lines.push(\n `${spaces} type: ${field.type.type}, // TODO: Define or import this type`\n );\n }\n\n // Optional\n if (field.type.optional) {\n lines.push(`${spaces} isOptional: true,`);\n }\n\n // Array\n if (field.type.array) {\n lines.push(`${spaces} isArray: true,`);\n }\n\n lines.push(`${spaces}},`);\n\n return lines.join('\\n');\n}\n\n/**\n * Generate import statements for a SchemaModel.\n */\nexport function generateImports(fields: SchemaField[]): string {\n const imports = new Set<string>();\n\n imports.add(\n \"import { defineSchemaModel, ScalarTypeEnum, EnumType } from '@lssm/lib.schema';\"\n );\n\n // Check if we need any custom type imports\n for (const field of fields) {\n if (!field.type.primitive && !field.enumValues && !field.scalarType) {\n // This is a reference to another model - would need import\n // imports.add(`import { ${field.type.type} } from './${toKebabCase(field.type.type)}';`);\n }\n }\n\n return Array.from(imports).join('\\n');\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lssm/lib.contracts-transformers",
3
- "version": "0.0.0-canary-20251219202229",
3
+ "version": "0.0.0-canary-20251220015515",
4
4
  "description": "Contract format transformations: import/export between ContractSpec and external formats (OpenAPI, AsyncAPI, etc.)",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -17,15 +17,15 @@
17
17
  "test": "bun test"
18
18
  },
19
19
  "dependencies": {
20
- "@lssm/lib.contracts": "0.0.0-canary-20251219202229",
21
- "@lssm/lib.schema": "0.0.0-canary-20251219202229",
20
+ "@lssm/lib.contracts": "0.0.0-canary-20251220015515",
21
+ "@lssm/lib.schema": "0.0.0-canary-20251220015515",
22
22
  "openapi-types": "^12.1.3",
23
23
  "yaml": "^2.7.1",
24
24
  "zod": "^4.1.13"
25
25
  },
26
26
  "devDependencies": {
27
- "@lssm/tool.tsdown": "0.0.0-canary-20251219202229",
28
- "@lssm/tool.typescript": "0.0.0-canary-20251219202229",
27
+ "@lssm/tool.tsdown": "0.0.0-canary-20251220015515",
28
+ "@lssm/tool.typescript": "0.0.0-canary-20251220015515",
29
29
  "tsdown": "^0.18.1",
30
30
  "typescript": "^5.9.3"
31
31
  },
@@ -49,7 +49,13 @@
49
49
  "./common": "./dist/common/index.js",
50
50
  "./openapi": "./dist/openapi/index.js",
51
51
  "./*": "./*"
52
- }
52
+ },
53
+ "registry": "https://registry.npmjs.org/"
53
54
  },
54
- "license": "MIT"
55
+ "license": "MIT",
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "https://github.com/lssm-tech/contractspec.git",
59
+ "directory": "packages/libs/contracts-transformers"
60
+ }
55
61
  }