@objectql/core 1.1.0 → 1.3.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 (72) hide show
  1. package/CHANGELOG.md +15 -6
  2. package/dist/action.d.ts +7 -0
  3. package/dist/action.js +23 -0
  4. package/dist/action.js.map +1 -0
  5. package/dist/app.d.ts +28 -0
  6. package/dist/app.js +211 -0
  7. package/dist/app.js.map +1 -0
  8. package/dist/driver.d.ts +2 -17
  9. package/dist/driver.js +52 -0
  10. package/dist/driver.js.map +1 -1
  11. package/dist/hook.d.ts +8 -0
  12. package/dist/hook.js +25 -0
  13. package/dist/hook.js.map +1 -0
  14. package/dist/index.d.ts +8 -25
  15. package/dist/index.js +8 -141
  16. package/dist/index.js.map +1 -1
  17. package/dist/loader.d.ts +9 -4
  18. package/dist/loader.js +206 -9
  19. package/dist/loader.js.map +1 -1
  20. package/dist/object.d.ts +3 -0
  21. package/dist/object.js +28 -0
  22. package/dist/object.js.map +1 -0
  23. package/dist/plugin.d.ts +2 -0
  24. package/dist/plugin.js +56 -0
  25. package/dist/plugin.js.map +1 -0
  26. package/dist/remote.d.ts +8 -0
  27. package/dist/remote.js +43 -0
  28. package/dist/remote.js.map +1 -0
  29. package/dist/repository.d.ts +3 -5
  30. package/dist/repository.js +107 -112
  31. package/dist/repository.js.map +1 -1
  32. package/jest.config.js +3 -0
  33. package/package.json +11 -7
  34. package/src/action.ts +40 -0
  35. package/src/app.ts +257 -0
  36. package/src/driver.ts +51 -21
  37. package/src/hook.ts +42 -0
  38. package/src/index.ts +8 -158
  39. package/src/loader.ts +184 -9
  40. package/src/object.ts +26 -0
  41. package/src/plugin.ts +53 -0
  42. package/src/remote.ts +50 -0
  43. package/src/repository.ts +123 -127
  44. package/test/action.test.ts +58 -0
  45. package/test/fixtures/project.action.js +8 -0
  46. package/test/hook.test.ts +60 -0
  47. package/test/loader.test.ts +1 -8
  48. package/test/metadata.test.ts +1 -1
  49. package/test/mock-driver.ts +1 -1
  50. package/test/remote.test.ts +119 -0
  51. package/test/repository.test.ts +42 -49
  52. package/test/utils.ts +54 -0
  53. package/tsconfig.json +7 -3
  54. package/tsconfig.tsbuildinfo +1 -1
  55. package/README.md +0 -53
  56. package/dist/metadata.d.ts +0 -104
  57. package/dist/metadata.js +0 -3
  58. package/dist/metadata.js.map +0 -1
  59. package/dist/query.d.ts +0 -10
  60. package/dist/query.js +0 -3
  61. package/dist/query.js.map +0 -1
  62. package/dist/registry.d.ts +0 -4
  63. package/dist/registry.js +0 -8
  64. package/dist/registry.js.map +0 -1
  65. package/dist/types.d.ts +0 -83
  66. package/dist/types.js +0 -6
  67. package/dist/types.js.map +0 -1
  68. package/src/metadata.ts +0 -143
  69. package/src/query.ts +0 -11
  70. package/src/registry.ts +0 -6
  71. package/src/types.ts +0 -115
  72. package/test/fixtures/project.action.ts +0 -6
