fexapi 0.1.2 → 0.1.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.
package/README.md CHANGED
@@ -37,15 +37,29 @@ Creates:
37
37
 
38
38
  ### 2) Edit schema file
39
39
 
40
- `fexapi/schema.fexapi` uses a simple DSL with only `:` and `,` (no semicolons):
40
+ `fexapi/schema.fexapi` supports readable multi-line route fields (single-line still works):
41
41
 
42
42
  ```txt
43
43
  # Server
44
44
  port: 4100
45
45
 
46
46
  # Routes
47
- GET /users: id:uuid,name:name,email:email,age:number,phone:phone,pic:url,courseName:string
48
- GET /courses: id:uuid,courseName:string,mentor:name
47
+ GET /users:
48
+ id:uuid
49
+ name:name
50
+ email:email
51
+ age:number
52
+ phone:phone
53
+ pic:url
54
+ courseName:string
55
+
56
+ GET /courses:
57
+ id:uuid
58
+ courseName:string
59
+ mentor:name
60
+
61
+ # one-line format is also valid:
62
+ # GET /users: id:uuid,name:name,email:email
49
63
  ```
50
64
 
51
65
  ### 3) Generate artifacts (updates migration)
@@ -1 +1 @@
1
- {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/project/detect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAyD5E,eAAO,MAAM,aAAa,GACxB,iBAAiB,MAAM,EACvB,aAAa,MAAM,KAClB,eAwDF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,kBAAkB,EAC7B,aAAW,KACV,MAkBF,CAAC"}
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/project/detect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAyD5E,eAAO,MAAM,aAAa,GACxB,iBAAiB,MAAM,EACvB,aAAa,MAAM,KAClB,eAwDF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,kBAAkB,EAC7B,aAAW,KACV,MA+BF,CAAC"}
@@ -105,9 +105,22 @@ const getSchemaTemplate = (framework, port = 4000) => {
105
105
  `port: ${port}`,
106
106
  "",
107
107
  "# Routes",
108
- "# Format: METHOD /endpoint: field:type,field:type",
109
- "GET /users: id:uuid,fullName:name,username:string,email:email,avatarUrl:url",
110
- "GET /posts: id:uuid,title:string,body:string,createdAt:date",
108
+ "# Format (single-line): METHOD /endpoint: field:type,field:type",
109
+ "# Format (multi-line):",
110
+ "# METHOD /endpoint:",
111
+ "# field:type",
112
+ "# field:type",
113
+ "GET /users:",
114
+ " id:uuid",
115
+ " fullName:name",
116
+ " username:string",
117
+ " email:email",
118
+ " avatarUrl:url",
119
+ "GET /posts:",
120
+ " id:uuid",
121
+ " title:string",
122
+ " body:string",
123
+ " createdAt:date",
111
124
  ].join("\n");
112
125
  };
113
126
  exports.getSchemaTemplate = getSchemaTemplate;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,MAAM,GACN,MAAM,GACN,OAAO,GACP,KAAK,GACL,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,eAAe,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAiGF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,MAAM,KACjB;IAAE,MAAM,CAAC,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAoD3C,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,MAAM,GACN,MAAM,GACN,OAAO,GACP,KAAK,GACL,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,eAAe,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAqHF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,MAAM,KACjB;IAAE,MAAM,CAAC,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAmG3C,CAAC"}
package/dist/schema.js CHANGED
@@ -14,7 +14,27 @@ const VALID_TYPES = [
14
14
  ];
15
15
  const DEFAULT_PORT = 4000;
16
16
  const ROUTE_FORMAT_ERROR_MESSAGE = "Invalid route definition. Expected format: " +
17
- "METHOD /endpoint: field:type,field:type";
17
+ "METHOD /endpoint: field:type,field:type (or multiline fields under METHOD /endpoint:)";
18
+ const isPortLine = (line) => /^port\s*:/i.test(line.trim());
19
+ const parseRouteHeader = (line) => {
20
+ const separatorIndex = line.indexOf(":");
21
+ if (separatorIndex === -1) {
22
+ return undefined;
23
+ }
24
+ const rawLeft = line.slice(0, separatorIndex);
25
+ const rawFields = line.slice(separatorIndex + 1);
26
+ const [rawMethod, rawPath] = rawLeft.trim().split(/\s+/, 2);
27
+ const method = rawMethod?.toUpperCase();
28
+ const path = rawPath?.trim();
29
+ if (!method || !path || !path.startsWith("/")) {
30
+ return undefined;
31
+ }
32
+ return {
33
+ method,
34
+ path,
35
+ inlineFields: rawFields,
36
+ };
37
+ };
18
38
  const parseField = (rawField) => {
19
39
  const [rawName, rawType] = rawField.split(":");
20
40
  const name = rawName?.trim();
@@ -35,38 +55,23 @@ const parseField = (rawField) => {
35
55
  type: type,
36
56
  };
37
57
  };
