fexapi 0.1.4 → 0.1.5
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/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +60 -0
- package/dist/cli/help.js +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +1 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +22 -4
- package/dist/config/schema-definitions.js +5 -5
- package/dist/project/detect.d.ts.map +1 -1
- package/dist/project/detect.js +78 -10
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +59 -8
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +33 -5
- package/package.json +1 -1
package/dist/cli/args.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,EAAE,KACjB;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAgBtC,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,cAAc,MAAM,EAAE,KACrB;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAalB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,YAAY,MAAM,EAAE,KACnB;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAalB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,WAAW,MAAM,EAAE,KAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CA6ExE,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,EAAE,KAEf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAC3E;IAAE,KAAK,EAAE,MAAM,CAAA;CAoFlB,CAAC"}
|
package/dist/cli/args.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseDevOptions = exports.parseServeOptions = exports.parseFormatOptions = exports.parseGenerateOptions = exports.parseInitOptions = void 0;
|
|
4
|
+
const findDuplicateFlags = (args, flags) => {
|
|
5
|
+
return flags.filter((flag) => args.filter((value) => value === flag).length > 1);
|
|
6
|
+
};
|
|
4
7
|
const parseInitOptions = (initArgs) => {
|
|
5
8
|
const validFlags = new Set(["--force"]);
|
|
6
9
|
const invalidFlags = initArgs.filter((value) => value.startsWith("-") && !validFlags.has(value));
|
|
7
10
|
if (invalidFlags.length > 0) {
|
|
8
11
|
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
9
12
|
}
|
|
13
|
+
const positionalArgs = initArgs.filter((value) => !value.startsWith("-"));
|
|
14
|
+
if (positionalArgs.length > 0) {
|
|
15
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
16
|
+
}
|
|
10
17
|
return { force: initArgs.includes("--force") };
|
|
11
18
|
};
|
|
12
19
|
exports.parseInitOptions = parseInitOptions;
|
|
@@ -15,6 +22,10 @@ const parseGenerateOptions = (generateArgs) => {
|
|
|
15
22
|
if (invalidFlags.length > 0) {
|
|
16
23
|
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
17
24
|
}
|
|
25
|
+
const positionalArgs = generateArgs.filter((value) => !value.startsWith("-"));
|
|
26
|
+
if (positionalArgs.length > 0) {
|
|
27
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
28
|
+
}
|
|
18
29
|
return {};
|
|
19
30
|
};
|
|
20
31
|
exports.parseGenerateOptions = parseGenerateOptions;
|
|
@@ -23,10 +34,22 @@ const parseFormatOptions = (formatArgs) => {
|
|
|
23
34
|
if (invalidFlags.length > 0) {
|
|
24
35
|
return { error: `Unknown option(s): ${invalidFlags.join(", ")}` };
|
|
25
36
|
}
|
|
37
|
+
const positionalArgs = formatArgs.filter((value) => !value.startsWith("-"));
|
|
38
|
+
if (positionalArgs.length > 0) {
|
|
39
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
40
|
+
}
|
|
26
41
|
return {};
|
|
27
42
|
};
|
|
28
43
|
exports.parseFormatOptions = parseFormatOptions;
|
|
29
44
|
const parseServeOptions = (serveArgs) => {
|
|
45
|
+
const duplicateFlags = findDuplicateFlags(serveArgs, [
|
|
46
|
+
"--host",
|
|
47
|
+
"--port",
|
|
48
|
+
"--log",
|
|
49
|
+
]);
|
|
50
|
+
if (duplicateFlags.length > 0) {
|
|
51
|
+
return { error: `Duplicate option(s): ${duplicateFlags.join(", ")}` };
|
|
52
|
+
}
|
|
30
53
|
const getFlagValue = (flagName) => {
|
|
31
54
|
const index = serveArgs.indexOf(flagName);
|
|
32
55
|
if (index === -1) {
|
|
@@ -53,6 +76,20 @@ const parseServeOptions = (serveArgs) => {
|
|
|
53
76
|
if (portValue && typeof portValue !== "string") {
|
|
54
77
|
return portValue;
|
|
55
78
|
}
|
|
79
|
+
const consumedIndexes = new Set();
|
|
80
|
+
serveArgs.forEach((value, index) => {
|
|
81
|
+
if (value === "--log") {
|
|
82
|
+
consumedIndexes.add(index);
|
|
83
|
+
}
|
|
84
|
+
if ((value === "--host" || value === "--port") && serveArgs[index + 1]) {
|
|
85
|
+
consumedIndexes.add(index);
|
|
86
|
+
consumedIndexes.add(index + 1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const positionalArgs = serveArgs.filter((_value, index) => !consumedIndexes.has(index));
|
|
90
|
+
if (positionalArgs.length > 0) {
|
|
91
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
92
|
+
}
|
|
56
93
|
const host = hostValue ?? "127.0.0.1";
|
|
57
94
|
const port = portValue ? Number(portValue) : undefined;
|
|
58
95
|
if (port !== undefined &&
|
|
@@ -63,6 +100,15 @@ const parseServeOptions = (serveArgs) => {
|
|
|
63
100
|
};
|
|
64
101
|
exports.parseServeOptions = parseServeOptions;
|
|
65
102
|
const parseDevOptions = (devArgs) => {
|
|
103
|
+
const duplicateFlags = findDuplicateFlags(devArgs, [
|
|
104
|
+
"--host",
|
|
105
|
+
"--port",
|
|
106
|
+
"--watch",
|
|
107
|
+
"--log",
|
|
108
|
+
]);
|
|
109
|
+
if (duplicateFlags.length > 0) {
|
|
110
|
+
return { error: `Duplicate option(s): ${duplicateFlags.join(", ")}` };
|
|
111
|
+
}
|
|
66
112
|
const getFlagValue = (flagName) => {
|
|
67
113
|
const index = devArgs.indexOf(flagName);
|
|
68
114
|
if (index === -1) {
|
|
@@ -90,6 +136,20 @@ const parseDevOptions = (devArgs) => {
|
|
|
90
136
|
if (portValue && typeof portValue !== "string") {
|
|
91
137
|
return portValue;
|
|
92
138
|
}
|
|
139
|
+
const consumedIndexes = new Set();
|
|
140
|
+
devArgs.forEach((value, index) => {
|
|
141
|
+
if (value === "--watch" || value === "--log") {
|
|
142
|
+
consumedIndexes.add(index);
|
|
143
|
+
}
|
|
144
|
+
if ((value === "--host" || value === "--port") && devArgs[index + 1]) {
|
|
145
|
+
consumedIndexes.add(index);
|
|
146
|
+
consumedIndexes.add(index + 1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
const positionalArgs = devArgs.filter((_value, index) => !consumedIndexes.has(index));
|
|
150
|
+
if (positionalArgs.length > 0) {
|
|
151
|
+
return { error: `Unexpected argument(s): ${positionalArgs.join(", ")}` };
|
|
152
|
+
}
|
|
93
153
|
const host = hostValue ?? "127.0.0.1";
|
|
94
154
|
const port = portValue ? Number(portValue) : undefined;
|
|
95
155
|
if (port !== undefined &&
|
package/dist/cli/help.js
CHANGED
|
@@ -34,7 +34,7 @@ const printHelp = () => {
|
|
|
34
34
|
console.log(" fexapi.config.json");
|
|
35
35
|
console.log(" fexapi.config.js");
|
|
36
36
|
console.log(" fexapi/schema.fexapi");
|
|
37
|
-
console.log(" schemas/*.yaml (optional, via wizard)");
|
|
37
|
+
console.log(" fexapi/schemas/*.yaml (optional, via wizard)");
|
|
38
38
|
console.log("");
|
|
39
39
|
console.log("Init wizard asks:");
|
|
40
40
|
console.log(" What port? (default: 3000)");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAgCA,eAAO,MAAM,aAAa,GAAI,2CAK3B;IACD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB,KAAG,
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAgCA,eAAO,MAAM,aAAa,GAAI,2CAK3B;IACD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB,KAAG,MAsHH,CAAC"}
|
package/dist/commands/dev.js
CHANGED
|
@@ -18,7 +18,7 @@ const isWatchedPath = (projectRoot, changedPath) => {
|
|
|
18
18
|
if (relativePath.startsWith("fexapi/")) {
|
|
19
19
|
return true;
|
|
20
20
|
}
|
|
21
|
-
return (relativePath.startsWith("schemas/") &&
|
|
21
|
+
return (relativePath.startsWith("fexapi/schemas/") &&
|
|
22
22
|
(relativePath.endsWith(".yaml") || relativePath.endsWith(".yml")));
|
|
23
23
|
};
|
|
24
24
|
const runDevCommand = ({ host, port, watchEnabled, logEnabled, }) => {
|
|
@@ -71,7 +71,6 @@ const runDevCommand = ({ host, port, watchEnabled, logEnabled, }) => {
|
|
|
71
71
|
const activeWatchers = [];
|
|
72
72
|
const watchTargets = [
|
|
73
73
|
(0, node_path_1.join)(projectRoot, "fexapi"),
|
|
74
|
-
(0, node_path_1.join)(projectRoot, "schemas"),
|
|
75
74
|
projectRoot,
|
|
76
75
|
];
|
|
77
76
|
for (const watchTarget of watchTargets) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA0LA,eAAO,MAAM,iBAAiB,GAAU,YAErC;IACD,KAAK,EAAE,OAAO,CAAC;CAChB,KAAG,OAAO,CAAC,MAAM,CAmJjB,CAAC"}
|
package/dist/commands/init.js
CHANGED
|
@@ -82,8 +82,8 @@ const getRuntimeConfigTemplate = ({ port, cors, includeSampleRoutes, }) => {
|
|
|
82
82
|
const routeSection = includeSampleRoutes
|
|
83
83
|
? [
|
|
84
84
|
" routes: {",
|
|
85
|
-
' "/users": { count:
|
|
86
|
-
' "/posts": { count:
|
|
85
|
+
' "/users": { count: 10, schema: "user" },',
|
|
86
|
+
' "/posts": { count: 20, schema: "post" },',
|
|
87
87
|
" },",
|
|
88
88
|
]
|
|
89
89
|
: [];
|
|
@@ -102,12 +102,22 @@ const SAMPLE_USER_SCHEMA = [
|
|
|
102
102
|
"fullName:",
|
|
103
103
|
" type: name",
|
|
104
104
|
" faker: person.fullName",
|
|
105
|
+
"username:",
|
|
106
|
+
" type: string",
|
|
107
|
+
" faker: internet.username",
|
|
105
108
|
"email:",
|
|
106
109
|
" type: email",
|
|
107
110
|
" faker: internet.email",
|
|
108
111
|
"avatarUrl:",
|
|
109
112
|
" type: url",
|
|
110
113
|
" faker: image.avatar",
|
|
114
|
+
"bio:",
|
|
115
|
+
" type: string",
|
|
116
|
+
" faker: lorem.sentence",
|
|
117
|
+
"isActive:",
|
|
118
|
+
" type: boolean",
|
|
119
|
+
"joinedAt:",
|
|
120
|
+
" type: date",
|
|
111
121
|
].join("\n");
|
|
112
122
|
const SAMPLE_POST_SCHEMA = [
|
|
113
123
|
"id:",
|
|
@@ -117,7 +127,15 @@ const SAMPLE_POST_SCHEMA = [
|
|
|
117
127
|
" faker: lorem.sentence",
|
|
118
128
|
"body:",
|
|
119
129
|
" type: string",
|
|
120
|
-
" faker: lorem.
|
|
130
|
+
" faker: lorem.paragraphs",
|
|
131
|
+
"authorId:",
|
|
132
|
+
" type: uuid",
|
|
133
|
+
"published:",
|
|
134
|
+
" type: boolean",
|
|
135
|
+
"likes:",
|
|
136
|
+
" type: number",
|
|
137
|
+
" min: 0",
|
|
138
|
+
" max: 500",
|
|
121
139
|
"createdAt:",
|
|
122
140
|
" type: date",
|
|
123
141
|
].join("\n");
|
|
@@ -133,7 +151,7 @@ const initializeProject = async ({ force, }) => {
|
|
|
133
151
|
const schemaPath = (0, node_path_1.join)(fexapiDirectoryPath, "schema.fexapi");
|
|
134
152
|
const configPath = (0, node_path_1.join)(projectRoot, "fexapi.config.json");
|
|
135
153
|
const runtimeConfigPath = (0, node_path_1.join)(projectRoot, "fexapi.config.js");
|
|
136
|
-
const schemasDirectoryPath = (0, node_path_1.join)(projectRoot, "schemas");
|
|
154
|
+
const schemasDirectoryPath = (0, node_path_1.join)(projectRoot, "fexapi", "schemas");
|
|
137
155
|
const userSchemaPath = (0, node_path_1.join)(schemasDirectoryPath, "user.yaml");
|
|
138
156
|
const postSchemaPath = (0, node_path_1.join)(schemasDirectoryPath, "post.yaml");
|
|
139
157
|
const wizardAnswers = await askInitWizardQuestions();
|
|
@@ -20,23 +20,23 @@ const isRecord = (value) => {
|
|
|
20
20
|
};
|
|
21
21
|
const parseSchemaDefinition = (schemaName, rawValue) => {
|
|
22
22
|
if (!isRecord(rawValue)) {
|
|
23
|
-
console.error(`Invalid schemas/${schemaName}.yaml: expected root object of field definitions.`);
|
|
23
|
+
console.error(`Invalid fexapi/schemas/${schemaName}.yaml: expected root object of field definitions.`);
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
26
|
const result = {};
|
|
27
27
|
for (const [fieldName, rawFieldConfig] of Object.entries(rawValue)) {
|
|
28
28
|
if (!isRecord(rawFieldConfig)) {
|
|
29
|
-
console.error(`Invalid schemas/${schemaName}.yaml field \`${fieldName}\`: expected object with type/faker/min/max.`);
|
|
29
|
+
console.error(`Invalid fexapi/schemas/${schemaName}.yaml field \`${fieldName}\`: expected object with type/faker/min/max.`);
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
32
|
const rawType = rawFieldConfig.type;
|
|
33
33
|
if (typeof rawType !== "string") {
|
|
34
|
-
console.error(`Invalid schemas/${schemaName}.yaml field \`${fieldName}\`: missing string \`type\`.`);
|
|
34
|
+
console.error(`Invalid fexapi/schemas/${schemaName}.yaml field \`${fieldName}\`: missing string \`type\`.`);
|
|
35
35
|
continue;
|
|
36
36
|
}
|
|
37
37
|
const normalizedType = rawType.trim().toLowerCase();
|
|
38
38
|
if (!VALID_TYPES.has(normalizedType)) {
|
|
39
|
-
console.error(`Invalid schemas/${schemaName}.yaml field \`${fieldName}\`: unknown type \`${rawType}\`.`);
|
|
39
|
+
console.error(`Invalid fexapi/schemas/${schemaName}.yaml field \`${fieldName}\`: unknown type \`${rawType}\`.`);
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
42
42
|
const minValue = rawFieldConfig.min;
|
|
@@ -57,7 +57,7 @@ const parseSchemaDefinition = (schemaName, rawValue) => {
|
|
|
57
57
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
58
58
|
};
|
|
59
59
|
const loadSchemaDefinitions = (projectRoot) => {
|
|
60
|
-
const schemasDirectoryPath = (0, node_path_1.join)(projectRoot, "schemas");
|
|
60
|
+
const schemasDirectoryPath = (0, node_path_1.join)(projectRoot, "fexapi", "schemas");
|
|
61
61
|
if (!(0, node_fs_1.existsSync)(schemasDirectoryPath)) {
|
|
62
62
|
return {};
|
|
63
63
|
}
|
|
@@ -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,
|
|
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,MAmGF,CAAC"}
|
package/dist/project/detect.js
CHANGED
|
@@ -94,33 +94,101 @@ const detectProject = (packageJsonPath, projectRoot) => {
|
|
|
94
94
|
};
|
|
95
95
|
exports.detectProject = detectProject;
|
|
96
96
|
const getSchemaTemplate = (framework, port = 4000) => {
|
|
97
|
-
const
|
|
98
|
-
? "
|
|
97
|
+
const frameworkLabel = framework === "nextjs"
|
|
98
|
+
? "Next.js"
|
|
99
99
|
: framework === "reactjs"
|
|
100
|
-
? "
|
|
101
|
-
:
|
|
100
|
+
? "React"
|
|
101
|
+
: framework === "vue"
|
|
102
|
+
? "Vue"
|
|
103
|
+
: framework === "nuxt"
|
|
104
|
+
? "Nuxt"
|
|
105
|
+
: framework === "svelte"
|
|
106
|
+
? "Svelte"
|
|
107
|
+
: framework === "sveltekit"
|
|
108
|
+
? "SvelteKit"
|
|
109
|
+
: framework === "angular"
|
|
110
|
+
? "Angular"
|
|
111
|
+
: framework === "solid"
|
|
112
|
+
? "Solid"
|
|
113
|
+
: framework === "remix"
|
|
114
|
+
? "Remix"
|
|
115
|
+
: framework === "astro"
|
|
116
|
+
? "Astro"
|
|
117
|
+
: "unknown";
|
|
102
118
|
return [
|
|
103
|
-
|
|
119
|
+
`# Framework: ${frameworkLabel}`,
|
|
120
|
+
"",
|
|
121
|
+
"# ──────────────────────────────────────────────",
|
|
104
122
|
"# Server",
|
|
123
|
+
"# ──────────────────────────────────────────────",
|
|
105
124
|
`port: ${port}`,
|
|
106
125
|
"",
|
|
126
|
+
"# ──────────────────────────────────────────────",
|
|
127
|
+
"# Available types",
|
|
128
|
+
"# string → random words",
|
|
129
|
+
"# number → random integer",
|
|
130
|
+
"# boolean → true / false",
|
|
131
|
+
"# uuid → unique id",
|
|
132
|
+
"# email → fake email",
|
|
133
|
+
"# name → full name",
|
|
134
|
+
"# url → fake URL",
|
|
135
|
+
"# phone → phone number",
|
|
136
|
+
"# date → ISO date string",
|
|
137
|
+
"# ──────────────────────────────────────────────",
|
|
138
|
+
"",
|
|
139
|
+
"# ──────────────────────────────────────────────",
|
|
107
140
|
"# Routes",
|
|
108
|
-
"#
|
|
109
|
-
"#
|
|
110
|
-
"#
|
|
111
|
-
"#
|
|
112
|
-
"#
|
|
141
|
+
"#",
|
|
142
|
+
"# Single-line: GET /items: id:uuid, name:string",
|
|
143
|
+
"# Multi-line:",
|
|
144
|
+
"# GET /items:",
|
|
145
|
+
"# id:uuid",
|
|
146
|
+
"# name:string",
|
|
147
|
+
"# ──────────────────────────────────────────────",
|
|
148
|
+
"",
|
|
149
|
+
"# ── Users ────────────────────────────────────",
|
|
150
|
+
"",
|
|
113
151
|
"GET /users:",
|
|
114
152
|
" id:uuid",
|
|
115
153
|
" fullName:name",
|
|
116
154
|
" username:string",
|
|
117
155
|
" email:email",
|
|
156
|
+
" phone:phone",
|
|
118
157
|
" avatarUrl:url",
|
|
158
|
+
" joinedAt:date",
|
|
159
|
+
"",
|
|
160
|
+
"POST /users:",
|
|
161
|
+
" id:uuid",
|
|
162
|
+
" fullName:name",
|
|
163
|
+
" username:string",
|
|
164
|
+
" email:email",
|
|
165
|
+
"",
|
|
166
|
+
"# ── Posts ────────────────────────────────────",
|
|
167
|
+
"",
|
|
119
168
|
"GET /posts:",
|
|
120
169
|
" id:uuid",
|
|
121
170
|
" title:string",
|
|
122
171
|
" body:string",
|
|
172
|
+
" authorId:uuid",
|
|
173
|
+
" published:boolean",
|
|
123
174
|
" createdAt:date",
|
|
175
|
+
"",
|
|
176
|
+
"POST /posts:",
|
|
177
|
+
" id:uuid",
|
|
178
|
+
" title:string",
|
|
179
|
+
" body:string",
|
|
180
|
+
" authorId:uuid",
|
|
181
|
+
"",
|
|
182
|
+
"PUT /posts:",
|
|
183
|
+
" id:uuid",
|
|
184
|
+
" title:string",
|
|
185
|
+
" body:string",
|
|
186
|
+
"",
|
|
187
|
+
"DELETE /posts: id:uuid",
|
|
188
|
+
"",
|
|
189
|
+
"# ── Comments (single-line example) ───────────",
|
|
190
|
+
"",
|
|
191
|
+
"GET /comments: id:uuid, body:string, postId:uuid, authorId:uuid, createdAt:date",
|
|
124
192
|
].join("\n");
|
|
125
193
|
};
|
|
126
194
|
exports.getSchemaTemplate = getSchemaTemplate;
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -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;
|
|
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;AAqKF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,MAAM,KACjB;IAAE,MAAM,CAAC,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAmH3C,CAAC"}
|
package/dist/schema.js
CHANGED
|
@@ -12,10 +12,27 @@ const VALID_TYPES = [
|
|
|
12
12
|
"name",
|
|
13
13
|
"phone",
|
|
14
14
|
];
|
|
15
|
+
const VALID_METHODS = new Set([
|
|
16
|
+
"GET",
|
|
17
|
+
"POST",
|
|
18
|
+
"PUT",
|
|
19
|
+
"PATCH",
|
|
20
|
+
"DELETE",
|
|
21
|
+
"HEAD",
|
|
22
|
+
"OPTIONS",
|
|
23
|
+
]);
|
|
15
24
|
const DEFAULT_PORT = 4000;
|
|
16
25
|
const ROUTE_FORMAT_ERROR_MESSAGE = "Invalid route definition. Expected format: " +
|
|
17
26
|
"METHOD /endpoint: field:type,field:type (or multiline fields under METHOD /endpoint:)";
|
|
27
|
+
const FIELD_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
18
28
|
const isPortLine = (line) => /^port\s*:/i.test(line.trim());
|
|
29
|
+
const stripInlineComment = (line) => {
|
|
30
|
+
const commentIndex = line.indexOf("#");
|
|
31
|
+
if (commentIndex === -1) {
|
|
32
|
+
return line.trim();
|
|
33
|
+
}
|
|
34
|
+
return line.slice(0, commentIndex).trim();
|
|
35
|
+
};
|
|
19
36
|
const parseRouteHeader = (line) => {
|
|
20
37
|
const separatorIndex = line.indexOf(":");
|
|
21
38
|
if (separatorIndex === -1) {
|
|
@@ -36,12 +53,23 @@ const parseRouteHeader = (line) => {
|
|
|
36
53
|
};
|
|
37
54
|
};
|
|
38
55
|
const parseField = (rawField) => {
|
|
39
|
-
const
|
|
56
|
+
const parts = rawField.split(":");
|
|
57
|
+
if (parts.length !== 2) {
|
|
58
|
+
return {
|
|
59
|
+
error: `Invalid field "${rawField}". Expected format name:type.`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const [rawName, rawType] = parts;
|
|
40
63
|
const name = rawName?.trim();
|
|
41
64
|
const type = rawType?.trim().toLowerCase();
|
|
42
65
|
if (!name) {
|
|
43
66
|
return { error: `Invalid field "${rawField}". Missing field name.` };
|
|
44
67
|
}
|
|
68
|
+
if (!FIELD_NAME_PATTERN.test(name)) {
|
|
69
|
+
return {
|
|
70
|
+
error: `Invalid field name "${name}". Use letters, numbers, and underscore only (must not start with a number).`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
45
73
|
if (!type) {
|
|
46
74
|
return { error: `Invalid field "${rawField}". Missing field type.` };
|
|
47
75
|
}
|
|
@@ -59,10 +87,16 @@ const parseRoute = ({ method, path, rawFields, }) => {
|
|
|
59
87
|
if (!method || !path) {
|
|
60
88
|
return { error: ROUTE_FORMAT_ERROR_MESSAGE };
|
|
61
89
|
}
|
|
90
|
+
if (!VALID_METHODS.has(method)) {
|
|
91
|
+
return {
|
|
92
|
+
error: `Unsupported HTTP method "${method}" for route ${method} ${path}. Valid methods: ${Array.from(VALID_METHODS).join(", ")}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
62
95
|
const fields = [];
|
|
96
|
+
const seenFieldNames = new Set();
|
|
63
97
|
for (const rawFieldLine of rawFields) {
|
|
64
98
|
for (const part of rawFieldLine.split(",")) {
|
|
65
|
-
const trimmedPart = part
|
|
99
|
+
const trimmedPart = stripInlineComment(part);
|
|
66
100
|
if (!trimmedPart) {
|
|
67
101
|
continue;
|
|
68
102
|
}
|
|
@@ -70,6 +104,12 @@ const parseRoute = ({ method, path, rawFields, }) => {
|
|
|
70
104
|
if ("error" in parsedField) {
|
|
71
105
|
return { error: parsedField.error };
|
|
72
106
|
}
|
|
107
|
+
if (seenFieldNames.has(parsedField.name)) {
|
|
108
|
+
return {
|
|
109
|
+
error: `Duplicate field "${parsedField.name}" in route ${method} ${path}.`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
seenFieldNames.add(parsedField.name);
|
|
73
113
|
fields.push(parsedField);
|
|
74
114
|
}
|
|
75
115
|
}
|
|
@@ -83,9 +123,10 @@ const parseFexapiSchema = (schemaText) => {
|
|
|
83
123
|
const routes = [];
|
|
84
124
|
const errors = [];
|
|
85
125
|
const lines = schemaText.split(/\r?\n/);
|
|
126
|
+
const seenRoutes = new Set();
|
|
86
127
|
for (let index = 0; index < lines.length; index += 1) {
|
|
87
128
|
const rawLine = lines[index] ?? "";
|
|
88
|
-
const trimmedLine = rawLine
|
|
129
|
+
const trimmedLine = stripInlineComment(rawLine);
|
|
89
130
|
if (!trimmedLine || trimmedLine.startsWith("#")) {
|
|
90
131
|
continue;
|
|
91
132
|
}
|
|
@@ -104,12 +145,18 @@ const parseFexapiSchema = (schemaText) => {
|
|
|
104
145
|
}
|
|
105
146
|
const header = parseRouteHeader(trimmedLine);
|
|
106
147
|
if (!header) {
|
|
107
|
-
errors.push(ROUTE_FORMAT_ERROR_MESSAGE);
|
|
148
|
+
errors.push(`${ROUTE_FORMAT_ERROR_MESSAGE} (line ${index + 1})`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const routeKey = `${header.method} ${header.path}`;
|
|
152
|
+
if (seenRoutes.has(routeKey)) {
|
|
153
|
+
errors.push(`Duplicate route definition: ${routeKey} (line ${index + 1})`);
|
|
108
154
|
continue;
|
|
109
155
|
}
|
|
110
156
|
const rawFields = [];
|
|
111
|
-
|
|
112
|
-
|
|
157
|
+
const inlineFields = stripInlineComment(header.inlineFields);
|
|
158
|
+
if (inlineFields) {
|
|
159
|
+
rawFields.push(inlineFields);
|
|
113
160
|
}
|
|
114
161
|
let lookaheadIndex = index + 1;
|
|
115
162
|
while (lookaheadIndex < lines.length) {
|
|
@@ -124,7 +171,10 @@ const parseFexapiSchema = (schemaText) => {
|
|
|
124
171
|
break;
|
|
125
172
|
}
|
|
126
173
|
if (/^\s+/.test(lookaheadRawLine)) {
|
|
127
|
-
|
|
174
|
+
const normalizedFieldLine = stripInlineComment(lookaheadTrimmedLine.replace(/^-+\s*/, ""));
|
|
175
|
+
if (normalizedFieldLine) {
|
|
176
|
+
rawFields.push(normalizedFieldLine);
|
|
177
|
+
}
|
|
128
178
|
lookaheadIndex += 1;
|
|
129
179
|
continue;
|
|
130
180
|
}
|
|
@@ -136,9 +186,10 @@ const parseFexapiSchema = (schemaText) => {
|
|
|
136
186
|
rawFields,
|
|
137
187
|
});
|
|
138
188
|
if ("error" in parsedRoute) {
|
|
139
|
-
errors.push(parsedRoute.error);
|
|
189
|
+
errors.push(`${parsedRoute.error} (line ${index + 1})`);
|
|
140
190
|
continue;
|
|
141
191
|
}
|
|
192
|
+
seenRoutes.add(routeKey);
|
|
142
193
|
routes.push(parsedRoute);
|
|
143
194
|
index = lookaheadIndex - 1;
|
|
144
195
|
}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EAExB,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAyLF,eAAO,MAAM,WAAW,GAAI,0EAOzB,aAAkB,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EAExB,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CAAC;AAyLF,eAAO,MAAM,WAAW,GAAI,0EAOzB,aAAkB,wFAuIpB,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -187,11 +187,39 @@ const startServer = ({ host = DEFAULT_HOST, port = DEFAULT_PORT, apiSpec, runtim
|
|
|
187
187
|
if (apiSpec) {
|
|
188
188
|
const matchedRoute = apiSpec.routes.find((route) => route.method === request.method && route.path === pathname);
|
|
189
189
|
if (matchedRoute) {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
const method = request.method ?? "GET";
|
|
191
|
+
if (method === "GET") {
|
|
192
|
+
const count = getCountFromUrl(request.url, 5);
|
|
193
|
+
const payloadKey = toCollectionKey(matchedRoute.path);
|
|
194
|
+
sendJson(response, 200, {
|
|
195
|
+
[payloadKey]: Array.from({ length: count }, () => createRecordFromRoute(matchedRoute)),
|
|
196
|
+
}, { cors: corsEnabled, delay: responseDelay });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (method === "DELETE") {
|
|
200
|
+
sendJson(response, 200, { message: `Deleted resource at ${matchedRoute.path}` }, { cors: corsEnabled, delay: responseDelay });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const bodyChunks = [];
|
|
204
|
+
request.on("data", (chunk) => bodyChunks.push(chunk));
|
|
205
|
+
request.on("end", () => {
|
|
206
|
+
let requestBody = {};
|
|
207
|
+
try {
|
|
208
|
+
const raw = Buffer.concat(bodyChunks).toString("utf-8");
|
|
209
|
+
if (raw.trim()) {
|
|
210
|
+
requestBody = JSON.parse(raw);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
}
|
|
215
|
+
const generatedRecord = createRecordFromRoute(matchedRoute);
|
|
216
|
+
const merged = { ...generatedRecord, ...requestBody };
|
|
217
|
+
const statusCode = method === "POST" ? 201 : 200;
|
|
218
|
+
sendJson(response, statusCode, merged, {
|
|
219
|
+
cors: corsEnabled,
|
|
220
|
+
delay: responseDelay,
|
|
221
|
+
});
|
|
222
|
+
});
|
|
195
223
|
return;
|
|
196
224
|
}
|
|
197
225
|
}
|