package/src/driver.ts CHANGED
@@ -1,24 +1,54 @@
1
- export interface Driver {
2
- find(objectName: string, query: any, options?: any): Promise<any[]>;
3
- findOne(objectName: string, id: string | number, query?: any, options?: any): Promise<any>;
4
- create(objectName: string, data: any, options?: any): Promise<any>;
5
- update(objectName: string, id: string | number, data: any, options?: any): Promise<any>;
6
- delete(objectName: string, id: string | number, options?: any): Promise<any>;
7
- count(objectName: string, filters: any, options?: any): Promise<number>;
8
-
9
- // Advanced
10
- aggregate?(objectName: string, query: any, options?: any): Promise<any>;
11
- distinct?(objectName: string, field: string, filters?: any, options?: any): Promise<any[]>;
1
+ import { Driver } from '@objectql/types';
2
+
3
+ export function createDriverFromConnection(connection: string): Driver {
4
+ let driverPackage = '';
5
+ let driverClass = '';
6
+ let driverConfig: any = {};
12
7
 
13
- // Bulk / Atomic
14
- createMany?(objectName: string, data: any[], options?: any): Promise<any>;
15
- updateMany?(objectName: string, filters: any, data: any, options?: any): Promise<any>;
16
- deleteMany?(objectName: string, filters: any, options?: any): Promise<any>;
17
- findOneAndUpdate?(objectName: string, filters: any, update: any, options?: any): Promise<any>;
8
+ if (connection.startsWith('mongodb://')) {
9
+ driverPackage = '@objectql/driver-mongo';
10
+ driverClass = 'MongoDriver';
11
+ driverConfig = { url: connection };
12
+ }
13
+ else if (connection.startsWith('sqlite://')) {
14
+ driverPackage = '@objectql/driver-knex';
15
+ driverClass = 'KnexDriver';
16
+ const filename = connection.replace('sqlite://', '');
17
+ driverConfig = {
18
+ client: 'sqlite3',
19
+ connection: { filename },
20
+ useNullAsDefault: true
21
+ };
22
+ }
23
+ else if (connection.startsWith('postgres://') || connection.startsWith('postgresql://')) {
24
+ driverPackage = '@objectql/driver-knex';
25
+ driverClass = 'KnexDriver';
26
+ driverConfig = {
27
+ client: 'pg',
28
+ connection: connection
29
+ };
30
+ }
31
+ else if (connection.startsWith('mysql://')) {
32
+ driverPackage = '@objectql/driver-knex';
33
+ driverClass = 'KnexDriver';
34
+ driverConfig = {
35
+ client: 'mysql2',
36
+ connection: connection
37
+ };
38
+ }
39
+ else {
40
+ throw new Error(`Unsupported connection protocol: ${connection}`);
41
+ }
18
42
 
19
- // Transaction
20
- beginTransaction?(): Promise<any>;
21
- commitTransaction?(trx: any): Promise<void>;
22
- rollbackTransaction?(trx: any): Promise<void>;
43
+ try {
44
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
45
+ const pkg = require(driverPackage);
46
+ const DriverClass = pkg[driverClass];
47
+ if (!DriverClass) {
48
+ throw new Error(`${driverClass} not found in ${driverPackage}`);
49
+ }
50
+ return new DriverClass(driverConfig);
51
+ } catch (e: any) {
52
+ throw new Error(`Failed to load driver ${driverPackage}. Please install it: npm install ${driverPackage}. Error: ${e.message}`);
53
+ }
23
54
  }
24
-
package/src/hook.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { HookContext, HookHandler, HookName, MetadataRegistry } from '@objectql/types';
2
+
3
+ export interface HookEntry {
4
+ objectName: string;
5
+ handler: HookHandler;
6
+ packageName?: string;
7
+ }
8
+
9
+ export function registerHookHelper(
10
+ hooks: Record<string, HookEntry[]>,
11
+ event: HookName,
12
+ objectName: string,
13
+ handler: HookHandler,
14
+ packageName?: string
15
+ ) {
16
+ if (!hooks[event]) {
17
+ hooks[event] = [];
18
+ }
19
+ hooks[event].push({ objectName, handler, packageName });
20
+ }
21
+
22
+ export async function triggerHookHelper(
23
+ metadata: MetadataRegistry,
24
+ runtimeHooks: Record<string, HookEntry[]>,
25
+ event: HookName,
26
+ objectName: string,
27
+ ctx: HookContext
28
+ ) {
29
+ // 1. Registry Hooks (File-based)
30
+ const fileHooks = metadata.get<any>('hook', objectName);
31
+ if (fileHooks && typeof fileHooks[event] === 'function') {
32
+ await fileHooks[event](ctx);
33
+ }
34
+
35
+ // 2. Programmatic Hooks
36
+ const hooks = runtimeHooks[event] || [];
37
+ for (const hook of hooks) {
38
+ if (hook.objectName === '*' || hook.objectName === objectName) {
39
+ await hook.handler(ctx);
40
+ }
41
+ }
42
+ }
package/src/index.ts CHANGED
@@ -1,159 +1,9 @@
1
- import * as path from 'path';
2
-
3
- export * from './metadata';
4
- export * from './types';
5
- export * from './driver';
6
- export * from './repository';
7
- export * from './query';
8
- export * from './registry';
9
1
  export * from './loader';
10
-
11
- import { ObjectConfig } from './metadata';
12
- import { ObjectQLContext, ObjectQLContextOptions, IObjectQL, ObjectQLConfig } from './types';
13
- import { ObjectRepository } from './repository';
14
- import { Driver } from './driver';
15
- import { MetadataLoader } from './loader';
16
- import { MetadataRegistry } from './registry';
17
-
18
- export class ObjectQL implements IObjectQL {
19
- public metadata: MetadataRegistry;
20
- private loader: MetadataLoader;
21
- private datasources: Record<string, Driver> = {};
22
-
23
- constructor(config: ObjectQLConfig) {
24
- this.metadata = config.registry || new MetadataRegistry();
25
- this.loader = new MetadataLoader(this.metadata);
26
- this.datasources = config.datasources;
27
-
28
- if (config.objects) {
29
- for (const [key, obj] of Object.entries(config.objects)) {
30
- this.registerObject(obj);
31
- }
32
- }
33
- if (config.packages) {
34
- for (const name of config.packages) {
35
- this.addPackage(name);
36
- }
37
- }
38
- }
39
-
40
- addPackage(name: string) {
41
- this.loader.loadPackage(name);
42
- }
43
-
44
-
45
- removePackage(name: string) {
46
- this.metadata.unregisterPackage(name);
47
- }
48
-
49
- loadFromDirectory(dir: string, packageName?: string) {
50
- this.loader.load(dir, packageName);
51
- }
52
-
53
- createContext(options: ObjectQLContextOptions): ObjectQLContext {
54
- const ctx: ObjectQLContext = {
55
- userId: options.userId,
56
- spaceId: options.spaceId,
57
- roles: options.roles || [],
58
- isSystem: options.isSystem,
59
- ignoreTriggers: options.ignoreTriggers,
60
- object: (name: string) => {
61
- return new ObjectRepository(name, ctx, this);
62
- },
63
- transaction: async (callback) => {
64
- const driver = this.datasources['default'];
65
- if (!driver || !driver.beginTransaction) {
66
- return callback(ctx);
67
- }
68
-
69
- let trx: any;
70
- try {
71
- trx = await driver.beginTransaction();
72
- } catch (e) {
73
- throw e;
74
- }
75
-
76
- const trxCtx: ObjectQLContext = {
77
- ...ctx,
78
- transactionHandle: trx,
79
- transaction: async (cb) => cb(trxCtx)
80
- };
81
-
82
- try {
83
- const result = await callback(trxCtx);
84
- if (driver.commitTransaction) await driver.commitTransaction(trx);
85
- return result;
86
- } catch (error) {
87
- if (driver.rollbackTransaction) await driver.rollbackTransaction(trx);
88
- throw error;
89
- }
90
- },
91
- sudo: () => {
92
- return this.createContext({ ...options, isSystem: true });
93
- }
94
- };
95
- return ctx;
96
- }
97
-
98
- registerObject(object: ObjectConfig) {
99
- // Normalize fields
100
- if (object.fields) {
101
- for (const [key, field] of Object.entries(object.fields)) {
102
- if (!field.name) {
103
- field.name = key;
104
- }
105
- }
106
- }
107
- this.metadata.register('object', {
108
- type: 'object',
109
- id: object.name,
110
- content: object
111
- });
112
- }
113
-
114
- getObject(name: string): ObjectConfig | undefined {
115
- return this.metadata.get<ObjectConfig>('object', name);
116
- }
117
-
118
- getConfigs(): Record<string, ObjectConfig> {
119
- const result: Record<string, ObjectConfig> = {};
120
- const objects = this.metadata.list<ObjectConfig>('object');
121
- for (const obj of objects) {
122
- result[obj.name] = obj;
123
- }
124
- return result;
125
- }
126
-
127
- datasource(name: string): Driver {
128
- const driver = this.datasources[name];
129
- if (!driver) {
130
- throw new Error(`Datasource '${name}' not found`);
131
- }
132
- return driver;
133
- }
134
-
135
- async init() {
136
- const ctx = this.createContext({ isSystem: true });
137
- const objects = this.metadata.list<ObjectConfig>('object');
138
- for (const obj of objects) {
139
- if (obj.data && obj.data.length > 0) {
140
- console.log(`Initializing data for object ${obj.name}...`);
141
- const repo = ctx.object(obj.name);
142
- for (const record of obj.data) {
143
- try {
144
- if (record._id) {
145
- const existing = await repo.findOne(record._id);
146
- if (existing) {
147
- continue;
148
- }
149
- }
150
- await repo.create(record);
151
- console.log(`Inserted init data for ${obj.name}: ${record._id || 'unknown id'}`);
152
- } catch (e) {
153
- console.error(`Failed to insert init data for ${obj.name}:`, e);
154
- }
155
- }
156
- }
157
- }
158
- }
159
- }
2
+ export * from './repository';
3
+ export * from './app';
4
+ export * from './plugin';
5
+ export * from './driver';
6
+ export * from './remote';
7
+ export * from './action';
8
+ export * from './hook';
9
+ export * from './object';
package/src/loader.ts CHANGED
@@ -1,22 +1,197 @@
1
- import { MetadataRegistry } from './registry';
2
- import { ObjectConfig } from './types';
3
- import { MetadataLoader as BaseLoader, registerObjectQLPlugins } from '@objectql/metadata';
1
+ import * as fs from 'fs';
2
+ import * as glob from 'fast-glob';
3
+ import * as path from 'path';
4
+ import { MetadataRegistry, ObjectConfig, LoaderPlugin, LoaderHandlerContext } from '@objectql/types';
5
+ import * as yaml from 'js-yaml';
4
6
 
5
- export class MetadataLoader extends BaseLoader {
6
- constructor(registry: MetadataRegistry) {
7
- super(registry);
8
- registerObjectQLPlugins(this);
7
+ export class ObjectLoader {
8
+ private plugins: LoaderPlugin[] = [];
9
+
10
+ constructor(protected registry: MetadataRegistry) {
11
+ this.registerBuiltinPlugins();
12
+ }
13
+
14
+ private registerBuiltinPlugins() {
15
+ // Objects
16
+ this.use({
17
+ name: 'object',
18
+ glob: ['**/*.object.yml', '**/*.object.yaml'],
19
+ handler: (ctx) => {
20
+ try {
21
+ const doc = yaml.load(ctx.content) as any;
22
+ if (!doc) return;
23
+
24
+ if (doc.name && doc.fields) {
25
+ registerObject(ctx.registry, doc, ctx.file, ctx.packageName || ctx.registry.getEntry('package-map', ctx.file)?.package);
26
+ } else {
27
+ for (const [key, value] of Object.entries(doc)) {
28
+ if (typeof value === 'object' && (value as any).fields) {
29
+ const obj = value as any;
30
+ if (!obj.name) obj.name = key;
31
+ registerObject(ctx.registry, obj, ctx.file, ctx.packageName);
32
+ }
33
+ }
34
+ }
35
+ } catch (e) {
36
+ console.error(`Error loading object from ${ctx.file}:`, e);
37
+ }
38
+ }
39
+ });
40
+
41
+ // Hooks
42
+ this.use({
43
+ name: 'hook',
44
+ glob: ['**/*.hook.ts', '**/*.hook.js'],
45
+ handler: (ctx) => {
46
+ const basename = path.basename(ctx.file);
47
+ // Extract object name from filename: user.hook.ts -> user
48
+ const objectName = basename.replace(/\.hook\.(ts|js)$/, '');
49
+
50
+ try {
51
+ const mod = require(ctx.file);
52
+ // Support default export or named exports
53
+ const hooks = mod.default || mod;
54
+
55
+ ctx.registry.register('hook', {
56
+ type: 'hook',
57
+ id: objectName, // Hook ID is the object name
58
+ path: ctx.file,
59
+ package: ctx.packageName,
60
+ content: hooks
61
+ });
62
+ } catch (e) {
63
+ console.error(`Error loading hook from ${ctx.file}:`, e);
64
+ }
65
+ }
66
+ });
67
+
68
+ // Actions
69
+ this.use({
70
+ name: 'action',
71
+ glob: ['**/*.action.ts', '**/*.action.js'],
72
+ handler: (ctx) => {
73
+ const basename = path.basename(ctx.file);
74
+ // Extract object name: invoice.action.ts -> invoice
75
+ const objectName = basename.replace(/\.action\.(ts|js)$/, '');
76
+
77
+ try {
78
+ const mod = require(ctx.file);
79
+
80
+ const actions: Record<string, any> = {};
81
+
82
+ for (const [key, value] of Object.entries(mod)) {
83
+ if (key === 'default') continue;
84
+ if (typeof value === 'object' && (value as any).handler) {
85
+ actions[key] = value;
86
+ }
87
+ }
88
+
89
+ if (Object.keys(actions).length > 0) {
90
+ ctx.registry.register('action', {
91
+ type: 'action',
92
+ id: objectName, // Action collection ID is the object name
93
+ path: ctx.file,
94
+ package: ctx.packageName,
95
+ content: actions
96
+ });
97
+ }
98
+
99
+ } catch (e) {
100
+ console.error(`Error loading action from ${ctx.file}:`, e);
101
+ }
102
+ }
103
+ });
104
+ }
105
+
106
+ use(plugin: LoaderPlugin) {
107
+ this.plugins.push(plugin);
108
+ }
109
+
110
+ load(dir: string, packageName?: string) {
111
+ for (const plugin of this.plugins) {
112
+ this.runPlugin(plugin, dir, packageName);
113
+ }
114
+ }
115
+
116
+ loadPackage(packageName: string) {
117
+ try {
118
+ const entryPath = require.resolve(packageName, { paths: [process.cwd()] });
119
+ // clean cache
120
+ delete require.cache[entryPath];
121
+ const packageDir = path.dirname(entryPath);
122
+ this.load(packageDir, packageName);
123
+ } catch (e) {
124
+ // fallback to directory
125
+ this.load(packageName, packageName);
126
+ }
9
127
  }
128
+
129
+ private runPlugin(plugin: LoaderPlugin, dir: string, packageName?: string) {
130
+ const files = glob.sync(plugin.glob, {
131
+ cwd: dir,
132
+ absolute: true
133
+ });
134
+
135
+ for (const file of files) {
136
+ try {
137
+ const ctx: LoaderHandlerContext = {
138
+ file,
139
+ content: '',
140
+ registry: this.registry,
141
+ packageName
142
+ };
143
+
144
+ // Pre-read for convenience
145
+ if (!file.match(/\.(js|ts|node)$/)) {
146
+ ctx.content = fs.readFileSync(file, 'utf8');
147
+ }
148
+
149
+ plugin.handler(ctx);
150
+
151
+ } catch (e) {
152
+ console.error(`Error in loader plugin '${plugin.name}' processing ${file}:`, e);
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ function registerObject(registry: MetadataRegistry, obj: any, file: string, packageName?: string) {
159
+ // Normalize fields
160
+ if (obj.fields) {
161
+ for (const [key, field] of Object.entries(obj.fields)) {
162
+ if (typeof field === 'object' && field !== null) {
163
+ if (!(field as any).name) {
164
+ (field as any).name = key;
165
+ }
166
+ }
167
+ }
168
+ }
169
+ registry.register('object', {
170
+ type: 'object',
171
+ id: obj.name,
172
+ path: file,
173
+ package: packageName,
174
+ content: obj
175
+ });
10
176
  }
11
177
 
12
178
  export function loadObjectConfigs(dir: string): Record<string, ObjectConfig> {
13
179
  const registry = new MetadataRegistry();
14
- const loader = new MetadataLoader(registry);
180
+ const loader = new ObjectLoader(registry);
15
181
  loader.load(dir);
182
+
183
+ // Merge actions into objects
184
+ const actions = registry.list<any>('action');
185
+ for (const act of actions) {
186
+ const obj = registry.get<ObjectConfig>('object', act.id);
187
+ if (obj) {
188
+ obj.actions = act.content;
189
+ }
190
+ }
191
+
16
192
  const result: Record<string, ObjectConfig> = {};
17
193
  for (const obj of registry.list<ObjectConfig>('object')) {
18
194
  result[obj.name] = obj;
19
195
  }
20
196
  return result;
21
197
  }
22
-
package/src/object.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { ObjectConfig, MetadataRegistry } from '@objectql/types';
2
+
3
+ export function registerObjectHelper(metadata: MetadataRegistry, object: ObjectConfig) {
4
+ // Normalize fields
5
+ if (object.fields) {
6
+ for (const [key, field] of Object.entries(object.fields)) {
7
+ if (!field.name) {
8
+ field.name = key;
9
+ }
10
+ }
11
+ }
12
+ metadata.register('object', {
13
+ type: 'object',
14
+ id: object.name,
15
+ content: object
16
+ });
17
+ }
18
+
19
+ export function getConfigsHelper(metadata: MetadataRegistry): Record<string, ObjectConfig> {
20
+ const result: Record<string, ObjectConfig> = {};
21
+ const objects = metadata.list<ObjectConfig>('object');
22
+ for (const obj of objects) {
23
+ result[obj.name] = obj;
24
+ }
25
+ return result;
26
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { ObjectQLPlugin } from '@objectql/types';
2
+
3
+ export function loadPlugin(packageName: string): ObjectQLPlugin {
4
+ let mod: any;
5
+ try {
6
+ const modulePath = require.resolve(packageName, { paths: [process.cwd()] });
7
+ mod = require(modulePath);
8
+ } catch (e) {
9
+ throw new Error(`Failed to resolve plugin '${packageName}': ${e}`);
10
+ }
11
+
12
+ // Helper to find plugin instance
13
+ const findPlugin = (candidate: any): ObjectQLPlugin | undefined => {
14
+ if (!candidate) return undefined;
15
+
16
+ // 1. Try treating as Class
17
+ if (typeof candidate === 'function') {
18
+ try {
19
+ const inst = new candidate();
20
+ if (inst && typeof inst.setup === 'function') {
21
+ return inst; // Found it!
22
+ }
23
+ } catch (e) {
24
+ // Not a constructor or instantiation failed
25
+ }
26
+ }
27
+
28
+ // 2. Try treating as Instance
29
+ if (candidate && typeof candidate.setup === 'function') {
30
+ if (candidate.name) return candidate;
31
+ }
32
+ return undefined;
33
+ };
34
+
35
+ // Search in default, module root, and all named exports
36
+ let instance = findPlugin(mod.default) || findPlugin(mod);
37
+
38
+ if (!instance && mod && typeof mod === 'object') {
39
+ for (const key of Object.keys(mod)) {
40
+ if (key === 'default') continue;
41
+ instance = findPlugin(mod[key]);
42
+ if (instance) break;
43
+ }
44
+ }
45
+
46
+ if (instance) {
47
+ (instance as any)._packageName = packageName;
48
+ return instance;
49
+ } else {
50
+ console.error(`[PluginLoader] Failed to find ObjectQLPlugin in '${packageName}'. Exports:`, Object.keys(mod));
51
+ throw new Error(`Plugin '${packageName}' must export a class or object implementing ObjectQLPlugin.`);
52
+ }
53
+ }
package/src/remote.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { RemoteDriver } from '@objectql/driver-remote';
2
+ import { ObjectConfig } from '@objectql/types';
3
+
4
+ export interface RemoteLoadResult {
5
+ driverName: string;
6
+ driver: RemoteDriver;
7
+ objects: ObjectConfig[];
8
+ }
9
+
10
+ export async function loadRemoteFromUrl(url: string): Promise<RemoteLoadResult | null> {
11
+ try {
12
+ const baseUrl = url.replace(/\/$/, '');
13
+ const metadataUrl = `${baseUrl}/api/metadata/objects`;
14
+
15
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
16
+ // @ts-ignore - Fetch is available in Node 18+
17
+ const res = await fetch(metadataUrl);
18
+ if (!res.ok) {
19
+ console.warn(`[ObjectQL] Remote ${url} returned ${res.status}`);
20
+ return null;
21
+ }
22
+
23
+ const data = await res.json() as any;
24
+ if (!data || !data.objects) return null;
25
+
26
+ const driverName = `remote:${baseUrl}`;
27
+ const driver = new RemoteDriver(baseUrl);
28
+ const objects: ObjectConfig[] = [];
29
+
30
+ await Promise.all(data.objects.map(async (summary: any) => {
31
+ try {
32
+ // @ts-ignore
33
+ const detailRes = await fetch(`${metadataUrl}/${summary.name}`);
34
+ if (detailRes.ok) {
35
+ const config = await detailRes.json() as ObjectConfig;
36
+ config.datasource = driverName;
37
+ objects.push(config);
38
+ }
39
+ } catch (e) {
40
+ console.warn(`[ObjectQL] Failed to load object ${summary.name} from ${url}`);
41
+ }
42
+ }));
43
+
44
+ return { driverName, driver, objects };
45
+
46
+ } catch (e: any) {
47
+ console.warn(`[ObjectQL] Remote connection error ${url}: ${e.message}`);
48
+ return null;
49
+ }
50
+ }