svelte-reflector 1.2.1 → 1.3.0

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.
@@ -4,7 +4,7 @@ import * as path from "node:path";
4
4
  import * as fs from "node:fs";
5
5
  import { Reflector } from "./main.js";
6
6
  import { Source } from "./file.js";
7
- import { parseValidatorFieldsFromConfig } from "./helpers/generate-doc.helper.js";
7
+ import { parseFieldConfigsFromConfig, parseTypeImportsFromTypesFile } from "./helpers/generate-doc.helper.js";
8
8
  //
9
9
  /** ajuda a pegar a 1ª env definida dentre várias chaves possíveis */
10
10
  function pickEnv(...keys) {
@@ -43,11 +43,12 @@ export async function reflector(manual = false) {
43
43
  }
44
44
  const DOC_URL = `${BACKEND_URL}openapi.json`;
45
45
  let data;
46
- let validators = new Map();
46
+ let fieldConfigs = new Map();
47
+ let typeImports = new Map();
47
48
  try {
48
49
  const documentation = await axios.get(DOC_URL, { timeout: 15000 });
49
50
  data = documentation.data;
50
- const backup = new Source({ path: "src/backup.json", data: JSON.stringify(data) });
51
+ const backup = new Source({ path: "src/reflector/backup.json", data: JSON.stringify(data) });
51
52
  await backup.save();
52
53
  }
53
54
  catch (e) {
@@ -56,18 +57,31 @@ export async function reflector(manual = false) {
56
57
  data = JSON.parse(fs.readFileSync(backupPath, "utf8"));
57
58
  }
58
59
  try {
59
- const fieldsConfig = path.resolve(process.cwd(), "src/reflector.config.ts");
60
- const configText = fs.readFileSync(fieldsConfig, "utf8");
61
- const parsedRelations = parseValidatorFieldsFromConfig(configText);
62
- parsedRelations.forEach((rel) => {
60
+ const configPath = path.resolve(process.cwd(), "src/reflector.config.ts");
61
+ const configText = fs.readFileSync(configPath, "utf8");
62
+ const parsedConfigs = parseFieldConfigsFromConfig(configText);
63
+ parsedConfigs.forEach((rel) => {
63
64
  rel.fields.forEach((field) => {
64
- validators.set(field, rel.validator);
65
+ const config = {};
66
+ if (rel.validator)
67
+ config.validator = rel.validator;
68
+ if (rel.type)
69
+ config.type = rel.type;
70
+ fieldConfigs.set(field, config);
65
71
  });
66
72
  });
67
73
  }
68
74
  catch (e) {
69
75
  console.warn("[reflector] Não consegui ler/parsear reflector.config.ts", e);
70
76
  }
77
+ try {
78
+ const typesPath = path.resolve(process.cwd(), "src/reflector.types.ts");
79
+ const typesText = fs.readFileSync(typesPath, "utf8");
80
+ typeImports = parseTypeImportsFromTypesFile(typesText);
81
+ }
82
+ catch {
83
+ // reflector.types.ts não encontrado — tipos customizados não terão imports
84
+ }
71
85
  // Lê reflector.json do projeto consumidor (opcional)
72
86
  let apiImport = "$lib/api";
73
87
  try {
@@ -85,7 +99,7 @@ export async function reflector(manual = false) {
85
99
  console.warn("[reflector] OpenAPI sem components; abortando.");
86
100
  return breakReflector();
87
101
  }
88
- const r = new Reflector({ components, paths, validators, apiImport });
102
+ const r = new Reflector({ components, paths, fieldConfigs, typeImports, apiImport });
89
103
  await r.build();
90
104
  await r.localSave(data);
91
105
  return breakReflector();
@@ -1,6 +1,8 @@
1
- interface LooseValidatorField {
1
+ interface LooseFieldConfig {
2
2
  fields: string[];
3
- validator: string;
3
+ validator?: string;
4
+ type?: string;
4
5
  }
5
- export declare function parseValidatorFieldsFromConfig(code: string): LooseValidatorField[];
6
+ export declare function parseFieldConfigsFromConfig(code: string): LooseFieldConfig[];
7
+ export declare function parseTypeImportsFromTypesFile(code: string): Map<string, string>;
6
8
  export {};
@@ -2,10 +2,10 @@ function stripComments(code) {
2
2
  // remove //... e /* ... */
3
3
  return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "");
4
4
  }
5
- function extractValidatorsArraySource(code) {
6
- // tenta achar: export const validators ... = [ ... ];
5
+ function extractFieldConfigsArraySource(code) {
6
+ // tenta achar: export const fieldConfigs ... = [ ... ];
7
7
  const cleaned = stripComments(code);
8
- const startIdx = cleaned.search(/\bexport\s+const\s+validators\b/);
8
+ const startIdx = cleaned.search(/\bexport\s+const\s+fieldConfigs\b/);
9
9
  if (startIdx < 0)
10
10
  return null;
11
11
  const fromStart = cleaned.slice(startIdx);
@@ -52,27 +52,74 @@ function extractValidatorsArraySource(code) {
52
52
  }
53
53
  return null;
54
54
  }
55
- export function parseValidatorFieldsFromConfig(code) {
56
- const arrSrc = extractValidatorsArraySource(code);
55
+ function extractObjectBlocks(arrSrc) {
56
+ const blocks = [];
57
+ let depth = 0;
58
+ let start = -1;
59
+ for (let i = 0; i < arrSrc.length; i++) {
60
+ const ch = arrSrc[i];
61
+ if (ch === "{") {
62
+ if (depth === 0)
63
+ start = i;
64
+ depth++;
65
+ }
66
+ else if (ch === "}") {
67
+ depth--;
68
+ if (depth === 0 && start >= 0) {
69
+ blocks.push(arrSrc.slice(start, i + 1));
70
+ start = -1;
71
+ }
72
+ }
73
+ }
74
+ return blocks;
75
+ }
76
+ export function parseFieldConfigsFromConfig(code) {
77
+ const arrSrc = extractFieldConfigsArraySource(code);
57
78
  if (!arrSrc)
58
79
  return [];
59
- // captura objetos do tipo:
60
- // { fields: ['email'], validator: validateInputs.email, }
61
- const objRegex = /\{\s*fields\s*:\s*\[([\s\S]*?)\]\s*,\s*validator\s*:\s*([A-Za-z0-9_$.]+)\s*,?\s*\}/g;
80
+ const blocks = extractObjectBlocks(arrSrc);
62
81
  const results = [];
63
- let m;
64
- while ((m = objRegex.exec(arrSrc))) {
65
- const fieldsRaw = m[1] ?? "";
66
- const validatorRaw = (m[2] ?? "").trim();
67
- // pega strings dentro do array de fields (aceita ' " `)
82
+ for (const block of blocks) {
83
+ // extrai fields: [...]
84
+ const fieldsMatch = block.match(/fields\s*:\s*\[([\s\S]*?)\]/);
85
+ if (!fieldsMatch)
86
+ continue;
68
87
  const fields = [];
69
88
  const strRegex = /['"`]([^'"`]+)['"`]/g;
70
89
  let sm;
71
- while ((sm = strRegex.exec(fieldsRaw))) {
90
+ while ((sm = strRegex.exec(fieldsMatch[1] ?? ""))) {
72
91
  if (sm[1])
73
92
  fields.push(sm[1]);
74
93
  }
75
- results.push({ fields, validator: validatorRaw });
94
+ if (fields.length === 0)
95
+ continue;
96
+ // extrai validator (referência sem aspas, ex: validateInputs.email)
97
+ const validatorMatch = block.match(/validator\s*:\s*([A-Za-z0-9_$.]+)/);
98
+ const validator = validatorMatch?.[1]?.trim();
99
+ // extrai type (string literal com aspas, ex: 'IconName')
100
+ const typeMatch = block.match(/type\s*:\s*['"`]([^'"`]+)['"`]/);
101
+ const type = typeMatch?.[1]?.trim();
102
+ const config = { fields };
103
+ if (validator)
104
+ config.validator = validator;
105
+ if (type)
106
+ config.type = type;
107
+ results.push(config);
76
108
  }
77
109
  return results;
78
110
  }
111
+ export function parseTypeImportsFromTypesFile(code) {
112
+ const result = new Map();
113
+ const importRegex = /import\s+type\s*\{([^}]+)\}\s*from\s*['"`]([^'"`]+)['"`]/g;
114
+ let m;
115
+ while ((m = importRegex.exec(code))) {
116
+ const names = (m[1] ?? "").split(",").map((n) => n.trim()).filter(Boolean);
117
+ const source = m[2] ?? "";
118
+ if (!source)
119
+ continue;
120
+ for (const name of names) {
121
+ result.set(name, `import type { ${name} } from '${source}'`);
122
+ }
123
+ }
124
+ return result;
125
+ }
package/dist/main.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Source } from "./file.js";
2
2
  import { Schema } from "./schema.js";
3
3
  import type { ComponentsObject, PathsObject, OpenAPIObject } from "./types/open-api-spec.interface.js";
4
- import type { FieldValidators } from "./types/types.js";
4
+ import type { FieldConfigs, TypeImports } from "./types/types.js";
5
5
  import { Module } from "./module.js";
6
6
  export declare const enumTypes: Map<string, string>;
7
7
  export declare const mockedParams: Set<string>;
@@ -13,6 +13,7 @@ export declare class Reflector {
13
13
  readonly src: Source;
14
14
  readonly typesSrc: Source;
15
15
  private readonly schemaMap;
16
+ private readonly typeImports;
16
17
  readonly fieldsFile: Source;
17
18
  readonly enumFile: Source;
18
19
  readonly mockedParamsFile: Source;
@@ -23,7 +24,8 @@ export declare class Reflector {
23
24
  constructor(params: {
24
25
  components: ComponentsObject;
25
26
  paths: PathsObject;
26
- validators: FieldValidators;
27
+ fieldConfigs: FieldConfigs;
28
+ typeImports: TypeImports;
27
29
  apiImport: string;
28
30
  });
29
31
  private getSchemas;
package/dist/main.js CHANGED
@@ -4,18 +4,19 @@ import { Source } from "./file.js";
4
4
  import { getEndpoint, isReferenceObject, splitByUppercase } from "./helpers/helpers.js";
5
5
  import { Schema } from "./schema.js";
6
6
  import { Module } from "./module.js";
7
- import { baseDir, generatedDir } from "./vars.global.js";
7
+ import { generatedDir } from "./vars.global.js";
8
8
  import { ReflectorFile } from "./reflector.js";
9
9
  export const enumTypes = new Map();
10
10
  export const mockedParams = new Set();
11
11
  export class Reflector {
12
12
  components;
13
13
  paths;
14
- localDoc = new Source({ path: path.resolve(process.cwd(), `${baseDir}/backup.json`) });
14
+ localDoc = new Source({ path: path.resolve(process.cwd(), `${generatedDir}/backup.json`) });
15
15
  propertiesNames = new Set();
16
16
  src = new Source({ path: path.resolve(process.cwd(), `${generatedDir}/controllers`) });
17
17
  typesSrc = new Source({ path: path.resolve(process.cwd(), `${generatedDir}/reflector.svelte.ts`) });
18
18
  schemaMap = new Map();
19
+ typeImports;
19
20
  fieldsFile = new Source({ path: path.resolve(process.cwd(), `${generatedDir}/fields.ts`) });
20
21
  enumFile = new Source({ path: path.resolve(process.cwd(), `${generatedDir}/enums.ts`) });
21
22
  mockedParamsFile = new Source({ path: path.resolve(process.cwd(), `${generatedDir}/mocked-params.svelte.ts`) });
@@ -24,8 +25,9 @@ export class Reflector {
24
25
  modules;
25
26
  apiImport;
26
27
  constructor(params) {
27
- const { components, paths, validators, apiImport } = params;
28
+ const { components, paths, fieldConfigs, typeImports, apiImport } = params;
28
29
  this.apiImport = apiImport;
30
+ this.typeImports = typeImports;
29
31
  // Limpa estado global entre execuções
30
32
  enumTypes.clear();
31
33
  mockedParams.clear();
@@ -34,12 +36,12 @@ export class Reflector {
34
36
  this.paths = paths;
35
37
  this.files = [];
36
38
  this.modules = this.getModules();
37
- const { propertiesNames, schemas } = this.getSchemas({ validators });
39
+ const { propertiesNames, schemas } = this.getSchemas({ fieldConfigs });
38
40
  this.propertiesNames = propertiesNames;
39
41
  this.schemas = schemas;
40
42
  }
41
43
  getSchemas(params) {
42
- const { validators } = params;
44
+ const { fieldConfigs } = params;
43
45
  const componentSchemas = this.components.schemas;
44
46
  const schemas = [];
45
47
  const propertiesNames = new Set();
@@ -57,7 +59,7 @@ export class Reflector {
57
59
  Object.keys(properties).forEach((prop) => {
58
60
  propertiesNames.add(prop);
59
61
  });
60
- schemas.push(new Schema({ ...schema, isEmpty: false, validators }));
62
+ schemas.push(new Schema({ ...schema, isEmpty: false, fieldConfigs }));
61
63
  }
62
64
  // Build schema lookup map for per-module splitting
63
65
  for (const schema of schemas) {
@@ -183,10 +185,14 @@ export class Reflector {
183
185
  buildSchemaFileContent(schemas) {
184
186
  // Collect enum deps from all schemas in this file
185
187
  const enumDeps = new Set();
188
+ const customTypeDeps = new Set();
186
189
  for (const s of schemas) {
187
190
  for (const e of s.enumDeps) {
188
191
  enumDeps.add(e);
189
192
  }
193
+ for (const t of s.customTypeDeps) {
194
+ customTypeDeps.add(t);
195
+ }
190
196
  }
191
197
  const treatedSchemas = schemas.map((s) => `${s.interface};\n${s.schema};`);
192
198
  const imports = [
@@ -196,6 +202,12 @@ export class Reflector {
196
202
  if (enumDeps.size > 0) {
197
203
  imports.push(`import type { ${[...enumDeps].join(", ")} } from "$reflector/enums"`);
198
204
  }
205
+ for (const typeName of customTypeDeps) {
206
+ const importStatement = this.typeImports.get(typeName);
207
+ if (importStatement) {
208
+ imports.push(importStatement + ";");
209
+ }
210
+ }
199
211
  imports.push("import { PUBLIC_ENVIRONMENT } from '$env/static/public';");
200
212
  imports.push("const isEmpty = PUBLIC_ENVIRONMENT !== 'DEV';");
201
213
  return imports.join("\n") + "\n" + treatedSchemas.join("\n");
@@ -10,14 +10,17 @@ export declare class PrimitiveProp {
10
10
  private readonly required;
11
11
  private readonly isNullable;
12
12
  readonly rawType: ReflectorParamType;
13
+ readonly customType: string | undefined;
13
14
  private readonly buildedConst;
14
15
  private readonly example;
15
16
  private readonly fallbackExample;
17
+ private get effectiveType();
16
18
  constructor(params: {
17
19
  name: string;
18
20
  schemaObject: SchemaObject;
19
21
  required: boolean;
20
22
  validator: string | undefined;
23
+ customType?: string | undefined;
21
24
  isParam: boolean | undefined;
22
25
  isNullable?: boolean | undefined;
23
26
  });
@@ -7,20 +7,25 @@ export class PrimitiveProp {
7
7
  required;
8
8
  isNullable;
9
9
  rawType;
10
+ customType;
10
11
  buildedConst;
11
12
  example;
12
13
  fallbackExample;
14
+ get effectiveType() {
15
+ return this.customType ?? this.rawType;
16
+ }
13
17
  constructor(params) {
14
- const { name, schemaObject, required, validator, isParam, isNullable } = params;
18
+ const { name, schemaObject, required, validator, customType, isParam, isNullable } = params;
15
19
  const { type: rawType } = schemaObject;
16
20
  this.isNullable = !!isNullable;
17
21
  const type = rawType ?? "string";
18
22
  const { emptyExample, example } = this.getExampleAndFallback({ schemaObject, type, name });
19
23
  this.example = example;
20
24
  this.fallbackExample = emptyExample;
21
- const buildedType = type;
25
+ const buildedType = customType ?? type;
22
26
  this.name = this.treatName(name);
23
27
  this.rawType = type ?? "any";
28
+ this.customType = customType;
24
29
  this.type = `BuildedInput<${buildedType}>`;
25
30
  this.required = required;
26
31
  this.isParam = !!isParam;
@@ -90,7 +95,7 @@ export class PrimitiveProp {
90
95
  };
91
96
  const buildedExample = `params?.empty || isEmpty ? ${this.fallbackExample} : ${this.example}`;
92
97
  const keyExpr = this.isNullable ? `params?.data?.${name} ?? null` : `params?.data?.${name}`;
93
- const typeParam = this.isNullable ? `<${this.rawType} | null>` : "";
98
+ const typeParam = this.isNullable ? `<${this.effectiveType} | null>` : "";
94
99
  return `
95
100
  build${typeParam}({ key: ${keyExpr}, placeholder: ${this.example}, example: ${buildedExample}, required: ${required}, ${buildedValidator()}})
96
101
  `;
@@ -104,12 +109,12 @@ export class PrimitiveProp {
104
109
  classBuild() {
105
110
  const req = (this.required || this.isNullable) ? "" : "?";
106
111
  const nullable = this.isNullable ? " | null" : "";
107
- return `${this.name}${req}: BuildedInput<${this.rawType}${nullable}>`;
112
+ return `${this.name}${req}: BuildedInput<${this.effectiveType}${nullable}>`;
108
113
  }
109
114
  interfaceBuild() {
110
115
  const req = this.required ? "" : "?";
111
116
  const nullable = this.isNullable ? " | null" : "";
112
- return `${this.name}${req}: ${this.rawType}${nullable}`;
117
+ return `${this.name}${req}: ${this.effectiveType}${nullable}`;
113
118
  }
114
119
  patchBuild() {
115
120
  return `readonly ${this.name} = $derived.by(() => '${this.name}' in page.params ? page.params.${this.name} : mockedParams.${this.name}) as string | null;`;
package/dist/schema.d.ts CHANGED
@@ -3,7 +3,7 @@ import { EnumProp } from "./props/enum.property.js";
3
3
  import { ObjectProp } from "./props/object.property.js";
4
4
  import { PrimitiveProp } from "./props/primitive.property.js";
5
5
  import type { SchemaObject, ReferenceObject } from "./types/open-api-spec.interface.js";
6
- import type { FieldValidators } from "./types/types.js";
6
+ import type { FieldConfigs } from "./types/types.js";
7
7
  export declare class Schema {
8
8
  name: string;
9
9
  primitiveProps: PrimitiveProp[];
@@ -14,6 +14,8 @@ export declare class Schema {
14
14
  readonly schemaDeps: string[];
15
15
  /** Enum type names used by this schema */
16
16
  readonly enumDeps: string[];
17
+ /** Custom type names used by this schema (from fieldConfigs) */
18
+ readonly customTypeDeps: string[];
17
19
  schema: string;
18
20
  interface: string;
19
21
  constructor(params: {
@@ -21,7 +23,7 @@ export declare class Schema {
21
23
  name: string;
22
24
  requireds: string[];
23
25
  isEmpty: boolean;
24
- validators: FieldValidators;
26
+ fieldConfigs: FieldConfigs;
25
27
  });
26
28
  private processObject;
27
29
  private processEntities;
package/dist/schema.js CHANGED
@@ -14,6 +14,8 @@ export class Schema {
14
14
  schemaDeps;
15
15
  /** Enum type names used by this schema */
16
16
  enumDeps;
17
+ /** Custom type names used by this schema (from fieldConfigs) */
18
+ customTypeDeps;
17
19
  schema;
18
20
  interface;
19
21
  constructor(params) {
@@ -41,6 +43,12 @@ export class Schema {
41
43
  enumDepsSet.add(prop.type);
42
44
  }
43
45
  this.enumDeps = [...enumDepsSet];
46
+ const customTypeDepsSet = new Set();
47
+ for (const prop of this.primitiveProps) {
48
+ if (prop.customType)
49
+ customTypeDepsSet.add(prop.customType);
50
+ }
51
+ this.customTypeDeps = [...customTypeDepsSet];
44
52
  const reflectorInterface = new ReflectorInterface({
45
53
  name: this.name,
46
54
  arrayProps: this.arrayProps,
@@ -105,12 +113,13 @@ export class Schema {
105
113
  }
106
114
  }
107
115
  processEntities(params) {
108
- const { properties, requireds, validators } = params;
116
+ const { properties, requireds, fieldConfigs } = params;
109
117
  for (const [key, value] of Object.entries(properties)) {
110
118
  if (isReferenceObject(value) || !value?.type) {
111
119
  if (!isReferenceObject(value) && value.additionalProperties) {
112
120
  const fakeStringSchema = { ...value, type: "string" };
113
- this.primitiveProps.push(new PrimitiveProp({ name: key, schemaObject: fakeStringSchema, required: requireds.includes(key), validator: validators.get(key), isParam: undefined, isNullable: value.nullable }));
121
+ const config = fieldConfigs.get(key);
122
+ this.primitiveProps.push(new PrimitiveProp({ name: key, schemaObject: fakeStringSchema, required: requireds.includes(key), validator: config?.validator, customType: config?.type, isParam: undefined, isNullable: value.nullable }));
114
123
  }
115
124
  else {
116
125
  this.processObject({ key, value });
@@ -130,12 +139,14 @@ export class Schema {
130
139
  this.enumProps.push(new EnumProp({ enums: value.enum, name, required, isParam: undefined, entityName: schemaName }));
131
140
  continue;
132
141
  }
133
- const validator = validators.get(key);
142
+ const config = fieldConfigs.get(key);
143
+ const validator = config?.validator;
144
+ const customType = config?.type;
134
145
  const type = value.type;
135
146
  if (type === "object") {
136
147
  if (schemaObject.additionalProperties) {
137
148
  const fakeStringSchema = { ...schemaObject, type: "string" };
138
- this.primitiveProps.push(new PrimitiveProp({ name, schemaObject: fakeStringSchema, required, validator, isParam: undefined, isNullable: schemaObject.nullable }));
149
+ this.primitiveProps.push(new PrimitiveProp({ name, schemaObject: fakeStringSchema, required, validator, customType, isParam: undefined, isNullable: schemaObject.nullable }));
139
150
  }
140
151
  continue;
141
152
  }
@@ -143,7 +154,7 @@ export class Schema {
143
154
  this.arrayProps.push(new ArrayProp({ schemaObject, schemaName, name, required, isParam: undefined, isEnum: false, isNullable: schemaObject.nullable }));
144
155
  continue;
145
156
  }
146
- this.primitiveProps.push(new PrimitiveProp({ name, schemaObject, required, validator, isParam: undefined, isNullable: schemaObject.nullable }));
157
+ this.primitiveProps.push(new PrimitiveProp({ name, schemaObject, required, validator, customType, isParam: undefined, isNullable: schemaObject.nullable }));
147
158
  }
148
159
  }
149
160
  }
@@ -15,10 +15,15 @@ export type Info = {
15
15
  moduleName: string;
16
16
  };
17
17
  export type Example = string | boolean | number;
18
- export interface ValidatorField {
19
- fields: string[];
20
- validator: string;
18
+ export interface FieldConfig {
19
+ validator?: string;
20
+ type?: string;
21
21
  }
22
- export type FieldValidators = Map<string, string>;
22
+ export type FieldConfigs = Map<string, FieldConfig>;
23
+ /**
24
+ * Maps custom type names (e.g. "IconName") to their full import statements
25
+ * (e.g. "import type { IconName } from '$lib/utils/icons/icons.type.svelte'")
26
+ */
27
+ export type TypeImports = Map<string, string>;
23
28
  export type AttributeProp = PrimitiveProp | ArrayProp | EnumProp;
24
29
  export type ParamType = "Paths" | "Querys" | "Headers";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-reflector",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Reflects zod types from openAPI schemas",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",