svelte-reflector 2.5.1 → 2.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -22,6 +22,15 @@ export class SchemaClassRenderer {
|
|
|
22
22
|
const keys = [];
|
|
23
23
|
const bundleParams = [];
|
|
24
24
|
let staticMethod = "";
|
|
25
|
+
// Optional, always-instantiated sub-DTOs (`nome? = $state<T>(new T)`): the
|
|
26
|
+
// runtime gate reads this set to skip an empty optional block instead of
|
|
27
|
+
// flagging its inner `required` fields. Emitted only when non-empty.
|
|
28
|
+
const optionalDtoNames = objectProps
|
|
29
|
+
.map((prop) => prop.optionalGateName())
|
|
30
|
+
.filter((name) => name !== null);
|
|
31
|
+
const optionalDtosDecl = optionalDtoNames.length
|
|
32
|
+
? `readonly _optionalDtos = new Set<string>([${optionalDtoNames.map((name) => `"${name}"`).join(", ")}])`
|
|
33
|
+
: "";
|
|
25
34
|
primitiveProps.forEach((prop) => {
|
|
26
35
|
constructorThis.push(prop.constructorBuild());
|
|
27
36
|
bundleParams.push(prop.bundleBuild());
|
|
@@ -93,7 +102,7 @@ export class SchemaClassRenderer {
|
|
|
93
102
|
: responseBundle;
|
|
94
103
|
const schema = `
|
|
95
104
|
export class ${name} {
|
|
96
|
-
${keys.join("
|
|
105
|
+
${[...keys, optionalDtosDecl].filter(Boolean).join(";\n ")}
|
|
97
106
|
|
|
98
107
|
${constructorCode}
|
|
99
108
|
|
|
@@ -31,7 +31,7 @@ export class SchemaPropertyClassifier {
|
|
|
31
31
|
isNullable: value.nullable,
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
-
return SchemaPropertyClassifier.classifyObject({ key, value });
|
|
34
|
+
return SchemaPropertyClassifier.classifyObject({ key, value, requireds });
|
|
35
35
|
}
|
|
36
36
|
const required = requireds.includes(key);
|
|
37
37
|
const items = value.items;
|
|
@@ -107,10 +107,10 @@ export class SchemaPropertyClassifier {
|
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
static classifyObject(params) {
|
|
110
|
-
const { value, key } = params;
|
|
110
|
+
const { value, key, requireds } = params;
|
|
111
111
|
if ("allOf" in value) {
|
|
112
112
|
const ref = value.allOf?.[0];
|
|
113
|
-
const isRequired =
|
|
113
|
+
const isRequired = requireds.includes(key);
|
|
114
114
|
const nullable = !!value.nullable;
|
|
115
115
|
if (ref && isReferenceObject(ref)) {
|
|
116
116
|
return new ObjectProp({ name: key, referenceObject: ref, isRequired, isNullable: nullable });
|
|
@@ -118,7 +118,7 @@ export class SchemaPropertyClassifier {
|
|
|
118
118
|
return null;
|
|
119
119
|
}
|
|
120
120
|
if (isReferenceObject(value)) {
|
|
121
|
-
return new ObjectProp({ name: key, referenceObject: value });
|
|
121
|
+
return new ObjectProp({ name: key, referenceObject: value, isRequired: requireds.includes(key) });
|
|
122
122
|
}
|
|
123
123
|
return null;
|
|
124
124
|
}
|
|
@@ -14,5 +14,14 @@ export declare class ObjectProp {
|
|
|
14
14
|
classBuild(): string;
|
|
15
15
|
interfaceBuild(): string;
|
|
16
16
|
bundleBuild(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Field name when this sub-DTO is emitted as optional AND always-instantiated
|
|
19
|
+
* (`nome? = $state<T>(new T)` — `!required && !nullable`), so the client-side
|
|
20
|
+
* gate (`validateForm`) must skip it when empty instead of validating its inner
|
|
21
|
+
* `required` fields as mandatory. `null` for required or nullable sub-DTOs (the
|
|
22
|
+
* latter defaults to `null` and is already skipped at runtime). Mirrors the `?`
|
|
23
|
+
* modifier `classBuild` emits, keeping the runtime gate consistent with the type.
|
|
24
|
+
*/
|
|
25
|
+
optionalGateName(): string | null;
|
|
17
26
|
hydrateBuild(): string;
|
|
18
27
|
}
|
|
@@ -7,7 +7,7 @@ export class ObjectProp {
|
|
|
7
7
|
const { referenceObject, name, isRequired, isNullable } = params;
|
|
8
8
|
this.name = name;
|
|
9
9
|
this.type = referenceObject.$ref.split("/").at(-1) ?? "";
|
|
10
|
-
this.required = isRequired ??
|
|
10
|
+
this.required = isRequired ?? false;
|
|
11
11
|
this.isNullable = !!isNullable;
|
|
12
12
|
}
|
|
13
13
|
constructorBuild() {
|
|
@@ -31,6 +31,17 @@ export class ObjectProp {
|
|
|
31
31
|
const nullable = this.isNullable ? "?? null" : "";
|
|
32
32
|
return `${this.name}: this.${this.name}?.bundle() ${nullable}`;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Field name when this sub-DTO is emitted as optional AND always-instantiated
|
|
36
|
+
* (`nome? = $state<T>(new T)` — `!required && !nullable`), so the client-side
|
|
37
|
+
* gate (`validateForm`) must skip it when empty instead of validating its inner
|
|
38
|
+
* `required` fields as mandatory. `null` for required or nullable sub-DTOs (the
|
|
39
|
+
* latter defaults to `null` and is already skipped at runtime). Mirrors the `?`
|
|
40
|
+
* modifier `classBuild` emits, keeping the runtime gate consistent with the type.
|
|
41
|
+
*/
|
|
42
|
+
optionalGateName() {
|
|
43
|
+
return !this.required && !this.isNullable ? this.name : null;
|
|
44
|
+
}
|
|
34
45
|
hydrateBuild() {
|
|
35
46
|
if (this.isNullable) {
|
|
36
47
|
return `if (data.${this.name} !== undefined) {
|
|
@@ -74,6 +74,7 @@ export class BuildedInput<T> {
|
|
|
74
74
|
private _value = $state<T>(null as any);
|
|
75
75
|
private serverErrorMessage = $state<string | null>(null);
|
|
76
76
|
private serverErrorValue = $state.raw<T | null>(null);
|
|
77
|
+
showError = $state(false);
|
|
77
78
|
sanitizer?: Sanitizer;
|
|
78
79
|
required: boolean;
|
|
79
80
|
nullable: boolean;
|
|
@@ -180,6 +181,14 @@ export class BuildedInput<T> {
|
|
|
180
181
|
return this.validator(this.value);
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
touch(): void {
|
|
185
|
+
this.showError = true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
untouch(): void {
|
|
189
|
+
this.showError = false;
|
|
190
|
+
}
|
|
191
|
+
|
|
183
192
|
setServerError(message: string) {
|
|
184
193
|
this.serverErrorMessage = message;
|
|
185
194
|
this.serverErrorValue = this.value;
|
|
@@ -241,6 +250,11 @@ export function build<T>(params: {
|
|
|
241
250
|
return new BuildedInput(params);
|
|
242
251
|
}
|
|
243
252
|
|
|
253
|
+
/**
|
|
254
|
+
* @deprecated Use `validateForm` — `isFormValid` faz `throw` no primeiro campo
|
|
255
|
+
* inválido (nunca retorna `false`), ignora o flag `required` e muta o schema
|
|
256
|
+
* (`delete schema.bundle`). Remoção real fica para o próximo major.
|
|
257
|
+
*/
|
|
244
258
|
export function isFormValid<T>(schema: PartialBuildedInput<T>): boolean {
|
|
245
259
|
delete (schema as { bundle?: unknown }).bundle;
|
|
246
260
|
|
|
@@ -266,6 +280,93 @@ export function isFormValid<T>(schema: PartialBuildedInput<T>): boolean {
|
|
|
266
280
|
return isValid;
|
|
267
281
|
}
|
|
268
282
|
|
|
283
|
+
function isNestedDto(v: unknown): v is Record<string, unknown> {
|
|
284
|
+
return v != null && typeof v === "object" && typeof (v as { bundle?: unknown }).bundle === "function";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function isInputEmpty(input: BuildedInput<unknown>): boolean {
|
|
288
|
+
return input.value === "" || input.value === null || input.value === undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Visita os `BuildedInput` do schema, recursando em DTO aninhado (campo com
|
|
293
|
+
* `.bundle()`) e em array de DTOs — simétrico ao `bundleInputs`/`bundle`, que
|
|
294
|
+
* também recursam. Sem short-circuit: aplica `fn` em cada campo.
|
|
295
|
+
*
|
|
296
|
+
* `skipOptionalEmpty`: pula um sub-DTO listado no `_optionalDtos` do pai cujos
|
|
297
|
+
* campos estão TODOS vazios. O codegen marca os sub-DTOs **opcionais
|
|
298
|
+
* always-instantiated** (`nome? = $state<T>(new T)`); sem isso o gate validaria
|
|
299
|
+
* o `required` interno de um bloco opcional em branco e bloquearia o submit.
|
|
300
|
+
*/
|
|
301
|
+
function forEachBuildedInput(
|
|
302
|
+
schema: object,
|
|
303
|
+
fn: (input: BuildedInput<unknown>) => void,
|
|
304
|
+
skipOptionalEmpty: boolean,
|
|
305
|
+
): void {
|
|
306
|
+
const optional = skipOptionalEmpty
|
|
307
|
+
? (schema as { _optionalDtos?: Set<string> })._optionalDtos
|
|
308
|
+
: undefined;
|
|
309
|
+
for (const [key, v] of Object.entries(schema)) {
|
|
310
|
+
if (isBuildedInput(v)) {
|
|
311
|
+
fn(v);
|
|
312
|
+
} else if (Array.isArray(v)) {
|
|
313
|
+
for (const item of v) {
|
|
314
|
+
if (isBuildedInput(item)) fn(item);
|
|
315
|
+
else if (isNestedDto(item)) forEachBuildedInput(item, fn, skipOptionalEmpty);
|
|
316
|
+
}
|
|
317
|
+
} else if (isNestedDto(v)) {
|
|
318
|
+
if (optional?.has(key) && isEmptyDto(v)) continue;
|
|
319
|
+
forEachBuildedInput(v, fn, skipOptionalEmpty);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function isEmptyDto(dto: object): boolean {
|
|
325
|
+
let empty = true;
|
|
326
|
+
forEachBuildedInput(
|
|
327
|
+
dto,
|
|
328
|
+
(input) => {
|
|
329
|
+
if (!isInputEmpty(input)) empty = false;
|
|
330
|
+
},
|
|
331
|
+
false,
|
|
332
|
+
);
|
|
333
|
+
return empty;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Gate de validação client-side honesto: percorre os campos `BuildedInput` do
|
|
338
|
+
* schema (recursando em DTO aninhado e array de DTOs), chama `touch()` em cada um
|
|
339
|
+
* (acende o erro por-campo no submit) e retorna `true` só se todos forem válidos.
|
|
340
|
+
* Campo vazio é inválido quando `required`; caso contrário consulta o `validator`.
|
|
341
|
+
* Sub-DTO opcional inteiramente vazio é pulado (não bloqueia bloco em branco).
|
|
342
|
+
* Sem `throw`, sem `toast`, sem mutar o schema. Tocar todos é de propósito (não
|
|
343
|
+
* `.every`, que short-circuita e deixaria campos sem acender).
|
|
344
|
+
*/
|
|
345
|
+
export function validateForm(schema: object): boolean {
|
|
346
|
+
let valid = true;
|
|
347
|
+
forEachBuildedInput(
|
|
348
|
+
schema,
|
|
349
|
+
(input) => {
|
|
350
|
+
input.touch();
|
|
351
|
+
const fieldValid = isInputEmpty(input) ? !input.required : input.validate() === null;
|
|
352
|
+
if (!fieldValid) valid = false;
|
|
353
|
+
},
|
|
354
|
+
true,
|
|
355
|
+
);
|
|
356
|
+
return valid;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Simétrico do `validateForm`: apaga `showError` de todos os campos do schema
|
|
361
|
+
* (incl. aninhados, sem pular opcional). Usar no `onClose`/reset de modal que
|
|
362
|
+
* reusa a instância do form. (Quando o endpoint faz `reset()` → `new Dto()`,
|
|
363
|
+
* `showError` já volta `false` de graça; o untouch só é necessário pra instância
|
|
364
|
+
* reutilizada.)
|
|
365
|
+
*/
|
|
366
|
+
export function untouchForm(schema: object): void {
|
|
367
|
+
forEachBuildedInput(schema, (input) => input.untouch(), false);
|
|
368
|
+
}
|
|
369
|
+
|
|
269
370
|
export function genericArrayBundler(data: string[]): string[];
|
|
270
371
|
export function genericArrayBundler<T extends { bundle: () => BundleResult<T> }>(
|
|
271
372
|
data: T[],
|