@objectql/cli 1.8.3 → 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 (94) hide show
  1. package/README.md +157 -1
  2. package/dist/commands/ai.js +4 -3
  3. package/dist/commands/ai.js.map +1 -1
  4. package/dist/commands/build.d.ts +12 -0
  5. package/dist/commands/build.js +119 -0
  6. package/dist/commands/build.js.map +1 -0
  7. package/dist/commands/database-push.d.ts +5 -0
  8. package/dist/commands/database-push.js +15 -0
  9. package/dist/commands/database-push.js.map +1 -0
  10. package/dist/commands/dev.d.ts +11 -0
  11. package/dist/commands/dev.js +111 -0
  12. package/dist/commands/dev.js.map +1 -0
  13. package/dist/commands/doctor.d.ts +4 -0
  14. package/dist/commands/doctor.js +37 -0
  15. package/dist/commands/doctor.js.map +1 -0
  16. package/dist/commands/format.d.ts +9 -0
  17. package/dist/commands/format.js +137 -0
  18. package/dist/commands/format.js.map +1 -0
  19. package/dist/commands/init.js +31 -30
  20. package/dist/commands/init.js.map +1 -1
  21. package/dist/commands/lint.d.ts +9 -0
  22. package/dist/commands/lint.js +120 -0
  23. package/dist/commands/lint.js.map +1 -0
  24. package/dist/commands/new.js +0 -52
  25. package/dist/commands/new.js.map +1 -1
  26. package/dist/commands/serve.d.ts +2 -0
  27. package/dist/commands/serve.js +122 -46
  28. package/dist/commands/serve.js.map +1 -1
  29. package/dist/commands/start.d.ts +12 -0
  30. package/dist/commands/start.js +134 -0
  31. package/dist/commands/start.js.map +1 -0
  32. package/dist/commands/test.d.ts +10 -0
  33. package/dist/commands/test.js +120 -0
  34. package/dist/commands/test.js.map +1 -0
  35. package/dist/index.js +189 -149
  36. package/dist/index.js.map +1 -1
  37. package/package.json +13 -8
  38. package/templates/hello-world/.vscode/extensions.json +7 -0
  39. package/templates/hello-world/CHANGELOG.md +41 -0
  40. package/templates/hello-world/README.md +29 -0
  41. package/templates/hello-world/package.json +24 -0
  42. package/templates/hello-world/src/index.ts +58 -0
  43. package/templates/hello-world/tsconfig.json +10 -0
  44. package/templates/starter/.vscode/extensions.json +7 -0
  45. package/{CHANGELOG.md → templates/starter/CHANGELOG.md} +47 -41
  46. package/templates/starter/README.md +17 -0
  47. package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
  48. package/templates/starter/jest.config.js +16 -0
  49. package/templates/starter/package.json +52 -0
  50. package/templates/starter/src/README.pages.md +110 -0
  51. package/templates/starter/src/demo.app.yml +4 -0
  52. package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
  53. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
  54. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
  55. package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
  56. package/templates/starter/src/modules/projects/projects.action.ts +472 -0
  57. package/templates/starter/src/modules/projects/projects.data.yml +13 -0
  58. package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
  59. package/templates/starter/src/modules/projects/projects.object.yml +148 -0
  60. package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
  61. package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
  62. package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
  63. package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
  64. package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
  65. package/templates/starter/src/seed.ts +55 -0
  66. package/templates/starter/src/types/index.ts +3 -0
  67. package/templates/starter/src/types/kitchen_sink.ts +101 -0
  68. package/templates/starter/src/types/projects.ts +49 -0
  69. package/templates/starter/src/types/tasks.ts +33 -0
  70. package/templates/starter/tsconfig.json +11 -0
  71. package/templates/starter/tsconfig.tsbuildinfo +1 -0
  72. package/AI_EXAMPLES.md +0 -154
  73. package/AI_IMPLEMENTATION_SUMMARY.md +0 -509
  74. package/AI_TUTORIAL.md +0 -144
  75. package/IMPLEMENTATION_SUMMARY.md +0 -437
  76. package/USAGE_EXAMPLES.md +0 -951
  77. package/__tests__/commands.test.ts +0 -316
  78. package/dist/commands/studio.d.ts +0 -5
  79. package/dist/commands/studio.js +0 -364
  80. package/dist/commands/studio.js.map +0 -1
  81. package/jest.config.js +0 -19
  82. package/src/commands/ai.ts +0 -508
  83. package/src/commands/generate.ts +0 -135
  84. package/src/commands/i18n.ts +0 -303
  85. package/src/commands/init.ts +0 -191
  86. package/src/commands/migrate.ts +0 -314
  87. package/src/commands/new.ts +0 -273
  88. package/src/commands/repl.ts +0 -120
  89. package/src/commands/serve.ts +0 -96
  90. package/src/commands/studio.ts +0 -354
  91. package/src/commands/sync.ts +0 -328
  92. package/src/index.ts +0 -274
  93. package/tsconfig.json +0 -15
  94. package/tsconfig.tsbuildinfo +0 -1
