@naiv/flazy-api 0.0.1 → 0.0.2
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/NAIV-INSTRUCTION.md +221 -0
- package/dist/cmd-chat.d.ts +1 -0
- package/dist/cmd-chat.js +130 -0
- package/dist/cmd-generate.d.ts +2 -1
- package/dist/cmd-generate.js +3 -1
- package/dist/cmd-run.d.ts +1 -1
- package/dist/cmd-run.js +40 -8
- package/dist/compile-folder.d.ts +16 -0
- package/dist/compile-folder.js +119 -0
- package/dist/exec.js +22 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/prompt.d.ts +10 -0
- package/dist/prompt.js +77 -0
- package/dist/server.d.ts +16 -2
- package/dist/server.js +183 -37
- package/package.json +12 -3
- package/dist/compile-folter.d.ts +0 -4
- package/dist/compile-folter.js +0 -70
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
## INSTRUCTION
|
|
2
|
+
|
|
3
|
+
### 1. Database Design
|
|
4
|
+
|
|
5
|
+
No comments on design files `//` or `/** */`.
|
|
6
|
+
|
|
7
|
+
#### Table
|
|
8
|
+
|
|
9
|
+
```naiv
|
|
10
|
+
table User {
|
|
11
|
+
id bigint pk inc notnull
|
|
12
|
+
name varchar(255) notnull
|
|
13
|
+
email varchar(255) unique notnull
|
|
14
|
+
password varchar(255) notnull
|
|
15
|
+
created_at timestamp notnull default=NOW()
|
|
16
|
+
updated_at timestamp notnull default=NOW()
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `inc` means auto increment.
|
|
21
|
+
- `notnull` means not null.
|
|
22
|
+
- `pk` means primary key.
|
|
23
|
+
- `unique` means unique.
|
|
24
|
+
- `default` means default value. `NOW()` is a function that returns current timestamp. If default value is string then write it with quote like `default="value"`. If number write it without quote like `default=123`.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
#### Relationship
|
|
28
|
+
|
|
29
|
+
```naiv
|
|
30
|
+
table User {
|
|
31
|
+
id bigint pk inc notnull
|
|
32
|
+
name varchar(255) notnull
|
|
33
|
+
email varchar(255) unique notnull
|
|
34
|
+
password varchar(255) notnull
|
|
35
|
+
created_at timestamp notnull default=NOW()
|
|
36
|
+
updated_at timestamp notnull default=NOW()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
table Product {
|
|
40
|
+
id bigint pk inc notnull
|
|
41
|
+
id_user_owner User.id notnull
|
|
42
|
+
name varchar(255) notnull
|
|
43
|
+
price decimal(10,2) notnull
|
|
44
|
+
created_at timestamp notnull default=NOW()
|
|
45
|
+
updated_at timestamp notnull default=NOW()
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`User` has link to `Product` by `id_user_owner`.
|
|
50
|
+
|
|
51
|
+
#### Enum
|
|
52
|
+
|
|
53
|
+
```naiv
|
|
54
|
+
enum ProductStatus {
|
|
55
|
+
DRAFT
|
|
56
|
+
PUBLISHED
|
|
57
|
+
ARCHIVED
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
table Product {
|
|
61
|
+
id bigint pk inc notnull
|
|
62
|
+
id_user_owner User.id notnull
|
|
63
|
+
name varchar(255) notnull
|
|
64
|
+
price decimal(10,2) notnull
|
|
65
|
+
status enum.ProductStatus notnull default=DRAFT
|
|
66
|
+
created_at timestamp notnull default=NOW()
|
|
67
|
+
updated_at timestamp notnull default=NOW()
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
default value for enum is written without quote like `default=DRAFT`.
|
|
72
|
+
|
|
73
|
+
### 2. API Design
|
|
74
|
+
|
|
75
|
+
API design in NAIV accept only method GET, POST, PUT, DELETE, PATCH. Each API must have unique alias, alias will be used as function call in http client which consume this api design. No comments on design files `//` or `/** */`. Here is how to write it:
|
|
76
|
+
|
|
77
|
+
#### GET
|
|
78
|
+
|
|
79
|
+
```naiv
|
|
80
|
+
api get /users {
|
|
81
|
+
alias getUsers
|
|
82
|
+
return array table.User required
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
simple api design for get all users. `required` means this api will response list of users. If not required means this api will response either list users or null. `array` means this api will response array of users. `table.User` means this api will response table structure of User entity on database design above.
|
|
87
|
+
|
|
88
|
+
Other possibility return type beside table reference are native types: `string`, `boolean`, `number`, and schema inline and schema reference (schema will be explained later).
|
|
89
|
+
|
|
90
|
+
```naiv
|
|
91
|
+
api get /users/:id {
|
|
92
|
+
alias getUserById
|
|
93
|
+
path {
|
|
94
|
+
id number required
|
|
95
|
+
}
|
|
96
|
+
return table.User required
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
simple api design for get user by id. no `array` means this api will response single user. `path` means this api will accept path parameter, path parameter must be required if it has path parameter on the url. path parameter only accept native types: `string`, `number`, and `boolean`.
|
|
101
|
+
|
|
102
|
+
```naiv
|
|
103
|
+
api get /users {
|
|
104
|
+
alias getUsers
|
|
105
|
+
query {
|
|
106
|
+
name string
|
|
107
|
+
limit number required
|
|
108
|
+
offset number required
|
|
109
|
+
}
|
|
110
|
+
return array table.User required
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
simple api design for get users by name. `query` means this api will accept query parameter, query parameter must be optional if it has query parameter on the url. `required` on query `limit` and `offset` means this api will accept query parameter `limit` and `offset` and it must be required. `name` on query is optional. query parameter only accept native types: `string`, `number`, and `boolean`.
|
|
115
|
+
|
|
116
|
+
```naiv
|
|
117
|
+
api get /profile {
|
|
118
|
+
alias getProfile
|
|
119
|
+
headers {
|
|
120
|
+
authorization string required
|
|
121
|
+
}
|
|
122
|
+
return table.User required
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
simple api design for get profile. `headers` means this api will accept headers parameter, headers parameter must be required if it has headers parameter on the url. headers parameter only accept native types: `string`, `number`, and `boolean`.
|
|
127
|
+
|
|
128
|
+
#### DELETE
|
|
129
|
+
|
|
130
|
+
same like GET, DELETE also accept path, query, and headers parameter.
|
|
131
|
+
|
|
132
|
+
#### POST
|
|
133
|
+
|
|
134
|
+
```naiv
|
|
135
|
+
api post /users {
|
|
136
|
+
alias createUser
|
|
137
|
+
body {
|
|
138
|
+
name string required
|
|
139
|
+
email string required
|
|
140
|
+
password string required
|
|
141
|
+
}
|
|
142
|
+
return table.User required
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
simple api design for create user. `body` means this api will accept body parameter, body parameter must be required if it has body parameter on the url. body parameter accept native types: `string`, `number`, `boolean`, and schema inline and schema reference (schema will be explained later).
|
|
147
|
+
|
|
148
|
+
```naiv
|
|
149
|
+
api post /sample-complex-post {
|
|
150
|
+
alias sampleComplexPost
|
|
151
|
+
body {
|
|
152
|
+
foo {
|
|
153
|
+
bar string required
|
|
154
|
+
baz number required
|
|
155
|
+
} required
|
|
156
|
+
qux string required
|
|
157
|
+
}
|
|
158
|
+
return string required
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
foo has type schema inline, it means foo is a schema that defined inside api design like inline object type in typescript.
|
|
163
|
+
|
|
164
|
+
```naiv
|
|
165
|
+
schema LoginRequest {
|
|
166
|
+
email string required
|
|
167
|
+
password string required
|
|
168
|
+
}
|
|
169
|
+
api post /login {
|
|
170
|
+
alias login
|
|
171
|
+
body {
|
|
172
|
+
data schema.LoginRequest required
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
token string required
|
|
176
|
+
user table.User required
|
|
177
|
+
} required
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`data` has type schema reference `LoginRequest`, it means `data` is a schema that defined outside api design. Both schema reference and schema inline also supported for return type. If you want to have an array inside schema inline, you can use `array` keyword.
|
|
182
|
+
|
|
183
|
+
```naiv
|
|
184
|
+
schema Foo {
|
|
185
|
+
bar array string required
|
|
186
|
+
tuz string required
|
|
187
|
+
}
|
|
188
|
+
schema Buzz {
|
|
189
|
+
foo schema.Foo required
|
|
190
|
+
}
|
|
191
|
+
api post /sample-complex-post-2 {
|
|
192
|
+
alias sampleComplexPost2
|
|
193
|
+
body {
|
|
194
|
+
foo schema.Foo required
|
|
195
|
+
buzz array schema.Buzz required
|
|
196
|
+
}
|
|
197
|
+
return string required
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
object inside array also supported on body and also return type.
|
|
202
|
+
|
|
203
|
+
#### PATCH, PUT
|
|
204
|
+
|
|
205
|
+
same like POST, PATCH and PUT also accept path, query, body, and headers parameter.
|
|
206
|
+
|
|
207
|
+
#### Streaming response
|
|
208
|
+
|
|
209
|
+
NAIV api design also support streaming response. Here is how to write it:
|
|
210
|
+
|
|
211
|
+
```naiv
|
|
212
|
+
api get /chat {
|
|
213
|
+
alias promptChat
|
|
214
|
+
query {
|
|
215
|
+
prompt string required
|
|
216
|
+
}
|
|
217
|
+
return stream of string required
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`stream of` keyword means this api will return stream of data. `stream of` accept native types: `string`, `number`, `boolean`, schema (inline or reference), and table reference.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cmdChat(): Promise<void>;
|
package/dist/cmd-chat.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.cmdChat = cmdChat;
|
|
16
|
+
const enquirer_1 = require("enquirer");
|
|
17
|
+
const listr2_1 = require("listr2");
|
|
18
|
+
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const path_1 = __importDefault(require("path"));
|
|
20
|
+
const llm_runner_1 = require("@graf-research/llm-runner");
|
|
21
|
+
const core_1 = require("@naiv/core");
|
|
22
|
+
const process_1 = require("process");
|
|
23
|
+
function cmdChat() {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
const filename = `api-specification.naiv`;
|
|
26
|
+
const file_abs_path = path_1.default.resolve((0, process_1.cwd)(), filename);
|
|
27
|
+
let code = '';
|
|
28
|
+
if (fs_1.default.existsSync(file_abs_path)) {
|
|
29
|
+
code = yield fs_1.default.promises.readFile(file_abs_path, 'utf-8');
|
|
30
|
+
console.warn(`File ${file_abs_path} is exist, this file will be modified by your instruction.`);
|
|
31
|
+
}
|
|
32
|
+
const answers = yield (0, enquirer_1.prompt)([{
|
|
33
|
+
type: "input",
|
|
34
|
+
name: "prompt",
|
|
35
|
+
message: `Explain briefly your API program`,
|
|
36
|
+
}]);
|
|
37
|
+
let success_compiled = false;
|
|
38
|
+
const secret_key = process.env.SK || '';
|
|
39
|
+
const llm_model = 'google/gemini-3-flash-preview';
|
|
40
|
+
const llm_endpoint = 'https://openrouter.ai/api/v1';
|
|
41
|
+
const llm = new llm_runner_1.ChatGPTLLM(secret_key, llm_model, undefined, llm_endpoint);
|
|
42
|
+
let attempt = 0;
|
|
43
|
+
let error_message = '';
|
|
44
|
+
do {
|
|
45
|
+
yield new listr2_1.Listr([{
|
|
46
|
+
title: attempt > 0 ? "Fixing error specification NAIV DSL" : code ? "Updating existing NAIV file" : "Generating data & API specification",
|
|
47
|
+
rendererOptions: {
|
|
48
|
+
persistentOutput: true,
|
|
49
|
+
},
|
|
50
|
+
task: () => __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
const prompt = yield generatePrompt(answers.prompt, code, error_message);
|
|
52
|
+
const response = yield llm.askNoContext(prompt);
|
|
53
|
+
code = extractCodeBlocks(response);
|
|
54
|
+
try {
|
|
55
|
+
success_compiled = Boolean((0, core_1.naiv_parser)(code));
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
error_message = (err === null || err === void 0 ? void 0 : err.message) || '';
|
|
59
|
+
console.log(error_message);
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}]).run();
|
|
63
|
+
attempt++;
|
|
64
|
+
} while (!success_compiled);
|
|
65
|
+
yield fs_1.default.promises.writeFile(file_abs_path, code);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function generatePrompt(instruction, generated_code, error_message) {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
const naiv_instruction = yield fs_1.default.promises.readFile(path_1.default.resolve(__dirname, '../NAIV-INSTRUCTION.md'), 'utf-8');
|
|
71
|
+
if (error_message && generated_code) {
|
|
72
|
+
return `
|
|
73
|
+
Read this NAIV DSL carefully
|
|
74
|
+
|
|
75
|
+
${naiv_instruction}
|
|
76
|
+
|
|
77
|
+
--
|
|
78
|
+
|
|
79
|
+
An LLM generated this code on the context "${instruction}".
|
|
80
|
+
|
|
81
|
+
\`\`\`naiv
|
|
82
|
+
${generated_code}
|
|
83
|
+
\`\`\`
|
|
84
|
+
|
|
85
|
+
but there is an error: ${error_message}.
|
|
86
|
+
|
|
87
|
+
Your task: fix the error and create naiv database and api dsl code in a single file based on the context "${instruction}".
|
|
88
|
+
|
|
89
|
+
please return only naiv code with code fence block without any other strings but typescript code! your code fence block must starts with "\`\`\`naiv" and ends with "\`\`\`"
|
|
90
|
+
`.trim();
|
|
91
|
+
}
|
|
92
|
+
if (generated_code) {
|
|
93
|
+
return `
|
|
94
|
+
Read this NAIV DSL carefully
|
|
95
|
+
|
|
96
|
+
${naiv_instruction}
|
|
97
|
+
|
|
98
|
+
--
|
|
99
|
+
|
|
100
|
+
Your task: modify existing naiv database and api dsl code below based on the context "${instruction}".
|
|
101
|
+
|
|
102
|
+
Existing naiv design
|
|
103
|
+
|
|
104
|
+
\`\`\`naiv
|
|
105
|
+
${generated_code}
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
please return only naiv code with code fence block without any other strings but typescript code! your code fence block must starts with "\`\`\`naiv" and ends with "\`\`\`"
|
|
109
|
+
`.trim();
|
|
110
|
+
}
|
|
111
|
+
return `
|
|
112
|
+
Read this NAIV DSL carefully
|
|
113
|
+
|
|
114
|
+
${naiv_instruction}
|
|
115
|
+
|
|
116
|
+
--
|
|
117
|
+
|
|
118
|
+
Your task: create naiv database and api dsl code in a single file based on the context "${instruction}".
|
|
119
|
+
|
|
120
|
+
please return only naiv code with code fence block without any other strings but typescript code! your code fence block must starts with "\`\`\`naiv" and ends with "\`\`\`"
|
|
121
|
+
`.trim();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function extractCodeBlocks(text) {
|
|
125
|
+
return /```(?:naiv|dsl)\b/i.test(text)
|
|
126
|
+
? [...text.matchAll(/```(?:naiv|dsl)\s*([\s\S]*?)```/gi)]
|
|
127
|
+
.map(m => m[1])
|
|
128
|
+
.join('\n')
|
|
129
|
+
: '';
|
|
130
|
+
}
|
package/dist/cmd-generate.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { NVResult } from "@naiv/core";
|
|
2
|
+
export declare function cmdGenerate(design_file_abs_path: string, out_folder: string): Promise<NVResult>;
|
package/dist/cmd-generate.js
CHANGED
|
@@ -15,10 +15,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.cmdGenerate = cmdGenerate;
|
|
16
16
|
const naiv_parser_1 = require("@naiv/core/build/naiv_parser");
|
|
17
17
|
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
18
19
|
const _1 = require(".");
|
|
19
20
|
function cmdGenerate(design_file_abs_path, out_folder) {
|
|
20
21
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
-
const result = (0, naiv_parser_1.
|
|
22
|
+
const result = (0, naiv_parser_1.naiv_parse_folder)(design_file_abs_path, { fs: fs_1.default, path: path_1.default });
|
|
22
23
|
const typeorm_model = _1.TypeORMModel.compile(result);
|
|
23
24
|
for (const f of typeorm_model.enum.files) {
|
|
24
25
|
writeFiles(f, out_folder);
|
|
@@ -32,6 +33,7 @@ function cmdGenerate(design_file_abs_path, out_folder) {
|
|
|
32
33
|
for (const f of typeorm_model.api.files) {
|
|
33
34
|
writeFiles(f, out_folder);
|
|
34
35
|
}
|
|
36
|
+
return result;
|
|
35
37
|
});
|
|
36
38
|
}
|
|
37
39
|
function writeFiles(output_1) {
|
package/dist/cmd-run.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function cmdRun(
|
|
1
|
+
export declare function cmdRun(naiv_cwd: string): Promise<void>;
|
package/dist/cmd-run.js
CHANGED
|
@@ -14,21 +14,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.cmdRun = cmdRun;
|
|
16
16
|
const cmd_generate_1 = require("./cmd-generate");
|
|
17
|
-
const
|
|
17
|
+
const compile_folder_1 = require("./compile-folder");
|
|
18
18
|
const server_1 = require("./server");
|
|
19
19
|
const path_1 = __importDefault(require("path"));
|
|
20
20
|
const fs_1 = __importDefault(require("fs"));
|
|
21
|
-
|
|
21
|
+
const glob_1 = require("glob");
|
|
22
|
+
function cmdRun(naiv_cwd) {
|
|
22
23
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
-
const
|
|
24
|
-
|
|
24
|
+
const list_naiv_file = (0, glob_1.globSync)("*.naiv", {
|
|
25
|
+
cwd: naiv_cwd,
|
|
26
|
+
absolute: true,
|
|
27
|
+
ignore: ["**/node_modules/**"]
|
|
28
|
+
});
|
|
29
|
+
if (list_naiv_file.length == 0) {
|
|
30
|
+
throw new Error(`No NAIV files is found. Create new data and API design with "--chat"`);
|
|
31
|
+
}
|
|
32
|
+
const working_directory = path_1.default.resolve(naiv_cwd, './__flazy');
|
|
33
|
+
if (!fs_1.default.existsSync(working_directory)) {
|
|
34
|
+
yield fs_1.default.promises.mkdir(working_directory);
|
|
35
|
+
}
|
|
36
|
+
const types_path = path_1.default.resolve(naiv_cwd, './__flazy/__types__');
|
|
25
37
|
yield fs_1.default.promises.rm(types_path, { force: true, recursive: true });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
let blueprint = [];
|
|
39
|
+
for (const naiv_file of list_naiv_file) {
|
|
40
|
+
blueprint.push(yield fs_1.default.promises.readFile(naiv_file, 'utf-8'));
|
|
41
|
+
}
|
|
42
|
+
const nv_result = yield (0, cmd_generate_1.cmdGenerate)(naiv_cwd, types_path);
|
|
43
|
+
yield (0, compile_folder_1.compileAndClean)({
|
|
44
|
+
source_abs_folder_path: types_path
|
|
45
|
+
});
|
|
46
|
+
const source_implementation_path = path_1.default.resolve(naiv_cwd, './__flazy/original-implementation');
|
|
47
|
+
const transp_implementation_path = path_1.default.resolve(naiv_cwd, './__flazy/__implementation__');
|
|
48
|
+
if (fs_1.default.existsSync(source_implementation_path)) {
|
|
49
|
+
yield fs_1.default.promises.rm(transp_implementation_path, { force: true, recursive: true });
|
|
50
|
+
yield (0, compile_folder_1.compileAndClean)({
|
|
51
|
+
source_abs_folder_path: source_implementation_path,
|
|
52
|
+
target_abs_folder_path: transp_implementation_path,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
yield fs_1.default.promises.mkdir(source_implementation_path);
|
|
57
|
+
}
|
|
58
|
+
yield (new server_1.XServer({ naivCwd: naiv_cwd })).run({
|
|
29
59
|
port: 9000,
|
|
30
60
|
types_path,
|
|
31
|
-
implementation_path
|
|
61
|
+
implementation_path: transp_implementation_path,
|
|
62
|
+
nv_result,
|
|
63
|
+
blueprint
|
|
32
64
|
});
|
|
33
65
|
});
|
|
34
66
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface CompileParam {
|
|
2
|
+
source_abs_folder_path: string;
|
|
3
|
+
target_abs_folder_path?: string;
|
|
4
|
+
clean_after_compiled?: boolean;
|
|
5
|
+
replace_keywords?: {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export declare function compileAndClean(param: CompileParam): Promise<void>;
|
|
10
|
+
interface CompileSingleFileParam {
|
|
11
|
+
source_root_folder_abs_path: string;
|
|
12
|
+
source_abs_file_path: string;
|
|
13
|
+
target_root_folder_abs_path: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function compileSingleFile(param: CompileSingleFileParam): Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.compileAndClean = compileAndClean;
|
|
16
|
+
exports.compileSingleFile = compileSingleFile;
|
|
17
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const glob_1 = require("glob");
|
|
21
|
+
function compileAndClean(param) {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
var _a;
|
|
24
|
+
const target_abs_folder_path = (_a = param.target_abs_folder_path) !== null && _a !== void 0 ? _a : param.source_abs_folder_path;
|
|
25
|
+
if (!path_1.default.isAbsolute(param.source_abs_folder_path)) {
|
|
26
|
+
throw new Error("Path must be absolute");
|
|
27
|
+
}
|
|
28
|
+
if (!fs_1.default.existsSync(param.source_abs_folder_path)) {
|
|
29
|
+
throw new Error("Folder does not exist");
|
|
30
|
+
}
|
|
31
|
+
console.log("Compiling:", param.source_abs_folder_path);
|
|
32
|
+
// Find all .ts files
|
|
33
|
+
const tsFiles = (0, glob_1.globSync)("**/*.ts", {
|
|
34
|
+
cwd: param.source_abs_folder_path,
|
|
35
|
+
absolute: true,
|
|
36
|
+
ignore: ["**/node_modules/**"]
|
|
37
|
+
});
|
|
38
|
+
if (!tsFiles.length) {
|
|
39
|
+
console.log("No TypeScript files found.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Create TS program
|
|
43
|
+
const program = typescript_1.default.createProgram(tsFiles, {
|
|
44
|
+
target: typescript_1.default.ScriptTarget.ES2020,
|
|
45
|
+
module: typescript_1.default.ModuleKind.CommonJS,
|
|
46
|
+
outDir: target_abs_folder_path,
|
|
47
|
+
rootDir: param.source_abs_folder_path,
|
|
48
|
+
experimentalDecorators: true,
|
|
49
|
+
emitDecoratorMetadata: true,
|
|
50
|
+
useDefineForClassFields: false,
|
|
51
|
+
strict: true,
|
|
52
|
+
noEmitOnError: false
|
|
53
|
+
});
|
|
54
|
+
// Compile
|
|
55
|
+
const emitResult = program.emit();
|
|
56
|
+
// const diagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
|
|
57
|
+
// diagnostics.forEach(d => {
|
|
58
|
+
// console.error(ts.flattenDiagnosticMessageText(d.messageText, "\n"));
|
|
59
|
+
// });
|
|
60
|
+
if (emitResult.emitSkipped) {
|
|
61
|
+
throw new Error("Compilation failed");
|
|
62
|
+
}
|
|
63
|
+
console.log("Compilation done.");
|
|
64
|
+
if (param.replace_keywords) {
|
|
65
|
+
const jsFiles = (0, glob_1.globSync)("**/*.js", {
|
|
66
|
+
cwd: param.target_abs_folder_path,
|
|
67
|
+
absolute: true,
|
|
68
|
+
ignore: ["**/node_modules/**"]
|
|
69
|
+
});
|
|
70
|
+
const keys = Object.keys(param.replace_keywords);
|
|
71
|
+
for (const file of jsFiles) {
|
|
72
|
+
let content = yield fs_1.default.promises.readFile(file, 'utf-8');
|
|
73
|
+
for (const key of keys) {
|
|
74
|
+
content = content.replace(new RegExp(key, 'g'), param.replace_keywords[key]);
|
|
75
|
+
}
|
|
76
|
+
yield fs_1.default.promises.writeFile(file, content);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (param.clean_after_compiled) {
|
|
80
|
+
// Remove .ts files
|
|
81
|
+
for (const file of tsFiles) {
|
|
82
|
+
yield fs_1.default.promises.rm(file);
|
|
83
|
+
}
|
|
84
|
+
console.log("TS files removed.");
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function compileSingleFile(param) {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
if (!path_1.default.isAbsolute(param.source_abs_file_path)) {
|
|
91
|
+
throw new Error("Path must be absolute");
|
|
92
|
+
}
|
|
93
|
+
if (!fs_1.default.existsSync(param.source_abs_file_path)) {
|
|
94
|
+
throw new Error("This function is not implemented yet!");
|
|
95
|
+
}
|
|
96
|
+
console.log("Compiling:", param.source_abs_file_path);
|
|
97
|
+
const program = typescript_1.default.createProgram([param.source_abs_file_path], {
|
|
98
|
+
target: typescript_1.default.ScriptTarget.ES2020,
|
|
99
|
+
module: typescript_1.default.ModuleKind.CommonJS,
|
|
100
|
+
rootDir: param.source_root_folder_abs_path,
|
|
101
|
+
outDir: param.target_root_folder_abs_path,
|
|
102
|
+
experimentalDecorators: true,
|
|
103
|
+
emitDecoratorMetadata: true,
|
|
104
|
+
useDefineForClassFields: false,
|
|
105
|
+
strict: true,
|
|
106
|
+
noEmitOnError: false,
|
|
107
|
+
});
|
|
108
|
+
// Compile
|
|
109
|
+
const emitResult = program.emit();
|
|
110
|
+
// const diagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
|
|
111
|
+
// diagnostics.forEach(d => {
|
|
112
|
+
// console.error(ts.flattenDiagnosticMessageText(d.messageText, "\n"));
|
|
113
|
+
// });
|
|
114
|
+
if (emitResult.emitSkipped) {
|
|
115
|
+
throw new Error("Compilation failed");
|
|
116
|
+
}
|
|
117
|
+
console.log("Compilation done.");
|
|
118
|
+
});
|
|
119
|
+
}
|
package/dist/exec.js
CHANGED
|
@@ -6,11 +6,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const arg_1 = __importDefault(require("arg"));
|
|
8
8
|
const cmd_run_1 = require("./cmd-run");
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const cmd_chat_1 = require("./cmd-chat");
|
|
10
|
+
try {
|
|
11
|
+
const args = (0, arg_1.default)({
|
|
12
|
+
"--run": String,
|
|
13
|
+
"-r": "--run",
|
|
14
|
+
"--chat": Boolean,
|
|
15
|
+
"-c": "--chat"
|
|
16
|
+
});
|
|
17
|
+
const dir_path = args['--run'];
|
|
18
|
+
const instruction = args['--chat'];
|
|
19
|
+
if (!dir_path && !instruction) {
|
|
20
|
+
throw new Error(`use "--run <folder path>" or "--chat" to begin.`);
|
|
21
|
+
}
|
|
22
|
+
if (dir_path) {
|
|
23
|
+
(0, cmd_run_1.cmdRun)(dir_path).then(() => console.log('completed.')).catch(console.error);
|
|
24
|
+
}
|
|
25
|
+
else if (instruction) {
|
|
26
|
+
(0, cmd_chat_1.cmdChat)();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
console.error(err === null || err === void 0 ? void 0 : err.toString());
|
|
15
31
|
}
|
|
16
|
-
(0, cmd_run_1.cmdRun)(args['--design']).then(() => console.log('completed.')).catch(console.error);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NVResult } from "@naiv/core";
|
|
2
2
|
import { ItemOutput } from "./data-types";
|
|
3
|
-
import {
|
|
3
|
+
import { XServer } from './server';
|
|
4
4
|
export declare namespace TypeORMModel {
|
|
5
5
|
interface Output {
|
|
6
6
|
table: ItemOutput;
|
|
@@ -10,4 +10,4 @@ export declare namespace TypeORMModel {
|
|
|
10
10
|
}
|
|
11
11
|
function compile(source: NVResult): Output;
|
|
12
12
|
}
|
|
13
|
-
export {
|
|
13
|
+
export { XServer };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.XServer = exports.TypeORMModel = void 0;
|
|
4
4
|
const build_from_table_1 = require("./build-from-table");
|
|
5
5
|
const build_from_enum_1 = require("./build-from-enum");
|
|
6
6
|
const build_from_schema_1 = require("./build-from-schema");
|
|
7
7
|
const build_from_api_1 = require("./build-from-api");
|
|
8
8
|
const server_1 = require("./server");
|
|
9
|
-
Object.defineProperty(exports, "
|
|
9
|
+
Object.defineProperty(exports, "XServer", { enumerable: true, get: function () { return server_1.XServer; } });
|
|
10
10
|
var TypeORMModel;
|
|
11
11
|
(function (TypeORMModel) {
|
|
12
12
|
function compile(source) {
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ImplPromptParam {
|
|
2
|
+
function_name: string;
|
|
3
|
+
types_relative_path: string;
|
|
4
|
+
target_implementation_path: string;
|
|
5
|
+
types_content: string;
|
|
6
|
+
description: string;
|
|
7
|
+
model_files_list: string[];
|
|
8
|
+
blueprint: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function getImplPrompt(param: ImplPromptParam): string;
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getImplPrompt = getImplPrompt;
|
|
4
|
+
function codefence(code) {
|
|
5
|
+
return `\`\`\`\n${code.trim()}\n\`\`\``;
|
|
6
|
+
}
|
|
7
|
+
function getImplPrompt(param) {
|
|
8
|
+
const function_name_first_lowcase = `${param.function_name[0].toLowerCase()}${param.function_name.slice(1)}`;
|
|
9
|
+
return `
|
|
10
|
+
This is a system blueprint that will related to the task
|
|
11
|
+
|
|
12
|
+
${codefence(param.blueprint)}
|
|
13
|
+
|
|
14
|
+
Look at this types and signature ${param.function_name} of an API:
|
|
15
|
+
|
|
16
|
+
File location: ${param.types_relative_path}
|
|
17
|
+
|
|
18
|
+
${codefence(param.types_content)}
|
|
19
|
+
|
|
20
|
+
TypeORM models available on this files location:
|
|
21
|
+
|
|
22
|
+
${param.model_files_list.map(x => `- ${x}`).join('\n')}
|
|
23
|
+
|
|
24
|
+
Your task: implement function that satisfies that types and signature. This function should do: "${param.description}".
|
|
25
|
+
|
|
26
|
+
This is an example of my expectation how you will implement the function:
|
|
27
|
+
|
|
28
|
+
${codefence(`
|
|
29
|
+
import { SomeModel } from "../__types__/model/table/SomeModel";
|
|
30
|
+
|
|
31
|
+
export const ${function_name_first_lowcase}: ${param.function_name} = async req => {
|
|
32
|
+
// implement function here...
|
|
33
|
+
}
|
|
34
|
+
`)}
|
|
35
|
+
|
|
36
|
+
You dont need to initialize AppDataSource or use getRepository to get model, just use the model directly since it already extends BaseEntity.
|
|
37
|
+
Function name ${function_name_first_lowcase} is intentional and must be exactly same, do not change.
|
|
38
|
+
|
|
39
|
+
Do not import third party library unless it built-in nodejs library and jsonwebtoken and bcrypt available, instead of other third party library use built-in alternative or just give mockup code/result.
|
|
40
|
+
|
|
41
|
+
please return only typescript code with code fence block without any other strings but typescript code!
|
|
42
|
+
|
|
43
|
+
`.trim();
|
|
44
|
+
}
|
|
45
|
+
// console.log(getImplPrompt({
|
|
46
|
+
// function_name: "T_getProductList",
|
|
47
|
+
// types_relative_path: "__flazy/__types__/api/T_getProductList.ts",
|
|
48
|
+
// target_implementation_path: "__flazy/original-implementation/T_getProductList.ts",
|
|
49
|
+
// types_content: `
|
|
50
|
+
// import { Response } from "express";
|
|
51
|
+
// import { ClassConstructor, Transform, Type, plainToInstance } from "class-transformer";
|
|
52
|
+
// import { IsNotEmpty, IsNumber, IsObject, IsBoolean, IsOptional, IsISO8601, IsString, IsEnum, ValidateNested, IsArray, ValidationError, validateOrReject } from "class-validator";
|
|
53
|
+
// import { Product } from '../model/table/Product'
|
|
54
|
+
// class ReturnType_0 {
|
|
55
|
+
// @IsNotEmpty({ message: 'total cannot be empty' })
|
|
56
|
+
// @Transform((param?: any): number | null => (param?.value === null || param?.value === undefined || param?.value === '') ? null : parseFloat(param.value))
|
|
57
|
+
// @IsNumber({}, { message: 'total must be a number (decimal)' })
|
|
58
|
+
// total!: number
|
|
59
|
+
// @IsNotEmpty({ message: 'data cannot be empty' })
|
|
60
|
+
// @IsArray()
|
|
61
|
+
// @ValidateNested({ each: true })
|
|
62
|
+
// @Type(() => Product)
|
|
63
|
+
// data!: Product[]
|
|
64
|
+
// }
|
|
65
|
+
// export type T_getProductList = (request: {
|
|
66
|
+
// }, response: Response) => Promise<ReturnType_0>;
|
|
67
|
+
// export const method = 'get';
|
|
68
|
+
// export const url_path = '/product';
|
|
69
|
+
// export const alias = 'T_getProductList';
|
|
70
|
+
// export const is_streaming = false;
|
|
71
|
+
// `.trim(),
|
|
72
|
+
// description: "get list of products",
|
|
73
|
+
// model_files_list: [
|
|
74
|
+
// '__flazy/__types__/model/Product.ts',
|
|
75
|
+
// '__flazy/__types__/model/User.ts'
|
|
76
|
+
// ]
|
|
77
|
+
// }))
|
package/dist/server.d.ts
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { Express } from 'express';
|
|
3
|
+
import { NVResult } from '@naiv/core';
|
|
3
4
|
export interface SystemParam {
|
|
4
5
|
api_prefix?: string;
|
|
5
6
|
port: number;
|
|
6
7
|
types_path: string;
|
|
7
8
|
implementation_path: string;
|
|
8
9
|
beforeStart?(): Promise<void>;
|
|
10
|
+
nv_result: NVResult;
|
|
11
|
+
blueprint: string[];
|
|
9
12
|
}
|
|
10
13
|
export interface ServerConstructorParam {
|
|
11
14
|
noCors?: boolean;
|
|
12
15
|
noTrustProxy?: boolean;
|
|
16
|
+
naivCwd: string;
|
|
13
17
|
}
|
|
14
|
-
export declare class
|
|
18
|
+
export declare class XServer {
|
|
15
19
|
express: Express;
|
|
20
|
+
private server_instance;
|
|
16
21
|
private constructor_param;
|
|
22
|
+
private server_param;
|
|
23
|
+
private AppDataSource;
|
|
24
|
+
private secret_key;
|
|
25
|
+
private llm_model;
|
|
26
|
+
private llm_endpoint;
|
|
27
|
+
private llm;
|
|
17
28
|
constructor(param?: ServerConstructorParam);
|
|
18
|
-
|
|
29
|
+
private restart;
|
|
30
|
+
run(param: SystemParam): Promise<XServer>;
|
|
19
31
|
private errorToString;
|
|
32
|
+
private getImplementationFunction;
|
|
33
|
+
private extractCodeBlocks;
|
|
20
34
|
_run(types_folder_abs_path: string, implementation_folder: string, api_prefix?: string): Promise<void>;
|
|
21
35
|
}
|
package/dist/server.js
CHANGED
|
@@ -45,14 +45,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
45
45
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
46
|
};
|
|
47
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
-
exports.
|
|
48
|
+
exports.XServer = void 0;
|
|
49
49
|
require("reflect-metadata");
|
|
50
|
+
const typeorm_1 = require("typeorm");
|
|
50
51
|
const express_1 = __importStar(require("express"));
|
|
51
52
|
const cors_1 = __importDefault(require("cors"));
|
|
52
53
|
const class_transformer_1 = require("class-transformer");
|
|
53
54
|
const class_validator_1 = require("class-validator");
|
|
54
55
|
const path_1 = __importDefault(require("path"));
|
|
55
56
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
57
|
+
const cmd_run_1 = require("./cmd-run");
|
|
58
|
+
const compile_folder_1 = require("./compile-folder");
|
|
59
|
+
const prompt_1 = require("./prompt");
|
|
60
|
+
const fs_1 = __importDefault(require("fs"));
|
|
61
|
+
const glob_1 = require("glob");
|
|
62
|
+
const llm_runner_1 = require("@graf-research/llm-runner");
|
|
56
63
|
const { createRequire } = require("module");
|
|
57
64
|
const Module = require("module");
|
|
58
65
|
// patch resolver
|
|
@@ -61,27 +68,80 @@ const nm = path_1.default.join(root, "node_modules");
|
|
|
61
68
|
Module.globalPaths.push(nm);
|
|
62
69
|
process.env.NODE_PATH = nm;
|
|
63
70
|
Module._initPaths();
|
|
64
|
-
class
|
|
71
|
+
class XServer {
|
|
65
72
|
constructor(param) {
|
|
66
73
|
this.express = (0, express_1.default)();
|
|
74
|
+
this.secret_key = process.env.SK || '';
|
|
75
|
+
this.llm_model = 'google/gemini-3-flash-preview';
|
|
76
|
+
this.llm_endpoint = 'https://openrouter.ai/api/v1';
|
|
77
|
+
this.llm = new llm_runner_1.ChatGPTLLM(this.secret_key, this.llm_model, undefined, this.llm_endpoint);
|
|
67
78
|
this.constructor_param = param;
|
|
68
79
|
}
|
|
80
|
+
restart() {
|
|
81
|
+
var _a;
|
|
82
|
+
console.log("Shutting down...");
|
|
83
|
+
(_a = this.server_instance) === null || _a === void 0 ? void 0 : _a.close(() => __awaiter(this, void 0, void 0, function* () {
|
|
84
|
+
var _a;
|
|
85
|
+
yield ((_a = this.AppDataSource) === null || _a === void 0 ? void 0 : _a.destroy());
|
|
86
|
+
console.log("HTTP server closed");
|
|
87
|
+
if (this.server_param) {
|
|
88
|
+
console.log("Restarting server...");
|
|
89
|
+
(0, cmd_run_1.cmdRun)(this.constructor_param.naivCwd);
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
69
93
|
run(param) {
|
|
70
94
|
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
-
var _a, _b, _c, _d;
|
|
72
|
-
|
|
95
|
+
var _a, _b, _c, _d, _e;
|
|
96
|
+
try {
|
|
97
|
+
const types_location = path_1.default.resolve(this.constructor_param.naivCwd, './__flazy/__types__/model/**/*.js');
|
|
98
|
+
console.log(`types loc: ${types_location}`);
|
|
99
|
+
this.AppDataSource = new typeorm_1.DataSource({
|
|
100
|
+
type: 'mysql',
|
|
101
|
+
host: 'zero-db.naiv.dev',
|
|
102
|
+
port: 23306,
|
|
103
|
+
username: 'uname_4982e1194bb34d2cbacba11dee',
|
|
104
|
+
password: 'pwd_7a64c3d31b3445e4b1e945517100d796',
|
|
105
|
+
database: 'db_7ff95570b7714369af4512ea287ca52d',
|
|
106
|
+
synchronize: true,
|
|
107
|
+
logging: false,
|
|
108
|
+
// migrations: [
|
|
109
|
+
// param?.naivCwd + '/migration/**.ts'
|
|
110
|
+
// ],
|
|
111
|
+
entities: [
|
|
112
|
+
types_location
|
|
113
|
+
]
|
|
114
|
+
});
|
|
115
|
+
const prefix_model = path_1.default.resolve(this.constructor_param.naivCwd, './__flazy/__types__/model');
|
|
116
|
+
Object.keys(require.cache).forEach((k) => {
|
|
117
|
+
if (k.startsWith(prefix_model)) {
|
|
118
|
+
delete require.cache[k];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
yield ((_a = this.AppDataSource) === null || _a === void 0 ? void 0 : _a.initialize());
|
|
122
|
+
console.log(`db initialized`);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.log(`Error initializing database: ${err === null || err === void 0 ? void 0 : err.toString()}`);
|
|
126
|
+
}
|
|
127
|
+
this.server_param = param;
|
|
128
|
+
if (!((_b = this.constructor_param) === null || _b === void 0 ? void 0 : _b.noCors)) {
|
|
73
129
|
this.express.use((0, cors_1.default)());
|
|
74
130
|
}
|
|
75
131
|
this.express.use(express_1.default.json({ limit: '5mb' }));
|
|
76
|
-
if (!((
|
|
132
|
+
if (!((_c = this.constructor_param) === null || _c === void 0 ? void 0 : _c.noTrustProxy)) {
|
|
77
133
|
this.express.set('trust proxy', true);
|
|
78
134
|
}
|
|
79
135
|
if (param.beforeStart) {
|
|
80
136
|
yield param.beforeStart();
|
|
81
137
|
}
|
|
82
138
|
yield this._run(param.types_path, param.implementation_path, param.api_prefix);
|
|
83
|
-
const port = (
|
|
84
|
-
this.express.
|
|
139
|
+
const port = (_e = (_d = param === null || param === void 0 ? void 0 : param.port) !== null && _d !== void 0 ? _d : process.env.PORT) !== null && _e !== void 0 ? _e : 3000;
|
|
140
|
+
this.express.post('/__restart__', (req, res) => {
|
|
141
|
+
this.restart();
|
|
142
|
+
res.status(200).send('restarting...');
|
|
143
|
+
});
|
|
144
|
+
this.server_instance = this.express.listen(port, () => {
|
|
85
145
|
console.log(`\n⚡️[server]: Server is running at http://localhost:${port}`);
|
|
86
146
|
});
|
|
87
147
|
return this;
|
|
@@ -98,42 +158,86 @@ class Server {
|
|
|
98
158
|
return keys.filter(key => constrains[key].length > 0).map(key => constrains[key]).join(', ');
|
|
99
159
|
}).join(', ');
|
|
100
160
|
}
|
|
161
|
+
getImplementationFunction(file, types_folder_abs_path, implementation_folder) {
|
|
162
|
+
const filename_without_ext = path_1.default.basename(file, path_1.default.extname(file));
|
|
163
|
+
const path_location = path_1.default.join(types_folder_abs_path, 'api', filename_without_ext);
|
|
164
|
+
delete require.cache[`${implementation_folder}/${file}`];
|
|
165
|
+
const types = require(path_location);
|
|
166
|
+
const method = types['method'];
|
|
167
|
+
const url_path = types['url_path'];
|
|
168
|
+
const alias = types['alias'];
|
|
169
|
+
const is_streaming = types['is_streaming'];
|
|
170
|
+
if (!alias) {
|
|
171
|
+
console.warn(`⚠️ api '${method} ${url_path}' doesnt have alias name, skip.`);
|
|
172
|
+
return {
|
|
173
|
+
fn: null,
|
|
174
|
+
types,
|
|
175
|
+
method,
|
|
176
|
+
url_path,
|
|
177
|
+
is_streaming,
|
|
178
|
+
alias
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
let f;
|
|
182
|
+
try {
|
|
183
|
+
f = require(path_1.default.join(implementation_folder, alias));
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
if (err.code === "MODULE_NOT_FOUND") {
|
|
187
|
+
console.warn(`⚠️ implementation for api '${method} ${url_path}' not found, skip.`);
|
|
188
|
+
return {
|
|
189
|
+
fn: null,
|
|
190
|
+
types,
|
|
191
|
+
method,
|
|
192
|
+
url_path,
|
|
193
|
+
is_streaming,
|
|
194
|
+
alias
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
const export_alias = alias.charAt(0).toLowerCase() + alias.slice(1);
|
|
200
|
+
const fn = f[export_alias];
|
|
201
|
+
if (!fn) {
|
|
202
|
+
console.warn(`⚠️ export function implementation for api '${method} ${url_path}' not found, skip.`);
|
|
203
|
+
return {
|
|
204
|
+
fn: null,
|
|
205
|
+
types,
|
|
206
|
+
method,
|
|
207
|
+
url_path,
|
|
208
|
+
is_streaming,
|
|
209
|
+
alias
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
fn,
|
|
214
|
+
types,
|
|
215
|
+
method,
|
|
216
|
+
url_path,
|
|
217
|
+
is_streaming,
|
|
218
|
+
alias
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
extractCodeBlocks(text) {
|
|
222
|
+
return /```(?:js|javascript|ts|typescript)\b/i.test(text)
|
|
223
|
+
? [...text.matchAll(/```(?:js|javascript|ts|typescript)\s*([\s\S]*?)```/gi)]
|
|
224
|
+
.map(m => m[1])
|
|
225
|
+
.join('\n')
|
|
226
|
+
: '';
|
|
227
|
+
}
|
|
101
228
|
_run(types_folder_abs_path_1, implementation_folder_1) {
|
|
102
229
|
return __awaiter(this, arguments, void 0, function* (types_folder_abs_path, implementation_folder, api_prefix = '/') {
|
|
230
|
+
var _a, _b, _c;
|
|
103
231
|
if (!this.express) {
|
|
104
232
|
throw new Error('🚨 ExpressJS has not been initialized yet');
|
|
105
233
|
}
|
|
106
234
|
const router = (0, express_1.Router)();
|
|
107
235
|
const files = yield promises_1.default.readdir(path_1.default.join(types_folder_abs_path, 'api'));
|
|
108
|
-
for (const file of files) {
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const method = types['method'];
|
|
112
|
-
const url_path = types['url_path'];
|
|
113
|
-
const alias = types['alias'];
|
|
114
|
-
const is_streaming = types['is_streaming'];
|
|
115
|
-
if (!alias) {
|
|
116
|
-
console.warn(`⚠️ api '${method} ${url_path}' doesnt have alias name, skip.`);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
let f;
|
|
120
|
-
try {
|
|
121
|
-
f = require(path_1.default.join(implementation_folder, alias));
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
if (err.code === "MODULE_NOT_FOUND") {
|
|
125
|
-
console.warn(`⚠️ implementation for api '${method} ${url_path}' not found, skip.`);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
throw err;
|
|
129
|
-
}
|
|
130
|
-
const export_alias = alias.charAt(0).toLowerCase() + alias.slice(1);
|
|
131
|
-
const fn = f[export_alias];
|
|
132
|
-
if (!fn) {
|
|
133
|
-
console.warn(`⚠️ export function implementation for api '${method} ${url_path}' not found, skip.`);
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
236
|
+
for (const file of files.filter(f => f.endsWith('.js'))) {
|
|
237
|
+
const { types, method, alias, url_path, is_streaming } = this.getImplementationFunction(file, types_folder_abs_path, implementation_folder);
|
|
238
|
+
const description = ((_c = (_b = (_a = this.server_param) === null || _a === void 0 ? void 0 : _a.nv_result.list_api.find(api => api.url_path == url_path)) === null || _b === void 0 ? void 0 : _b.data.descriptions) === null || _c === void 0 ? void 0 : _c.join('\n')) || '';
|
|
136
239
|
router[method.toLowerCase()](url_path, (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
240
|
+
var _a, _b;
|
|
137
241
|
const t_headers = types[`T_${alias}_headers`];
|
|
138
242
|
const t_query = types[`T_${alias}_query`];
|
|
139
243
|
const t_path = types[`T_${alias}_path`];
|
|
@@ -163,6 +267,49 @@ class Server {
|
|
|
163
267
|
path: req.params,
|
|
164
268
|
body: req.body,
|
|
165
269
|
};
|
|
270
|
+
const prefix_source_path = path_1.default.resolve(this.constructor_param.naivCwd, './__flazy/original-implementation');
|
|
271
|
+
const prefix_target_path = path_1.default.resolve(this.constructor_param.naivCwd, './__flazy/__implementation__');
|
|
272
|
+
const types_abs_path = path_1.default.resolve(this.constructor_param.naivCwd, './__flazy/__types__/model');
|
|
273
|
+
const filename_ts = `${file.slice(0, -3)}.ts`;
|
|
274
|
+
const source_abs_file_path = `${prefix_source_path}/${filename_ts}`;
|
|
275
|
+
if (!fs_1.default.existsSync(source_abs_file_path)) {
|
|
276
|
+
try {
|
|
277
|
+
yield (0, compile_folder_1.compileSingleFile)({
|
|
278
|
+
source_root_folder_abs_path: prefix_source_path,
|
|
279
|
+
source_abs_file_path,
|
|
280
|
+
target_root_folder_abs_path: prefix_target_path
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
catch (_c) {
|
|
284
|
+
const types_path_abs = path_1.default.resolve(this.constructor_param.naivCwd, `./__flazy/__types__/api/${filename_ts}`);
|
|
285
|
+
const cwd = path_1.default.resolve(this.constructor_param.naivCwd) + '/';
|
|
286
|
+
const impl_param = {
|
|
287
|
+
function_name: file.slice(0, -3),
|
|
288
|
+
types_relative_path: `__flazy/__types__/api/${filename_ts}`,
|
|
289
|
+
target_implementation_path: `__flazy/original-implementation/api/${filename_ts}`,
|
|
290
|
+
types_content: yield fs_1.default.promises.readFile(types_path_abs, 'utf-8'),
|
|
291
|
+
description,
|
|
292
|
+
model_files_list: (0, glob_1.globSync)("**/*.ts", {
|
|
293
|
+
cwd: types_abs_path,
|
|
294
|
+
absolute: true,
|
|
295
|
+
ignore: ["**/node_modules/**"]
|
|
296
|
+
}).map(x => x.replace(cwd, '')),
|
|
297
|
+
blueprint: (_b = (_a = this.server_param) === null || _a === void 0 ? void 0 : _a.blueprint.join('\n')) !== null && _b !== void 0 ? _b : ''
|
|
298
|
+
};
|
|
299
|
+
const llm_response = yield this.llm.askNoContext((0, prompt_1.getImplPrompt)(impl_param));
|
|
300
|
+
const code = this.extractCodeBlocks(llm_response);
|
|
301
|
+
yield fs_1.default.promises.writeFile(`${prefix_source_path}/${filename_ts}`, code);
|
|
302
|
+
yield (0, compile_folder_1.compileSingleFile)({
|
|
303
|
+
source_root_folder_abs_path: prefix_source_path,
|
|
304
|
+
source_abs_file_path,
|
|
305
|
+
target_root_folder_abs_path: prefix_target_path
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const { fn } = this.getImplementationFunction(file, types_folder_abs_path, implementation_folder);
|
|
310
|
+
if (!fn) {
|
|
311
|
+
throw new Error(`This function is not implemented yet!!`);
|
|
312
|
+
}
|
|
166
313
|
if (is_streaming) {
|
|
167
314
|
res.status(200);
|
|
168
315
|
yield fn(request_params, (chunk) => res.write(chunk), res);
|
|
@@ -183,10 +330,9 @@ class Server {
|
|
|
183
330
|
res.status(500).send(err_msg);
|
|
184
331
|
}
|
|
185
332
|
}));
|
|
186
|
-
console.info(`✅api [${is_streaming ? 'stream' : 'normal'}] '${method} ${url_path}' (${alias}) is ready`);
|
|
187
333
|
}
|
|
188
334
|
this.express.use(api_prefix, router);
|
|
189
335
|
});
|
|
190
336
|
}
|
|
191
337
|
}
|
|
192
|
-
exports.
|
|
338
|
+
exports.XServer = XServer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naiv/flazy-api",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
|
-
"package.json"
|
|
19
|
+
"package.json",
|
|
20
|
+
"NAIV-INSTRUCTION.md"
|
|
20
21
|
],
|
|
21
22
|
"keywords": [],
|
|
22
23
|
"author": "",
|
|
@@ -28,18 +29,26 @@
|
|
|
28
29
|
"@types/express": "^5.0.3",
|
|
29
30
|
"@types/lodash": "^4.17.13",
|
|
30
31
|
"@types/node": "^22.8.1",
|
|
32
|
+
"@types/uuid": "^10.0.0",
|
|
31
33
|
"typescript": "^5.6.3"
|
|
32
34
|
},
|
|
33
35
|
"dependencies": {
|
|
36
|
+
"@graf-research/llm-runner": "^0.0.21",
|
|
34
37
|
"@naiv/core": "^0.0.15",
|
|
35
38
|
"arg": "^5.0.2",
|
|
39
|
+
"bcrypt": "^6.0.0",
|
|
36
40
|
"class-transformer": "^0.5.1",
|
|
37
41
|
"class-validator": "^0.14.2",
|
|
38
42
|
"cors": "^2.8.5",
|
|
43
|
+
"enquirer": "^2.4.1",
|
|
39
44
|
"express": "^5.1.0",
|
|
40
45
|
"glob": "^13.0.2",
|
|
46
|
+
"jsonwebtoken": "^9.0.3",
|
|
47
|
+
"listr2": "^10.1.0",
|
|
41
48
|
"lodash": "^4.17.21",
|
|
49
|
+
"mysql2": "^3.17.0",
|
|
42
50
|
"reflect-metadata": "^0.2.2",
|
|
43
|
-
"typeorm": "^0.3.28"
|
|
51
|
+
"typeorm": "^0.3.28",
|
|
52
|
+
"uuid": "^13.0.0"
|
|
44
53
|
}
|
|
45
54
|
}
|
package/dist/compile-folter.d.ts
DELETED
package/dist/compile-folter.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.compileAndClean = compileAndClean;
|
|
16
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
17
|
-
const path_1 = __importDefault(require("path"));
|
|
18
|
-
const fs_1 = __importDefault(require("fs"));
|
|
19
|
-
const glob_1 = require("glob");
|
|
20
|
-
/**
|
|
21
|
-
* Compile a TS folder and remove .ts files
|
|
22
|
-
*/
|
|
23
|
-
function compileAndClean(absFolderPath) {
|
|
24
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
if (!path_1.default.isAbsolute(absFolderPath)) {
|
|
26
|
-
throw new Error("Path must be absolute");
|
|
27
|
-
}
|
|
28
|
-
if (!fs_1.default.existsSync(absFolderPath)) {
|
|
29
|
-
throw new Error("Folder does not exist");
|
|
30
|
-
}
|
|
31
|
-
console.log("Compiling:", absFolderPath);
|
|
32
|
-
// Find all .ts files
|
|
33
|
-
const tsFiles = (0, glob_1.globSync)("**/*.ts", {
|
|
34
|
-
cwd: absFolderPath,
|
|
35
|
-
absolute: true,
|
|
36
|
-
ignore: ["**/node_modules/**"]
|
|
37
|
-
});
|
|
38
|
-
if (!tsFiles.length) {
|
|
39
|
-
console.log("No TypeScript files found.");
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
// Create TS program
|
|
43
|
-
const program = typescript_1.default.createProgram(tsFiles, {
|
|
44
|
-
target: typescript_1.default.ScriptTarget.ES2020,
|
|
45
|
-
module: typescript_1.default.ModuleKind.CommonJS,
|
|
46
|
-
outDir: absFolderPath,
|
|
47
|
-
rootDir: absFolderPath,
|
|
48
|
-
experimentalDecorators: true,
|
|
49
|
-
emitDecoratorMetadata: true,
|
|
50
|
-
useDefineForClassFields: false,
|
|
51
|
-
strict: true,
|
|
52
|
-
noEmitOnError: false
|
|
53
|
-
});
|
|
54
|
-
// Compile
|
|
55
|
-
const emitResult = program.emit();
|
|
56
|
-
// const diagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
|
|
57
|
-
// diagnostics.forEach(d => {
|
|
58
|
-
// console.error(ts.flattenDiagnosticMessageText(d.messageText, "\n"));
|
|
59
|
-
// });
|
|
60
|
-
if (emitResult.emitSkipped) {
|
|
61
|
-
throw new Error("Compilation failed");
|
|
62
|
-
}
|
|
63
|
-
console.log("Compilation done.");
|
|
64
|
-
// Remove .ts files
|
|
65
|
-
for (const file of tsFiles) {
|
|
66
|
-
yield fs_1.default.promises.rm(file);
|
|
67
|
-
}
|
|
68
|
-
console.log("TS files removed.");
|
|
69
|
-
});
|
|
70
|
-
}
|