@objectql/cli 1.8.4 → 1.9.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.
- package/README.md +2 -2
- package/dist/commands/database-push.d.ts +5 -0
- package/dist/commands/database-push.js +15 -0
- package/dist/commands/database-push.js.map +1 -0
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.js +94 -6
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +37 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +31 -30
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/serve.d.ts +2 -0
- package/dist/commands/serve.js +122 -46
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/start.d.ts +1 -0
- package/dist/commands/start.js +15 -0
- package/dist/commands/start.js.map +1 -1
- package/dist/index.js +173 -210
- package/dist/index.js.map +1 -1
- package/package.json +13 -7
- package/templates/hello-world/.vscode/extensions.json +7 -0
- package/templates/hello-world/CHANGELOG.md +41 -0
- package/templates/hello-world/README.md +29 -0
- package/templates/hello-world/package.json +24 -0
- package/templates/hello-world/src/index.ts +58 -0
- package/templates/hello-world/tsconfig.json +10 -0
- package/templates/starter/.vscode/extensions.json +7 -0
- package/{CHANGELOG.md → templates/starter/CHANGELOG.md} +36 -42
- package/templates/starter/README.md +17 -0
- package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
- package/templates/starter/jest.config.js +16 -0
- package/templates/starter/package.json +52 -0
- package/templates/starter/src/README.pages.md +110 -0
- package/templates/starter/src/demo.app.yml +4 -0
- package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
- package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
- package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
- package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
- package/templates/starter/src/modules/projects/projects.action.ts +472 -0
- package/templates/starter/src/modules/projects/projects.data.yml +13 -0
- package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
- package/templates/starter/src/modules/projects/projects.object.yml +148 -0
- package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
- package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
- package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
- package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
- package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
- package/templates/starter/src/seed.ts +55 -0
- package/templates/starter/src/types/index.ts +3 -0
- package/templates/starter/src/types/kitchen_sink.ts +101 -0
- package/templates/starter/src/types/projects.ts +49 -0
- package/templates/starter/src/types/tasks.ts +33 -0
- package/templates/starter/tsconfig.json +11 -0
- package/templates/starter/tsconfig.tsbuildinfo +1 -0
- package/AI_EXAMPLES.md +0 -154
- package/AI_IMPLEMENTATION_SUMMARY.md +0 -509
- package/AI_TUTORIAL.md +0 -144
- package/IMPLEMENTATION_SUMMARY.md +0 -437
- package/USAGE_EXAMPLES.md +0 -951
- package/__tests__/commands.test.ts +0 -426
- package/jest.config.js +0 -19
- package/src/commands/ai.ts +0 -509
- package/src/commands/build.ts +0 -98
- package/src/commands/dev.ts +0 -23
- package/src/commands/format.ts +0 -110
- package/src/commands/generate.ts +0 -135
- package/src/commands/i18n.ts +0 -303
- package/src/commands/init.ts +0 -191
- package/src/commands/lint.ts +0 -98
- package/src/commands/migrate.ts +0 -314
- package/src/commands/new.ts +0 -221
- package/src/commands/repl.ts +0 -120
- package/src/commands/serve.ts +0 -96
- package/src/commands/start.ts +0 -100
- package/src/commands/sync.ts +0 -328
- package/src/commands/test.ts +0 -98
- package/src/index.ts +0 -356
- package/tsconfig.json +0 -15
- package/tsconfig.tsbuildinfo +0 -1
package/src/commands/new.ts
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import * as yaml from 'js-yaml';
|
|
5
|
-
|
|
6
|
-
interface NewOptions {
|
|
7
|
-
type: string;
|
|
8
|
-
name: string;
|
|
9
|
-
dir?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const METADATA_TYPES = [
|
|
13
|
-
'object',
|
|
14
|
-
'action',
|
|
15
|
-
'hook',
|
|
16
|
-
'permission',
|
|
17
|
-
'validation',
|
|
18
|
-
'workflow',
|
|
19
|
-
'data'
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
const TEMPLATES: Record<string, any> = {
|
|
23
|
-
object: {
|
|
24
|
-
label: '{{label}}',
|
|
25
|
-
fields: {
|
|
26
|
-
name: {
|
|
27
|
-
type: 'text',
|
|
28
|
-
label: 'Name',
|
|
29
|
-
required: true
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
action: {
|
|
34
|
-
label: '{{label}} Action',
|
|
35
|
-
object: '{{objectName}}',
|
|
36
|
-
type: 'record',
|
|
37
|
-
handler: 'action_{{name}}'
|
|
38
|
-
},
|
|
39
|
-
hook: {
|
|
40
|
-
label: '{{label}} Hook',
|
|
41
|
-
object: '{{objectName}}',
|
|
42
|
-
triggers: ['before_insert', 'after_insert']
|
|
43
|
-
},
|
|
44
|
-
permission: {
|
|
45
|
-
label: '{{label}} Permissions',
|
|
46
|
-
object: '{{objectName}}',
|
|
47
|
-
profiles: {
|
|
48
|
-
admin: {
|
|
49
|
-
allow_read: true,
|
|
50
|
-
allow_create: true,
|
|
51
|
-
allow_edit: true,
|
|
52
|
-
allow_delete: true
|
|
53
|
-
},
|
|
54
|
-
user: {
|
|
55
|
-
allow_read: true,
|
|
56
|
-
allow_create: false,
|
|
57
|
-
allow_edit: false,
|
|
58
|
-
allow_delete: false
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
validation: {
|
|
63
|
-
label: '{{label}} Validation',
|
|
64
|
-
object: '{{objectName}}',
|
|
65
|
-
rules: [
|
|
66
|
-
{
|
|
67
|
-
name: 'required_name',
|
|
68
|
-
type: 'field',
|
|
69
|
-
field: 'name',
|
|
70
|
-
rule: 'required',
|
|
71
|
-
message: 'Name is required'
|
|
72
|
-
}
|
|
73
|
-
]
|
|
74
|
-
},
|
|
75
|
-
workflow: {
|
|
76
|
-
label: '{{label}} Workflow',
|
|
77
|
-
object: '{{objectName}}',
|
|
78
|
-
trigger: 'on_create',
|
|
79
|
-
actions: [
|
|
80
|
-
{
|
|
81
|
-
type: 'field_update',
|
|
82
|
-
field: 'status',
|
|
83
|
-
value: 'draft'
|
|
84
|
-
}
|
|
85
|
-
]
|
|
86
|
-
},
|
|
87
|
-
data: {
|
|
88
|
-
label: '{{label}} Data',
|
|
89
|
-
object: '{{objectName}}',
|
|
90
|
-
records: []
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export async function newMetadata(options: NewOptions) {
|
|
95
|
-
const { type, name, dir = '.' } = options;
|
|
96
|
-
|
|
97
|
-
// Validate type
|
|
98
|
-
if (!METADATA_TYPES.includes(type)) {
|
|
99
|
-
console.error(chalk.red(`❌ Unknown metadata type: ${type}`));
|
|
100
|
-
console.log(chalk.gray(`Available types: ${METADATA_TYPES.join(', ')}`));
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Validate name
|
|
105
|
-
if (!name || !/^[a-z][a-z0-9_]*$/.test(name)) {
|
|
106
|
-
console.error(chalk.red('❌ Invalid name. Must be lowercase with underscores (e.g., my_object)'));
|
|
107
|
-
process.exit(1);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const targetDir = path.resolve(process.cwd(), dir);
|
|
111
|
-
|
|
112
|
-
// Create directory if it doesn't exist
|
|
113
|
-
if (!fs.existsSync(targetDir)) {
|
|
114
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const filename = `${name}.${type}.yml`;
|
|
118
|
-
const filePath = path.join(targetDir, filename);
|
|
119
|
-
|
|
120
|
-
// Check if file already exists
|
|
121
|
-
if (fs.existsSync(filePath)) {
|
|
122
|
-
console.error(chalk.red(`❌ File already exists: ${filePath}`));
|
|
123
|
-
process.exit(1);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Get template and replace placeholders
|
|
127
|
-
let template = TEMPLATES[type];
|
|
128
|
-
const label = nameToLabel(name);
|
|
129
|
-
const objectName = type === 'object' ? name : extractObjectName(name);
|
|
130
|
-
|
|
131
|
-
template = JSON.parse(
|
|
132
|
-
JSON.stringify(template)
|
|
133
|
-
.replace(/\{\{label\}\}/g, label)
|
|
134
|
-
.replace(/\{\{name\}\}/g, name)
|
|
135
|
-
.replace(/\{\{objectName\}\}/g, objectName)
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
// Write YAML file
|
|
139
|
-
const yamlContent = yaml.dump(template, {
|
|
140
|
-
indent: 2,
|
|
141
|
-
lineWidth: 120,
|
|
142
|
-
noRefs: true
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
fs.writeFileSync(filePath, yamlContent, 'utf-8');
|
|
146
|
-
|
|
147
|
-
console.log(chalk.green(`✅ Created ${filename}`));
|
|
148
|
-
console.log(chalk.gray(` Path: ${filePath}`));
|
|
149
|
-
|
|
150
|
-
// If it's an action or hook, also create the TypeScript implementation file
|
|
151
|
-
if (type === 'action' || type === 'hook') {
|
|
152
|
-
await createTsImplementation(type, name, targetDir);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function createTsImplementation(type: 'action' | 'hook', name: string, dir: string) {
|
|
157
|
-
const filename = `${name}.${type}.ts`;
|
|
158
|
-
const filePath = path.join(dir, filename);
|
|
159
|
-
|
|
160
|
-
if (fs.existsSync(filePath)) {
|
|
161
|
-
console.log(chalk.yellow(`⚠ TypeScript file already exists: ${filename}`));
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let template = '';
|
|
166
|
-
|
|
167
|
-
if (type === 'action') {
|
|
168
|
-
template = `import { ActionContext } from '@objectql/types';
|
|
169
|
-
|
|
170
|
-
export async function action_${name}(context: ActionContext) {
|
|
171
|
-
const { record, user } = context;
|
|
172
|
-
|
|
173
|
-
// TODO: Implement action logic
|
|
174
|
-
console.log('Action ${name} triggered for record:', record._id);
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
success: true,
|
|
178
|
-
message: 'Action completed successfully'
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
`;
|
|
182
|
-
} else if (type === 'hook') {
|
|
183
|
-
template = `import { HookContext } from '@objectql/types';
|
|
184
|
-
|
|
185
|
-
export async function beforeInsert(context: HookContext) {
|
|
186
|
-
const { doc } = context;
|
|
187
|
-
|
|
188
|
-
// TODO: Implement before insert logic
|
|
189
|
-
console.log('Before insert hook for ${name}');
|
|
190
|
-
|
|
191
|
-
// Modify doc as needed
|
|
192
|
-
return doc;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export async function afterInsert(context: HookContext) {
|
|
196
|
-
const { doc } = context;
|
|
197
|
-
|
|
198
|
-
// TODO: Implement after insert logic
|
|
199
|
-
console.log('After insert hook for ${name}');
|
|
200
|
-
}
|
|
201
|
-
`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
fs.writeFileSync(filePath, template, 'utf-8');
|
|
205
|
-
console.log(chalk.green(`✅ Created ${filename}`));
|
|
206
|
-
console.log(chalk.gray(` Path: ${filePath}`));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function nameToLabel(name: string): string {
|
|
210
|
-
return name
|
|
211
|
-
.split('_')
|
|
212
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
213
|
-
.join(' ');
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function extractObjectName(name: string): string {
|
|
217
|
-
// Try to extract object name from name like "project_status" -> "project"
|
|
218
|
-
// This is a heuristic and might not always be correct
|
|
219
|
-
const parts = name.split('_');
|
|
220
|
-
return parts[0];
|
|
221
|
-
}
|
package/src/commands/repl.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import * as repl from 'repl';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import { ObjectQL } from '@objectql/core';
|
|
5
|
-
import { register } from 'ts-node';
|
|
6
|
-
|
|
7
|
-
export async function startRepl(configPath?: string) {
|
|
8
|
-
const cwd = process.cwd();
|
|
9
|
-
|
|
10
|
-
// Register ts-node to handle TS config loading
|
|
11
|
-
register({
|
|
12
|
-
transpileOnly: true,
|
|
13
|
-
compilerOptions: {
|
|
14
|
-
module: "commonjs"
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// 1. Resolve Config File
|
|
19
|
-
let configFile = configPath;
|
|
20
|
-
if (!configFile) {
|
|
21
|
-
const potentialFiles = ['objectql.config.ts', 'objectql.config.js'];
|
|
22
|
-
for (const file of potentialFiles) {
|
|
23
|
-
if (fs.existsSync(path.join(cwd, file))) {
|
|
24
|
-
configFile = file;
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (!configFile) {
|
|
31
|
-
console.error("❌ No configuration file found (objectql.config.ts/js).");
|
|
32
|
-
console.log("Please create one that exports an ObjectQL instance.");
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log(`🚀 Loading configuration from ${configFile}...`);
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const configModule = require(path.join(cwd, configFile));
|
|
40
|
-
// Support default export or named export 'app' or 'objectql' or 'db'
|
|
41
|
-
const app = configModule.default || configModule.app || configModule.objectql || configModule.db;
|
|
42
|
-
|
|
43
|
-
if (!(app instanceof ObjectQL)) {
|
|
44
|
-
console.error("❌ The config file must export an instance of 'ObjectQL' as default or 'app'/'db'.");
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// 2. Init ObjectQL
|
|
49
|
-
await app.init();
|
|
50
|
-
console.log("✅ ObjectQL Initialized.");
|
|
51
|
-
|
|
52
|
-
// 3. Start REPL
|
|
53
|
-
const r = repl.start({
|
|
54
|
-
prompt: 'objectql> ',
|
|
55
|
-
useColors: true
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Enable Auto-Await for Promises
|
|
59
|
-
const defaultEval = r.eval;
|
|
60
|
-
(r as any).eval = (cmd: string, context: any, filename: string, callback: any) => {
|
|
61
|
-
defaultEval.call(r, cmd, context, filename, async (err: Error | null, result: any) => {
|
|
62
|
-
if (err) return callback(err, null);
|
|
63
|
-
if (result && typeof result.then === 'function') {
|
|
64
|
-
try {
|
|
65
|
-
const value = await result;
|
|
66
|
-
callback(null, value);
|
|
67
|
-
} catch (e: any) {
|
|
68
|
-
callback(e, null);
|
|
69
|
-
}
|
|
70
|
-
} else {
|
|
71
|
-
callback(null, result);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// 4. Inject Context
|
|
77
|
-
r.context.app = app;
|
|
78
|
-
r.context.db = app; // Alias for db
|
|
79
|
-
r.context.object = (name: string) => app.getObject(name);
|
|
80
|
-
|
|
81
|
-
// Helper to get a repo quickly: tasks.find() instead of app.object('tasks').find()
|
|
82
|
-
const objects = app.metadata.list('object');
|
|
83
|
-
for (const obj of objects) {
|
|
84
|
-
// Inject repositories as top-level globals if valid identifiers
|
|
85
|
-
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(obj.name)) {
|
|
86
|
-
// We use a getter to lazily create context with system privileges
|
|
87
|
-
Object.defineProperty(r.context, obj.name, {
|
|
88
|
-
get: () => {
|
|
89
|
-
// HACK: We need to construct a repository.
|
|
90
|
-
// Since `ObjectRepository` is exported from `@objectql/core`, we can use it if we import it.
|
|
91
|
-
// But `app` is passed from user land. We can rely on `require('@objectql/core')` here.
|
|
92
|
-
const { ObjectRepository } = require('@objectql/core');
|
|
93
|
-
|
|
94
|
-
const replContext: any = {
|
|
95
|
-
roles: ['admin'],
|
|
96
|
-
isSystem: true,
|
|
97
|
-
userId: 'REPL'
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
replContext.object = (n: string) => new ObjectRepository(n, replContext, app);
|
|
101
|
-
replContext.transaction = async (cb: any) => cb(replContext);
|
|
102
|
-
replContext.sudo = () => replContext;
|
|
103
|
-
|
|
104
|
-
return new ObjectRepository(obj.name, replContext, app);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
console.log(`\nAvailable Objects: ${objects.map((o: any) => o.name).join(', ')}`);
|
|
111
|
-
console.log(`Usage: tasks.find() (Auto-await enabled)`);
|
|
112
|
-
|
|
113
|
-
// Fix for REPL sometimes not showing prompt immediately
|
|
114
|
-
r.displayPrompt();
|
|
115
|
-
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.error("Failed to load or start:", error);
|
|
118
|
-
process.exit(1);
|
|
119
|
-
}
|
|
120
|
-
}
|
package/src/commands/serve.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { ObjectQL } from '@objectql/core';
|
|
2
|
-
import { SqlDriver } from '@objectql/driver-sql';
|
|
3
|
-
import { ObjectLoader } from '@objectql/platform-node';
|
|
4
|
-
import { createNodeHandler } from '@objectql/server';
|
|
5
|
-
import { createServer } from 'http';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
|
|
9
|
-
const CONSOLE_HTML = `
|
|
10
|
-
<!DOCTYPE html>
|
|
11
|
-
<html lang="en">
|
|
12
|
-
<head>
|
|
13
|
-
<meta charset="utf-8" />
|
|
14
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
15
|
-
<title>ObjectQL Swagger UI</title>
|
|
16
|
-
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css" />
|
|
17
|
-
<style>
|
|
18
|
-
body { margin: 0; padding: 0; }
|
|
19
|
-
</style>
|
|
20
|
-
</head>
|
|
21
|
-
<body>
|
|
22
|
-
<div id="swagger-ui"></div>
|
|
23
|
-
<script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js" crossorigin></script>
|
|
24
|
-
<script>
|
|
25
|
-
window.onload = () => {
|
|
26
|
-
window.ui = SwaggerUIBundle({
|
|
27
|
-
url: '/openapi.json',
|
|
28
|
-
dom_id: '#swagger-ui',
|
|
29
|
-
});
|
|
30
|
-
};
|
|
31
|
-
</script>
|
|
32
|
-
</body>
|
|
33
|
-
</html>
|
|
34
|
-
`;
|
|
35
|
-
|
|
36
|
-
export async function serve(options: { port: number; dir: string }) {
|
|
37
|
-
console.log(chalk.blue('Starting ObjectQL Dev Server...'));
|
|
38
|
-
|
|
39
|
-
const rootDir = path.resolve(process.cwd(), options.dir);
|
|
40
|
-
console.log(chalk.gray(`Loading schema from: ${rootDir}`));
|
|
41
|
-
|
|
42
|
-
// 1. Init ObjectQL with in-memory SQLite for Dev
|
|
43
|
-
const app = new ObjectQL({
|
|
44
|
-
datasources: {
|
|
45
|
-
default: new SqlDriver({
|
|
46
|
-
client: 'sqlite3',
|
|
47
|
-
connection: {
|
|
48
|
-
filename: ':memory:' // Or local file './dev.db'
|
|
49
|
-
},
|
|
50
|
-
useNullAsDefault: true
|
|
51
|
-
})
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// 2. Load Schema
|
|
56
|
-
try {
|
|
57
|
-
const loader = new ObjectLoader(app.metadata);
|
|
58
|
-
loader.load(rootDir);
|
|
59
|
-
await app.init();
|
|
60
|
-
console.log(chalk.green('✅ Schema loaded successfully.'));
|
|
61
|
-
} catch (e: any) {
|
|
62
|
-
console.error(chalk.red('❌ Failed to load schema:'), e.message);
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 3. Create Handler
|
|
67
|
-
const internalHandler = createNodeHandler(app);
|
|
68
|
-
|
|
69
|
-
// 4. Start Server
|
|
70
|
-
const server = createServer(async (req, res) => {
|
|
71
|
-
// Serve Swagger UI
|
|
72
|
-
if (req.method === 'GET' && (req.url === '/swagger' || req.url === '/swagger/')) {
|
|
73
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
74
|
-
res.end(CONSOLE_HTML);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Redirect / to /swagger for better DX
|
|
79
|
-
if (req.method === 'GET' && req.url === '/') {
|
|
80
|
-
res.writeHead(302, { 'Location': '/swagger' });
|
|
81
|
-
res.end();
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Delegate to API Handler
|
|
86
|
-
await internalHandler(req, res);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
server.listen(options.port, () => {
|
|
90
|
-
console.log(chalk.green(`\n🚀 Server ready at http://localhost:${options.port}`));
|
|
91
|
-
console.log(chalk.green(`📚 Swagger UI: http://localhost:${options.port}/swagger`));
|
|
92
|
-
console.log(chalk.blue(`📖 OpenAPI Spec: http://localhost:${options.port}/openapi.json`));
|
|
93
|
-
console.log(chalk.gray('\nTry a curl command:'));
|
|
94
|
-
console.log(`curl -X POST http://localhost:${options.port} -H "Content-Type: application/json" -d '{"op": "find", "object": "YourObject", "args": {}}'`);
|
|
95
|
-
});
|
|
96
|
-
}
|
package/src/commands/start.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { ObjectQL } from '@objectql/core';
|
|
2
|
-
import { SqlDriver } from '@objectql/driver-sql';
|
|
3
|
-
import { ObjectLoader } from '@objectql/platform-node';
|
|
4
|
-
import { createNodeHandler } from '@objectql/server';
|
|
5
|
-
import { createServer } from 'http';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
|
|
10
|
-
interface StartOptions {
|
|
11
|
-
port: number;
|
|
12
|
-
dir: string;
|
|
13
|
-
config?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Flexible config type that handles both ObjectQLConfig and custom config formats
|
|
17
|
-
interface LoadedConfig {
|
|
18
|
-
datasources?: Record<string, any>;
|
|
19
|
-
datasource?: Record<string, any>;
|
|
20
|
-
[key: string]: any;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Start production server
|
|
25
|
-
* Loads configuration from objectql.config.ts/js if available
|
|
26
|
-
*/
|
|
27
|
-
export async function start(options: StartOptions) {
|
|
28
|
-
console.log(chalk.blue('Starting ObjectQL Production Server...'));
|
|
29
|
-
|
|
30
|
-
const rootDir = path.resolve(process.cwd(), options.dir);
|
|
31
|
-
console.log(chalk.gray(`Loading schema from: ${rootDir}`));
|
|
32
|
-
|
|
33
|
-
// Try to load configuration
|
|
34
|
-
let config: LoadedConfig | null = null;
|
|
35
|
-
const configPath = options.config || path.join(process.cwd(), 'objectql.config.ts');
|
|
36
|
-
|
|
37
|
-
if (fs.existsSync(configPath)) {
|
|
38
|
-
try {
|
|
39
|
-
console.log(chalk.gray(`Loading config from: ${configPath}`));
|
|
40
|
-
// Use require for .js files or ts-node for .ts files
|
|
41
|
-
if (configPath.endsWith('.ts')) {
|
|
42
|
-
require('ts-node/register');
|
|
43
|
-
}
|
|
44
|
-
const loadedModule = require(configPath);
|
|
45
|
-
// Handle both default export and direct export
|
|
46
|
-
config = loadedModule.default || loadedModule;
|
|
47
|
-
} catch (e: any) {
|
|
48
|
-
console.warn(chalk.yellow(`⚠️ Failed to load config: ${e.message}`));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Initialize datasource from config or use default SQLite
|
|
53
|
-
// Note: Config files may use 'datasource' (singular) while ObjectQLConfig uses 'datasources' (plural)
|
|
54
|
-
const datasourceConfig = config?.datasources?.default || config?.datasource?.default || {
|
|
55
|
-
client: 'sqlite3',
|
|
56
|
-
connection: {
|
|
57
|
-
filename: process.env.DATABASE_FILE || './objectql.db'
|
|
58
|
-
},
|
|
59
|
-
useNullAsDefault: true
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const driver = new SqlDriver(datasourceConfig);
|
|
63
|
-
const app = new ObjectQL({
|
|
64
|
-
datasources: { default: driver }
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Load Schema
|
|
68
|
-
try {
|
|
69
|
-
const loader = new ObjectLoader(app.metadata);
|
|
70
|
-
loader.load(rootDir);
|
|
71
|
-
await app.init();
|
|
72
|
-
console.log(chalk.green('✅ Schema loaded successfully.'));
|
|
73
|
-
} catch (e: any) {
|
|
74
|
-
console.error(chalk.red('❌ Failed to load schema:'), e.message);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Create Handler
|
|
79
|
-
const handler = createNodeHandler(app);
|
|
80
|
-
|
|
81
|
-
// Start Server
|
|
82
|
-
const server = createServer(async (req, res) => {
|
|
83
|
-
await handler(req, res);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
server.listen(options.port, () => {
|
|
87
|
-
console.log(chalk.green(`\n✅ Server started in production mode`));
|
|
88
|
-
console.log(chalk.green(`🚀 API endpoint: http://localhost:${options.port}`));
|
|
89
|
-
console.log(chalk.blue(`📖 OpenAPI Spec: http://localhost:${options.port}/openapi.json`));
|
|
90
|
-
|
|
91
|
-
// Handle graceful shutdown
|
|
92
|
-
process.on('SIGTERM', () => {
|
|
93
|
-
console.log(chalk.yellow('\n⚠️ SIGTERM received, shutting down gracefully...'));
|
|
94
|
-
server.close(() => {
|
|
95
|
-
console.log(chalk.green('✅ Server closed'));
|
|
96
|
-
process.exit(0);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
}
|