@objectql/core 1.3.1 → 1.5.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/src/loader.ts CHANGED
@@ -101,6 +101,51 @@ export class ObjectLoader {
101
101
  }
102
102
  }
103
103
  });
104
+
105
+ // Generic YAML Metadata Loaders
106
+ const metaTypes = ['view', 'form', 'menu', 'permission', 'report', 'workflow', 'validation', 'data'];
107
+
108
+ for (const type of metaTypes) {
109
+ this.use({
110
+ name: type,
111
+ glob: [`**/*.${type}.yml`, `**/*.${type}.yaml`],
112
+ handler: (ctx) => {
113
+ try {
114
+ const doc = yaml.load(ctx.content) as any;
115
+ if (!doc) return;
116
+
117
+ // Use 'name' from doc, or filename base (without extension)
118
+ let id = doc.name;
119
+ if (!id && type !== 'data') {
120
+ const basename = path.basename(ctx.file);
121
+ // e.g. "my-view.view.yml" -> "my-view"
122
+ // Regex: remove .type.yml or .type.yaml
123
+ const re = new RegExp(`\\.${type}\\.(yml|yaml)$`);
124
+ id = basename.replace(re, '');
125
+ }
126
+
127
+ // Data entries might not need a name, but for registry we need an ID.
128
+ // For data, we can use filename if not present.
129
+ if (!id && type === 'data') {
130
+ id = path.basename(ctx.file);
131
+ }
132
+
133
+ // Ensure name is in the doc for consistency
134
+ if (!doc.name) doc.name = id;
135
+
136
+ ctx.registry.register(type, {
137
+ type: type,
138
+ id: id,
139
+ path: ctx.file,
140
+ package: ctx.packageName,
141
+ content: doc
142
+ });
143
+ } catch (e) {
144
+ console.error(`Error loading ${type} from ${ctx.file}:`, e);
145
+ }
146
+ }
147
+ })
148
+ }
104
149
  }
105
150
 
