diff --git a/packages/java/parser-jvm-plugin-transfertypes/pom.xml b/packages/java/parser-jvm-plugin-transfertypes/pom.xml
index 1184e499a2..70e87583c9 100644
--- a/packages/java/parser-jvm-plugin-transfertypes/pom.xml
+++ b/packages/java/parser-jvm-plugin-transfertypes/pom.xml
@@ -2,71 +2,76 @@
- 4.0.0
+ 4.0.0
-
- com.vaadin
- hilla-project
- 24.7-SNAPSHOT
- ../../../pom.xml
-
+
+ com.vaadin
+ hilla-project
+ 24.7-SNAPSHOT
+ ../../../pom.xml
+
- hilla-parser-jvm-plugin-transfertypes
- Hilla JVM Parser Transfer Types Plugin
- jar
+ hilla-parser-jvm-plugin-transfertypes
+ Hilla JVM Parser Transfer Types Plugin
+ jar
-
- ${project.parent.basedir}
-
+
+ ${project.parent.basedir}
+
-
-
- com.vaadin
- hilla-parser-jvm-core
- ${project.version}
-
-
- org.jspecify
- jspecify
-
-
- io.github.classgraph
- classgraph
-
-
- org.junit.jupiter
- junit-jupiter
- test
-
-
- com.vaadin
- hilla-parser-jvm-utils
- ${project.version}
-
-
- com.vaadin
- hilla-parser-jvm-plugin-backbone
- ${project.version}
-
-
- com.vaadin
- hilla-runtime-plugin-transfertypes
- ${project.version}
-
-
- org.springframework.data
- spring-data-commons
-
-
- com.vaadin
- hilla-parser-jvm-test-utils
- ${project.version}
- test
-
-
- io.projectreactor
- reactor-core
- test
-
-
+
+
+ com.vaadin
+ hilla-parser-jvm-core
+ ${project.version}
+
+
+ org.jspecify
+ jspecify
+
+
+ io.github.classgraph
+ classgraph
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ com.vaadin
+ hilla-parser-jvm-utils
+ ${project.version}
+
+
+ com.vaadin
+ hilla-parser-jvm-plugin-backbone
+ ${project.version}
+
+
+ com.vaadin
+ hilla-runtime-plugin-transfertypes
+ ${project.version}
+
+
+ org.springframework.data
+ spring-data-commons
+
+
+ com.vaadin
+ hilla-parser-jvm-test-utils
+ ${project.version}
+ test
+
+
+ io.projectreactor
+ reactor-core
+ test
+
+
+ org.springframework
+ spring-web
+ test
+
+
diff --git a/packages/java/parser-jvm-plugin-transfertypes/src/main/java/com/vaadin/hilla/parser/plugins/transfertypes/TransferTypesPlugin.java b/packages/java/parser-jvm-plugin-transfertypes/src/main/java/com/vaadin/hilla/parser/plugins/transfertypes/TransferTypesPlugin.java
index 1bb43420c9..cb8ab2a9ae 100644
--- a/packages/java/parser-jvm-plugin-transfertypes/src/main/java/com/vaadin/hilla/parser/plugins/transfertypes/TransferTypesPlugin.java
+++ b/packages/java/parser-jvm-plugin-transfertypes/src/main/java/com/vaadin/hilla/parser/plugins/transfertypes/TransferTypesPlugin.java
@@ -12,6 +12,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+
import com.vaadin.hilla.mappedtypes.Order;
import com.vaadin.hilla.mappedtypes.Pageable;
import com.vaadin.hilla.mappedtypes.Sort;
@@ -25,10 +26,9 @@
import com.vaadin.hilla.parser.models.ClassRefSignatureModel;
import com.vaadin.hilla.parser.models.SignatureModel;
import com.vaadin.hilla.parser.plugins.backbone.BackbonePlugin;
-import com.vaadin.hilla.parser.plugins.backbone.nodes.CompositeTypeSignatureNode;
-import com.vaadin.hilla.parser.plugins.backbone.nodes.TypeSignatureNode;
import com.vaadin.hilla.parser.plugins.backbone.nodes.TypedNode;
import com.vaadin.hilla.runtime.transfertypes.EndpointSubscription;
+import com.vaadin.hilla.runtime.transfertypes.File;
import com.vaadin.hilla.runtime.transfertypes.Flux;
public final class TransferTypesPlugin
@@ -48,6 +48,8 @@ public final class TransferTypesPlugin
classMap.put(JsonNode.class.getName(), Object.class);
classMap.put(ObjectNode.class.getName(), Object.class);
classMap.put(ArrayNode.class.getName(), List.class);
+ classMap.put("org.springframework.web.multipart.MultipartFile",
+ File.class);
}
public TransferTypesPlugin() {
diff --git a/packages/java/parser-jvm-plugin-transfertypes/src/test/java/com/vaadin/hilla/parser/plugins/transfertypes/file/Endpoint.java b/packages/java/parser-jvm-plugin-transfertypes/src/test/java/com/vaadin/hilla/parser/plugins/transfertypes/file/Endpoint.java
new file mode 100644
index 0000000000..cbfd93a487
--- /dev/null
+++ b/packages/java/parser-jvm-plugin-transfertypes/src/test/java/com/vaadin/hilla/parser/plugins/transfertypes/file/Endpoint.java
@@ -0,0 +1,11 @@
+package com.vaadin.hilla.parser.plugins.transfertypes.file;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Endpoint {
+}
diff --git a/packages/java/parser-jvm-plugin-transfertypes/src/test/java/com/vaadin/hilla/parser/plugins/transfertypes/file/MultipartFileEndpoint.java b/packages/java/parser-jvm-plugin-transfertypes/src/test/java/com/vaadin/hilla/parser/plugins/transfertypes/file/MultipartFileEndpoint.java
new file mode 100644
index 0000000000..17f1a507dd
--- /dev/null
+++ b/packages/java/parser-jvm-plugin-transfertypes/src/test/java/com/vaadin/hilla/parser/plugins/transfertypes/file/MultipartFileEndpoint.java
@@ -0,0 +1,9 @@
+package com.vaadin.hilla.parser.plugins.transfertypes.file;
+
+import org.springframework.web.multipart.MultipartFile;
+
+@Endpoint
+public class MultipartFileEndpoint {
+ public void uploadFile(MultipartFile file) {
+ }
+}
diff --git a/packages/java/parser-jvm-plugin-transfertypes/src/test/java/com/vaadin/hilla/parser/plugins/transfertypes/file/MultipartFileTest.java b/packages/java/parser-jvm-plugin-transfertypes/src/test/java/com/vaadin/hilla/parser/plugins/transfertypes/file/MultipartFileTest.java
new file mode 100644
index 0000000000..07f119ada7
--- /dev/null
+++ b/packages/java/parser-jvm-plugin-transfertypes/src/test/java/com/vaadin/hilla/parser/plugins/transfertypes/file/MultipartFileTest.java
@@ -0,0 +1,30 @@
+package com.vaadin.hilla.parser.plugins.transfertypes.file;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import com.vaadin.hilla.parser.core.Parser;
+import com.vaadin.hilla.parser.plugins.backbone.BackbonePlugin;
+import com.vaadin.hilla.parser.plugins.transfertypes.TransferTypesPlugin;
+import com.vaadin.hilla.parser.plugins.transfertypes.test.helpers.TestHelper;
+
+public class MultipartFileTest {
+ private final TestHelper helper = new TestHelper(getClass());
+
+ @Test
+ public void should_ReplaceMultipartFileClassWithLocalFileClass()
+ throws IOException, URISyntaxException {
+ var openAPI = new Parser()
+ .classPath(Set.of(helper.getTargetDir().toString()))
+ .endpointAnnotations(List.of(Endpoint.class))
+ .addPlugin(new BackbonePlugin())
+ .addPlugin(new TransferTypesPlugin())
+ .execute(List.of(MultipartFileEndpoint.class));
+
+ helper.executeParserWithConfig(openAPI);
+ }
+}
diff --git a/packages/java/parser-jvm-plugin-transfertypes/src/test/resources/com/vaadin/hilla/parser/plugins/transfertypes/file/openapi.json b/packages/java/parser-jvm-plugin-transfertypes/src/test/resources/com/vaadin/hilla/parser/plugins/transfertypes/file/openapi.json
new file mode 100644
index 0000000000..8fb7e41a4f
--- /dev/null
+++ b/packages/java/parser-jvm-plugin-transfertypes/src/test/resources/com/vaadin/hilla/parser/plugins/transfertypes/file/openapi.json
@@ -0,0 +1,60 @@
+{
+ "openapi" : "3.0.1",
+ "info" : {
+ "title" : "Hilla Application",
+ "version" : "1.0.0"
+ },
+ "servers" : [
+ {
+ "url" : "http://localhost:8080/connect",
+ "description" : "Hilla Backend"
+ }
+ ],
+ "tags" : [
+ {
+ "name" : "MultipartFileEndpoint",
+ "x-class-name" : "com.vaadin.hilla.parser.plugins.transfertypes.file.MultipartFileEndpoint"
+ }
+ ],
+ "paths" : {
+ "/MultipartFileEndpoint/uploadFile" : {
+ "post" : {
+ "tags" : [
+ "MultipartFileEndpoint"
+ ],
+ "operationId" : "MultipartFileEndpoint_uploadFile_POST",
+ "requestBody" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "type" : "object",
+ "properties" : {
+ "file" : {
+ "nullable" : true,
+ "anyOf" : [
+ {
+ "$ref" : "#/components/schemas/com.vaadin.hilla.runtime.transfertypes.File"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses" : {
+ "200" : {
+ "description" : ""
+ }
+ }
+ }
+ }
+ },
+ "components" : {
+ "schemas" : {
+ "com.vaadin.hilla.runtime.transfertypes.File" : {
+ "type" : "object"
+ }
+ }
+ }
+}
diff --git a/packages/java/runtime-plugin-transfertypes/src/main/java/com/vaadin/hilla/runtime/transfertypes/File.java b/packages/java/runtime-plugin-transfertypes/src/main/java/com/vaadin/hilla/runtime/transfertypes/File.java
new file mode 100644
index 0000000000..9bdf18ba66
--- /dev/null
+++ b/packages/java/runtime-plugin-transfertypes/src/main/java/com/vaadin/hilla/runtime/transfertypes/File.java
@@ -0,0 +1,4 @@
+package com.vaadin.hilla.runtime.transfertypes;
+
+public record File() {
+}
diff --git a/packages/ts/generator-cli/src/GeneratorIO.ts b/packages/ts/generator-cli/src/GeneratorIO.ts
index a866776cdb..fdab9afe32 100644
--- a/packages/ts/generator-cli/src/GeneratorIO.ts
+++ b/packages/ts/generator-cli/src/GeneratorIO.ts
@@ -3,7 +3,6 @@ import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { dirname, isAbsolute, join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
-import type File from '@vaadin/hilla-generator-core/File.js';
import Plugin, { type PluginConstructor } from '@vaadin/hilla-generator-core/Plugin.js';
import type LoggerFactory from '@vaadin/hilla-generator-utils/LoggerFactory.js';
import GeneratorIOException from './GeneratorIOException.js';
diff --git a/packages/ts/generator-cli/test/GeneratorIO.spec.ts b/packages/ts/generator-cli/test/GeneratorIO.spec.ts
index 06806e9ec8..fb704b473e 100644
--- a/packages/ts/generator-cli/test/GeneratorIO.spec.ts
+++ b/packages/ts/generator-cli/test/GeneratorIO.spec.ts
@@ -2,7 +2,6 @@ import { statSync } from 'node:fs';
import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
-import File from '@vaadin/hilla-generator-core/File.js';
import LoggerFactory from '@vaadin/hilla-generator-utils/LoggerFactory.js';
import chaiAsPromised from 'chai-as-promised';
import { expect, chai, describe, it, beforeEach, afterEach } from 'vitest';
diff --git a/packages/ts/generator-core/package.json b/packages/ts/generator-core/package.json
index 1ff722f069..b9ad2c8590 100644
--- a/packages/ts/generator-core/package.json
+++ b/packages/ts/generator-core/package.json
@@ -44,9 +44,6 @@
},
"./SharedStorage.js": {
"types": "./SharedStorage.d.ts"
- },
- "./utils.js": {
- "default": "./utils.js"
}
},
"repository": {
diff --git a/packages/ts/generator-core/src/File.ts b/packages/ts/generator-core/src/File.ts
deleted file mode 100644
index 5143491e5b..0000000000
--- a/packages/ts/generator-core/src/File.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Blob, type BlobOptions } from 'node:buffer';
-import type { BinaryLike } from 'node:crypto';
-
-export type FileOptions = Readonly<
- BlobOptions & {
- lastModified?: number;
- }
->;
-
-export default class File extends Blob {
- readonly #name: string;
-
- constructor(fileBits: Array, fileName: string, options?: FileOptions) {
- super(fileBits, options);
- this.#name = fileName;
- }
-
- get name(): string {
- return this.#name;
- }
-}
diff --git a/packages/ts/generator-core/src/Generator.ts b/packages/ts/generator-core/src/Generator.ts
index dd99cfa8d5..971bd5ea13 100644
--- a/packages/ts/generator-core/src/Generator.ts
+++ b/packages/ts/generator-core/src/Generator.ts
@@ -3,11 +3,10 @@ import type LoggerFactory from '@vaadin/hilla-generator-utils/LoggerFactory.js';
import type { OpenAPIV3 } from 'openapi-types';
import type { ReadonlyDeep } from 'type-fest';
import ts from 'typescript';
-import File from './File.js';
import type { PluginConstructor } from './Plugin.js';
import PluginManager from './PluginManager.js';
import ReferenceResolver from './ReferenceResolver.js';
-import type SharedStorage from './SharedStorage.js';
+import type { SharedStorage } from './SharedStorage.js';
export type GeneratorContext = Readonly<{
logger: LoggerFactory;
@@ -37,6 +36,7 @@ export default class Generator {
outputDir: this.#outputDir,
pluginStorage: new Map(),
sources: [],
+ transferTypes: new Map(),
};
this.#logger.global.debug('Executing plugins');
diff --git a/packages/ts/generator-core/src/Plugin.ts b/packages/ts/generator-core/src/Plugin.ts
index eabfab89a4..6e1e13de78 100644
--- a/packages/ts/generator-core/src/Plugin.ts
+++ b/packages/ts/generator-core/src/Plugin.ts
@@ -2,7 +2,7 @@ import type LoggerFactory from '@vaadin/hilla-generator-utils/LoggerFactory.js';
import type { Logger } from '@vaadin/hilla-generator-utils/LoggerFactory.js';
import type { Constructor } from 'type-fest';
import type ReferenceResolver from './ReferenceResolver.js';
-import type SharedStorage from './SharedStorage.js';
+import type { SharedStorage } from './SharedStorage.js';
export default abstract class Plugin {
readonly resolver: ReferenceResolver;
@@ -19,7 +19,7 @@ export default abstract class Plugin {
abstract get path(): string;
- abstract execute(storage: SharedStorage): Promise;
+ abstract execute(storage: SharedStorage): Promise | void;
}
export type PluginConstructor = Constructor>;
diff --git a/packages/ts/generator-core/src/PluginManager.ts b/packages/ts/generator-core/src/PluginManager.ts
index 59f468f652..644e0c25a6 100644
--- a/packages/ts/generator-core/src/PluginManager.ts
+++ b/packages/ts/generator-core/src/PluginManager.ts
@@ -2,13 +2,14 @@ import type LoggerFactory from '@vaadin/hilla-generator-utils/LoggerFactory.js';
import type Plugin from './Plugin.js';
import type { PluginConstructor } from './Plugin.js';
import type ReferenceResolver from './ReferenceResolver.js';
-import type SharedStorage from './SharedStorage.js';
+import type { SharedStorage } from './SharedStorage.js';
export default class PluginManager {
readonly #plugins: Plugin[];
constructor(plugins: readonly PluginConstructor[], resolver: ReferenceResolver, logger: LoggerFactory) {
const standardPlugins = [
+ 'TransferTypesPlugin', // should go before Backbone Plugin
'BackbonePlugin',
'ClientPlugin',
'BarrelPlugin',
diff --git a/packages/ts/generator-core/src/Schema.ts b/packages/ts/generator-core/src/Schema.ts
index e7fd41e42c..de871f4867 100644
--- a/packages/ts/generator-core/src/Schema.ts
+++ b/packages/ts/generator-core/src/Schema.ts
@@ -1,6 +1,7 @@
import type { OpenAPIV3 } from 'openapi-types';
-import type { ReadonlyDeep } from 'type-fest';
-import { convertFullyQualifiedNameToRelativePath, simplifyFullyQualifiedName, type Nullified } from './utils.js';
+import type { ReadonlyDeep, Simplify } from 'type-fest';
+
+export type Nullified = T & Record;
export type ReferenceSchema = ReadonlyDeep;
export type ArraySchema = ReadonlyDeep;
@@ -20,7 +21,7 @@ export type ComposedSchema =
| OneOfRuleComposedSchema;
export type NonComposedRegularSchema = Readonly> & RegularSchema;
-export type NonComposedSchema = NonComposedRegularSchema | ReferenceSchema;
+export type NonComposedSchema = Simplify;
export type BooleanSchema = NonComposedRegularSchema & Readonly<{ type: 'boolean' }>;
export type IntegerSchema = NonComposedRegularSchema & Readonly<{ type: 'integer' }>;
@@ -33,7 +34,7 @@ export type EmptyObjectSchema = ObjectSchema & Readonly>>;
export type MapSchema = EmptyObjectSchema & Readonly>>;
-export type Schema = ReferenceSchema | RegularSchema;
+export type Schema = ReadonlyDeep;
export function isReferenceSchema(schema: Schema): schema is ReferenceSchema {
return '$ref' in schema;
@@ -128,14 +129,26 @@ export function isMapSchema(schema: Schema): schema is MapSchema {
return isEmptyObject(schema) && !!schema.additionalProperties;
}
+export function simplifyFullyQualifiedName(name: string): string {
+ return name.substring(name.lastIndexOf(name.includes('$') ? '$' : '.') + 1, name.length);
+}
+
export function convertReferenceSchemaToSpecifier({ $ref }: ReferenceSchema): string {
return simplifyFullyQualifiedName($ref);
}
const COMPONENTS_SCHEMAS_REF_LENGTH = '#/components/schemas/'.length;
-export function convertReferenceSchemaToPath({ $ref }: ReferenceSchema): string {
- return convertFullyQualifiedNameToRelativePath($ref.substring(COMPONENTS_SCHEMAS_REF_LENGTH));
+export function convertReferenceSchemaToFullyQualifiedName({ $ref }: ReferenceSchema): string {
+ return $ref.substring(COMPONENTS_SCHEMAS_REF_LENGTH);
+}
+
+export function convertFullyQualifiedNameToRelativePath(name: string): string {
+ return name.replace(/[$.]/gu, '/');
+}
+
+export function convertReferenceSchemaToPath(schema: ReferenceSchema): string {
+ return convertFullyQualifiedNameToRelativePath(convertReferenceSchemaToFullyQualifiedName(schema));
}
export function resolveReference(
diff --git a/packages/ts/generator-core/src/SharedStorage.d.ts b/packages/ts/generator-core/src/SharedStorage.d.ts
index 2280afd82f..538c24cfdb 100644
--- a/packages/ts/generator-core/src/SharedStorage.d.ts
+++ b/packages/ts/generator-core/src/SharedStorage.d.ts
@@ -1,14 +1,17 @@
import type { $Refs } from '@apidevtools/swagger-parser';
import type { OpenAPIV3 } from 'openapi-types';
import type { ReadonlyDeep } from 'type-fest';
-import type { SourceFile } from 'typescript';
+import type { SourceFile, TypeNode } from 'typescript';
-type SharedStorage = Readonly<{
+export type TransferTypeMaker = (typeArguments: readonly TypeNode[] | undefined) => TypeNode;
+
+export type TransferTypes = Map;
+
+export type SharedStorage = Readonly<{
api: ReadonlyDeep;
apiRefs: $Refs;
- sources: SourceFile[];
- pluginStorage: Map;
outputDir?: string;
+ pluginStorage: Map;
+ sources: SourceFile[];
+ transferTypes: TransferTypes;
}>;
-
-export default SharedStorage;
diff --git a/packages/ts/generator-core/src/utils.ts b/packages/ts/generator-core/src/utils.ts
deleted file mode 100644
index b2cc01dc79..0000000000
--- a/packages/ts/generator-core/src/utils.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export type Nullified = T & Record;
-
-export function simplifyFullyQualifiedName(name: string): string {
- return name.substring(name.lastIndexOf(name.includes('$') ? '$' : '.') + 1, name.length);
-}
-
-const QUALIFIED_NAME_DELIMITER = /[$.]/gu;
-
-export function convertFullyQualifiedNameToRelativePath(name: string): string {
- return name.replace(QUALIFIED_NAME_DELIMITER, '/');
-}
diff --git a/packages/ts/generator-plugin-backbone/src/EndpointMethodOperationProcessor.ts b/packages/ts/generator-plugin-backbone/src/EndpointMethodOperationProcessor.ts
index 6de83c9f9d..2a31760537 100644
--- a/packages/ts/generator-plugin-backbone/src/EndpointMethodOperationProcessor.ts
+++ b/packages/ts/generator-plugin-backbone/src/EndpointMethodOperationProcessor.ts
@@ -1,5 +1,6 @@
/* eslint-disable max-params */
import type Plugin from '@vaadin/hilla-generator-core/Plugin.js';
+import type { TransferTypes } from '@vaadin/hilla-generator-core/SharedStorage.js';
import ClientPlugin from '@vaadin/hilla-generator-plugin-client';
import type DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';
import equal from 'fast-deep-equal';
@@ -11,9 +12,6 @@ import EndpointMethodResponseProcessor from './EndpointMethodResponseProcessor.j
export type EndpointMethodOperation = ReadonlyDeep;
-export const INIT_TYPE_NAME = 'EndpointRequestInit';
-export const HILLA_FRONTEND_NAME = '@vaadin/hilla-frontend';
-
export default abstract class EndpointMethodOperationProcessor {
// eslint-disable-next-line @typescript-eslint/max-params
static createProcessor(
@@ -22,6 +20,7 @@ export default abstract class EndpointMethodOperationProcessor {
endpointMethodName: string,
operation: EndpointMethodOperation,
dependencies: DependencyManager,
+ transferTypes: TransferTypes,
owner: Plugin,
): EndpointMethodOperationProcessor | undefined {
switch (httpMethod) {
@@ -32,6 +31,7 @@ export default abstract class EndpointMethodOperationProcessor {
endpointMethodName,
operation,
dependencies,
+ transferTypes,
owner,
);
}
@@ -46,6 +46,7 @@ export default abstract class EndpointMethodOperationProcessor {
class EndpointMethodOperationPOSTProcessor extends EndpointMethodOperationProcessor {
readonly #dependencies: DependencyManager;
+ readonly #transferTypes: TransferTypes;
readonly #endpointMethodName: string;
readonly #endpointName: string;
readonly #operation: EndpointMethodOperation;
@@ -57,6 +58,7 @@ class EndpointMethodOperationPOSTProcessor extends EndpointMethodOperationProces
endpointMethodName: string,
operation: EndpointMethodOperation,
dependencies: DependencyManager,
+ transferTypes: TransferTypes,
owner: Plugin,
) {
super();
@@ -65,21 +67,18 @@ class EndpointMethodOperationPOSTProcessor extends EndpointMethodOperationProces
this.#endpointName = endpointName;
this.#endpointMethodName = endpointMethodName;
this.#operation = operation;
+ this.#transferTypes = transferTypes;
}
async process(outputDir?: string): Promise {
const { exports, imports, paths } = this.#dependencies;
this.#owner.logger.debug(`${this.#endpointName}.${this.#endpointMethodName} - processing POST method`);
- const initTypeIdentifier = imports.named.getIdentifier(
- paths.createBareModulePath(HILLA_FRONTEND_NAME),
- INIT_TYPE_NAME,
- )!;
const { initParam, packedParameters, parameters } = new EndpointMethodRequestBodyProcessor(
this.#operation.requestBody,
this.#dependencies,
+ this.#transferTypes,
this.#owner,
- initTypeIdentifier,
).process();
const methodIdentifier = exports.named.add(this.#endpointMethodName);
@@ -116,7 +115,13 @@ class EndpointMethodOperationPOSTProcessor extends EndpointMethodOperationProces
const responseTypes = Object.entries(this.#operation.responses)
.flatMap(([code, response]) =>
- new EndpointMethodResponseProcessor(code, response, this.#dependencies, this.#owner).process(),
+ new EndpointMethodResponseProcessor(
+ code,
+ response,
+ this.#dependencies,
+ this.#transferTypes,
+ this.#owner,
+ ).process(),
)
.filter((value, index, arr) => arr.findIndex((v) => equal(v, value)) === index);
diff --git a/packages/ts/generator-plugin-backbone/src/EndpointMethodRequestBodyProcessor.ts b/packages/ts/generator-plugin-backbone/src/EndpointMethodRequestBodyProcessor.ts
index 07c6bffe14..0113cd518f 100644
--- a/packages/ts/generator-plugin-backbone/src/EndpointMethodRequestBodyProcessor.ts
+++ b/packages/ts/generator-plugin-backbone/src/EndpointMethodRequestBodyProcessor.ts
@@ -5,10 +5,11 @@ import {
type NonEmptyObjectSchema,
type Schema,
} from '@vaadin/hilla-generator-core/Schema.js';
+import type { TransferTypes } from '@vaadin/hilla-generator-core/SharedStorage.js';
import type DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';
import type { OpenAPIV3 } from 'openapi-types';
import type { ReadonlyDeep } from 'type-fest';
-import ts, { type ObjectLiteralExpression, type ParameterDeclaration } from 'typescript';
+import ts, { type Identifier, type ObjectLiteralExpression, type ParameterDeclaration } from 'typescript';
import TypeSchemaProcessor from './TypeSchemaProcessor.js';
import { defaultMediaType } from './utils.js';
@@ -17,41 +18,48 @@ export type EndpointMethodRequestBody = ReadonlyDeep;
-export default class EndpointMethodRequestBodyProcessor {
- static readonly #defaultInitParamName = 'init';
+const DEFAULT_INIT_PARAM_NAME = 'init';
+const INIT_TYPE_NAME = 'EndpointRequestInit';
+const HILLA_FRONTEND_NAME = '@vaadin/hilla-frontend';
+export default class EndpointMethodRequestBodyProcessor {
readonly #dependencies: DependencyManager;
+ readonly #transferTypes: TransferTypes;
readonly #owner: Plugin;
readonly #requestBody?: EndpointMethodRequestBody;
- readonly #initTypeIdentifier: ts.Identifier;
constructor(
requestBody: ReadonlyDeep | undefined,
dependencies: DependencyManager,
+ transferTypes: TransferTypes,
owner: Plugin,
- initTypeIdentifier: ts.Identifier,
) {
this.#owner = owner;
this.#dependencies = dependencies;
this.#requestBody = requestBody ? owner.resolver.resolve(requestBody) : undefined;
- this.#initTypeIdentifier = initTypeIdentifier;
+ this.#transferTypes = transferTypes;
}
process(): EndpointMethodRequestBodyProcessingResult {
+ const { imports, paths } = this.#dependencies;
+ const path = paths.createBareModulePath(HILLA_FRONTEND_NAME);
+ const initTypeIdentifier =
+ imports.named.getIdentifier(path, INIT_TYPE_NAME) ?? imports.named.add(path, INIT_TYPE_NAME);
+
if (!this.#requestBody) {
return {
- initParam: ts.factory.createIdentifier(EndpointMethodRequestBodyProcessor.#defaultInitParamName),
+ initParam: ts.factory.createIdentifier(DEFAULT_INIT_PARAM_NAME),
packedParameters: ts.factory.createObjectLiteralExpression(),
parameters: [
ts.factory.createParameterDeclaration(
undefined,
undefined,
- EndpointMethodRequestBodyProcessor.#defaultInitParamName,
+ DEFAULT_INIT_PARAM_NAME,
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
- ts.factory.createTypeReferenceNode(this.#initTypeIdentifier),
+ ts.factory.createTypeReferenceNode(initTypeIdentifier),
),
],
};
@@ -59,7 +67,7 @@ export default class EndpointMethodRequestBodyProcessor {
const parameterData = this.#extractParameterData(this.#requestBody.content[defaultMediaType].schema);
const parameterNames = parameterData.map(([name]) => name);
- let initParamName = EndpointMethodRequestBodyProcessor.#defaultInitParamName;
+ let initParamName = DEFAULT_INIT_PARAM_NAME;
while (parameterNames.includes(initParamName)) {
initParamName = `_${initParamName}`;
@@ -72,7 +80,7 @@ export default class EndpointMethodRequestBodyProcessor {
),
parameters: [
...parameterData.map(([name, schema]) => {
- const nodes = new TypeSchemaProcessor(schema, this.#dependencies).process();
+ const nodes = new TypeSchemaProcessor(schema, this.#dependencies, this.#transferTypes).process();
return ts.factory.createParameterDeclaration(
undefined,
@@ -87,7 +95,7 @@ export default class EndpointMethodRequestBodyProcessor {
undefined,
initParamName,
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
- ts.factory.createTypeReferenceNode(this.#initTypeIdentifier),
+ ts.factory.createTypeReferenceNode(initTypeIdentifier),
),
],
};
diff --git a/packages/ts/generator-plugin-backbone/src/EndpointMethodResponseProcessor.ts b/packages/ts/generator-plugin-backbone/src/EndpointMethodResponseProcessor.ts
index 798b499529..23fdef2a73 100644
--- a/packages/ts/generator-plugin-backbone/src/EndpointMethodResponseProcessor.ts
+++ b/packages/ts/generator-plugin-backbone/src/EndpointMethodResponseProcessor.ts
@@ -1,4 +1,5 @@
import type Plugin from '@vaadin/hilla-generator-core/Plugin.js';
+import type { TransferTypes } from '@vaadin/hilla-generator-core/SharedStorage.js';
import type DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';
import type { OpenAPIV3 } from 'openapi-types';
import type { ReadonlyDeep } from 'type-fest';
@@ -12,19 +13,23 @@ export type EndpointMethodResponse = ReadonlyDeep;
export default class EndpointMethodResponseProcessor {
readonly #code: string;
readonly #dependencies: DependencyManager;
+ readonly #transferTypes: TransferTypes;
readonly #owner: Plugin;
readonly #response: EndpointMethodResponse;
+ // eslint-disable-next-line @typescript-eslint/max-params
constructor(
code: string,
response: EndpointMethodResponses[string],
dependencyManager: DependencyManager,
+ transferTypes: TransferTypes,
owner: Plugin,
) {
this.#code = code;
this.#owner = owner;
this.#dependencies = dependencyManager;
this.#response = owner.resolver.resolve(response);
+ this.#transferTypes = transferTypes;
}
process(): readonly TypeNode[] {
@@ -40,6 +45,6 @@ export default class EndpointMethodResponseProcessor {
#processOk(): readonly TypeNode[] {
const rawSchema = this.#response.content?.[defaultMediaType]?.schema;
- return rawSchema ? new TypeSchemaProcessor(rawSchema, this.#dependencies).process() : [];
+ return rawSchema ? new TypeSchemaProcessor(rawSchema, this.#dependencies, this.#transferTypes).process() : [];
}
}
diff --git a/packages/ts/generator-plugin-backbone/src/EndpointProcessor.ts b/packages/ts/generator-plugin-backbone/src/EndpointProcessor.ts
index da06f0a191..7661926870 100644
--- a/packages/ts/generator-plugin-backbone/src/EndpointProcessor.ts
+++ b/packages/ts/generator-plugin-backbone/src/EndpointProcessor.ts
@@ -1,4 +1,5 @@
import type Plugin from '@vaadin/hilla-generator-core/Plugin.js';
+import type { SharedStorage, TransferTypes } from '@vaadin/hilla-generator-core/SharedStorage.js';
import ClientPlugin from '@vaadin/hilla-generator-plugin-client';
import createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';
import DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';
@@ -6,27 +7,20 @@ import PathManager from '@vaadin/hilla-generator-utils/dependencies/PathManager.
import { OpenAPIV3 } from 'openapi-types';
import type { ReadonlyDeep } from 'type-fest';
import type { SourceFile, Statement } from 'typescript';
-import EndpointMethodOperationProcessor, {
- HILLA_FRONTEND_NAME,
- INIT_TYPE_NAME,
-} from './EndpointMethodOperationProcessor.js';
+import EndpointMethodOperationProcessor from './EndpointMethodOperationProcessor.js';
export default class EndpointProcessor {
static async create(
name: string,
- owner: Plugin,
methods: Map>,
- outputDir?: string,
+ storage: SharedStorage,
+ owner: Plugin,
): Promise {
- const endpoint = new EndpointProcessor(name, owner, methods, outputDir);
+ const endpoint = new EndpointProcessor(name, methods, storage, owner);
endpoint.#dependencies.imports.default.add(
- endpoint.#dependencies.paths.createRelativePath(await ClientPlugin.getClientFileName(outputDir)),
+ endpoint.#dependencies.paths.createRelativePath(await ClientPlugin.getClientFileName(storage.outputDir)),
'client',
);
- endpoint.#dependencies.imports.named.add(
- endpoint.#dependencies.paths.createBareModulePath(HILLA_FRONTEND_NAME),
- INIT_TYPE_NAME,
- );
return endpoint;
}
@@ -35,18 +29,20 @@ export default class EndpointProcessor {
readonly #methods: Map>;
readonly #name: string;
readonly #outputDir: string | undefined;
+ readonly #transferTypes: TransferTypes;
readonly #owner: Plugin;
private constructor(
name: string,
- owner: Plugin,
methods: Map>,
- outputDir?: string,
+ storage: SharedStorage,
+ owner: Plugin,
) {
this.#name = name;
this.#owner = owner;
this.#methods = methods;
- this.#outputDir = outputDir;
+ this.#outputDir = storage.outputDir;
+ this.#transferTypes = storage.transferTypes;
}
async process(): Promise {
@@ -81,6 +77,7 @@ export default class EndpointProcessor {
method,
pathItem[httpMethod]!,
this.#dependencies,
+ this.#transferTypes,
this.#owner,
)?.process(this.#outputDir),
),
diff --git a/packages/ts/generator-plugin-backbone/src/EntityProcessor.ts b/packages/ts/generator-plugin-backbone/src/EntityProcessor.ts
index 884bb34a95..a83ae722a9 100644
--- a/packages/ts/generator-plugin-backbone/src/EntityProcessor.ts
+++ b/packages/ts/generator-plugin-backbone/src/EntityProcessor.ts
@@ -14,11 +14,10 @@ import {
isObjectSchema,
isReferenceSchema,
type ObjectSchema,
-} from '@vaadin/hilla-generator-core/Schema.js';
-import {
convertFullyQualifiedNameToRelativePath,
simplifyFullyQualifiedName,
-} from '@vaadin/hilla-generator-core/utils.js';
+} from '@vaadin/hilla-generator-core/Schema.js';
+import type { SharedStorage, TransferTypes } from '@vaadin/hilla-generator-core/SharedStorage.js';
import createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';
import DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';
import PathManager from '@vaadin/hilla-generator-utils/dependencies/PathManager.js';
@@ -28,6 +27,7 @@ import ts, {
type SourceFile,
type Statement,
type TypeElement,
+ type TypeParameterDeclaration,
} from 'typescript';
import TypeSchemaProcessor from './TypeSchemaProcessor.js';
import { findTypeParameters } from './utils.js';
@@ -38,16 +38,18 @@ export class EntityProcessor {
readonly #fullyQualifiedName: string;
readonly #name: string;
readonly #outputPathManager = new PathManager({ extension: 'ts' });
+ readonly #transferTypes: TransferTypes;
readonly #owner: Plugin;
readonly #path: string;
- constructor(name: string, component: Schema, owner: Plugin) {
+ constructor(name: string, component: Schema, storage: SharedStorage, owner: Plugin) {
this.#component = component;
this.#owner = owner;
this.#fullyQualifiedName = name;
this.#name = simplifyFullyQualifiedName(name);
this.#path = convertFullyQualifiedNameToRelativePath(name);
this.#dependencies = new DependencyManager(new PathManager({ extension: '.js', relativeTo: dirname(this.#path) }));
+ this.#transferTypes = storage.transferTypes;
}
get #id(): Identifier {
@@ -159,7 +161,7 @@ export class EntityProcessor {
#processTypeElements({ properties }: ObjectSchema): readonly TypeElement[] {
return Object.entries(properties ?? {}).map(([name, schema]) => {
- const [type] = new TypeSchemaProcessor(schema, this.#dependencies).process();
+ const [type] = new TypeSchemaProcessor(schema, this.#dependencies, this.#transferTypes).process();
return ts.factory.createPropertySignature(
undefined,
@@ -170,7 +172,7 @@ export class EntityProcessor {
});
}
- static #processTypeParameters(schema: Schema): readonly ts.TypeParameterDeclaration[] | undefined {
+ static #processTypeParameters(schema: Schema): readonly TypeParameterDeclaration[] | undefined {
return findTypeParameters(schema)
?.map(String)
.map((name) =>
diff --git a/packages/ts/generator-plugin-backbone/src/TypeSchemaProcessor.ts b/packages/ts/generator-plugin-backbone/src/TypeSchemaProcessor.ts
index 64faf82678..e1028e75b2 100644
--- a/packages/ts/generator-plugin-backbone/src/TypeSchemaProcessor.ts
+++ b/packages/ts/generator-plugin-backbone/src/TypeSchemaProcessor.ts
@@ -1,6 +1,7 @@
import {
type ArraySchema,
- convertReferenceSchemaToPath,
+ convertFullyQualifiedNameToRelativePath,
+ convertReferenceSchemaToFullyQualifiedName,
convertReferenceSchemaToSpecifier,
decomposeSchema,
isArraySchema,
@@ -17,6 +18,7 @@ import {
type ReferenceSchema,
type Schema,
} from '@vaadin/hilla-generator-core/Schema.js';
+import type { TransferTypeMaker } from '@vaadin/hilla-generator-core/SharedStorage.js';
import type DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';
import ts, { type TypeNode } from 'typescript';
import { findTypeArguments, findTypeVariable } from './utils.js';
@@ -55,10 +57,12 @@ export default class TypeSchemaProcessor {
declare ['constructor']: typeof TypeSchemaProcessor;
readonly #dependencies: DependencyManager;
readonly #schema: Schema;
+ readonly #transferTypes: Map;
- constructor(schema: Schema, dependencies: DependencyManager) {
+ constructor(schema: Schema, dependencies: DependencyManager, transferTypes: Map) {
this.#schema = schema;
this.#dependencies = dependencies;
+ this.#transferTypes = transferTypes;
}
process(): readonly TypeNode[] {
@@ -93,7 +97,7 @@ export default class TypeSchemaProcessor {
}
#processArray(schema: ArraySchema): TypeNode {
- const nodes = new TypeSchemaProcessor(schema.items, this.#dependencies).process();
+ const nodes = new TypeSchemaProcessor(schema.items, this.#dependencies, this.#transferTypes).process();
return ts.factory.createTypeReferenceNode('Array', [ts.factory.createUnionTypeNode(nodes)]);
}
@@ -102,7 +106,7 @@ export default class TypeSchemaProcessor {
let valuesTypeNode: TypeNode;
if (typeof valuesType !== 'boolean') {
- const nodes = new TypeSchemaProcessor(valuesType, this.#dependencies).process();
+ const nodes = new TypeSchemaProcessor(valuesType, this.#dependencies, this.#transferTypes).process();
valuesTypeNode = ts.factory.createUnionTypeNode(nodes);
} else {
valuesTypeNode = ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
@@ -114,18 +118,24 @@ export default class TypeSchemaProcessor {
]);
}
- #processTypeArguments(schema: Schema): readonly ts.TypeNode[] | undefined {
+ #processTypeArguments(schema: Schema): readonly TypeNode[] | undefined {
// Type arguments are processed recursively
return findTypeArguments(schema)
- ?.allOf.map((s) => new TypeSchemaProcessor(s, this.#dependencies).process())
+ ?.allOf.map((s) => new TypeSchemaProcessor(s, this.#dependencies, this.#transferTypes).process())
.map((t) => ts.factory.createUnionTypeNode(t));
}
- #processReference(schema: ReferenceSchema, typeArguments: readonly ts.TypeNode[] | undefined): TypeNode {
+ #processReference(schema: ReferenceSchema, typeArguments: readonly TypeNode[] | undefined): TypeNode {
const { imports, paths } = this.#dependencies;
+ const fullyQualifiedName = convertReferenceSchemaToFullyQualifiedName(schema);
+
+ if (this.#transferTypes.has(fullyQualifiedName)) {
+ return this.#transferTypes.get(fullyQualifiedName)!(typeArguments);
+ }
+
const specifier = convertReferenceSchemaToSpecifier(schema);
- const path = paths.createRelativePath(convertReferenceSchemaToPath(schema));
+ const path = paths.createRelativePath(convertFullyQualifiedNameToRelativePath(fullyQualifiedName));
const identifier = imports.default.getIdentifier(path) ?? imports.default.add(path, specifier, true);
diff --git a/packages/ts/generator-plugin-backbone/src/index.ts b/packages/ts/generator-plugin-backbone/src/index.ts
index 5cfaf6d4eb..ea2f7d1f59 100644
--- a/packages/ts/generator-plugin-backbone/src/index.ts
+++ b/packages/ts/generator-plugin-backbone/src/index.ts
@@ -1,5 +1,5 @@
import Plugin from '@vaadin/hilla-generator-core/Plugin.js';
-import type SharedStorage from '@vaadin/hilla-generator-core/SharedStorage.js';
+import type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';
import type { OpenAPIV3 } from 'openapi-types';
import type { ReadonlyDeep } from 'type-fest';
import type { SourceFile } from 'typescript';
@@ -53,8 +53,8 @@ export default class BackbonePlugin extends Plugin {
});
const processors = await Promise.all(
- [...endpoints.entries()].map(async ([endpointName, methods]) =>
- EndpointProcessor.create(endpointName, this, methods, storage.outputDir),
+ Array.from(endpoints.entries(), async ([endpointName, methods]) =>
+ EndpointProcessor.create(endpointName, methods, storage, this),
),
);
@@ -66,7 +66,7 @@ export default class BackbonePlugin extends Plugin {
return storage.api.components?.schemas
? Object.entries(storage.api.components.schemas).map(([name, component]) =>
- new EntityProcessor(name, component, this).process(),
+ new EntityProcessor(name, component, storage, this).process(),
)
: [];
}
diff --git a/packages/ts/generator-plugin-barrel/src/BarrelProcessor.ts b/packages/ts/generator-plugin-barrel/src/BarrelProcessor.ts
index 42f0b0c091..b74ff86697 100644
--- a/packages/ts/generator-plugin-barrel/src/BarrelProcessor.ts
+++ b/packages/ts/generator-plugin-barrel/src/BarrelProcessor.ts
@@ -6,7 +6,7 @@ import PathManager from '@vaadin/hilla-generator-utils/dependencies/PathManager.
import type { SourceFile } from 'typescript';
export default class BarrelProcessor {
- static readonly BARREL_FILE_NAME = 'endpoints.ts';
+ static readonly BARREL_FILE_NAME = 'endpoints';
declare ['constructor']: typeof BarrelProcessor;
readonly #endpoints: readonly SourceFile[];
readonly #outputPathManager = new PathManager({ extension: 'ts' });
@@ -18,7 +18,7 @@ export default class BarrelProcessor {
}
process(): SourceFile {
- this.#owner.logger.debug(`Generating '${this.constructor.BARREL_FILE_NAME}' file`);
+ this.#owner.logger.debug(`Generating '${this.constructor.BARREL_FILE_NAME}.ts' file`);
const { exports, imports } = this.#endpoints.reduce(
(acc, { fileName }) => {
@@ -35,7 +35,7 @@ export default class BarrelProcessor {
return createSourceFile(
[...imports.toCode(), ...exports.toCode()],
- this.#outputPathManager.createRelativePath('endpoints'),
+ this.#outputPathManager.createRelativePath(this.constructor.BARREL_FILE_NAME),
);
}
}
diff --git a/packages/ts/generator-plugin-barrel/src/index.ts b/packages/ts/generator-plugin-barrel/src/index.ts
index 1d28b4649e..86239fd371 100644
--- a/packages/ts/generator-plugin-barrel/src/index.ts
+++ b/packages/ts/generator-plugin-barrel/src/index.ts
@@ -1,5 +1,5 @@
import Plugin from '@vaadin/hilla-generator-core/Plugin.js';
-import type SharedStorage from '@vaadin/hilla-generator-core/SharedStorage.js';
+import type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';
import BackbonePlugin, { BackbonePluginSourceType } from '@vaadin/hilla-generator-plugin-backbone';
import PluginError from '@vaadin/hilla-generator-utils/PluginError.js';
import type { SourceFile } from 'typescript';
diff --git a/packages/ts/generator-plugin-client/src/index.ts b/packages/ts/generator-plugin-client/src/index.ts
index 44d195aa6d..e8d01914bf 100644
--- a/packages/ts/generator-plugin-client/src/index.ts
+++ b/packages/ts/generator-plugin-client/src/index.ts
@@ -1,7 +1,7 @@
import { open } from 'fs/promises';
import { fileURLToPath } from 'url';
import Plugin from '@vaadin/hilla-generator-core/Plugin.js';
-import type SharedStorage from '@vaadin/hilla-generator-core/SharedStorage.js';
+import type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';
import ClientProcessor from './ClientProcessor.js';
export default class ClientPlugin extends Plugin {
diff --git a/packages/ts/generator-plugin-model/src/EntityModelProcessor.ts b/packages/ts/generator-plugin-model/src/EntityModelProcessor.ts
index a23c5639fe..9b4b0a4919 100644
--- a/packages/ts/generator-plugin-model/src/EntityModelProcessor.ts
+++ b/packages/ts/generator-plugin-model/src/EntityModelProcessor.ts
@@ -1,6 +1,7 @@
/* eslint-disable symbol-description */
import { dirname } from 'path/posix';
import {
+ convertFullyQualifiedNameToRelativePath,
convertReferenceSchemaToPath,
convertReferenceSchemaToSpecifier,
decomposeSchema,
@@ -11,11 +12,8 @@ import {
type ObjectSchema,
type ReferenceSchema,
type Schema,
-} from '@vaadin/hilla-generator-core/Schema.js';
-import {
- convertFullyQualifiedNameToRelativePath,
simplifyFullyQualifiedName,
-} from '@vaadin/hilla-generator-core/utils.js';
+} from '@vaadin/hilla-generator-core/Schema.js';
import createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';
import DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';
import PathManager from '@vaadin/hilla-generator-utils/dependencies/PathManager.js';
@@ -27,7 +25,7 @@ import ts, {
type Statement,
} from 'typescript';
import { ModelSchemaExpressionProcessor, ModelSchemaTypeProcessor } from './ModelSchemaProcessor.js';
-import { importBuiltInFormModel, type Context, createModelBuildingCallback, createEmptyValueMaker } from './utils.js';
+import { type Context, createEmptyValueMaker, createModelBuildingCallback, importBuiltInFormModel } from './utils.js';
export type DependencyData = Readonly<{
id: Identifier;
diff --git a/packages/ts/generator-plugin-model/src/index.ts b/packages/ts/generator-plugin-model/src/index.ts
index 67f8146e76..92638bf21b 100644
--- a/packages/ts/generator-plugin-model/src/index.ts
+++ b/packages/ts/generator-plugin-model/src/index.ts
@@ -1,5 +1,5 @@
import Plugin from '@vaadin/hilla-generator-core/Plugin.js';
-import type SharedStorage from '@vaadin/hilla-generator-core/SharedStorage.js';
+import type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';
import type { OpenAPIV3 } from 'openapi-types';
import type { ReadonlyDeep } from 'type-fest';
import type { SourceFile } from 'typescript';
diff --git a/packages/ts/generator-plugin-push/src/index.ts b/packages/ts/generator-plugin-push/src/index.ts
index a6a5dd31d0..3135775cf0 100644
--- a/packages/ts/generator-plugin-push/src/index.ts
+++ b/packages/ts/generator-plugin-push/src/index.ts
@@ -1,5 +1,5 @@
import Plugin from '@vaadin/hilla-generator-core/Plugin.js';
-import type SharedStorage from '@vaadin/hilla-generator-core/SharedStorage.js';
+import type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';
import type { OpenAPIV3 } from 'openapi-types';
import type { ReadonlyObjectDeep } from 'type-fest/source/readonly-deep';
import { type EndpointOperations, PushProcessor } from './PushProcessor.js';
diff --git a/packages/ts/generator-plugin-signals/src/index.ts b/packages/ts/generator-plugin-signals/src/index.ts
index 2242af4791..487ba37430 100644
--- a/packages/ts/generator-plugin-signals/src/index.ts
+++ b/packages/ts/generator-plugin-signals/src/index.ts
@@ -1,5 +1,5 @@
import Plugin from '@vaadin/hilla-generator-core/Plugin.js';
-import type SharedStorage from '@vaadin/hilla-generator-core/SharedStorage.js';
+import type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';
import type { OpenAPIV3 } from 'openapi-types';
import SignalProcessor from './SignalProcessor.js';
diff --git a/packages/ts/generator-plugin-subtypes/src/ModelFixProcessor.ts b/packages/ts/generator-plugin-subtypes/src/ModelFixProcessor.ts
index ae2b954a97..6ec356dfe0 100644
--- a/packages/ts/generator-plugin-subtypes/src/ModelFixProcessor.ts
+++ b/packages/ts/generator-plugin-subtypes/src/ModelFixProcessor.ts
@@ -1,12 +1,6 @@
import createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';
-import ts, { type PropertyName, type SourceFile } from 'typescript';
-
-function propertyNameToString(node: PropertyName): string | null {
- if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
- return node.text;
- }
- return null;
-}
+import ts, { type SourceFile } from 'typescript';
+import { propertyNameToString } from './utils.js';
export class ModelFixProcessor {
readonly #source: SourceFile;
diff --git a/packages/ts/generator-plugin-subtypes/src/SubTypesProcessor.ts b/packages/ts/generator-plugin-subtypes/src/SubTypesProcessor.ts
index 04c1a42d47..acae8c2b39 100644
--- a/packages/ts/generator-plugin-subtypes/src/SubTypesProcessor.ts
+++ b/packages/ts/generator-plugin-subtypes/src/SubTypesProcessor.ts
@@ -3,20 +3,20 @@ import {
convertReferenceSchemaToPath,
convertReferenceSchemaToSpecifier,
type ReferenceSchema,
+ simplifyFullyQualifiedName,
} from '@vaadin/hilla-generator-core/Schema.js';
-import { simplifyFullyQualifiedName } from '@vaadin/hilla-generator-core/utils.js';
import createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';
import DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';
import PathManager from '@vaadin/hilla-generator-utils/dependencies/PathManager.js';
-import ts from 'typescript';
+import ts, { type SourceFile } from 'typescript';
export class SubTypesProcessor {
readonly #typeName: string;
- readonly #source: ts.SourceFile;
+ readonly #source: SourceFile;
readonly #oneOf: ReferenceSchema[];
readonly #dependencies;
- constructor(typeName: string, source: ts.SourceFile, oneOf: ReferenceSchema[]) {
+ constructor(typeName: string, source: SourceFile, oneOf: ReferenceSchema[]) {
this.#typeName = typeName;
this.#source = source;
this.#oneOf = oneOf;
@@ -25,17 +25,17 @@ export class SubTypesProcessor {
);
}
- process(): ts.SourceFile {
+ process(): SourceFile {
const { exports, imports, paths } = this.#dependencies;
- // import all sub types and return them
+ // import all subtypes and return them
const subTypes = this.#oneOf.map((schema) => {
const path = paths.createRelativePath(convertReferenceSchemaToPath(schema));
const subType = convertReferenceSchemaToSpecifier(schema);
return imports.default.add(path, subType, true);
});
- // create a union type from the sub types
+ // create a union type from the subtypes
const union = ts.factory.createUnionTypeNode(
subTypes.map((subType) => ts.factory.createTypeReferenceNode(subType)),
);
diff --git a/packages/ts/generator-plugin-subtypes/src/TypeFixProcessor.ts b/packages/ts/generator-plugin-subtypes/src/TypeFixProcessor.ts
index d47335cf41..279ed98553 100644
--- a/packages/ts/generator-plugin-subtypes/src/TypeFixProcessor.ts
+++ b/packages/ts/generator-plugin-subtypes/src/TypeFixProcessor.ts
@@ -1,15 +1,9 @@
import createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';
-import ts from 'typescript';
-
-function propertyNameToString(node: ts.PropertyName): string | null {
- if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
- return node.text;
- }
- return null;
-}
+import ts, { type SourceFile } from 'typescript';
+import { propertyNameToString } from './utils.js';
export class TypeFixProcessor {
- readonly #source: ts.SourceFile;
+ readonly #source: SourceFile;
readonly #typeValue: string;
constructor(source: ts.SourceFile, typeValue: string) {
@@ -17,7 +11,7 @@ export class TypeFixProcessor {
this.#typeValue = typeValue;
}
- process(): ts.SourceFile {
+ process(): SourceFile {
const statements = this.#source.statements.map((statement) => {
// search in the interface definition
if (ts.isInterfaceDeclaration(statement)) {
diff --git a/packages/ts/generator-plugin-subtypes/src/index.ts b/packages/ts/generator-plugin-subtypes/src/index.ts
index 5aaf296f69..2db37889ad 100644
--- a/packages/ts/generator-plugin-subtypes/src/index.ts
+++ b/packages/ts/generator-plugin-subtypes/src/index.ts
@@ -1,7 +1,7 @@
import Plugin from '@vaadin/hilla-generator-core/Plugin.js';
-import type { ReferenceSchema } from '@vaadin/hilla-generator-core/Schema.js';
-import type SharedStorage from '@vaadin/hilla-generator-core/SharedStorage.js';
-import { convertFullyQualifiedNameToRelativePath } from '@vaadin/hilla-generator-core/utils.js';
+import { convertFullyQualifiedNameToRelativePath } from '@vaadin/hilla-generator-core/Schema.js';
+import type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';
+import type { OpenAPIV3 } from 'openapi-types';
import { ModelFixProcessor } from './ModelFixProcessor.js';
import { SubTypesProcessor } from './SubTypesProcessor.js';
import { TypeFixProcessor } from './TypeFixProcessor.js';
@@ -29,13 +29,17 @@ export default class SubTypesPlugin extends Plugin {
const fn = `${convertFullyQualifiedNameToRelativePath(baseKey)}.ts`;
const source = sources.find(({ fileName }) => fileName === fn)!;
// replace the (empty) source with a newly-generated one
- const newSource = new SubTypesProcessor(baseKey, source, baseComponent.oneOf).process();
+ const newSource = new SubTypesProcessor(
+ baseKey,
+ source,
+ baseComponent.oneOf as OpenAPIV3.ReferenceObject[],
+ ).process();
sources.splice(sources.indexOf(source), 1, newSource);
// mentioned types in the oneOf need to be fixed as well
- baseComponent.oneOf.forEach((schema) => {
+ (baseComponent.oneOf as OpenAPIV3.ReferenceObject[]).forEach((schema) => {
if ('$ref' in schema) {
- const path = (schema as ReferenceSchema).$ref;
+ const path = schema.$ref;
Object.entries(components).forEach(([subKey, subComponent]) => {
if ('anyOf' in subComponent && subKey === path.substring('#/components/schemas/'.length)) {
subComponent.anyOf?.forEach((s) => {
diff --git a/packages/ts/generator-plugin-subtypes/src/utils.ts b/packages/ts/generator-plugin-subtypes/src/utils.ts
new file mode 100644
index 0000000000..853439973f
--- /dev/null
+++ b/packages/ts/generator-plugin-subtypes/src/utils.ts
@@ -0,0 +1,8 @@
+import ts, { type PropertyName } from 'typescript';
+
+export function propertyNameToString(node: PropertyName): string | null {
+ if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
+ return node.text;
+ }
+ return null;
+}
diff --git a/packages/ts/generator-plugin-transfertypes/.eslintrc b/packages/ts/generator-plugin-transfertypes/.eslintrc
new file mode 100644
index 0000000000..6ac92d4ca9
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/.eslintrc
@@ -0,0 +1,6 @@
+{
+ "extends": ["../../../.eslintrc"],
+ "parserOptions": {
+ "project": "./tsconfig.json"
+ }
+}
diff --git a/packages/ts/generator-plugin-transfertypes/.lintstagedrc.js b/packages/ts/generator-plugin-transfertypes/.lintstagedrc.js
new file mode 100644
index 0000000000..937dc6639f
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/.lintstagedrc.js
@@ -0,0 +1,6 @@
+import { commands, extensions } from '../../../.lintstagedrc.js';
+
+export default {
+ [`src/**/*.{${extensions}}`]: commands,
+ [`test/**/*.{${extensions}}`]: commands,
+};
diff --git a/packages/ts/generator-plugin-transfertypes/LICENSE b/packages/ts/generator-plugin-transfertypes/LICENSE
new file mode 100644
index 0000000000..261eeb9e9f
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/ts/generator-plugin-transfertypes/README.md b/packages/ts/generator-plugin-transfertypes/README.md
new file mode 100644
index 0000000000..a9856d9bc1
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/README.md
@@ -0,0 +1 @@
+# Hilla TypeScript Generator JsonSubTypes Support Plugin
diff --git a/packages/ts/generator-plugin-transfertypes/package.json b/packages/ts/generator-plugin-transfertypes/package.json
new file mode 100644
index 0000000000..ba0326e9de
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "@vaadin/hilla-generator-plugin-transfertypes",
+ "version": "24.7.0-alpha10",
+ "description": "A plugin to replace types in the generated code",
+ "main": "index.js",
+ "type": "module",
+ "engines": {
+ "node": ">= 16.13"
+ },
+ "scripts": {
+ "clean:build": "git clean -fx . -e .vite -e node_modules",
+ "build": "concurrently npm:build:*",
+ "build:transpile": "tsc --isolatedModules -p tsconfig.build.json",
+ "build:copy": "cd src && copyfiles **/*.d.ts ..",
+ "lint": "eslint src test",
+ "lint:fix": "eslint src test --fix",
+ "test": "vitest --run",
+ "test:update": "vitest --update",
+ "test:coverage": "vitest --coverage",
+ "typecheck": "tsc --noEmit"
+ },
+ "exports": {
+ ".": {
+ "default": "./index.js"
+ },
+ "./index.js": {
+ "default": "./index.js"
+ }
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/vaadin/hilla.git",
+ "directory": "packages/ts/generator-plugin-transfertypes"
+ },
+ "keywords": [
+ "hilla",
+ "typescript",
+ "generator"
+ ],
+ "author": "Vaadin Ltd.",
+ "license": "Apache 2.0",
+ "bugs": {
+ "url": "https://github.com/vaadin/hilla/issues"
+ },
+ "homepage": "https://hilla.dev",
+ "files": [
+ "*.{d.ts.map,d.ts,js.map,js}"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {
+ "@vaadin/hilla-generator-core": "24.7.0-alpha10",
+ "@vaadin/hilla-generator-plugin-client": "24.7.0-alpha10",
+ "@vaadin/hilla-generator-plugin-model": "24.7.0-alpha10",
+ "@vaadin/hilla-generator-utils": "24.7.0-alpha10",
+ "fast-deep-equal": "3.1.3",
+ "openapi-types": "12.1.3",
+ "typescript": "5.7.3"
+ }
+}
diff --git a/packages/ts/generator-plugin-transfertypes/src/index.ts b/packages/ts/generator-plugin-transfertypes/src/index.ts
new file mode 100644
index 0000000000..26b4bd0c00
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/src/index.ts
@@ -0,0 +1,20 @@
+import Plugin from '@vaadin/hilla-generator-core/Plugin.js';
+import type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';
+import type { Writable } from 'type-fest';
+import ts from 'typescript';
+
+export default class TransferTypesPlugin extends Plugin {
+ override get path(): string {
+ return import.meta.url;
+ }
+
+ override execute({ api: { components }, transferTypes }: SharedStorage): void {
+ transferTypes.set('com.vaadin.hilla.runtime.transfertypes.File', () => ts.factory.createTypeReferenceNode('File'));
+
+ if (components?.schemas) {
+ (components as Writable).schemas = Object.fromEntries(
+ Object.entries(components.schemas).filter(([key]) => key !== 'com.vaadin.hilla.runtime.transfertypes.File'),
+ );
+ }
+ }
+}
diff --git a/packages/ts/generator-plugin-transfertypes/test/file-type/MultipartFile.json b/packages/ts/generator-plugin-transfertypes/test/file-type/MultipartFile.json
new file mode 100644
index 0000000000..8fb7e41a4f
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/test/file-type/MultipartFile.json
@@ -0,0 +1,60 @@
+{
+ "openapi" : "3.0.1",
+ "info" : {
+ "title" : "Hilla Application",
+ "version" : "1.0.0"
+ },
+ "servers" : [
+ {
+ "url" : "http://localhost:8080/connect",
+ "description" : "Hilla Backend"
+ }
+ ],
+ "tags" : [
+ {
+ "name" : "MultipartFileEndpoint",
+ "x-class-name" : "com.vaadin.hilla.parser.plugins.transfertypes.file.MultipartFileEndpoint"
+ }
+ ],
+ "paths" : {
+ "/MultipartFileEndpoint/uploadFile" : {
+ "post" : {
+ "tags" : [
+ "MultipartFileEndpoint"
+ ],
+ "operationId" : "MultipartFileEndpoint_uploadFile_POST",
+ "requestBody" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "type" : "object",
+ "properties" : {
+ "file" : {
+ "nullable" : true,
+ "anyOf" : [
+ {
+ "$ref" : "#/components/schemas/com.vaadin.hilla.runtime.transfertypes.File"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses" : {
+ "200" : {
+ "description" : ""
+ }
+ }
+ }
+ }
+ },
+ "components" : {
+ "schemas" : {
+ "com.vaadin.hilla.runtime.transfertypes.File" : {
+ "type" : "object"
+ }
+ }
+ }
+}
diff --git a/packages/ts/generator-plugin-transfertypes/test/file-type/MultipartFile.spec.ts b/packages/ts/generator-plugin-transfertypes/test/file-type/MultipartFile.spec.ts
new file mode 100644
index 0000000000..786f60c592
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/test/file-type/MultipartFile.spec.ts
@@ -0,0 +1,18 @@
+import BackbonePlugin from '@vaadin/hilla-generator-plugin-backbone';
+import { describe, it, expect } from 'vitest';
+import TransferTypesPlugin from '../../src/index.js';
+import { createGenerator, loadInput } from '../utils/common.js';
+
+describe('TransferTypesPlugin', () => {
+ describe('for MultipartFile type', () => {
+ it('correctly replaces the incoming type', async () => {
+ const sectionName = 'MultipartFile';
+ const generator = createGenerator([TransferTypesPlugin, BackbonePlugin]);
+ const input = await loadInput(sectionName, import.meta.url);
+ const files = await generator.process(input);
+ expect(files).to.have.length(1);
+ expect(files[0].name).to.equal('MultipartFileEndpoint.ts');
+ await expect(await files[0].text()).toMatchFileSnapshot('./fixtures/MultipartFileEndpoint.snap.ts');
+ });
+ });
+});
diff --git a/packages/ts/generator-plugin-transfertypes/test/file-type/fixtures/MultipartFileEndpoint.snap.ts b/packages/ts/generator-plugin-transfertypes/test/file-type/fixtures/MultipartFileEndpoint.snap.ts
new file mode 100644
index 0000000000..91477191d6
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/test/file-type/fixtures/MultipartFileEndpoint.snap.ts
@@ -0,0 +1,4 @@
+import { EndpointRequestInit as EndpointRequestInit_1 } from "@vaadin/hilla-frontend";
+import client_1 from "./connect-client.default.js";
+async function uploadFile_1(file: File | undefined, init?: EndpointRequestInit_1): Promise { return client_1.call("MultipartFileEndpoint", "uploadFile", { file }, init); }
+export { uploadFile_1 as uploadFile };
diff --git a/packages/ts/generator-plugin-transfertypes/test/utils/common.ts b/packages/ts/generator-plugin-transfertypes/test/utils/common.ts
new file mode 100644
index 0000000000..85af6fa91f
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/test/utils/common.ts
@@ -0,0 +1,14 @@
+import { readFile } from 'node:fs/promises';
+import Generator from '@vaadin/hilla-generator-core/Generator.js';
+import type { PluginConstructor } from '@vaadin/hilla-generator-core/Plugin.js';
+import LoggerFactory from '@vaadin/hilla-generator-utils/LoggerFactory.js';
+
+export const pathBase = 'com/vaadin/hilla/parser/plugins/transfertypes';
+
+export function createGenerator(plugins: readonly PluginConstructor[]): Generator {
+ return new Generator(plugins, { logger: new LoggerFactory({ name: 'tsgen-test-transfertypes', verbose: true }) });
+}
+
+export async function loadInput(name: string, importMeta: string): Promise {
+ return readFile(new URL(`./${name}.json`, importMeta), 'utf8');
+}
diff --git a/packages/ts/generator-plugin-transfertypes/tsconfig.build.json b/packages/ts/generator-plugin-transfertypes/tsconfig.build.json
new file mode 100644
index 0000000000..445f3f4492
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/tsconfig.build.json
@@ -0,0 +1,8 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "."
+ },
+ "include": ["src"]
+}
diff --git a/packages/ts/generator-plugin-transfertypes/tsconfig.json b/packages/ts/generator-plugin-transfertypes/tsconfig.json
new file mode 100644
index 0000000000..cf0eb8454e
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "../../../tsconfig.json",
+ "include": ["src", "test"],
+ "exclude": ["test/**/*.snap.ts"]
+}
diff --git a/packages/ts/generator-plugin-transfertypes/vitest.config.ts b/packages/ts/generator-plugin-transfertypes/vitest.config.ts
new file mode 100644
index 0000000000..cfb31652df
--- /dev/null
+++ b/packages/ts/generator-plugin-transfertypes/vitest.config.ts
@@ -0,0 +1,3 @@
+import sharedConfig from '../../../scripts/vite/node.vitest.config.js';
+
+export default sharedConfig;
diff --git a/scripts/build.ts b/scripts/build.ts
index 1a6c10a128..d5ddcbb9fb 100644
--- a/scripts/build.ts
+++ b/scripts/build.ts
@@ -11,7 +11,7 @@ const scriptsDir = new URL('./', import.meta.url);
const packageRoot = pathToFileURL(process.cwd() + sep);
const [packageJsonFile, srcFiles] = await Promise.all([
readFile(new URL('package.json', packageRoot), 'utf8'),
- glob('src/**/*.{ts,tsx,obj.css}', { ignore: ['**/*.d.ts'] }),
+ glob('src/**/*.{ts,tsx,obj.css}', { ignore: ['**/*.d.ts', '**/*.t.ts'] }),
]);
const packageJson: PackageJson = JSON.parse(packageJsonFile);
@@ -55,7 +55,7 @@ await build({
// Adds a __REGISTER__ function definition everywhere in the built code where
// the call for that function exists.
inject: [fileURLToPath(new URL('./register.js', scriptsDir))],
- entryPoints: srcFiles.map((file) => new URL(file, packageRoot)).map(fileURLToPath),
+ entryPoints: srcFiles.map((file) => new URL(file, packageRoot)).map((url) => fileURLToPath(url)),
format: 'esm',
outdir: fileURLToPath(packageRoot),
packages: 'external',