@@ -1,314 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import chalk from 'chalk';
4
-
5
- interface MigrateOptions {
6
- config?: string;
7
- dir?: string;
8
- }
9
-
10
- interface MigrateCreateOptions {
11
- name: string;
12
- dir?: string;
13
- }
14
-
15
- interface MigrateStatusOptions {
16
- config?: string;
17
- dir?: string;
18
- }
19
-
20
- const MIGRATION_TEMPLATE = `import { ObjectQL } from '@objectql/core';
21
-
22
- /**
23
- * Migration: {{name}}
24
- * Created: {{timestamp}}
25
- */
26
- export async function up(app: ObjectQL) {
27
- // TODO: Implement migration logic
28
- console.log('Running migration: {{name}}');
29
-
30
- // Example: Add a new field to an object
31
- // const tasks = app.getObject('tasks');
32
- // await tasks.updateSchema({
33
- // fields: {
34
- // new_field: { type: 'text', label: 'New Field' }
35
- // }
36
- // });
37
- }
38
-
39
- export async function down(app: ObjectQL) {
40
- // TODO: Implement rollback logic
41
- console.log('Rolling back migration: {{name}}');
42
-
43
- // Example: Remove the field
44
- // const tasks = app.getObject('tasks');
45
- // await tasks.updateSchema({
46
- // fields: {
47
- // new_field: undefined
48
- // }
49
- // });
50
- }
51
- `;
52
-
53
- /**
54
- * Run pending migrations
55
- */
56
- export async function migrate(options: MigrateOptions) {
57
- const migrationsDir = path.resolve(process.cwd(), options.dir || './migrations');
58
-
59
- console.log(chalk.blue('🔄 Running migrations...'));
60
- console.log(chalk.gray(`Migrations directory: ${migrationsDir}\n`));
61
-
62
- if (!fs.existsSync(migrationsDir)) {
63
- console.log(chalk.yellow('⚠ No migrations directory found'));
64
- console.log(chalk.gray('Create one with: objectql migrate:create --name init'));
65
- return;
66
- }
67
-
68
- try {
69
- // Load ObjectQL instance from config
70
- const app = await loadObjectQLInstance(options.config);
71
-
72
- // Get list of migration files
73
- const files = fs.readdirSync(migrationsDir)
74
- .filter(f => f.endsWith('.ts') || f.endsWith('.js'))
75
- .sort();
76
-
77
- if (files.length === 0) {
78
- console.log(chalk.yellow('⚠ No migration files found'));
79
- return;
80
- }
81
-
82
- // Get already run migrations
83
- const migrations = app.getObject('_migrations');
84
- let runMigrations: string[] = [];
85
-
86
- try {
87
- const result = await migrations.find({
88
- fields: ['name'],
89
- sort: [['created_at', 'asc']]
90
- });
91
- runMigrations = result.records.map((r: any) => r.name);
92
- } catch (err) {
93
- // Migrations table doesn't exist yet, create it
94
- console.log(chalk.gray('Creating migrations tracking table...'));
95
- await createMigrationsTable(app);
96
- }
97
-
98
- // Run pending migrations
99
- let ranCount = 0;
100
- for (const file of files) {
101
- const migrationName = file.replace(/\.(ts|js)$/, '');
102
-
103
- if (runMigrations.includes(migrationName)) {
104
- console.log(chalk.gray(`⊘ ${migrationName} (already run)`));
105
- continue;
106
- }
107
-
108
- console.log(chalk.blue(`▶ ${migrationName}`));
109
-
110
- const migrationPath = path.join(migrationsDir, file);
111
- const migration = require(migrationPath);
112
-
113
- if (!migration.up) {
114
- console.log(chalk.red(` ✗ No 'up' function found`));
115
- continue;
116
- }
117
-
118
- try {
119
- await migration.up(app);
120
-
121
- // Record migration
122
- await migrations.insert({
123
- name: migrationName,
124
- run_at: new Date()
125
- });
126
-
127
- console.log(chalk.green(` ✓ Complete`));
128
- ranCount++;
129
- } catch (error: any) {
130
- console.log(chalk.red(` ✗ Failed: ${error.message}`));
131
- throw error;
132
- }
133
- }
134
-
135
- console.log(chalk.green(`\n✅ Ran ${ranCount} migration(s)`));
136
-
137
- } catch (error: any) {
138
- console.error(chalk.red(`❌ Migration failed: ${error.message}`));
139
- process.exit(1);
140
- }
141
- }
142
-
143
- /**
144
- * Create a new migration file with boilerplate code
145
- */
146
- export async function migrateCreate(options: MigrateCreateOptions) {
147
- const { name } = options;
148
- const migrationsDir = path.resolve(process.cwd(), options.dir || './migrations');
149
-
150
- // Validate name
151
- if (!name || !/^[a-z][a-z0-9_]*$/.test(name)) {
152
- console.error(chalk.red('❌ Invalid migration name. Use lowercase with underscores (e.g., add_status_field)'));
153
- process.exit(1);
154
- }
155
-
156
- // Create migrations directory if it doesn't exist
157
- if (!fs.existsSync(migrationsDir)) {
158
- fs.mkdirSync(migrationsDir, { recursive: true });
159
- console.log(chalk.gray(`Created directory: ${migrationsDir}`));
160
- }
161
-
162
- // Generate filename with timestamp
163
- const timestamp = new Date().toISOString().replace(/[:\-T.]/g, '').slice(0, 14);
164
- const filename = `${timestamp}_${name}.ts`;
165
- const filePath = path.join(migrationsDir, filename);
166
-
167
- // Create migration file from template
168
- const content = MIGRATION_TEMPLATE
169
- .replace(/\{\{name\}\}/g, name)
170
- .replace(/\{\{timestamp\}\}/g, new Date().toISOString());
171
-
172
- fs.writeFileSync(filePath, content, 'utf-8');
173
-
174
- console.log(chalk.green(`✅ Created migration: ${filename}`));
175
- console.log(chalk.gray(`Path: ${filePath}`));
176
- console.log(chalk.gray(`\nNext steps:`));
177
- console.log(chalk.gray(` 1. Edit the migration file to add your changes`));
178
- console.log(chalk.gray(` 2. Run: objectql migrate`));
179
- }
180
-
181
- /**
182
- * Show migration status - displays pending and completed migrations
183
- * @param options - Configuration options including config path and migrations directory
184
- */
185
- export async function migrateStatus(options: MigrateStatusOptions) {
186
- const migrationsDir = path.resolve(process.cwd(), options.dir || './migrations');
187
-
188
- console.log(chalk.blue('📊 Migration Status\n'));
189
-
190
- if (!fs.existsSync(migrationsDir)) {
191
- console.log(chalk.yellow('⚠ No migrations directory found'));
192
- return;
193
- }
194
-
195
- try {
196
- // Load ObjectQL instance
197
- const app = await loadObjectQLInstance(options.config);
198
-
199
- // Get list of migration files
200
- const files = fs.readdirSync(migrationsDir)
201
- .filter(f => f.endsWith('.ts') || f.endsWith('.js'))
202
- .sort();
203
-
204
- if (files.length === 0) {
205
- console.log(chalk.gray('No migration files found'));
206
- return;
207
- }
208
-
209
- // Get run migrations
210
- const migrations = app.getObject('_migrations');
211
- let runMigrations: string[] = [];
212
-
213
- try {
214
- const result = await migrations.find({
215
- fields: ['name', 'run_at'],
216
- sort: [['run_at', 'asc']]
217
- });
218
- runMigrations = result.records.map((r: any) => r.name);
219
- } catch (err) {
220
- // Migrations table doesn't exist
221
- runMigrations = [];
222
- }
223
-
224
- // Display status
225
- let pendingCount = 0;
226
- for (const file of files) {
227
- const migrationName = file.replace(/\.(ts|js)$/, '');
228
- const isRun = runMigrations.includes(migrationName);
229
-
230
- if (isRun) {
231
- console.log(chalk.green(`✓ ${migrationName}`));
232
- } else {
233
- console.log(chalk.yellow(`○ ${migrationName} (pending)`));
234
- pendingCount++;
235
- }
236
- }
237
-
238
- console.log(chalk.blue(`\n📊 Summary:`));
239
- console.log(chalk.gray(`Total migrations: ${files.length}`));
240
- console.log(chalk.gray(`Run: ${files.length - pendingCount}`));
241
- console.log(chalk.gray(`Pending: ${pendingCount}`));
242
-
243
- } catch (error: any) {
244
- console.error(chalk.red(`❌ Failed to get status: ${error.message}`));
245
- process.exit(1);
246
- }
247
- }
248
-
249
- async function loadObjectQLInstance(configPath?: string): Promise<any> {
250
- const cwd = process.cwd();
251
-
252
- // Try to load from config file
253
- let configFile = configPath;
254
- if (!configFile) {
255
- const potentialFiles = ['objectql.config.ts', 'objectql.config.js'];
256
- for (const file of potentialFiles) {
257
- if (fs.existsSync(path.join(cwd, file))) {
258
- configFile = file;
259
- break;
260
- }
261
- }
262
- }
263
-
264
- if (!configFile) {
265
- throw new Error('No configuration file found (objectql.config.ts/js)');
266
- }
267
-
268
- // Register ts-node for TypeScript support
269
- try {
270
- require('ts-node').register({
271
- transpileOnly: true,
272
- compilerOptions: {
273
- module: 'commonjs'
274
- }
275
- });
276
- } catch (err) {
277
- // ts-node not available, try to load JS directly
278
- }
279
-
280
- const configModule = require(path.join(cwd, configFile));
281
- const app = configModule.default || configModule.app || configModule.objectql || configModule.db;
282
-
283
- if (!app) {
284
- throw new Error('Config file must export an ObjectQL instance');
285
- }
286
-
287
- await app.init();
288
- return app;
289
- }
290
-
291
- async function createMigrationsTable(app: any) {
292
- // Create a system object to track migrations
293
- app.metadata.register('object', {
294
- name: '_migrations',
295
- label: 'Migrations',
296
- system: true,
297
- fields: {
298
- name: {
299
- type: 'text',
300
- label: 'Migration Name',
301
- required: true,
302
- unique: true
303
- },
304
- run_at: {
305
- type: 'datetime',
306
- label: 'Run At',
307
- required: true
308
- }
309
- }
310
- });
311
-
312
- // Sync to database
313
- await app.getObject('_migrations').sync();
314
- }
@@ -1,273 +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
- 'view',
15
- 'form',
16
- 'page',
17
- 'action',
18
- 'hook',
19
- 'permission',
20
- 'validation',
21
- 'workflow',
22
- 'report',
23
- 'menu',
24
- 'data'
25
- ];
26
-
27
- const TEMPLATES: Record<string, any> = {
28
- object: {
29
- label: '{{label}}',
30
- fields: {
31
- name: {
32
- type: 'text',
33
- label: 'Name',
34
- required: true
35
- }
36
- }
37
- },
38
- view: {
39
- label: '{{label}} View',
40
- object: '{{objectName}}',
41
- columns: [
42
- { field: 'name', label: 'Name' }
43
- ]
44
- },
45
- form: {
46
- label: '{{label}} Form',
47
- object: '{{objectName}}',
48
- layout: {
49
- sections: [
50
- {
51
- label: 'Basic Information',
52
- fields: ['name']
53
- }
54
- ]
55
- }
56
- },
57
- page: {
58
- label: '{{label}} Page',
59
- type: 'standard',
60
- components: [
61
- {
62
- type: 'title',
63
- text: '{{label}}'
64
- }
65
- ]
66
- },
67
- action: {
68
- label: '{{label}} Action',
69
- object: '{{objectName}}',
70
- type: 'record',
71
- handler: 'action_{{name}}'
72
- },
73
- hook: {
74
- label: '{{label}} Hook',
75
- object: '{{objectName}}',
76
- triggers: ['before_insert', 'after_insert']
77
- },
78
- permission: {
79
- label: '{{label}} Permissions',
80
- object: '{{objectName}}',
81
- profiles: {
82
- admin: {
83
- allow_read: true,
84
- allow_create: true,
85
- allow_edit: true,
86
- allow_delete: true
87
- },
88
- user: {
89
- allow_read: true,
90
- allow_create: false,
91
- allow_edit: false,
92
- allow_delete: false
93
- }
94
- }
95
- },
96
- validation: {
97
- label: '{{label}} Validation',
98
- object: '{{objectName}}',
99
- rules: [
100
- {
101
- name: 'required_name',
102
- type: 'field',
103
- field: 'name',
104
- rule: 'required',
105
- message: 'Name is required'
106
- }
107
- ]
108
- },
109
- workflow: {
110
- label: '{{label}} Workflow',
111
- object: '{{objectName}}',
112
- trigger: 'on_create',
113
- actions: [
114
- {
115
- type: 'field_update',
116
- field: 'status',
117
- value: 'draft'
118
- }
119
- ]
120
- },
121
- report: {
122
- label: '{{label}} Report',
123
- type: 'tabular',
124
- object: '{{objectName}}',
125
- columns: [
126
- { field: 'name', label: 'Name' }
127
- ]
128
- },
129
- menu: {
130
- label: '{{label}} Menu',
131
- items: [
132
- {
133
- label: 'Home',
134
- page: 'home',
135
- icon: 'home'
136
- }
137
- ]
138
- },
139
- data: {
140
- label: '{{label}} Data',
141
- object: '{{objectName}}',
142
- records: []
143
- }
144
- };
145
-
146
- export async function newMetadata(options: NewOptions) {
147
- const { type, name, dir = '.' } = options;
148
-
149
- // Validate type
150
- if (!METADATA_TYPES.includes(type)) {
151
- console.error(chalk.red(`❌ Unknown metadata type: ${type}`));
152
- console.log(chalk.gray(`Available types: ${METADATA_TYPES.join(', ')}`));
153
- process.exit(1);
154
- }
155
-
156
- // Validate name
157
- if (!name || !/^[a-z][a-z0-9_]*$/.test(name)) {
158
- console.error(chalk.red('❌ Invalid name. Must be lowercase with underscores (e.g., my_object)'));
159
- process.exit(1);
160
- }
161
-
162
- const targetDir = path.resolve(process.cwd(), dir);
163
-
164
- // Create directory if it doesn't exist
165
- if (!fs.existsSync(targetDir)) {
166
- fs.mkdirSync(targetDir, { recursive: true });
167
- }
168
-
169
- const filename = `${name}.${type}.yml`;
170
- const filePath = path.join(targetDir, filename);
171
-
172
- // Check if file already exists
173
- if (fs.existsSync(filePath)) {
174
- console.error(chalk.red(`❌ File already exists: ${filePath}`));
175
- process.exit(1);
176
- }
177
-
178
- // Get template and replace placeholders
179
- let template = TEMPLATES[type];
180
- const label = nameToLabel(name);
181
- const objectName = type === 'object' ? name : extractObjectName(name);
182
-
183
- template = JSON.parse(
184
- JSON.stringify(template)
185
- .replace(/\{\{label\}\}/g, label)
186
- .replace(/\{\{name\}\}/g, name)
187
- .replace(/\{\{objectName\}\}/g, objectName)
188
- );
189
-
190
- // Write YAML file
191
- const yamlContent = yaml.dump(template, {
192
- indent: 2,
193
- lineWidth: 120,
194
- noRefs: true
195
- });
196
-
197
- fs.writeFileSync(filePath, yamlContent, 'utf-8');
198
-
199
- console.log(chalk.green(`✅ Created ${filename}`));
200
- console.log(chalk.gray(` Path: ${filePath}`));
201
-
202
- // If it's an action or hook, also create the TypeScript implementation file
203
- if (type === 'action' || type === 'hook') {
204
- await createTsImplementation(type, name, targetDir);
205
- }
206
- }
207
-
208
- async function createTsImplementation(type: 'action' | 'hook', name: string, dir: string) {
209
- const filename = `${name}.${type}.ts`;
210
- const filePath = path.join(dir, filename);
211
-
212
- if (fs.existsSync(filePath)) {
213
- console.log(chalk.yellow(`⚠ TypeScript file already exists: ${filename}`));
214
- return;
215
- }
216
-
217
- let template = '';
218
-
219
- if (type === 'action') {
220
- template = `import { ActionContext } from '@objectql/types';
221
-
222
- export async function action_${name}(context: ActionContext) {
223
- const { record, user } = context;
224
-
225
- // TODO: Implement action logic
226
- console.log('Action ${name} triggered for record:', record._id);
227
-
228
- return {
229
- success: true,
230
- message: 'Action completed successfully'
231
- };
232
- }
233
- `;
234
- } else if (type === 'hook') {
235
- template = `import { HookContext } from '@objectql/types';
236
-
237
- export async function beforeInsert(context: HookContext) {
238
- const { doc } = context;
239
-
240
- // TODO: Implement before insert logic
241
- console.log('Before insert hook for ${name}');
242
-
243
- // Modify doc as needed
244
- return doc;
245
- }
246
-
247
- export async function afterInsert(context: HookContext) {
248
- const { doc } = context;
249
-
250
- // TODO: Implement after insert logic
251
- console.log('After insert hook for ${name}');
252
- }
253
- `;
254
- }
255
-
256
- fs.writeFileSync(filePath, template, 'utf-8');
257
- console.log(chalk.green(`✅ Created ${filename}`));
258
- console.log(chalk.gray(` Path: ${filePath}`));
259
- }
260
-
261
- function nameToLabel(name: string): string {
262
- return name
263
- .split('_')
264
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
265
- .join(' ');
266
- }
267
-
268
- function extractObjectName(name: string): string {
269
- // Try to extract object name from name like "project_status" -> "project"
270
- // This is a heuristic and might not always be correct
271
- const parts = name.split('_');
272
- return parts[0];
273
- }
@@ -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
- }