106
151
  use(plugin: LoaderPlugin) {
@@ -127,9 +172,39 @@ export class ObjectLoader {
127
172
  }
128
173
 
129
174
  private runPlugin(plugin: LoaderPlugin, dir: string, packageName?: string) {
175
+ // Enforce path conventions:
176
+ // 1. Never scan node_modules (unless explicitly loaded via loadPackage which sets cwd inside it)
177
+ // 2. Ignore build artifacts (dist, build, out) to avoid double-loading metadata if both src and dist exist.
178
+ // Note: If you want to load from 'dist', you must explicitly point the loader to it (e.g. loader.load('./dist')).
179
+ // In that case, the patterns won't match relative to the CWD.
180
+ // Path conventions:
181
+ // 1. Always ignore node_modules and .git
182
+ const ignore = [
183
+ '**/node_modules/**',
184
+ '**/.git/**'
185
+ ];
186
+
187
+ // 2. Intelligent handling of build artifacts (dist/build)
188
+ // If 'src' exists in the scan directory, we assume it's a Development Environment.
189
+ // In Dev, we ignore 'dist' to avoid duplicate loading (ts in src vs js in dist).
190
+ // In Production (no src), we must NOT ignore 'dist', otherwise we can't load compiled hooks/actions.
191
+ const srcPath = path.join(dir, 'src');
192
+ const hasSrc = fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory();
193
+
194
+ if (hasSrc) {
195
+ ignore.push('**/dist/**', '**/build/**', '**/out/**');
196
+ }
197
+
198
+ // 3. User instruction: "src 不行的" (src is not viable for metadata in production)
199
+ // Metadata (.yml) should ideally be placed in 'objects/' or root, not 'src/',
200
+ // to simplify packaging (so you don't need to copy assets from src to dist).
201
+ // However, we do not strictly block 'src' scanning here to avoid breaking dev workflow.
202
+ // The exclusion of 'dist' in dev mode (above) handles the code duality.
203
+
130
204
  const files = glob.sync(plugin.glob, {
131
205
  cwd: dir,
132
- absolute: true
206
+ absolute: true,
207
+ ignore
133
208
  });
134
209
 
135
210
  for (const file of files) {
@@ -166,6 +241,38 @@ function registerObject(registry: MetadataRegistry, obj: any, file: string, pack
166
241
  }
167
242
  }
168
243
  }
244
+
245
+ // Check for existing object to Merge
246
+ const existing = registry.getEntry('object', obj.name);
247
+ if (existing) {
248
+ const base = existing.content;
249
+
250
+ // Merge Fields: New fields overwrite old ones
251
+ if (obj.fields) {
252
+ base.fields = { ...base.fields, ...obj.fields };
253
+ }
254
+
255
+ // Merge Actions
256
+ if (obj.actions) {
257
+ base.actions = { ...base.actions, ...obj.actions };
258
+ }
259
+
260
+ // Merge Indexes
261
+ if (obj.indexes) {
262
+ base.indexes = { ...base.indexes, ...obj.indexes };
263
+ }
264
+
265
+ // Override Top-level Properties if provided
266
+ if (obj.label) base.label = obj.label;
267
+ if (obj.icon) base.icon = obj.icon;
268
+ if (obj.description) base.description = obj.description;
269
+ if (obj.datasource) base.datasource = obj.datasource;
270
+
271
+ // Update the content reference
272
+ existing.content = base;
273
+ return;
274
+ }
275
+
169
276
  registry.register('object', {
170
277
  type: 'object',
171
278
  id: obj.name,
package/src/repository.ts CHANGED
@@ -39,12 +39,26 @@ export class ObjectRepository {
39
39
  };
40
40
  }
41
41
 
42
+ private getUserFromContext() {
43
+ if (!this.context.userId) {
44
+ return undefined;
45
+ }
46
+ // Construct user object from context, including relevant properties
47
+ return {
48
+ id: this.context.userId,
49
+ spaceId: this.context.spaceId,
50
+ roles: this.context.roles,
51
+ isSystem: this.context.isSystem
52
+ };
53
+ }
54
+
42
55
  async find(query: UnifiedQuery = {}): Promise<any[]> {
43
56
  const hookCtx: RetrievalHookContext = {
44
57
  ...this.context,
45
58
  objectName: this.objectName,
46
59
  operation: 'find',
47
60
  api: this.getHookAPI(),
61
+ user: this.getUserFromContext(),
48
62
  state: {},
49
63
  query
50
64
  };
@@ -65,7 +79,10 @@ export class ObjectRepository {
65
79
  ...this.context,
66
80
  objectName: this.objectName,
67
81
  operation: 'find',
68
- api: this.getHookAPI(), state: {}, query: { _id: idOrQuery }
82
+ api: this.getHookAPI(),
83
+ user: this.getUserFromContext(),
84
+ state: {},
85
+ query: { _id: idOrQuery }
69
86
  };
70
87
  await this.app.triggerHook('beforeFind', this.objectName, hookCtx);
71
88
 
@@ -86,6 +103,7 @@ export class ObjectRepository {
86
103
  objectName: this.objectName,
87
104
  operation: 'count',
88
105
  api: this.getHookAPI(),
106
+ user: this.getUserFromContext(),
89
107
  state: {},
90
108
  query: filters
91
109
  };
@@ -105,6 +123,7 @@ export class ObjectRepository {
105
123
  operation: 'create',
106
124
  state: {},
107
125
  api: this.getHookAPI(),
126
+ user: this.getUserFromContext(),
108
127
  data: doc
109
128
  };
110
129
  await this.app.triggerHook('beforeCreate', this.objectName, hookCtx);
@@ -122,14 +141,17 @@ export class ObjectRepository {
122
141
  }
123
142
 
124
143
  async update(id: string | number, doc: any, options?: any): Promise<any> {
144
+ const previousData = await this.findOne(id);
125
145
  const hookCtx: UpdateHookContext = {
126
146
  ...this.context,
127
147
  objectName: this.objectName,
128
148
  operation: 'update',
129
149
  state: {},
130
150
  api: this.getHookAPI(),
151
+ user: this.getUserFromContext(),
131
152
  id,
132
153
  data: doc,
154
+ previousData,
133
155
  isModified: (field) => hookCtx.data ? Object.prototype.hasOwnProperty.call(hookCtx.data, field) : false
134
156
  };
135
157
  await this.app.triggerHook('beforeUpdate', this.objectName, hookCtx);
@@ -142,13 +164,16 @@ export class ObjectRepository {
142
164
  }
143
165
 
144
166
  async delete(id: string | number): Promise<any> {
167
+ const previousData = await this.findOne(id);
145
168
  const hookCtx: MutationHookContext = {
146
169
  ...this.context,
147
170
  objectName: this.objectName,
148
171
  operation: 'delete',
149
172
  state: {},
150
173
  api: this.getHookAPI(),
151
- id
174
+ user: this.getUserFromContext(),
175
+ id,
176
+ previousData
152
177
  };
153
178
  await this.app.triggerHook('beforeDelete', this.objectName, hookCtx);
154
179
 
@@ -221,7 +246,8 @@ export class ObjectRepository {
221
246
  actionName,
222
247
  id,
223
248
  input: params,
224
- api
249
+ api,
250
+ user: this.getUserFromContext()
225
251
  };
226
252
  return await this.app.executeAction(this.objectName, actionName, ctx);
227
253
  }