38
- const parseRoute = (line) => {
39
- const separatorIndex = line.indexOf(":");
40
- if (separatorIndex === -1) {
41
- return { error: ROUTE_FORMAT_ERROR_MESSAGE };
42
- }
43
- const rawLeft = line.slice(0, separatorIndex);
44
- const rawFields = line.slice(separatorIndex + 1);
45
- if (!rawLeft || !rawFields) {
46
- return { error: ROUTE_FORMAT_ERROR_MESSAGE };
47
- }
48
- const [rawMethod, rawPath] = rawLeft.trim().split(/\s+/, 2);
49
- const method = rawMethod?.toUpperCase();
50
- const path = rawPath?.trim();
58
+ const parseRoute = ({ method, path, rawFields, }) => {
51
59
  if (!method || !path) {
52
- return {
53
- error: "Invalid route definition. Missing METHOD or /endpoint before ':'.",
54
- };
55
- }
56
- if (!path.startsWith("/")) {
57
- return { error: `Route path must start with '/': ${path}` };
60
+ return { error: ROUTE_FORMAT_ERROR_MESSAGE };
58
61
  }
59
62
  const fields = [];
60
- for (const part of rawFields.split(",")) {
61
- const trimmedPart = part.trim();
62
- if (!trimmedPart) {
63
- continue;
64
- }
65
- const parsedField = parseField(trimmedPart);
66
- if ("error" in parsedField) {
67
- return { error: parsedField.error };
63
+ for (const rawFieldLine of rawFields) {
64
+ for (const part of rawFieldLine.split(",")) {
65
+ const trimmedPart = part.trim();
66
+ if (!trimmedPart) {
67
+ continue;
68
+ }
69
+ const parsedField = parseField(trimmedPart);
70
+ if ("error" in parsedField) {
71
+ return { error: parsedField.error };
72
+ }
73
+ fields.push(parsedField);
68
74
  }
69
- fields.push(parsedField);
70
75
  }
71
76
  if (fields.length === 0) {
72
77
  return { error: `Route ${method} ${path} has no valid fields.` };
@@ -77,13 +82,15 @@ const parseFexapiSchema = (schemaText) => {
77
82
  let port = DEFAULT_PORT;
78
83
  const routes = [];
79
84
  const errors = [];
80
- const lines = schemaText
81
- .split(/\r?\n/)
82
- .map((line) => line.trim())
83
- .filter((line) => line.length > 0 && !line.startsWith("#"));
84
- for (const line of lines) {
85
- if (line.toLowerCase().startsWith("port:")) {
86
- const rawPort = line.slice(line.indexOf(":") + 1).trim();
85
+ const lines = schemaText.split(/\r?\n/);
86
+ for (let index = 0; index < lines.length; index += 1) {
87
+ const rawLine = lines[index] ?? "";
88
+ const trimmedLine = rawLine.trim();
89
+ if (!trimmedLine || trimmedLine.startsWith("#")) {
90
+ continue;
91
+ }
92
+ if (isPortLine(trimmedLine)) {
93
+ const rawPort = trimmedLine.slice(trimmedLine.indexOf(":") + 1).trim();
87
94
  const parsedPort = Number(rawPort);
88
95
  if (!Number.isInteger(parsedPort) ||
89
96
  parsedPort < 1 ||
@@ -95,12 +102,45 @@ const parseFexapiSchema = (schemaText) => {
95
102
  }
96
103
  continue;
97
104
  }
98
- const parsedRoute = parseRoute(line);
105
+ const header = parseRouteHeader(trimmedLine);
106
+ if (!header) {
107
+ errors.push(ROUTE_FORMAT_ERROR_MESSAGE);
108
+ continue;
109
+ }
110
+ const rawFields = [];
111
+ if (header.inlineFields.trim()) {
112
+ rawFields.push(header.inlineFields);
113
+ }
114
+ let lookaheadIndex = index + 1;
115
+ while (lookaheadIndex < lines.length) {
116
+ const lookaheadRawLine = lines[lookaheadIndex] ?? "";
117
+ const lookaheadTrimmedLine = lookaheadRawLine.trim();
118
+ if (!lookaheadTrimmedLine || lookaheadTrimmedLine.startsWith("#")) {
119
+ lookaheadIndex += 1;
120
+ continue;
121
+ }
122
+ if (isPortLine(lookaheadTrimmedLine) ||
123
+ parseRouteHeader(lookaheadTrimmedLine)) {
124
+ break;
125
+ }
126
+ if (/^\s+/.test(lookaheadRawLine)) {
127
+ rawFields.push(lookaheadTrimmedLine.replace(/^-+\s*/, ""));
128
+ lookaheadIndex += 1;
129
+ continue;
130
+ }
131
+ break;
132
+ }
133
+ const parsedRoute = parseRoute({
134
+ method: header.method,
135
+ path: header.path,
136
+ rawFields,
137
+ });
99
138
  if ("error" in parsedRoute) {
100
139
  errors.push(parsedRoute.error);
101
140
  continue;
102
141
  }
103
142
  routes.push(parsedRoute);
143
+ index = lookaheadIndex - 1;
104
144
  }
105
145
  if (routes.length === 0) {
106
146
  errors.push("No routes defined in schema.fexapi.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fexapi",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Mock API generation CLI tool for local development and testing",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",