@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.
Files changed (80) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/database-push.d.ts +5 -0
  3. package/dist/commands/database-push.js +15 -0
  4. package/dist/commands/database-push.js.map +1 -0
  5. package/dist/commands/dev.d.ts +2 -0
  6. package/dist/commands/dev.js +94 -6
  7. package/dist/commands/dev.js.map +1 -1
  8. package/dist/commands/doctor.d.ts +4 -0
  9. package/dist/commands/doctor.js +37 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/init.js +31 -30
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/serve.d.ts +2 -0
  14. package/dist/commands/serve.js +122 -46
  15. package/dist/commands/serve.js.map +1 -1
  16. package/dist/commands/start.d.ts +1 -0
  17. package/dist/commands/start.js +15 -0
  18. package/dist/commands/start.js.map +1 -1
  19. package/dist/index.js +173 -210
  20. package/dist/index.js.map +1 -1
  21. package/package.json +13 -7
  22. package/templates/hello-world/.vscode/extensions.json +7 -0
  23. package/templates/hello-world/CHANGELOG.md +41 -0
  24. package/templates/hello-world/README.md +29 -0
  25. package/templates/hello-world/package.json +24 -0
  26. package/templates/hello-world/src/index.ts +58 -0
  27. package/templates/hello-world/tsconfig.json +10 -0
  28. package/templates/starter/.vscode/extensions.json +7 -0
  29. package/{CHANGELOG.md → templates/starter/CHANGELOG.md} +36 -42
  30. package/templates/starter/README.md +17 -0
  31. package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
  32. package/templates/starter/jest.config.js +16 -0
  33. package/templates/starter/package.json +52 -0
  34. package/templates/starter/src/README.pages.md +110 -0
  35. package/templates/starter/src/demo.app.yml +4 -0
  36. package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
  37. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
  38. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
  39. package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
  40. package/templates/starter/src/modules/projects/projects.action.ts +472 -0
  41. package/templates/starter/src/modules/projects/projects.data.yml +13 -0
  42. package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
  43. package/templates/starter/src/modules/projects/projects.object.yml +148 -0
  44. package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
  45. package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
  46. package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
  47. package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
  48. package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
  49. package/templates/starter/src/seed.ts +55 -0
  50. package/templates/starter/src/types/index.ts +3 -0
  51. package/templates/starter/src/types/kitchen_sink.ts +101 -0
  52. package/templates/starter/src/types/projects.ts +49 -0
  53. package/templates/starter/src/types/tasks.ts +33 -0
  54. package/templates/starter/tsconfig.json +11 -0
  55. package/templates/starter/tsconfig.tsbuildinfo +1 -0
  56. package/AI_EXAMPLES.md +0 -154
  57. package/AI_IMPLEMENTATION_SUMMARY.md +0 -509
  58. package/AI_TUTORIAL.md +0 -144
  59. package/IMPLEMENTATION_SUMMARY.md +0 -437
  60. package/USAGE_EXAMPLES.md +0 -951
  61. package/__tests__/commands.test.ts +0 -426
  62. package/jest.config.js +0 -19
  63. package/src/commands/ai.ts +0 -509
  64. package/src/commands/build.ts +0 -98
  65. package/src/commands/dev.ts +0 -23
  66. package/src/commands/format.ts +0 -110
  67. package/src/commands/generate.ts +0 -135
  68. package/src/commands/i18n.ts +0 -303
  69. package/src/commands/init.ts +0 -191
  70. package/src/commands/lint.ts +0 -98
  71. package/src/commands/migrate.ts +0 -314
  72. package/src/commands/new.ts +0 -221
  73. package/src/commands/repl.ts +0 -120
  74. package/src/commands/serve.ts +0 -96
  75. package/src/commands/start.ts +0 -100
  76. package/src/commands/sync.ts +0 -328
  77. package/src/commands/test.ts +0 -98
  78. package/src/index.ts +0 -356
  79. package/tsconfig.json +0 -15
  80. package/tsconfig.tsbuildinfo +0 -1
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }