@lssm/lib.contracts-transformers 0.0.0-canary-20251217023603
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.
- package/README.md +91 -0
- package/dist/common/index.js +1 -0
- package/dist/common/utils.js +1 -0
- package/dist/index.js +1 -0
- package/dist/openapi/differ.js +2 -0
- package/dist/openapi/exporter.js +1 -0
- package/dist/openapi/importer.js +2 -0
- package/dist/openapi/index.js +1 -0
- package/dist/openapi/parser.js +1 -0
- package/dist/openapi/schema-converter.js +4 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @lssm/lib.contracts-transformers
|
|
2
|
+
|
|
3
|
+
Contract format transformations: bidirectional import/export between ContractSpec and external API specification formats.
|
|
4
|
+
|
|
5
|
+
## Supported Formats
|
|
6
|
+
|
|
7
|
+
- **OpenAPI 3.x** - Import from and export to OpenAPI specifications (JSON/YAML, URL/file)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add @lssm/lib.contracts-transformers
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Export ContractSpec to OpenAPI
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { openApiForRegistry } from '@lssm/lib.contracts-transformers/openapi';
|
|
21
|
+
import { SpecRegistry } from '@lssm/lib.contracts';
|
|
22
|
+
|
|
23
|
+
const registry = new SpecRegistry();
|
|
24
|
+
// ... register your specs ...
|
|
25
|
+
|
|
26
|
+
const openApiDoc = openApiForRegistry(registry, {
|
|
27
|
+
title: 'My API',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
description: 'API generated from ContractSpec',
|
|
30
|
+
servers: [{ url: 'https://api.example.com' }],
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Import from OpenAPI
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { parseOpenApi, importFromOpenApi } from '@lssm/lib.contracts-transformers/openapi';
|
|
38
|
+
|
|
39
|
+
// Parse OpenAPI from file or URL
|
|
40
|
+
const openApiDoc = await parseOpenApi('./api.yaml');
|
|
41
|
+
// Or from URL
|
|
42
|
+
const openApiDoc = await parseOpenApi('https://api.example.com/openapi.json');
|
|
43
|
+
|
|
44
|
+
// Convert to ContractSpec specs
|
|
45
|
+
const importResult = importFromOpenApi(openApiDoc, {
|
|
46
|
+
prefix: 'myApi',
|
|
47
|
+
tags: ['users', 'orders'], // Optional: filter by tags
|
|
48
|
+
exclude: ['deprecated_endpoint'], // Optional: exclude by operationId
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// importResult contains generated spec code as strings
|
|
52
|
+
for (const spec of importResult.specs) {
|
|
53
|
+
console.log(spec.name, spec.code);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Diff ContractSpec vs OpenAPI
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { diffSpecs } from '@lssm/lib.contracts-transformers/openapi';
|
|
61
|
+
|
|
62
|
+
const diffs = diffSpecs(existingSpecs, importedSpecs);
|
|
63
|
+
|
|
64
|
+
for (const diff of diffs) {
|
|
65
|
+
console.log(`${diff.operationId}: ${diff.changes.length} changes`);
|
|
66
|
+
for (const change of diff.changes) {
|
|
67
|
+
console.log(` - ${change.path}: ${change.type}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Architecture
|
|
73
|
+
|
|
74
|
+
This library is organized by format:
|
|
75
|
+
|
|
76
|
+
- `openapi/` - OpenAPI 3.x transformations
|
|
77
|
+
- `parser.ts` - Parse OpenAPI from JSON/YAML/URL
|
|
78
|
+
- `importer.ts` - Convert OpenAPI to ContractSpec
|
|
79
|
+
- `exporter.ts` - Convert ContractSpec to OpenAPI
|
|
80
|
+
- `differ.ts` - Diff specs for sync operations
|
|
81
|
+
- `schema-converter.ts` - JSON Schema <-> SchemaModel conversion
|
|
82
|
+
- `common/` - Shared utilities and types
|
|
83
|
+
|
|
84
|
+
## Future Formats
|
|
85
|
+
|
|
86
|
+
The library is designed to be extensible for additional formats:
|
|
87
|
+
|
|
88
|
+
- AsyncAPI (event-driven APIs)
|
|
89
|
+
- gRPC/Protobuf
|
|
90
|
+
- GraphQL Schema
|
|
91
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{deepEqual as e,extractPathParams as t,getByPath as n,normalizePath as r,toCamelCase as i,toFileName as a,toKebabCase as o,toPascalCase as s,toSnakeCase as c,toSpecName as l,toValidIdentifier as u}from"./utils.js";export{e as deepEqual,t as extractPathParams,n as getByPath,r as normalizePath,i as toCamelCase,a as toFileName,o as toKebabCase,s as toPascalCase,c as toSnakeCase,l as toSpecName,u as toValidIdentifier};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){return e.replace(/[-_./\s]+(.)?/g,(e,t)=>t?t.toUpperCase():``).replace(/^./,e=>e.toUpperCase())}function t(t){let n=e(t);return n.charAt(0).toLowerCase()+n.slice(1)}function n(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).replace(/[\s_./]+/g,`-`).toLowerCase()}function r(e){return e.replace(/([a-z])([A-Z])/g,`$1_$2`).replace(/[\s\-./]+/g,`_`).toLowerCase()}function i(e){let t=e.replace(/[^a-zA-Z0-9_$]/g,`_`);return/^[0-9]/.test(t)&&(t=`_`+t),t}function a(e,n){let r=t(e);return n?`${n}.${r}`:r}function o(e){return n(e.replace(/\./g,`-`))+`.ts`}function s(e,t){if(e===t)return!0;if(e===null||t===null||typeof e!=typeof t)return!1;if(typeof e==`object`){let n=e,r=t,i=Object.keys(n),a=Object.keys(r);if(i.length!==a.length)return!1;for(let e of i)if(!a.includes(e)||!s(n[e],r[e]))return!1;return!0}return!1}function c(e,t){let n=t.split(`.`).filter(Boolean),r=e;for(let e of n){if(typeof r!=`object`||!r)return;r=r[e]}return r}function l(e){return(e.match(/\{([^}]+)\}/g)||[]).map(e=>e.slice(1,-1))}function u(e){let t=e.replace(/^\/+|\/+$/g,``);return t=t.replace(/\/+/g,`/`),`/`+t}export{s as deepEqual,l as extractPathParams,c as getByPath,u as normalizePath,t as toCamelCase,o as toFileName,n as toKebabCase,e as toPascalCase,r as toSnakeCase,a as toSpecName,i as toValidIdentifier};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{detectFormat as e,detectVersion as t,parseOpenApi as n,parseOpenApiDocument as r,parseOpenApiString as i}from"./openapi/parser.js";import{defaultRestPath as a,openApiForRegistry as o,openApiToJson as s,openApiToYaml as c}from"./openapi/exporter.js";import{deepEqual as l,extractPathParams as u,getByPath as d,normalizePath as f,toCamelCase as p,toFileName as m,toKebabCase as h,toPascalCase as g,toSnakeCase as _,toSpecName as v,toValidIdentifier as y}from"./common/utils.js";import{generateImports as b,generateSchemaModelCode as x,getScalarType as S,jsonSchemaToField as C,jsonSchemaToType as w}from"./openapi/schema-converter.js";import{importFromOpenApi as T,importOperation as E}from"./openapi/importer.js";import{createSpecDiff as D,diffAll as O,diffSpecVsOperation as k,diffSpecs as A,formatDiffChanges as j}from"./openapi/differ.js";import"./openapi/index.js";export{D as createSpecDiff,l as deepEqual,a as defaultRestPath,e as detectFormat,t as detectVersion,O as diffAll,k as diffSpecVsOperation,A as diffSpecs,u as extractPathParams,j as formatDiffChanges,b as generateImports,x as generateSchemaModelCode,d as getByPath,S as getScalarType,T as importFromOpenApi,E as importOperation,C as jsonSchemaToField,w as jsonSchemaToType,f as normalizePath,o as openApiForRegistry,s as openApiToJson,c as openApiToYaml,n as parseOpenApi,r as parseOpenApiDocument,i as parseOpenApiString,p as toCamelCase,m as toFileName,h as toKebabCase,g as toPascalCase,_ as toSnakeCase,v as toSpecName,y as toValidIdentifier};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{deepEqual as e}from"../common/utils.js";function t(t,n,r,i){if(e(n,r))return null;let a=`modified`;return n==null?a=`added`:r==null?a=`removed`:typeof n!=typeof r&&(a=`type_changed`),{path:t,type:a,oldValue:n,newValue:r,description:i}}function n(e,r,i,a){let o=[];if(!r&&!i)return o;if(!r)return o.push({path:e,type:`added`,newValue:i,description:`Added ${e}`}),o;if(!i)return o.push({path:e,type:`removed`,oldValue:r,description:`Removed ${e}`}),o;let s=new Set([...Object.keys(r),...Object.keys(i)]);for(let c of s){let s=e?`${e}.${c}`:c;if(a.ignorePaths?.some(e=>s.startsWith(e)))continue;let l=r[c],u=i[c];if(typeof l==`object`&&typeof u==`object`)o.push(...n(s,l,u,a));else{let e=t(s,l,u,`Changed ${s}`);e&&o.push(e)}}return o}function r(n,r,i={}){let a=[];if(!i.ignoreDescriptions){let e=t(`meta.description`,n.meta.description,r.summary??r.description,`Description changed`);e&&a.push(e)}if(!i.ignoreTags){let t=[...n.meta.tags??[]].sort(),i=[...r.tags].sort();e(t,i)||a.push({path:`meta.tags`,type:`modified`,oldValue:t,newValue:i,description:`Tags changed`})}if(!i.ignoreTransport){let e=n.transport?.rest?.method??(n.meta.kind===`query`?`GET`:`POST`),t=r.method.toUpperCase();e!==t&&a.push({path:`transport.rest.method`,type:`modified`,oldValue:e,newValue:t,description:`HTTP method changed`});let i=n.transport?.rest?.path;i&&i!==r.path&&a.push({path:`transport.rest.path`,type:`modified`,oldValue:i,newValue:r.path,description:`Path changed`})}return n.meta.stability===`deprecated`!==r.deprecated&&a.push({path:`meta.stability`,type:`modified`,oldValue:n.meta.stability,newValue:r.deprecated?`deprecated`:`stable`,description:`Deprecation status changed`}),a}function i(e,t,r={}){let i=[],a=n(`meta`,e.meta,t.meta,{...r,ignorePaths:[...r.ignorePaths??[],...r.ignoreDescriptions?[`meta.description`,`meta.goal`,`meta.context`]:[],...r.ignoreTags?[`meta.tags`]:[]]});if(i.push(...a),!r.ignoreTransport){let a=n(`transport`,e.transport,t.transport,r);i.push(...a)}let o=n(`policy`,e.policy,t.policy,r);return i.push(...o),i}function a(e,t,n,r={}){let a=[],o=!1;return t?(a=i(t,n.spec,r),o=a.length===0):a=[{path:``,type:`added`,newValue:n.spec,description:`New spec imported from OpenAPI`}],{operationId:e,existing:t,incoming:n,changes:a,isEquivalent:o}}function o(e,t,n={}){let r=[],i=new Set;for(let o of t){let t=o.source.sourceId,s;for(let[n,r]of e){let e=r.meta.name;if(n===t||e.includes(t)){s=r,i.add(n);break}}r.push(a(t,s,o,n))}for(let[t,n]of e)i.has(t)||r.push({operationId:t,existing:n,incoming:void 0,changes:[{path:``,type:`removed`,oldValue:n,description:`Spec no longer exists in OpenAPI source`}],isEquivalent:!1});return r}function s(e){if(e.length===0)return`No changes detected`;let t=[];for(let n of e){let e={added:`+`,removed:`-`,modified:`~`,type_changed:`!`,required_changed:`?`}[n.type];t.push(`${e} ${n.path}: ${n.description}`),n.type===`modified`||n.type===`type_changed`?(t.push(` old: ${JSON.stringify(n.oldValue)}`),t.push(` new: ${JSON.stringify(n.newValue)}`)):n.type===`added`?t.push(` value: ${JSON.stringify(n.newValue)}`):n.type===`removed`&&t.push(` was: ${JSON.stringify(n.oldValue)}`)}return t.join(`
|
|
2
|
+
`)}export{a as createSpecDiff,o as diffAll,r as diffSpecVsOperation,i as diffSpecs,s as formatDiffChanges};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as e}from"zod";function t(e,t){return`${e.replace(/\./g,`_`)}_v${t}`}function n(e,n,r){return`${e}_${t(n,r)}`}function r(e,t){return(t??(e===`query`?`GET`:`POST`)).toLowerCase()}function i(e,t){return`/${e.replace(/\./g,`/`)}/v${t}`}function a(e){let t=e.transport?.rest?.path??i(e.meta.name,e.meta.version);return t.startsWith(`/`)?t:`/${t}`}function o(t){return t?e.toJSONSchema(t.getZod()):null}function s(e){return{input:o(e.io.input),output:o(e.io.output),meta:{name:e.meta.name,version:e.meta.version,kind:e.meta.kind,description:e.meta.description,tags:e.meta.tags??[],stability:e.meta.stability??`stable`}}}function c(e,i={}){let o=e.listSpecs().filter(e=>e.meta.kind===`command`||e.meta.kind===`query`).slice().sort((e,t)=>{let n=e.meta.name.localeCompare(t.meta.name);return n===0?e.meta.version-t.meta.version:n}),c={openapi:`3.1.0`,info:{title:i.title??`ContractSpec API`,version:i.version??`0.0.0`,...i.description?{description:i.description}:{}},...i.servers?{servers:i.servers}:{},paths:{},components:{schemas:{}}};for(let e of o){let i=s(e),o=r(e.meta.kind,e.transport?.rest?.method),l=a(e),u=t(e.meta.name,e.meta.version),d=c.paths[l]??={},f={operationId:u,summary:e.meta.description??e.meta.name,description:e.meta.description,tags:e.meta.tags??[],"x-contractspec":{name:e.meta.name,version:e.meta.version,kind:e.meta.kind},responses:{}};if(i.input){let t=n(`Input`,e.meta.name,e.meta.version);c.components.schemas[t]=i.input,f.requestBody={required:!0,content:{"application/json":{schema:{$ref:`#/components/schemas/${t}`}}}}}let p={};if(i.output){let t=n(`Output`,e.meta.name,e.meta.version);c.components.schemas[t]=i.output,p[200]={description:`OK`,content:{"application/json":{schema:{$ref:`#/components/schemas/${t}`}}}}}else p[200]={description:`OK`};f.responses=p,d[o]=f}return c}function l(e,t={}){let n=c(e,t);return JSON.stringify(n,null,2)}function u(e,t={}){return d(c(e,t))}function d(e,t=0){let n=` `.repeat(t),r=``;if(Array.isArray(e))for(let i of e)typeof i==`object`&&i?r+=`${n}-\n${d(i,t+1)}`:r+=`${n}- ${JSON.stringify(i)}\n`;else if(typeof e==`object`&&e)for(let[i,a]of Object.entries(e))Array.isArray(a)||typeof a==`object`&&a?r+=`${n}${i}:\n${d(a,t+1)}`:r+=`${n}${i}: ${JSON.stringify(a)}\n`;return r}export{i as defaultRestPath,c as openApiForRegistry,l as openApiToJson,u as openApiToYaml};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{toFileName as e,toPascalCase as t,toSpecName as n,toValidIdentifier as r}from"../common/utils.js";import{generateImports as i,generateSchemaModelCode as a}from"./schema-converter.js";const o=[`post`,`put`,`delete`,`patch`];function s(e){return o.includes(e.toLowerCase())?`command`:`query`}function c(e,t){if(!e.security||e.security.length===0)return t;for(let t of e.security)if(Object.keys(t).length===0)return`anonymous`;return`user`}function l(e){let t=[];for(let n of e.pathParams)t.push({name:n.name,schema:n.schema,required:!0});for(let n of e.queryParams)t.push({name:n.name,schema:n.schema,required:n.required});let n=[`authorization`,`content-type`,`accept`,`user-agent`];for(let r of e.headerParams)n.includes(r.name.toLowerCase())||t.push({name:r.name,schema:r.schema,required:r.required});if(e.requestBody?.schema){let n=e.requestBody.schema;if(`$ref`in n)t.push({name:`body`,schema:n,required:e.requestBody.required});else{let e=n,r=e.properties,i=e.required??[];if(r)for(let[e,n]of Object.entries(r))t.push({name:e,schema:n,required:i.includes(e)})}}return t.length===0?{schema:null,fields:[]}:{schema:{type:`object`,properties:t.reduce((e,t)=>(e[t.name]=t.schema,e),{}),required:t.filter(e=>e.required).map(e=>e.name)},fields:t}}function u(e){for(let t of[`200`,`201`,`202`,`204`]){let n=e.responses[t];if(n?.schema)return n.schema}for(let[t,n]of Object.entries(e.responses))if(t.startsWith(`2`)&&n.schema)return n.schema;return null}function d(e,a,o,l){let u=n(e.operationId,a.prefix),d=s(e.method),f=c(e,a.defaultAuth??`user`),p=[];p.push(`import { defineCommand, defineQuery } from '@lssm/lib.contracts';`),(o||l)&&p.push(i([...o?.fields??[],...l?.fields??[]])),p.push(``),o&&o.code&&(p.push(`// Input schema`),p.push(o.code),p.push(``)),l&&l.code&&(p.push(`// Output schema`),p.push(l.code),p.push(``));let m=d===`command`?`defineCommand`:`defineQuery`,h=r(t(e.operationId));return p.push(`/**`),p.push(` * ${e.summary??e.operationId}`),e.description&&(p.push(` *`),p.push(` * ${e.description}`)),p.push(` *`),p.push(` * @source OpenAPI: ${e.method.toUpperCase()} ${e.path}`),p.push(` */`),p.push(`export const ${h}Spec = ${m}({`),p.push(` meta: {`),p.push(` name: '${u}',`),p.push(` version: 1,`),p.push(` stability: '${a.defaultStability??`stable`}',`),p.push(` owners: [${(a.defaultOwners??[]).map(e=>`'${e}'`).join(`, `)}],`),p.push(` tags: [${e.tags.map(e=>`'${e}'`).join(`, `)}],`),p.push(` description: ${JSON.stringify(e.summary??e.operationId)},`),p.push(` goal: ${JSON.stringify(e.description??`Imported from OpenAPI`)},`),p.push(` context: 'Imported from OpenAPI: ${e.method.toUpperCase()} ${e.path}',`),p.push(` },`),p.push(` io: {`),o?p.push(` input: ${o.name},`):p.push(` input: null,`),l?p.push(` output: ${l.name},`):p.push(` output: null, // TODO: Define output schema`),p.push(` },`),p.push(` policy: {`),p.push(` auth: '${f}',`),p.push(` },`),p.push(` transport: {`),p.push(` rest: {`),p.push(` method: '${e.method.toUpperCase()}',`),p.push(` path: '${e.path}',`),p.push(` },`),p.push(` },`),p.push(`});`),p.join(`
|
|
2
|
+
`)}function f(t,r={}){let{tags:i,exclude:o=[],include:s}=r,c=[],f=[],p=[];for(let m of t.operations){if(i&&i.length>0&&!m.tags.some(e=>i.includes(e))){f.push({sourceId:m.operationId,reason:`No matching tags (has: ${m.tags.join(`, `)})`});continue}if(s&&s.length>0){if(!s.includes(m.operationId)){f.push({sourceId:m.operationId,reason:`Not in include list`});continue}}else if(o.includes(m.operationId)){f.push({sourceId:m.operationId,reason:`In exclude list`});continue}if(m.deprecated&&r.defaultStability!==`deprecated`){f.push({sourceId:m.operationId,reason:`Deprecated operation`});continue}try{let{schema:i}=l(m),o=i?a(i,`${m.operationId}Input`):null,s=u(m),f=d(m,r,o,s?a(s,`${m.operationId}Output`):null),p=e(n(m.operationId,r.prefix)),h={rest:{method:m.method.toUpperCase(),path:m.path,params:{path:m.pathParams.map(e=>e.name),query:m.queryParams.map(e=>e.name),header:m.headerParams.map(e=>e.name),cookie:m.cookieParams.map(e=>e.name)}}},g={type:`openapi`,sourceId:m.operationId,operationId:m.operationId,openApiVersion:t.version,importedAt:new Date};c.push({spec:{},code:f,fileName:p,source:g,transportHints:h})}catch(e){p.push({sourceId:m.operationId,error:e instanceof Error?e.message:String(e)})}}return{specs:c,skipped:f,errors:p,summary:{total:t.operations.length,imported:c.length,skipped:f.length,errors:p.length}}}function p(e,t={}){let{schema:n}=l(e),r=n?a(n,`${e.operationId}Input`):null,i=u(e);return d(e,t,r,i?a(i,`${e.operationId}Output`):null)}export{f as importFromOpenApi,p as importOperation};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{detectFormat as e,detectVersion as t,parseOpenApi as n,parseOpenApiDocument as r,parseOpenApiString as i}from"./parser.js";import{defaultRestPath as a,openApiForRegistry as o,openApiToJson as s,openApiToYaml as c}from"./exporter.js";import{generateImports as l,generateSchemaModelCode as u,getScalarType as d,jsonSchemaToField as f,jsonSchemaToType as p}from"./schema-converter.js";import{importFromOpenApi as m,importOperation as h}from"./importer.js";import{createSpecDiff as g,diffAll as _,diffSpecVsOperation as v,diffSpecs as y,formatDiffChanges as b}from"./differ.js";export{g as createSpecDiff,a as defaultRestPath,e as detectFormat,t as detectVersion,_ as diffAll,v as diffSpecVsOperation,y as diffSpecs,b as formatDiffChanges,l as generateImports,u as generateSchemaModelCode,d as getScalarType,m as importFromOpenApi,h as importOperation,f as jsonSchemaToField,p as jsonSchemaToType,o as openApiForRegistry,s as openApiToJson,c as openApiToYaml,n as parseOpenApi,r as parseOpenApiDocument,i as parseOpenApiString};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{parse as e}from"yaml";const t=[`get`,`post`,`put`,`delete`,`patch`,`head`,`options`,`trace`];function n(t,n=`json`){return n===`yaml`?e(t):JSON.parse(t)}function r(e){let t=e.trim();return t.startsWith(`{`)||t.startsWith(`[`)?`json`:`yaml`}function i(e){return e.openapi.startsWith(`3.1`)?`3.1`:`3.0`}function a(e){return typeof e==`object`&&!!e&&`$ref`in e}function o(e,t){if(!t.startsWith(`#/`))return;let n=t.slice(2).split(`/`),r=e;for(let e of n){if(typeof r!=`object`||!r)return;r=r[e]}return r}function s(e,t){if(t)return a(t)?o(e,t.$ref)??t:t}function c(e,t){let n={path:[],query:[],header:[],cookie:[]};if(!t)return n;for(let r of t){let t;if(a(r)){let n=o(e,r.$ref);if(!n)continue;t=n}else t=r;let i={name:t.name,in:t.in,required:t.required??t.in===`path`,description:t.description,schema:t.schema,deprecated:t.deprecated??!1};n[t.in]?.push(i)}return n}function l(e,t){return e+t.split(`/`).filter(Boolean).map(e=>e.startsWith(`{`)&&e.endsWith(`}`)?`By`+e.slice(1,-1).charAt(0).toUpperCase()+e.slice(2,-1):e.charAt(0).toUpperCase()+e.slice(1)).join(``)}function u(e,t,n,r,i){let u=c(e,[...i??[],...r.parameters??[]]),d;if(r.requestBody){let t=a(r.requestBody)?o(e,r.requestBody.$ref):r.requestBody;if(t){let n=Object.keys(t.content??{})[0]??`application/json`,r=t.content?.[n];r?.schema&&(d={required:t.required??!1,schema:s(e,r.schema),contentType:n})}}let f={};for(let[t,n]of Object.entries(r.responses??{})){let r=a(n)?o(e,n.$ref):n;if(r){let n=Object.keys(r.content??{})[0],i=n?r.content?.[n]:void 0;f[t]={description:r.description,schema:i?.schema?s(e,i.schema):void 0,contentType:n}}}let p=r?.[`x-contractspec`];return{operationId:r.operationId??l(t,n),method:t,path:n,summary:r.summary,description:r.description,tags:r.tags??[],pathParams:u.path,queryParams:u.query,headerParams:u.header,cookieParams:u.cookie,requestBody:d,responses:f,deprecated:r.deprecated??!1,security:r.security,contractSpecMeta:p}}function d(e,n={}){let r=i(e),a=[],o=[];for(let[n,r]of Object.entries(e.paths??{})){if(!r)continue;let i=r.parameters;for(let s of t){let t=r[s];if(t)try{o.push(u(e,s,n,t,i))}catch(e){a.push(`Failed to parse ${s.toUpperCase()} ${n}: ${e}`)}}}let s={},c=e.components;if(c?.schemas)for(let[e,t]of Object.entries(c.schemas))s[e]=t;let l=(e.servers??[]).map(e=>({url:e.url,description:e.description,variables:e.variables}));return{document:e,version:r,info:{title:e.info.title,version:e.info.version,description:e.info.description},operations:o,schemas:s,servers:l,warnings:a}}async function f(e,t={}){let{fetch:i=globalThis.fetch,readFile:a,timeout:o=3e4}=t,s,c;if(e.startsWith(`http://`)||e.startsWith(`https://`)){let t=new AbortController,n=setTimeout(()=>t.abort(),o);try{let n=await i(e,{signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`);s=await n.text()}finally{clearTimeout(n)}c=e.endsWith(`.yaml`)||e.endsWith(`.yml`)?`yaml`:e.endsWith(`.json`)?`json`:r(s)}else{if(!a)throw Error(`readFile adapter required for file paths`);s=await a(e),c=e.endsWith(`.yaml`)||e.endsWith(`.yml`)?`yaml`:e.endsWith(`.json`)?`json`:r(s)}return d(n(s,c),t)}export{r as detectFormat,i as detectVersion,f as parseOpenApi,d as parseOpenApiDocument,n as parseOpenApiString};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{toCamelCase as e,toPascalCase as t,toValidIdentifier as n}from"../common/utils.js";const r={string:`ScalarTypeEnum.STRING`,integer:`ScalarTypeEnum.INT`,number:`ScalarTypeEnum.FLOAT`,boolean:`ScalarTypeEnum.BOOLEAN`,"string:date":`ScalarTypeEnum.DATE`,"string:date-time":`ScalarTypeEnum.DATE_TIME`,"string:email":`ScalarTypeEnum.EMAIL`,"string:uri":`ScalarTypeEnum.URL`,"string:uuid":`ScalarTypeEnum.ID`};function i(e){return`$ref`in e}function a(e){let t=e.split(`/`);return t[t.length-1]??`Unknown`}function o(e,n){if(i(e))return{type:t(a(e.$ref)),optional:!1,array:!1,primitive:!1};let r=e,s=r.type,c=r.format,l=r.nullable;if(s===`array`){let e=r.items;return e?{...o(e,n),array:!0,optional:l??!1}:{type:`unknown`,optional:l??!1,array:!0,primitive:!1}}return s===`object`||r.properties?{type:n?t(n):`Record<string, unknown>`,optional:l??!1,array:!1,primitive:!1}:r.enum?{type:n?t(n):`string`,optional:l??!1,array:!1,primitive:!1}:(c&&`${s}${c}`,s===`string`?{type:`string`,optional:l??!1,array:!1,primitive:!0}:s===`integer`||s===`number`?{type:`number`,optional:l??!1,array:!1,primitive:!0}:s===`boolean`?{type:`boolean`,optional:l??!1,array:!1,primitive:!0}:{type:`unknown`,optional:l??!1,array:!1,primitive:!1})}function s(e){if(i(e))return;let t=e,n=t.type,a=t.format;if(n)return r[a?`${n}:${a}`:n]??r[n]}function c(t,r,a){let c=o(t,r),l=s(t),u;if(!i(t)){let e=t.enum;e&&(u=e.map(String))}return{name:n(e(r)),type:{...c,optional:!a||c.optional,description:i(t)?void 0:t.description},scalarType:l,enumValues:u}}function l(e,r,o=0){let s=` `.repeat(o),l=[],d;if(i(e))return{name:t(a(e.$ref)),fields:[],code:`// Reference to ${e.$ref}`};let f=e;d=f.description;let p=f.properties,m=f.required??[];if(!p)return{name:t(r),description:d,fields:[],code:`${s}// Empty schema for ${r}`};for(let[e,t]of Object.entries(p)){let n=m.includes(e);l.push(c(t,e,n))}let h=[],g=t(n(r));h.push(`${s}export const ${g} = defineSchemaModel({`),h.push(`${s} name: '${g}',`),d&&h.push(`${s} description: ${JSON.stringify(d)},`),h.push(`${s} fields: {`);for(let e of l){let t=u(e,o+2);h.push(t)}return h.push(`${s} },`),h.push(`${s}});`),{name:g,description:d,fields:l,code:h.join(`
|
|
2
|
+
`)}}function u(e,t){let n=` `.repeat(t),r=[];return r.push(`${n}${e.name}: {`),e.enumValues?r.push(`${n} type: new EnumType([${e.enumValues.map(e=>`'${e}'`).join(`, `)}]),`):e.scalarType?r.push(`${n} type: ${e.scalarType},`):r.push(`${n} type: ${e.type.type}, // TODO: Define or import this type`),e.type.optional&&r.push(`${n} isOptional: true,`),e.type.array&&r.push(`${n} isArray: true,`),r.push(`${n}},`),r.join(`
|
|
3
|
+
`)}function d(e){let t=new Set;t.add(`import { defineSchemaModel, ScalarTypeEnum, EnumType } from '@lssm/lib.schema';`);for(let t of e)!t.type.primitive&&!t.enumValues&&t.scalarType;return Array.from(t).join(`
|
|
4
|
+
`)}export{d as generateImports,l as generateSchemaModelCode,s as getScalarType,c as jsonSchemaToField,o as jsonSchemaToType};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lssm/lib.contracts-transformers",
|
|
3
|
+
"version": "0.0.0-canary-20251217023603",
|
|
4
|
+
"description": "Contract format transformations: import/export between ContractSpec and external formats (OpenAPI, AsyncAPI, etc.)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
|
|
8
|
+
"build": "bun build:bundle && bun build:types",
|
|
9
|
+
"build:bundle": "tsdown",
|
|
10
|
+
"build:types": "tsc --noEmit",
|
|
11
|
+
"dev": "bun build:bundle --watch",
|
|
12
|
+
"clean": "rimraf dist .turbo",
|
|
13
|
+
"lint": "bun lint:fix",
|
|
14
|
+
"lint:fix": "eslint src --fix",
|
|
15
|
+
"lint:check": "eslint src",
|
|
16
|
+
"test": "bun test"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@lssm/lib.contracts": "workspace:*",
|
|
20
|
+
"@lssm/lib.schema": "workspace:*",
|
|
21
|
+
"openapi-types": "^12.1.3",
|
|
22
|
+
"yaml": "^2.7.1",
|
|
23
|
+
"zod": "^4.1.13"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@lssm/tool.tsdown": "workspace:*",
|
|
27
|
+
"@lssm/tool.typescript": "workspace:*",
|
|
28
|
+
"tsdown": "^0.17.4",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"module": "./dist/index.js",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": "./src/index.ts",
|
|
40
|
+
"./common": "./src/common/index.ts",
|
|
41
|
+
"./openapi": "./src/openapi/index.ts",
|
|
42
|
+
"./*": "./*"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"exports": {
|
|
47
|
+
".": "./dist/index.js",
|
|
48
|
+
"./common": "./dist/common/index.js",
|
|
49
|
+
"./openapi": "./dist/openapi/index.js",
|
|
50
|
+
"./*": "./*"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|