mrjono1 / joi-to-typescript Goto Github PK
View Code? Open in Web Editor NEWConvert Joi Schemas to TypeScript interfaces
License: MIT License
Convert Joi Schemas to TypeScript interfaces
License: MIT License
Ran into an issue using Joi.override with AnySchema..valid()
demoSchema.ts
import Joi from "joi";
export default Joi.object({
// OK:
// foo: Joi.string().valid("bar").valid("baz")
// OK:
// foo: Joi.string().valid("bar").valid("baz", "qux")
// NOT OK:
foo: Joi.string().valid("bar").valid(Joi.override, "baz", "qux")
// TypeError: value.replace is not a function
// at toStringLiteral (\node_modules\joi-to-typescript\dist\main\utils.js:26:22)
})
.meta({ className: "demo" })
Test code used to confirm that the joi is valid:
testDemo.ts
import demo from "./demoSchema"
// confirm that joi is valid:
// yarn ts-node ./test/sandbox/testDemo.ts
{
// should err
const { value, warning, error } = demo.validate({ foo: "bar" })
if (!error) throw new Error("should have errored")
} {
// should not err
const { value, warning, error } = demo.validate({ foo: "baz" })
if (error) throw new Error("should not have errored")
}
console.log("done")
We have some custom types we're using (Mainly to control the value format in a consolidated place)
An example:
export const Joi = Joi.extend(joi => {
return {
type: 'time',
base: joi.string().pattern(/^\d{2}:\d{2}:\d{2}-\d{4}$/),
};
})
const UserSchema = Joi.object({
name: Joi.string().required(),
timeSubmitted: Joi.time().required()
})
But it's getting generated as this:
interface UserSchema {
name: string;
time: unknown;
}
Is there a way to generate time
as a string since that's its base type?
When exporting a labeled Joi schema that results in a primitive TypeScript type, joi-to-typescript
generates an invalid output. This pattern is very useful when you want to reuse Joi schemas (e.g. an EmailSchema
can be used by both UserSchema
and CompanySchema
).
export const EmailSchema = Joi.string()
.email({ tlds: { allow: false } })
.label('Email')
export const CompanySchema = Joi.object({
email: EmailSchema
})
export const UserSchema = Joi.object({
email: EmailSchema.required()
})
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export type Email = string
export type Company = {
email?: Email
}
export type User = {
email: Email
}
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
string // Here is the issue, should be (type Email = string)
export type Company = {
email?: Email
}
export type User = {
email: Email
}
With this schema:
export const RegisterAccountParams = Joi.object({
name: Joi.string()
.label("Company Name")
.required(),
phoneNumber: Joi.string()
.label("Company Phone Number")
.required(),
customerEmail: Joi.string()
.label("Customer Email")
.required(),
website: Joi.string()
.label("Website")
});
this output is generated:
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
import { Customer Email, Company Name, Company Phone Number, Website } from '.';
export interface RegisterAccountParams {
customerEmail: Customer Email;
name: Company Name;
phoneNumber: Company Phone Number;
website?: Website;
}
The import statement is clearly incorrect. It seems to me that joi-to-typescript takes label
to mean that we're referencing another named exported schema, which is not our intention.
Labels are handy for customizing the error messages that Joi throws on validation failures. Is there a way that this behaviour might be made configurable, or perhaps default to ignoring label
modifiers on object properties?
To be clear, we expect an output similar to this one:
export interface RegisterAccountParams {
customerEmail: string;
name: string;
phoneNumber: string;
website?: string;
}
I noticed an unfortunate behavior when extending renamed schemas. Here is a small example.
Source schemas:
export const BaseSchemaBar = Joi.object({
bar: Joi.number().required()
}).meta({ className: 'BaseSchemaDTO' });
export const BaseExtensionSchema = Joi.object({
baz: BaseSchemaBar.keys({
foo: Joi.boolean().required()
})
}).meta({ className: 'BaseExtensionDTO' });
Resulted interfaces:
export interface BaseSchemaDTO {
bar: number;
}
export interface BaseExtensionDTO {
baz: BaseSchemaDTO;
}
As you can see, foo
property is missing.
Source schemas:
export const BaseSchemaBar = Joi.object({
bar: Joi.number().required()
});
export const BaseExtensionSchema = Joi.object({
baz: BaseSchemaBar.keys({
foo: Joi.boolean().required()
})
});
Resulted interfaces:
export interface BaseSchemaBar {
bar: number;
}
export interface BaseExtensionSchema {
baz: {
bar: number;
foo: boolean;
};
}
Here all fields are fine.
Unit test files should be excluded from interfaces generation (ideally, by default in a future major version).
To avoid listing all of them, what about adding file patterns support to excludeFiles
option (e.g. ['**/*.test.ts
', '**/*.spec.ts']) or adding a separate
excludeFilesPatterns` option to support patterns or regexp ?
Also, a similar includeFiles
option (matching patterns) would be useful!
This way Ts wouldn't error if they still try to use a valid joi object
Nice module, though I was facing an issue while using the function type.
So while converting joi schema push: j.function().required()
to typescript type using this module leads to
push?: (...args: any[]) => any;
whereas the correct output should be push: (...args: any[]) => any;
at the moment the project skips sub directories
You can wrap your tests in a describe("suite that tests basic stuff", () => {})
which will group things together. Not sure if this makes your 01, 02, etc numbering better
Add an option to clear the typeOutputDirectory
to ensure there is no junk files
According to Joi documentation using Joi.string().allow('') is valid and a type of string | ''
would be expected.
Instead a type of ''
is generated which is incorrect and does not allow both valid strings and empty strings to be used in the type
It would be nice if this tool had a simple CLI interface such as
$ npx joi-to-typescript --schema-dir ./schemas --output-dir ./types --sort-by-name
I use this great tool in many projects, and I usually end up wrapping it in a simple file which simply calls convertFromDirectory()
with my directories as parameters, and adding that wrapper file seems a bit redundant really.
Would a PR for this be of interest? If so, any preference on CLI helpers - meow? Any thoughts on syntax? To me, the most relevant usage is to convert a directory of schema files and store the generated files in another directory, as per the example above. And options should be fairly straight-forward. Would this be sufficient or is there a need for e.g. converting a single schema file etc?
Picked up the latest, and the output to console is tons of JSON. I made sure debug: false, but still all the output is in JSON.
{"type":"string","flags":{"description":"end segment of url for this page","presence":"required"},"rules":[{"name":"min","args":{"limit":1}},{"name":"max","args":{"limit":100}},{"name":"case","args":{"direction":"lower"}},{"name":"pattern","args":{"regex":"/^[a-z0-9]+(?:-[a-z0-9]+)*$/"}}],"metas":[{"className":"SlugValue"}]}
{"type":"string","rules":[{"name":"uri"}]}
{"type":"string","rules":[{"name":"uri"}],"metas":[{"className":"ImageValue"}]}
{"type":"string","rules":[{"name":"uri"}],"metas":[{"className":"ImageValue"}]}
{"type":"date","flags":{"format":"javascript"}}
{"type":"date","flags":{"format":"javascript"},"metas":[{"className":"DateValue"}]}
{"type":"date","flags":{"format":"javascript"},"metas":[{"className":"DateValue"}]}
{"type":"string"}
{"type":"string","metas":[{"className":"NoValueRecord"}]}
Consder the following code:
export const ExampleSchema = Joi.object({
a: Joi.allow(1).required(),
}).meta({ className: 'Example' });`
// This generates what I would expect on the TS interface side:
export interface Example {
a: 1;
}
However when I try for only a null value it works differently:
export const ExampleSchema = Joi.object({
a: Joi.allow(null).required(),
}).meta({ className: 'Example' });`
// generates
export interface Example {
a: any | null;
}
Is this a bug or intended behavior? If it is intended is there a way I can define a property on a schema to always expect to be null?
When Forbidden and/or Strip is used, the resulting typescript model should not have the entry defined in its model.
export const examplePOSTBodySchema = baseSchema
.fork(['id'], (x) => x.forbidden().strip(true))
.meta({className: 'examplePOSTBody'});
Here I am forking a base schema and trying to remove the id parameter. Unfortunately Forbidden is not supported so id is still output in the resulting model.
Hey @mrjono1, really love the work you do on this project!
I'm just asking is there a way to add a option in the command to make the generated interfaces have readonly
attributes? I'd say this is a good thing because enforcing immutability is a good thing in some cases. Probably an option like generateReadonlyAttributes: true
?
Example of this could be:
const userSchema = joi.object({
name: joi.string().required(),
}).meta({ className: 'User' });
// Generated interface
interface User {
readonly name: string;
}
Hi,
I'm not sure if you guys are aware of this issue/bug.
Joi:
name: Joi.string().allow(null).required
TypeScript:
name: string | null
This result is correct. However, when I use it with the array type, it doesn't generate | null
Joi:
imageUrls: Joi.array().items(Joi.string()).allow(null).required()
TypeScript:
imageUrls: string[]
It's supposed to be imageUrls: string[] | null
The same issue with object.
When 2 schema files have same name in different directories, only the first one generates an interface file.
example :
- schemas
- folder1
- mySchema.ts
- mySchema2.ts
- folder2
- mySchema.ts
- mySchema3.ts
generates the following interfaces :
- interfaces
- folder1
- mySchema.ts
- mySchema2.ts
- folder2
- mySchema3.ts
=> interfaces/folder2/mySchema.ts
is missing.
Hi there, first of all thanks for a great job so far!
The library works fine with a one directory that have only one directory with joi schemas
But it is not really comfortable otherwise, especially if you have dozen of joi schemas and complex structure in your project
What about breaking major changes in API? why not to use glob
in search joi schema files?
const defaultOutputFile = (srcPath: string, fileName: string) => path.join(
srcPath,
`${path.basename(fileName, '.ts')}.type.ts`
)
export function convertFromDirectory({
schemaFile,
outputFile = defaultOutputFile,
// ...
}{
schemaFile: string | string[];
outputFile?: (scrPath: string, fileName: string) => string
// ...
}) {
// ... implementation
}
Benefits
The usage is more flexible and fits more use cases.
Below old options can be dropped from API because it could be done by this new realisation
schemaFileSuffix
convertFromDirectory({
schemaFile: './src/**/*.schema.ts',
outputFile: (srcPath: string, fileName: string) => path.join(srcPath, fileName.replace('.schema', '')),
}})
flattenTree
convertFromDirectory({
schemaFile: './src/**/*.schema.ts',
outputFile: (srcPath: string, fileName: string) => path.join('./src/types', fileName),
}})
rootDirectoryOnly
convertFromDirectory({
schemaFile: './src/*.schema.ts',
}})
ignoreFiles
can be be done by glob in a variety of ways out of boxCaveats
I am not sure about indexAllToRoot
โ it could be hard to implement
What do you think? Will you accept such PR?
For version 2.0.1 when using a Joi schema that is created via .concat()
and then adding a new className via .meta()
the resulting type that is generated does not use the new className.
As an example:
Schema:
import Joi from 'joi';
export const A = Joi.object({
a: Joi.string()
}).meta({ className: 'A' });
export const B = Joi.object({
b: Joi.string()
}).meta({ className: 'B' });
export const AB = A
.concat(B)
.meta({ className: 'AB' });
What is generated:
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export interface A {
a?: string;
b?: string;
}
export interface A {
a?: string;
}
export interface B {
b?: string;
}
What is expected:
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
export interface A {
a?: string;
}
export interface B {
b?: string;
}
export interface AB {
a?: string;
b?: string;
}
I found an issue, which I think is related to #53
Starting Schema - schema/Base.ts
export const InteractionTypeSchema = Joi.string().valid("CREATE", "DELETE", "UPDATE")
.meta({ className: 'InteractionType' });
export const InteractionSchema = Joi.object({
companyId: Joi.string().required(),
userId: Joi.string().required(),
appVersion: Joi.string().required(),
interactionType: InteractionTypeSchema,
})
.meta({ className: 'Interaction' });
Generates - interfaces/Base.ts
export interface Interaction {
appVersion: string;
companyId: string;
interactionType?: InteractionType | 'CREATE' | 'DELETE' | 'UPDATE';
userId: string;
}
export type InteractionType = 'CREATE' | 'DELETE' | 'UPDATE';
I think interactionType should only reference InteractionType and not CREATE, DELETE, UPDATE
OS: Linux pop-os 5.19.0-76051900-generic
Node: v16.17.0
Joi: 17.6.0
Joi-to-typescript: 4.0.5
Hi there,
I have the following schema which is intended to require only one of the keys described in .xor()
, throwing an error if neither one or both are present in the object. Additionally, if the key provided was the one suffixed with "_FILE", it should be renamed to remove that suffix.
const ConfigSchema = Joi.object({
JWT_SECRET: Joi.string(),
JWT_SECRET_FILE: Joi.string(),
MONGODB_NAME: Joi.string(),
MONGODB_NAME_FILE: Joi.string()
})
.xor('JWT_SECRET_FILE', 'JWT_SECRET')
.xor('MONGODB_NAME_FILE', 'MONGODB_NAME')
.rename('JWT_SECRET_FILE', 'JWT_SECRET')
.rename('MONGODB_NAME_FILE', 'MONGODB_NAME')
This works fine at runtime, and I can write a TypeScript interface to work with the validated schema exactly as I intend to through the rest of my code:
export interface IConfig {
JWT_SECRET: string;
MONGODB_NAME: string;
}
However, when I use joi-to-typescript the resulting interface contains the properties that I need to remove as well:
export interface IConfig {
JWT_SECRET: string;
JWT_SECRET_FILE: string;
MONGODB_NAME: string;
MONGODB_NAME_FILE: string;
}
Is a missing feature of joi-to-typescript? Or perhaps I'm not using Joi's .xor()
and .rename()
methods properly (although they seem to work fine)? For reference, I have setup an npm script to run joi-to-typescript as follows (as I do not have ts-node installed globally):
"scripts": {
"types": "node_modules/.bin/ts-node src/schemas/types.ts"
}
// src/schemas/types.ts
import { convertFromDirectory } from 'joi-to-typescript';
convertFromDirectory({
schemaDirectory: './src/schemas',
typeOutputDirectory: './src/interfaces',
defaultToRequired: true,
ignoreFiles: ['types.ts'],
debug: true
});
the dist/module and dist/main files were not published to NPM
When using convertFromDirectory
, if the schema directory contains an index.ts
file this results in the corresponding generated index.ts
containing the TypeScript types defined directly inline rather than exporting via their generated files.
For example, without an index.ts
in the schema directory, the generated index.ts
correctly looks something like:
export * from './Artist';
export * from './ArtistList';
export * from './NewArtist';
However, if an index.ts
exists in the schema directory, the generated index.ts
becomes:
export interface Artist {
...
}
export interface ArtistList {
...
}
export interface NewArtist {
...
}
The generation of index.ts
should be the same regardless of the presence of a schema index.ts
Hello, wondering what's happening here with the [object Object] output:
demoSchema.ts
import Joi from "joi";
const foo = Joi.object({ bar: "baz" })
export const demoSchema = Joi.object({ foo })
.meta({ className: "demo" })
emitted demo.ts
export interface demo {
foo?: {
bar?: [object Object] | 'baz';
};
}
Allow Labels to contain spaces and other characters but auto remove them
I would like to have an index.ts
in the schemaDirectory
that exports all Joi schemas without joi-to-typescript
trying to convert it.
The reason for this is because an index.ts
allows me to easily import the Joi schemas elsewhere in the code base without the need for one import statement per schema.
Describe the bug
After updating from version 1.10.0 to 1.10.1 you get the following error :
`Running joi-to-typescript...
(node:3844) UnhandledPromiseRejectionWarning: Error: Cannot find module 'src/schemas/index.ts'
Require stack:
--unhandled-rejections=strict
(see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)To Reproduce
Run:
import { convertFromDirectory } from "joi-to-typescript";
const types = async (): Promise<void> => {
console.log("Running joi-to-typescript...");
// Configure your settings here
const result = await convertFromDirectory({
schemaDirectory: "./src/schemas",
typeOutputDirectory: "./src/interfaces",
debug: true,
defaultToRequired: true,
indexAllToRoot: true,
});
if (result) {
console.log("Completed joi-to-typescript");
} else {
console.log("Failed to run joi-to-typescript");
}
};
types();
with version 1.10.1
Reverting back to the previous version solves the problem
I think we could support simple use cases of when like
Joi.object({
type: Joi.boolean().valid(true, false),
trueField: Joi.string().when('type', {
is: true,
then: Joi.required(),
otherwise: Joi.forbidden()
}),
falseField: Joi.string().when('type', {
is: false,
then: Joi.required(),
otherwise: Joi.forbidden()
})
})
this would support an object like { type: false, falseField: 'aStr' }
and { type: true, trueField: 'aStr' }
and would error on something like { type: false, trueField: 'shouldNotWork' }
Right now the lib would make a type like
{type: true | false; trueField: string; falseField: string}
which is correct but could be better
Under the hood joi uses references to figure out what field to look at and generate a good validator. There can be a lot of runtime stuff references can use but i think simple sibling key references could be supported. Handling a reference to a grand parent object is probably too much at the moment.
I think if we support a simple case like this then fall back to what we do now if the reference/is condition is too complicated would be nice.
I'm pretty sure that Joi should be a peer dependency instead of a normal dependency.
I haven't had a use case like this before so I will investigate if this is the case I may do a major version bump for this change.
A low priority task is to stop the mutation of appSettings
/Settings
and instead pass the mutated values as parameters
When generating types from the following schemas the nested object is not correctly referenced.
import Joi from 'joi';
export const BarSchema = Joi.object({
id: Joi.number().required().description('Id').example(1)
}).label('Bar');
export const FooSchema = Joi.object({
id: Joi.number().required().description('Id').example(1),
bar: BarSchema.required().description('Bar')
}).label('Foo');
Results in the below, with object being used instead of Bar.
/**
* This file was automatically generated by joi-to-typescript
* Do not modify this file manually
*/
import { object } from '.';
/**
* Bar
*/
export interface Bar {
/**
* id
* Id
*/
id: number;
}
/**
* Foo
*/
export interface Foo {
/**
* bar
* Bar
*/
bar: object;
/**
* id
* Id
*/
id: number;
}
When generating a schema with a Joi.number()
that has valid()
or allow()
the resulting type is not a number but assumed the valid values as the types of the field.
To Reproduce
export const TestSchema = Joi.object({
'clean': Joi.number(),
'valid': Joi.number().valid(0, 1),
'allow': Joi.number().allow(2, 3)
}).label('Test');
Expected behavior
export interface Test {
allow?: number;
clean?: number;
valid?: number;
}
Actual behavior
export interface Test {
allow?: 2 | 3;
clean?: number;
valid?: 0 | 1;
}
Hey,
overall a very nice package!
I noticed an issue when using enums:
import Joi from "joi";
export const roleSchema = Joi.string()
.valid("Admin", "User")
.label("Role");
export const userSchema = Joi.object({
firstName: Joi.string().required(),
lastName: Joi.string().required(),
role: roleSchema.required(),
}).label("User");
This will generate the following output:
export type Role = 'Admin' | 'User';
export interface User {
firstName: string;
lastName: string;
role: Role | 'Admin' | 'User';
}
However role: Role
would be enough and and more readable ;)
Regards
Hi!
I just realised that if you extend a schema, e.g.
const WorkspaceSchema = Joi.object({
statistics:Joi.object({
views: Joi.number()
})
}).label('IWorkspace')
const ProjectSchema = WorkspaceSchema.keys({
statistics:Joi.object({
users:Joi.number()
})
}).label('IProject')
You end up with two interfaces:
export interface IWorkspace {
statistics: {
views: number;
};
}
and
export interface IProject {
statistics: {
users: number
}
}
Wouldn't it make more sense to extend IProject with IWorkspace?
export interface IProject extends IWorkspace {
statistics: {
users: number
}
}
Create an example script to run this package from the cli
I have a Readme.md file in a directory with schemas and it attempted to parse the then crashed.
expected result is only parsing *.ts
files
I haven't used .label() to defined the interface name. I would like this package to use the variable name and chop the word schema off it like it does for the file names
To Reproduce
export const UserSchema = Joi.object({});
Expected behavior
export interface User {}
Actual behavior
export interface UserSchema {}
Schema:
const schema = Joi.object()
.label('SignUp')
.keys({
password: Joi.string()
.required()
.description('The password of the authenticating user')
.example('test-PASSWORD123'),
repeatPassword: Joi.string()
.required()
.allow(Joi.ref('password'))
.description('Repeat the password to ensure no typos')
.example('test-PASSWORD123')
});
Result:
joi-to-typescript/dist/main/utils.js:26
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
^
TypeError: value.replace is not a function
at toStringLiteral (joi-to-typescript/dist/main/utils.js:26:22)
at joi-to-typescript/dist/main/parse.js:341:92
at Array.map (<anonymous>)
at parseStringSchema (joi-to-typescript/dist/main/parse.js:339:42)
at parseHelper (joi-to-typescript/dist/main/parse.js:189:20)
at parseSchema (joi-to-typescript/dist/main/parse.js:265:26)
at joi-to-typescript/dist/main/parse.js:423:30
at joi-to-typescript/dist/main/utils.js:13:27
at Array.reduce (<anonymous>)
at filterMap (joi-to-typescript/dist/main/utils.js:12:17)
Shape returned at this line:
{ ref: { path: [ 'password' ] } }
Is it possible for me to just skip all processing on this field using a meta flag or something?
I want to be able to build from schemas in another package (separate from the package where the destination interfaces are built and written to). Currently this doesn't seem to be possible as the other package has "type": "module"
in its' package.json
file.
The resulting error is thus:
(node:39837) UnhandledPromiseRejectionWarning: Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /Users/some-other-package/src/schemas/OneSchema.ts
require() of ES modules is not supported.
require() of /Users/some-other-package/src/schemas/OneSchema.ts from /Users/my-types-package/node_modules/joi-to-typescript/dist/analyseSchemaFile.js is an ES module file as it is a .ts file whose nearest parent package.json contains "type": "module" which defines all .ts files in that package scope as ES modules.
Instead change the requiring code to use import(), or remove "type": "module" from /Users/some-other-package/package.json.
Removing "type": "module"
from said package does indeed squash the error, but that is sub-optimal as this package is required by others and should be an ES module package.
Is there any way of resolving this?
My build code is basically the same as your example, except I've plonked your source schemas into the "other" package:
const result = await convertFromDirectory({
schemaDirectory: '../some-other-package/src/schemas',
typeOutputDirectory: './lib/built',
debug: true
})
Thanks in advance for any help.
The joi schema:
const ChainInfo = Joi.object({
id: Joi.string().optional(),
host: Joi.string()
.uri({ scheme: ['http', 'https'] })
.required()
.allow('none'),
})
.options(options)
.meta({ unknownType: 'string', className: 'ChainInfoType' });
The generated type:
export interface ChainInfoType {
host: 'none';
id?: string;
}
Expected type:
export interface ChainInfoType {
host: 'string';
id?: string;
}
Hello @mrjono1, Thanks again for contributing joi-to-typescript to the community.
Do you have documentation about the generation flows and data structures that contributors can learn from when they work on a contribution? I saw in the readme the contribution section but it doesn't deal with documentations.
Thanks
Eran
When creating a dictionary that has a more complex type than primitives, for example another schema, how can I correctly add that type reference?
For example, my simplified case below:
export const Item = Joi.object({ id: Joi.string().required() })
export const Items = Joi.object().unknown(true).meta({ unknownType: 'Array<Item>' })
Items
is a dictionary where each entry is list of Item
. This works fine for the generated file for these types, since Item
is after all defined there. But if I use Items
in another schema, Array<Item>
will be wrong since the import will be missing (or a copy of the type itself).
Thanks for the great project! I've noticed that in all cases where this library writes the type any
, the type unknown
would be preferred. Was this intentional, or would you be open to a PR that changed this behavior? Here is a solid explanation of the difference.
I'm trying to set this up for a project I'm working on, and it's working great, minus that it seems like the indexAllToRoot
option is non-functional, no matter what I set it to, it outputs an index.ts
file in the interfaces route.
Project Structure
makefile
generateInterfaces.ts
src:
schemas:
config.ts
types:
What I want to be generated:
makefile
generateInterfaces.ts
src:
schemas:
config.ts
types:
config.ts
Versus what actually gets generated:
makefile
generateInterfaces.ts
src:
schemas:
config.ts
types:
config.ts
index.ts
This isn't really blocking my project because I just added an additional command to my makefile
to delete the index.ts
for now, however it would be nice if the option did what it was claiming to do? (or maybe had a better description if I'm misunderstanding what it does)
Files:
// generateInterfaces.ts
import { convertFromDirectory } from "joi-to-typescript";
convertFromDirectory({
indentationChacters: `\t`,
schemaDirectory: "./src/schemas",
typeOutputDirectory: "./src/types",
treatDefaultedOptionalAsRequired: true,
indexAllToRoot: false,
fileHeader: `/*
This file was automatically generated by joi-to-typescript
Do not modify this file manually
*/`
});
// src/schemas/config.ts
import Joi from "joi";
export const serverOptionsSchema = Joi.object({
port: Joi.number().port().description(`The port the server will run on`),
}).description(`The options specific to the web server`);
export const configSchema = Joi.object({
server: serverOptionsSchema,
}).description(`The configuration format for the server`);
# makefile
NODE = node
PACKAGE_MANAGER = pnpm
OUT_DIR = dist
BIN := $(shell $(PACKAGE_MANAGER) bin)
.PHONY: $(OUT_DIR) dev prod run
$(OUT_DIR): interfaces
tsc --outDir=$(OUT_DIR)
interfaces:
$(BIN)/ts-node generateInterfaces.ts
rm src/types/index.ts
Environment information:
If there's any extra information required let me know and I'll happily provide it.
I recently needed to add type:"module"
to my package.json
and setup my modules: "ESNext"
. This has caused joi-to-typescript to break with the following error.
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /scripts/makeTypes.ts
at new NodeError (node:internal/errors:372:5)
at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:56:11)
at defaultGetFormat (node:internal/modules/esm/get_format:83:38)
at defaultLoad (node:internal/modules/esm/load:21:20)
at ESMLoader.load (node:internal/modules/esm/loader:407:26)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:326:22)
at new ModuleJob (node:internal/modules/esm/module_job:66:26)
at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:345:17)
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:304:34)
at async Promise.all (index 0) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
Now, if I add this to my tsconfig.json:
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
}
},
and remove type: "module"
from package.json
While I can clearly remove / readd the type module to my package.json, I was wondering if there is a build target fix that would resolve this... e.g. not commonjs module.
Any thoughts?
Here is the issue that appears I run the script:
npm run types
Running joi-to-typescript...
TypeError: parsedSchema.children.flatMap is not a function
at Object.getAllCustomTypes (/Users/josephkhan/htdocs/new-preparations/joi-to-typescript/example/node_modules/joi-to-typescript/dist/parse.js:27:38)
at Object.convertSchema (/Users/josephkhan/htdocs/new-preparations/joi-to-typescript/example/node_modules/joi-to-typescript/dist/index.js:54:37)
at Object.analyseSchemaFile (/Users/josephkhan/htdocs/new-preparations/joi-to-typescript/example/node_modules/joi-to-typescript/dist/analyseSchemaFile.js:29:39)
at process._tickCallback (internal/process/next_tick.js:68:7)
at Function.Module.runMain (internal/modules/cjs/loader.js:832:11)
at main (/Users/josephkhan/htdocs/new-preparations/joi-to-typescript/example/node_modules/ts-node/src/bin.ts:198:14)
at Object.<anonymous> (/Users/josephkhan/htdocs/new-preparations/joi-to-typescript/example/node_modules/ts-node/src/bin.ts:288:3)
at Module._compile (internal/modules/cjs/loader.js:776:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
Is there something I am missing?
I'm not sure if anyone has a use case for this but I'll put this idea here in case anyone is interested
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.