Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 167 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,167 @@
# @duplojs/data-parser-tools
[![NPM version](https://img.shields.io/npm/v/@duplojs/data-parser-tools)](https://www.npmjs.com/package/@duplojs/data-parser-tools)
<a name="top"></a>

<p align="center">
<img src="https://utils.duplojs.dev/images/logo.png" alt="logo" width="250px" />
</p>
<p align="center">
<span style="font-size: 24px; font-weight: bold;">DataParser Tools</span>
</p>
<p align="center">
<a href='#'>
<img src='https://img.shields.io/badge/types-TypeScript-blue?logo=typescript&style=plastic' alt='coverage' />
</a>
<a href="#">
<img src="https://img.shields.io/badge/coverage-100%25-green?style=plastic" alt="lang">
</a>
<a href="https://www.npmjs.com/package/@duplojs/data-parser-tools">
<img src="https://img.shields.io/npm/v/@duplojs/data-parser-tools" alt="lang">
</a>
</p>

`@duplojs/data-parser-tools` is a library that convert `dataParser` schema to choice format (typescript, jsonSchema)

## Installation

To consume `@duplojs/data-parser-tools`, you need to install the npm package and zod.
```bash
npm install @duplojs/data-parser-tools@0 @duplojs/utils@1 @duplojs/server-utils@0
```

## Usage

The library exposes two converters:
- `@duplojs/data-parser-tools/toTypescript`
- `@duplojs/data-parser-tools/toJsonSchema`

### 1) Generate a TypeScript type with `render`

```ts
import { DPE } from "@duplojs/utils";
import { render, defaultTransformers } from "@duplojs/data-parser-tools/toTypescript";

const userSchema = DPE.object({
id: DPE.number(),
name: DPE.string(),
}).addIdentifier("User");

const tsType = render(userSchema, {
identifier: "User",
mode: "out",
transformers: defaultTransformers,
});

console.log(tsType);
// export type User = { id: number; name: string; };
```

`identifier` is the final exported name.
`mode` can be:
- `"out"`: output format (strict)
- `"in"`: input format (includes accepted input variants, for example date/time)

### 2) Generate a JSON Schema

```ts
import { DPE } from "@duplojs/utils";
import { render, defaultTransformers } from "@duplojs/data-parser-tools/toJsonSchema";

const userSchema = DPE.object({
id: DPE.number(),
name: DPE.string(),
}).addIdentifier("User");

const jsonSchema = render(userSchema, {
identifier: "User",
mode: "out",
transformers: defaultTransformers,
version: "jsonSchema7", // jsonSchema4 | jsonSchema7 | jsonSchema202012 | openApi3 | openApi31
});

console.log(jsonSchema.$ref); // "#/definitions/User"
```

### 3) Add an identifier to a schema (`addIdentifier`)

`addIdentifier` clones the schema and attaches an internal reusable name during rendering.

```ts
const base = DPE.object({ value: DPE.string() });
const named = base.addIdentifier("MyNamedSchema");
```

If the name passed to `render({ identifier })` differs from the schema identifier, an alias is generated (for example: `export type PublicName = MyNamedSchema;`).

### 4) Use hooks

Hooks let you intercept/replace a schema before transformation.
- `output("next", schema)`: continue the hook chain
- `output("stop", schema)`: stop the chain and transform this schema

```ts
import { DPE } from "@duplojs/utils";
import { render, defaultTransformers, type TransformerHook } from "@duplojs/data-parser-tools/toTypescript";

const forceStringHook: TransformerHook = ({ output }) => output("stop", DPE.string());

const result = render(DPE.number(), {
identifier: "HookExample",
mode: "out",
transformers: defaultTransformers,
hooks: [forceStringHook],
});

console.log(result);
// export type HookExample = string;
```

### 5) Recursive schemas

Recursive references are supported through `DPE.lazy(...)`.

```ts
import { DPE } from "@duplojs/utils";
import { render, defaultTransformers } from "@duplojs/data-parser-tools/toTypescript";

type Node = { children: Node[] };

const nodeSchema: DPE.Contract<Node> = DPE.object({
children: DPE.array(DPE.lazy(() => nodeSchema)),
}).addIdentifier("Node");

const result = render(nodeSchema, {
identifier: "Node",
mode: "out",
transformers: defaultTransformers,
});
```

### 6) Custom types: `date`, `time`, `file`

```ts
import { DPE } from "@duplojs/utils";
import { SDP } from "@duplojs/server-utils";
import { render, defaultTransformers } from "@duplojs/data-parser-tools/toTypescript";

const schema = DPE.object({
createdAt: DPE.date(),
startAt: DPE.time(),
avatar: SDP.file(),
});

const outType = render(schema, {
identifier: "PayloadOut",
mode: "out",
transformers: defaultTransformers,
});

const inType = render(schema, {
identifier: "PayloadIn",
mode: "in",
transformers: defaultTransformers,
});
```

In practice:
- `date` / `time` in `"out"` produce template-literals (`date...`, `time...`)
- `date` / `time` in `"in"` also accept additional input variants
- `file` maps to `FileInterface` (imported from `@duplojs/server-utils/file`)
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ exports[`integration 1`] = `
"createdAt": {
"anyOf": [
{
"format": "date-time",
"pattern": "^date-?(?<value>\\d{1,16})(?<sign>[+-])$",
"type": "string",
},
Expand Down
8 changes: 5 additions & 3 deletions integration/toTypescript/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`integration 1`] = `
"export type UserProfile = {
"import type { TheDate, TheTime } from "@duplojs/utils/date";

export type UserProfile = {
id: \`user-\${number}-db1\`;
name: string;
email: string;
Expand Down Expand Up @@ -29,7 +31,7 @@ exports[`integration 1`] = `
number,
number
];
createdAt: \`date\${number}\${"-" | "+"}\`;
startAt: \`time\${number}\${"-" | "+"}\`;
createdAt: TheDate;
startAt: TheTime;
};"
`;
26 changes: 22 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
"vitest": "3.2.4"
},
"peerDependencies": {
"@duplojs/utils": ">=1.4.36 <2.0.0"
"@duplojs/utils": ">=1.5.1 <2.0.0",
"@duplojs/server-utils": ">=0.1.4 < 1.0.0"
},
"dependencies": {
"typescript": "5.9.2"
Expand Down
36 changes: 18 additions & 18 deletions scripts/toJsonSchema/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {
type TransformerMode,
type TransformerHook,
type createTransformer,
type SupportedVersions,
type MapperSupportedVersions,
type JsonSchema,
supportedVersions,
buildRef,
type SupportedVersions,
} from "./transformer";
import { createToJsonSchemaKind } from "./kind";
import { getRecursiveDataParser } from "@scripts/utils/getRecursiveDataParser";
Expand All @@ -29,7 +30,7 @@ export class DataParserToJsonSchemaRenderError extends kindHeritage(
}

export interface RenderParams<
GenericVersion extends unknown,
GenericVersion extends SupportedVersions,
> {
readonly identifier: string;
readonly transformers: readonly ReturnType<typeof createTransformer>[];
Expand All @@ -40,14 +41,14 @@ export interface RenderParams<
}

type RenderResult<
GenericVersion extends keyof SupportedVersions,
GenericVersion extends SupportedVersions,
> = Or<[
IsEqual<GenericVersion, "openApi3">,
IsEqual<GenericVersion, "openApi31">,
]> extends true
? {
$ref: `#/components/schemas/${string}`;
openapi: SupportedVersions[GenericVersion];
openapi: MapperSupportedVersions[GenericVersion];
components: {
schemas: Record<string, JsonSchema>;
};
Expand All @@ -58,25 +59,24 @@ type RenderResult<
]> extends true
? {
$ref: `#/$defs/${string}`;
$schema: SupportedVersions[GenericVersion];
$schema: MapperSupportedVersions[GenericVersion];
definitions: Record<string, JsonSchema>;
}
: IsEqual<GenericVersion, "jsonSchema202012"> extends true
? {
$ref: `#/definitions/${string}`;
$schema: SupportedVersions[GenericVersion];
$schema: MapperSupportedVersions[GenericVersion];
$defs: Record<string, JsonSchema>;
}
: never;

export function render<
GenericVersion extends keyof SupportedVersions,
GenericVersion extends SupportedVersions,
>(
schema: DP.DataParsers,
params: RenderParams<GenericVersion>,
): RenderResult<GenericVersion> {
const context: MapContext = new Map(params.context);
const version = supportedVersions[params.version];

const result = transformer(
schema,
Expand All @@ -85,7 +85,7 @@ export function render<
context,
mode: params.mode ?? "out",
hooks: params.hooks ?? [],
version,
version: params.version,
recursiveDataParsers: getRecursiveDataParser(schema),
},
);
Expand Down Expand Up @@ -118,29 +118,29 @@ export function render<
};

if (
version === supportedVersions.openApi3
|| version === supportedVersions.openApi31
params.version === "openApi3"
|| params.version === "openApi31"
) {
return {
$ref: buildRef(params.identifier, version),
openapi: version,
$ref: buildRef(params.identifier, params.version),
openapi: supportedVersions[params.version],
components: {
schemas: definitionsWithIdentifier,
},
} as never;
}

if (version === supportedVersions.jsonSchema202012) {
if (params.version === "jsonSchema202012") {
return {
$ref: buildRef(params.identifier, version),
$schema: version,
$ref: buildRef(params.identifier, params.version),
$schema: params.version,
$defs: definitionsWithIdentifier,
} as never;
}

return {
$ref: buildRef(params.identifier, version),
$schema: version,
$ref: buildRef(params.identifier, params.version),
$schema: supportedVersions[params.version],
definitions: definitionsWithIdentifier,
} as never;
}
Loading