@objectstack/objectql 1.0.11 → 1.1.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/dist/index.js CHANGED
@@ -20,10 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ DEFAULT_EXTENDER_PRIORITY: () => DEFAULT_EXTENDER_PRIORITY,
24
+ DEFAULT_OWNER_PRIORITY: () => DEFAULT_OWNER_PRIORITY,
23
25
  ObjectQL: () => ObjectQL,
24
26
  ObjectQLPlugin: () => ObjectQLPlugin,
25
27
  ObjectStackProtocolImplementation: () => ObjectStackProtocolImplementation,
26
- SchemaRegistry: () => SchemaRegistry
28
+ RESERVED_NAMESPACES: () => RESERVED_NAMESPACES,
29
+ SchemaRegistry: () => SchemaRegistry,
30
+ computeFQN: () => computeFQN,
31
+ parseFQN: () => parseFQN
27
32
  });
28
33
  module.exports = __toCommonJS(index_exports);
29
34
 
@@ -31,29 +36,276 @@ module.exports = __toCommonJS(index_exports);
31
36
  var import_data = require("@objectstack/spec/data");
32
37
  var import_kernel = require("@objectstack/spec/kernel");
33
38
  var import_ui = require("@objectstack/spec/ui");
39
+ var RESERVED_NAMESPACES = /* @__PURE__ */ new Set(["base", "system"]);
40
+ var DEFAULT_OWNER_PRIORITY = 100;
41
+ var DEFAULT_EXTENDER_PRIORITY = 200;
42
+ function computeFQN(namespace, shortName) {
43
+ if (!namespace || RESERVED_NAMESPACES.has(namespace)) {
44
+ return shortName;
45
+ }
46
+ return `${namespace}__${shortName}`;
47
+ }
48
+ function parseFQN(fqn) {
49
+ const idx = fqn.indexOf("__");
50
+ if (idx === -1) {
51
+ return { namespace: void 0, shortName: fqn };
52
+ }
53
+ return {
54
+ namespace: fqn.slice(0, idx),
55
+ shortName: fqn.slice(idx + 2)
56
+ };
57
+ }
58
+ function mergeObjectDefinitions(base, extension) {
59
+ const merged = { ...base };
60
+ if (extension.fields) {
61
+ merged.fields = { ...base.fields, ...extension.fields };
62
+ }
63
+ if (extension.validations) {
64
+ merged.validations = [...base.validations || [], ...extension.validations];
65
+ }
66
+ if (extension.indexes) {
67
+ merged.indexes = [...base.indexes || [], ...extension.indexes];
68
+ }
69
+ if (extension.label !== void 0) merged.label = extension.label;
70
+ if (extension.pluralLabel !== void 0) merged.pluralLabel = extension.pluralLabel;
71
+ if (extension.description !== void 0) merged.description = extension.description;
72
+ return merged;
73
+ }
34
74
  var SchemaRegistry = class {
75
+ static get logLevel() {
76
+ return this._logLevel;
77
+ }
78
+ static set logLevel(level) {
79
+ this._logLevel = level;
80
+ }
81
+ static log(msg) {
82
+ if (this._logLevel === "silent" || this._logLevel === "error" || this._logLevel === "warn") return;
83
+ console.log(msg);
84
+ }
85
+ // ==========================================
86
+ // Namespace Management
87
+ // ==========================================
88
+ /**
89
+ * Register a namespace for a package.
90
+ * Enforces namespace uniqueness within the instance.
91
+ *
92
+ * @throws Error if namespace is already registered to a different package
93
+ */
94
+ static registerNamespace(namespace, packageId) {
95
+ if (!namespace) return;
96
+ const existing = this.namespaceRegistry.get(namespace);
97
+ if (existing && existing !== packageId) {
98
+ throw new Error(
99
+ `Namespace "${namespace}" is already registered to package "${existing}". Package "${packageId}" cannot use the same namespace.`
100
+ );
101
+ }
102
+ this.namespaceRegistry.set(namespace, packageId);
103
+ this.log(`[Registry] Registered namespace: ${namespace} \u2192 ${packageId}`);
104
+ }
105
+ /**
106
+ * Unregister a namespace when a package is uninstalled.
107
+ */
108
+ static unregisterNamespace(namespace, packageId) {
109
+ const existing = this.namespaceRegistry.get(namespace);
110
+ if (existing === packageId) {
111
+ this.namespaceRegistry.delete(namespace);
112
+ this.log(`[Registry] Unregistered namespace: ${namespace}`);
113
+ }
114
+ }
115
+ /**
116
+ * Get the package that owns a namespace.
117
+ */
118
+ static getNamespaceOwner(namespace) {
119
+ return this.namespaceRegistry.get(namespace);
120
+ }
121
+ // ==========================================
122
+ // Object Registration (Ownership Model)
123
+ // ==========================================
124
+ /**
125
+ * Register an object with ownership semantics.
126
+ *
127
+ * @param schema - The object definition
128
+ * @param packageId - The owning package ID
129
+ * @param namespace - The package namespace (for FQN computation)
130
+ * @param ownership - 'own' (single owner) or 'extend' (additive merge)
131
+ * @param priority - Merge priority (lower applied first, higher wins on conflict)
132
+ *
133
+ * @throws Error if trying to 'own' an object that already has an owner
134
+ */
135
+ static registerObject(schema, packageId, namespace, ownership = "own", priority = ownership === "own" ? DEFAULT_OWNER_PRIORITY : DEFAULT_EXTENDER_PRIORITY) {
136
+ const shortName = schema.name;
137
+ const fqn = computeFQN(namespace, shortName);
138
+ if (namespace) {
139
+ this.registerNamespace(namespace, packageId);
140
+ }
141
+ let contributors = this.objectContributors.get(fqn);
142
+ if (!contributors) {
143
+ contributors = [];
144
+ this.objectContributors.set(fqn, contributors);
145
+ }
146
+ if (ownership === "own") {
147
+ const existingOwner = contributors.find((c) => c.ownership === "own");
148
+ if (existingOwner && existingOwner.packageId !== packageId) {
149
+ throw new Error(
150
+ `Object "${fqn}" is already owned by package "${existingOwner.packageId}". Package "${packageId}" cannot claim ownership. Use 'extend' to add fields.`
151
+ );
152
+ }
153
+ const idx = contributors.findIndex((c) => c.packageId === packageId && c.ownership === "own");
154
+ if (idx !== -1) {
155
+ contributors.splice(idx, 1);
156
+ console.warn(`[Registry] Re-registering owned object: ${fqn} from ${packageId}`);
157
+ }
158
+ } else {
159
+ const idx = contributors.findIndex((c) => c.packageId === packageId && c.ownership === "extend");
160
+ if (idx !== -1) {
161
+ contributors.splice(idx, 1);
162
+ }
163
+ }
164
+ const contributor = {
165
+ packageId,
166
+ namespace: namespace || "",
167
+ ownership,
168
+ priority,
169
+ definition: { ...schema, name: fqn }
170
+ // Store with FQN as name
171
+ };
172
+ contributors.push(contributor);
173
+ contributors.sort((a, b) => a.priority - b.priority);
174
+ this.mergedObjectCache.delete(fqn);
175
+ this.log(`[Registry] Registered object: ${fqn} (${ownership}, priority=${priority}) from ${packageId}`);
176
+ return fqn;
177
+ }
35
178
  /**
36
- * Universal Register Method
37
- * @param type The category of metadata (e.g., 'object', 'app', 'plugin')
38
- * @param item The metadata item itself
39
- * @param keyField The property to use as the unique key (default: 'name')
179
+ * Resolve an object by FQN, merging all contributions.
180
+ * Returns the merged object or undefined if not found.
40
181
  */
41
- static registerItem(type, item, keyField = "name") {
182
+ static resolveObject(fqn) {
183
+ const cached = this.mergedObjectCache.get(fqn);
184
+ if (cached) return cached;
185
+ const contributors = this.objectContributors.get(fqn);
186
+ if (!contributors || contributors.length === 0) {
187
+ return void 0;
188
+ }
189
+ const ownerContrib = contributors.find((c) => c.ownership === "own");
190
+ if (!ownerContrib) {
191
+ console.warn(`[Registry] Object "${fqn}" has extenders but no owner. Skipping.`);
192
+ return void 0;
193
+ }
194
+ let merged = { ...ownerContrib.definition };
195
+ for (const contrib of contributors) {
196
+ if (contrib.ownership === "extend") {
197
+ merged = mergeObjectDefinitions(merged, contrib.definition);
198
+ }
199
+ }
200
+ this.mergedObjectCache.set(fqn, merged);
201
+ return merged;
202
+ }
203
+ /**
204
+ * Get object by name (FQN or short name with fallback scan).
205
+ * For compatibility, tries exact match first, then scans for suffix match.
206
+ */
207
+ static getObject(name) {
208
+ const direct = this.resolveObject(name);
209
+ if (direct) return direct;
210
+ for (const fqn of this.objectContributors.keys()) {
211
+ const { shortName } = parseFQN(fqn);
212
+ if (shortName === name) {
213
+ return this.resolveObject(fqn);
214
+ }
215
+ }
216
+ return void 0;
217
+ }
218
+ /**
219
+ * Get all registered objects (merged).
220
+ *
221
+ * @param packageId - Optional filter: only objects contributed by this package
222
+ */
223
+ static getAllObjects(packageId) {
224
+ const results = [];
225
+ for (const fqn of this.objectContributors.keys()) {
226
+ if (packageId) {
227
+ const contributors = this.objectContributors.get(fqn);
228
+ const hasContribution = contributors?.some((c) => c.packageId === packageId);
229
+ if (!hasContribution) continue;
230
+ }
231
+ const merged = this.resolveObject(fqn);
232
+ if (merged) {
233
+ merged._packageId = this.getObjectOwner(fqn)?.packageId;
234
+ results.push(merged);
235
+ }
236
+ }
237
+ return results;
238
+ }
239
+ /**
240
+ * Get all contributors for an object.
241
+ */
242
+ static getObjectContributors(fqn) {
243
+ return this.objectContributors.get(fqn) || [];
244
+ }
245
+ /**
246
+ * Get the owner contributor for an object.
247
+ */
248
+ static getObjectOwner(fqn) {
249
+ const contributors = this.objectContributors.get(fqn);
250
+ return contributors?.find((c) => c.ownership === "own");
251
+ }
252
+ /**
253
+ * Unregister all objects contributed by a package.
254
+ *
255
+ * @throws Error if trying to uninstall an owner that has extenders
256
+ */
257
+ static unregisterObjectsByPackage(packageId, force = false) {
258
+ for (const [fqn, contributors] of this.objectContributors.entries()) {
259
+ const packageContribs = contributors.filter((c) => c.packageId === packageId);
260
+ for (const contrib of packageContribs) {
261
+ if (contrib.ownership === "own" && !force) {
262
+ const otherExtenders = contributors.filter(
263
+ (c) => c.packageId !== packageId && c.ownership === "extend"
264
+ );
265
+ if (otherExtenders.length > 0) {
266
+ throw new Error(
267
+ `Cannot uninstall package "${packageId}": object "${fqn}" is extended by ${otherExtenders.map((c) => c.packageId).join(", ")}. Uninstall extenders first.`
268
+ );
269
+ }
270
+ }
271
+ const idx = contributors.indexOf(contrib);
272
+ if (idx !== -1) {
273
+ contributors.splice(idx, 1);
274
+ this.log(`[Registry] Removed ${contrib.ownership} contribution to ${fqn} from ${packageId}`);
275
+ }
276
+ }
277
+ if (contributors.length === 0) {
278
+ this.objectContributors.delete(fqn);
279
+ }
280
+ this.mergedObjectCache.delete(fqn);
281
+ }
282
+ }
283
+ // ==========================================
284
+ // Generic Metadata (Non-Object Types)
285
+ // ==========================================
286
+ /**
287
+ * Universal Register Method for non-object metadata.
288
+ */
289
+ static registerItem(type, item, keyField = "name", packageId) {
42
290
  if (!this.metadata.has(type)) {
43
291
  this.metadata.set(type, /* @__PURE__ */ new Map());
44
292
  }
45
293
  const collection = this.metadata.get(type);
46
- const key = String(item[keyField]);
294
+ const baseName = String(item[keyField]);
295
+ if (packageId) {
296
+ item._packageId = packageId;
297
+ }
47
298
  try {
48
299
  this.validate(type, item);
49
300
  } catch (e) {
50
- console.error(`[Registry] Validation failed for ${type} ${key}: ${e.message}`);
301
+ console.error(`[Registry] Validation failed for ${type} ${baseName}: ${e.message}`);
51
302
  }
52
- if (collection.has(key)) {
53
- console.warn(`[Registry] Overwriting ${type}: ${key}`);
303
+ const storageKey = packageId ? `${packageId}:${baseName}` : baseName;
304
+ if (collection.has(storageKey)) {
305
+ console.warn(`[Registry] Overwriting ${type}: ${storageKey}`);
54
306
  }
55
- collection.set(key, item);
56
- console.log(`[Registry] Registered ${type}: ${key}`);
307
+ collection.set(storageKey, item);
308
+ this.log(`[Registry] Registered ${type}: ${storageKey}`);
57
309
  }
58
310
  /**
59
311
  * Validate Metadata against Spec Zod Schemas
@@ -62,9 +314,12 @@ var SchemaRegistry = class {
62
314
  if (type === "object") {
63
315
  return import_data.ObjectSchema.parse(item);
64
316
  }
65
- if (type === "app") {
317
+ if (type === "apps") {
66
318
  return import_ui.AppSchema.parse(item);
67
319
  }
320
+ if (type === "package") {
321
+ return import_kernel.InstalledPackageSchema.parse(item);
322
+ }
68
323
  if (type === "plugin") {
69
324
  return import_kernel.ManifestSchema.parse(item);
70
325
  }
@@ -75,66 +330,198 @@ var SchemaRegistry = class {
75
330
  */
76
331
  static unregisterItem(type, name) {
77
332
  const collection = this.metadata.get(type);
78
- if (collection && collection.has(name)) {
79
- collection.delete(name);
80
- console.log(`[Registry] Unregistered ${type}: ${name}`);
81
- } else {
333
+ if (!collection) {
82
334
  console.warn(`[Registry] Attempted to unregister non-existent ${type}: ${name}`);
335
+ return;
336
+ }
337
+ if (collection.has(name)) {
338
+ collection.delete(name);
339
+ this.log(`[Registry] Unregistered ${type}: ${name}`);
340
+ return;
83
341
  }
342
+ for (const key of collection.keys()) {
343
+ if (key.endsWith(`:${name}`)) {
344
+ collection.delete(key);
345
+ this.log(`[Registry] Unregistered ${type}: ${key}`);
346
+ return;
347
+ }
348
+ }
349
+ console.warn(`[Registry] Attempted to unregister non-existent ${type}: ${name}`);
84
350
  }
85
351
  /**
86
352
  * Universal Get Method
87
353
  */
88
354
  static getItem(type, name) {
89
- return this.metadata.get(type)?.get(name);
355
+ if (type === "object" || type === "objects") {
356
+ return this.getObject(name);
357
+ }
358
+ const collection = this.metadata.get(type);
359
+ if (!collection) return void 0;
360
+ const direct = collection.get(name);
361
+ if (direct) return direct;
362
+ for (const [key, item] of collection) {
363
+ if (key.endsWith(`:${name}`)) return item;
364
+ }
365
+ return void 0;
90
366
  }
91
367
  /**
92
368
  * Universal List Method
93
369
  */
94
- static listItems(type) {
95
- return Array.from(this.metadata.get(type)?.values() || []);
370
+ static listItems(type, packageId) {
371
+ if (type === "object" || type === "objects") {
372
+ return this.getAllObjects(packageId);
373
+ }
374
+ const items = Array.from(this.metadata.get(type)?.values() || []);
375
+ if (packageId) {
376
+ return items.filter((item) => item._packageId === packageId);
377
+ }
378
+ return items;
96
379
  }
97
380
  /**
98
381
  * Get all registered metadata types (Kinds)
99
382
  */
100
383
  static getRegisteredTypes() {
101
- return Array.from(this.metadata.keys());
384
+ const types = Array.from(this.metadata.keys());
385
+ if (!types.includes("object") && this.objectContributors.size > 0) {
386
+ types.push("object");
387
+ }
388
+ return types;
102
389
  }
103
390
  // ==========================================
104
- // Typed Helper Methods (Shortcuts)
391
+ // Package Management
105
392
  // ==========================================
106
- /**
107
- * Object Helpers
108
- */
109
- static registerObject(schema) {
110
- this.registerItem("object", schema, "name");
393
+ static installPackage(manifest, settings) {
394
+ const now = (/* @__PURE__ */ new Date()).toISOString();
395
+ const pkg = {
396
+ manifest,
397
+ status: "installed",
398
+ enabled: true,
399
+ installedAt: now,
400
+ updatedAt: now,
401
+ settings
402
+ };
403
+ if (manifest.namespace) {
404
+ this.registerNamespace(manifest.namespace, manifest.id);
405
+ }
406
+ if (!this.metadata.has("package")) {
407
+ this.metadata.set("package", /* @__PURE__ */ new Map());
408
+ }
409
+ const collection = this.metadata.get("package");
410
+ if (collection.has(manifest.id)) {
411
+ console.warn(`[Registry] Overwriting package: ${manifest.id}`);
412
+ }
413
+ collection.set(manifest.id, pkg);
414
+ this.log(`[Registry] Installed package: ${manifest.id} (${manifest.name})`);
415
+ return pkg;
111
416
  }
112
- static getObject(name) {
113
- return this.getItem("object", name);
417
+ static uninstallPackage(id) {
418
+ const pkg = this.getPackage(id);
419
+ if (!pkg) {
420
+ console.warn(`[Registry] Package not found for uninstall: ${id}`);
421
+ return false;
422
+ }
423
+ if (pkg.manifest.namespace) {
424
+ this.unregisterNamespace(pkg.manifest.namespace, id);
425
+ }
426
+ this.unregisterObjectsByPackage(id);
427
+ const collection = this.metadata.get("package");
428
+ if (collection) {
429
+ collection.delete(id);
430
+ this.log(`[Registry] Uninstalled package: ${id}`);
431
+ return true;
432
+ }
433
+ return false;
114
434
  }
115
- static getAllObjects() {
116
- return this.listItems("object");
435
+ static getPackage(id) {
436
+ return this.metadata.get("package")?.get(id);
117
437
  }
118
- /**
119
- * Plugin Helpers
120
- */
438
+ static getAllPackages() {
439
+ return this.listItems("package");
440
+ }
441
+ static enablePackage(id) {
442
+ const pkg = this.getPackage(id);
443
+ if (pkg) {
444
+ pkg.enabled = true;
445
+ pkg.status = "installed";
446
+ pkg.statusChangedAt = (/* @__PURE__ */ new Date()).toISOString();
447
+ pkg.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
448
+ this.log(`[Registry] Enabled package: ${id}`);
449
+ }
450
+ return pkg;
451
+ }
452
+ static disablePackage(id) {
453
+ const pkg = this.getPackage(id);
454
+ if (pkg) {
455
+ pkg.enabled = false;
456
+ pkg.status = "disabled";
457
+ pkg.statusChangedAt = (/* @__PURE__ */ new Date()).toISOString();
458
+ pkg.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
459
+ this.log(`[Registry] Disabled package: ${id}`);
460
+ }
461
+ return pkg;
462
+ }
463
+ // ==========================================
464
+ // App Helpers
465
+ // ==========================================
466
+ static registerApp(app, packageId) {
467
+ this.registerItem("apps", app, "name", packageId);
468
+ }
469
+ static getApp(name) {
470
+ return this.getItem("apps", name);
471
+ }
472
+ static getAllApps() {
473
+ return this.listItems("apps");
474
+ }
475
+ // ==========================================
476
+ // Plugin Helpers
477
+ // ==========================================
121
478
  static registerPlugin(manifest) {
122
479
  this.registerItem("plugin", manifest, "id");
123
480
  }
124
481
  static getAllPlugins() {
125
482
  return this.listItems("plugin");
126
483
  }
127
- /**
128
- * Kind (Metadata Type) Helpers
129
- */
484
+ // ==========================================
485
+ // Kind Helpers
486
+ // ==========================================
130
487
  static registerKind(kind) {
131
488
  this.registerItem("kind", kind, "id");
132
489
  }
133
490
  static getAllKinds() {
134
491
  return this.listItems("kind");
135
492
  }
493
+ // ==========================================
494
+ // Reset (for testing)
495
+ // ==========================================
496
+ /**
497
+ * Clear all registry state. Use only for testing.
498
+ */
499
+ static reset() {
500
+ this.objectContributors.clear();
501
+ this.mergedObjectCache.clear();
502
+ this.namespaceRegistry.clear();
503
+ this.metadata.clear();
504
+ this.log("[Registry] Reset complete");
505
+ }
136
506
  };
137
- // Nested Map: Type -> Name/ID -> MetadataItem
507
+ // ==========================================
508
+ // Logging control
509
+ // ==========================================
510
+ /** Controls verbosity of registry console messages. Default: 'info'. */
511
+ SchemaRegistry._logLevel = "info";
512
+ // ==========================================
513
+ // Object-specific storage (Ownership Model)
514
+ // ==========================================
515
+ /** FQN → Contributor[] (all packages that own/extend this object) */
516
+ SchemaRegistry.objectContributors = /* @__PURE__ */ new Map();
517
+ /** FQN → Merged ServiceObject (cached, invalidated on changes) */
518
+ SchemaRegistry.mergedObjectCache = /* @__PURE__ */ new Map();
519
+ /** Namespace → PackageId (ensures namespace uniqueness) */
520
+ SchemaRegistry.namespaceRegistry = /* @__PURE__ */ new Map();
521
+ // ==========================================
522
+ // Generic metadata storage (non-object types)
523
+ // ==========================================
524
+ /** Type → Name/ID → MetadataItem */
138
525
  SchemaRegistry.metadata = /* @__PURE__ */ new Map();
139
526
 
140
527
  // src/protocol.ts
@@ -178,7 +565,7 @@ var ObjectStackProtocolImplementation = class {
178
565
  async getMetaItems(request) {
179
566
  return {
180
567
  type: request.type,
181
- items: SchemaRegistry.listItems(request.type)
568
+ items: SchemaRegistry.listItems(request.type, request.packageId)
182
569
  };
183
570
  }
184
571
  async getMetaItem(request) {
@@ -191,31 +578,57 @@ var ObjectStackProtocolImplementation = class {
191
578
  async getUiView(request) {
192
579
  const schema = SchemaRegistry.getObject(request.object);
193
580
  if (!schema) throw new Error(`Object ${request.object} not found`);
194
- let view;
581
+ const fields = schema.fields || {};
582
+ const fieldKeys = Object.keys(fields);
195
583
  if (request.type === "list") {
196
- view = {
197
- type: "list",
198
- object: request.object,
199
- columns: Object.keys(schema.fields || {}).slice(0, 5).map((f) => ({
200
- field: f,
201
- label: schema.fields[f].label || f
202
- }))
584
+ const priorityFields = ["name", "title", "label", "subject", "email", "status", "type", "category", "created_at"];
585
+ let columns = fieldKeys.filter((k) => priorityFields.includes(k));
586
+ if (columns.length < 5) {
587
+ const remaining = fieldKeys.filter((k) => !columns.includes(k) && k !== "id" && !fields[k].hidden);
588
+ columns = [...columns, ...remaining.slice(0, 5 - columns.length)];
589
+ }
590
+ return {
591
+ list: {
592
+ type: "grid",
593
+ object: request.object,
594
+ label: schema.label || schema.name,
595
+ columns: columns.map((f) => ({
596
+ field: f,
597
+ label: fields[f]?.label || f,
598
+ sortable: true
599
+ })),
600
+ sort: fields["created_at"] ? [{ field: "created_at", order: "desc" }] : void 0,
601
+ searchableFields: columns.slice(0, 3)
602
+ // Make first few textual columns searchable
603
+ }
203
604
  };
204
605
  } else {
205
- view = {
206
- type: "form",
207
- object: request.object,
208
- sections: [
209
- {
210
- label: "General",
211
- fields: Object.keys(schema.fields || {}).map((f) => ({
212
- field: f
213
- }))
214
- }
215
- ]
606
+ const formFields = fieldKeys.filter((k) => k !== "id" && k !== "created_at" && k !== "modified_at" && !fields[k].hidden).map((f) => ({
607
+ field: f,
608
+ label: fields[f]?.label,
609
+ required: fields[f]?.required,
610
+ readonly: fields[f]?.readonly,
611
+ type: fields[f]?.type,
612
+ // Default to 2 columns for most, 1 for textareas
613
+ colSpan: fields[f]?.type === "textarea" || fields[f]?.type === "html" ? 2 : 1
614
+ }));
615
+ return {
616
+ form: {
617
+ type: "simple",
618
+ object: request.object,
619
+ label: `Edit ${schema.label || schema.name}`,
620
+ sections: [
621
+ {
622
+ label: "General Information",
623
+ columns: 2,
624
+ collapsible: false,
625
+ collapsed: false,
626
+ fields: formFields
627
+ }
628
+ ]
629
+ }
216
630
  };
217
631
  }
218
- return view;
219
632
  }
220
633
  async findData(request) {
221
634
  const options = { ...request.query };
@@ -455,23 +868,98 @@ var ObjectQL = class {
455
868
  }
456
869
  /**
457
870
  * Register contribution (Manifest)
871
+ *
872
+ * Installs the manifest as a Package (the unit of installation),
873
+ * then decomposes it into individual metadata items (objects, apps, actions, etc.)
874
+ * and registers each into the SchemaRegistry.
875
+ *
876
+ * Key: Package ≠ App. The manifest is the package. The apps[] array inside
877
+ * the manifest contains UI navigation definitions (AppSchema).
458
878
  */
459
879
  registerApp(manifest) {
460
- const id = manifest.id;
461
- this.logger.debug("Registering app manifest", { id });
880
+ const id = manifest.id || manifest.name;
881
+ const namespace = manifest.namespace;
882
+ this.logger.debug("Registering package manifest", { id, namespace });
883
+ SchemaRegistry.installPackage(manifest);
884
+ this.logger.debug("Installed Package", { id: manifest.id, name: manifest.name, namespace });
462
885
  if (manifest.objects) {
463
886
  if (Array.isArray(manifest.objects)) {
464
887
  this.logger.debug("Registering objects from manifest (Array)", { id, objectCount: manifest.objects.length });
465
888
  for (const objDef of manifest.objects) {
466
- SchemaRegistry.registerObject(objDef);
467
- this.logger.debug("Registered Object", { object: objDef.name, from: id });
889
+ const fqn = SchemaRegistry.registerObject(objDef, id, namespace, "own");
890
+ this.logger.debug("Registered Object", { fqn, from: id });
468
891
  }
469
892
  } else {
470
893
  this.logger.debug("Registering objects from manifest (Map)", { id, objectCount: Object.keys(manifest.objects).length });
471
894
  for (const [name, objDef] of Object.entries(manifest.objects)) {
472
895
  objDef.name = name;
473
- SchemaRegistry.registerObject(objDef);
474
- this.logger.debug("Registered Object", { object: name, from: id });
896
+ const fqn = SchemaRegistry.registerObject(objDef, id, namespace, "own");
897
+ this.logger.debug("Registered Object", { fqn, from: id });
898
+ }
899
+ }
900
+ }
901
+ if (Array.isArray(manifest.objectExtensions) && manifest.objectExtensions.length > 0) {
902
+ this.logger.debug("Registering object extensions", { id, count: manifest.objectExtensions.length });
903
+ for (const ext of manifest.objectExtensions) {
904
+ const targetFqn = ext.extend;
905
+ const priority = ext.priority ?? 200;
906
+ const extDef = {
907
+ name: targetFqn,
908
+ // Use the target FQN as name
909
+ fields: ext.fields,
910
+ label: ext.label,
911
+ pluralLabel: ext.pluralLabel,
912
+ description: ext.description,
913
+ validations: ext.validations,
914
+ indexes: ext.indexes
915
+ };
916
+ SchemaRegistry.registerObject(extDef, id, void 0, "extend", priority);
917
+ this.logger.debug("Registered Object Extension", { target: targetFqn, priority, from: id });
918
+ }
919
+ }
920
+ if (Array.isArray(manifest.apps) && manifest.apps.length > 0) {
921
+ this.logger.debug("Registering apps from manifest", { id, count: manifest.apps.length });
922
+ for (const app of manifest.apps) {
923
+ const appName = app.name || app.id;
924
+ if (appName) {
925
+ SchemaRegistry.registerApp(app, id);
926
+ this.logger.debug("Registered App", { app: appName, from: id });
927
+ }
928
+ }
929
+ }
930
+ if (manifest.name && manifest.navigation && !manifest.apps?.length) {
931
+ SchemaRegistry.registerApp(manifest, id);
932
+ this.logger.debug("Registered manifest-as-app", { app: manifest.name, from: id });
933
+ }
934
+ const metadataArrayKeys = [
935
+ "actions",
936
+ "dashboards",
937
+ "reports",
938
+ "flows",
939
+ "agents",
940
+ "apis",
941
+ "ragPipelines",
942
+ "profiles",
943
+ "sharingRules"
944
+ ];
945
+ for (const key of metadataArrayKeys) {
946
+ const items = manifest[key];
947
+ if (Array.isArray(items) && items.length > 0) {
948
+ this.logger.debug(`Registering ${key} from manifest`, { id, count: items.length });
949
+ for (const item of items) {
950
+ const itemName = item.name || item.id;
951
+ if (itemName) {
952
+ SchemaRegistry.registerItem(key, item, "name", id);
953
+ }
954
+ }
955
+ }
956
+ }
957
+ const seedData = manifest.data;
958
+ if (Array.isArray(seedData) && seedData.length > 0) {
959
+ this.logger.debug("Registering seed data datasets", { id, count: seedData.length });
960
+ for (const dataset of seedData) {
961
+ if (dataset.object) {
962
+ SchemaRegistry.registerItem("data", dataset, "object", id);
475
963
  }
476
964
  }
477
965
  }
@@ -507,6 +995,23 @@ var ObjectQL = class {
507
995
  getSchema(objectName) {
508
996
  return SchemaRegistry.getObject(objectName);
509
997
  }
998
+ /**
999
+ * Resolve an object name to its Fully Qualified Name (FQN).
1000
+ *
1001
+ * Short names like 'task' are resolved to FQN like 'todo__task'
1002
+ * via SchemaRegistry lookup. If no match is found, the name is
1003
+ * returned as-is (for ad-hoc / unregistered objects).
1004
+ *
1005
+ * This ensures that all driver operations use a consistent key
1006
+ * regardless of whether the caller uses the short name or FQN.
1007
+ */
1008
+ resolveObjectName(name) {
1009
+ const schema = SchemaRegistry.getObject(name);
1010
+ if (schema) {
1011
+ return schema.name;
1012
+ }
1013
+ return name;
1014
+ }
510
1015
  /**
511
1016
  * Helper to get the target driver
512
1017
  */
@@ -590,6 +1095,7 @@ var ObjectQL = class {
590
1095
  // Data Access Methods (IDataEngine Interface)
591
1096
  // ============================================
592
1097
  async find(object, query) {
1098
+ object = this.resolveObjectName(object);
593
1099
  this.logger.debug("Find operation starting", { object, query });
594
1100
  const driver = this.getDriver(object);
595
1101
  const ast = this.toQueryAST(object, query);
@@ -613,6 +1119,7 @@ var ObjectQL = class {
613
1119
  }
614
1120
  }
615
1121
  async findOne(objectName, query) {
1122
+ objectName = this.resolveObjectName(objectName);
616
1123
  this.logger.debug("FindOne operation", { objectName });
617
1124
  const driver = this.getDriver(objectName);
618
1125
  const ast = this.toQueryAST(objectName, query);
@@ -620,6 +1127,7 @@ var ObjectQL = class {
620
1127
  return driver.findOne(objectName, ast);
621
1128
  }
622
1129
  async insert(object, data, options) {
1130
+ object = this.resolveObjectName(object);
623
1131
  this.logger.debug("Insert operation starting", { object, isBatch: Array.isArray(data) });
624
1132
  const driver = this.getDriver(object);
625
1133
  const hookContext = {
@@ -650,6 +1158,7 @@ var ObjectQL = class {
650
1158
  }
651
1159
  }
652
1160
  async update(object, data, options) {
1161
+ object = this.resolveObjectName(object);
653
1162
  this.logger.debug("Update operation starting", { object });
654
1163
  const driver = this.getDriver(object);
655
1164
  let id = data.id || data._id;
@@ -685,6 +1194,7 @@ var ObjectQL = class {
685
1194
  }
686
1195
  }
687
1196
  async delete(object, options) {
1197
+ object = this.resolveObjectName(object);
688
1198
  this.logger.debug("Delete operation starting", { object });
689
1199
  const driver = this.getDriver(object);
690
1200
  let id = void 0;
@@ -720,6 +1230,7 @@ var ObjectQL = class {
720
1230
  }
721
1231
  }
722
1232
  async count(object, query) {
1233
+ object = this.resolveObjectName(object);
723
1234
  const driver = this.getDriver(object);
724
1235
  if (driver.count) {
725
1236
  const ast = this.toQueryAST(object, { filter: query?.filter });
@@ -729,6 +1240,7 @@ var ObjectQL = class {
729
1240
  return res.length;
730
1241
  }
731
1242
  async aggregate(object, query) {
1243
+ object = this.resolveObjectName(object);
732
1244
  const driver = this.getDriver(object);
733
1245
  this.logger.debug(`Aggregate on ${object} using ${driver.name}`, query);
734
1246
  throw new Error("Aggregate not yet fully implemented in ObjectQL->Driver mapping");
@@ -752,7 +1264,8 @@ var ObjectQLPlugin = class {
752
1264
  this.version = "1.0.0";
753
1265
  this.init = async (ctx) => {
754
1266
  if (!this.ql) {
755
- this.ql = new ObjectQL(this.hostContext);
1267
+ const hostCtx = { ...this.hostContext, logger: ctx.logger };
1268
+ this.ql = new ObjectQL(hostCtx);
756
1269
  }
757
1270
  ctx.registerService("objectql", this.ql);
758
1271
  let hasMetadata = false;
@@ -805,9 +1318,14 @@ var ObjectQLPlugin = class {
805
1318
  };
806
1319
  // Annotate the CommonJS export names for ESM import in node:
807
1320
  0 && (module.exports = {
1321
+ DEFAULT_EXTENDER_PRIORITY,
1322
+ DEFAULT_OWNER_PRIORITY,
808
1323
  ObjectQL,
809
1324
  ObjectQLPlugin,
810
1325
  ObjectStackProtocolImplementation,
811
- SchemaRegistry
1326
+ RESERVED_NAMESPACES,
1327
+ SchemaRegistry,
1328
+ computeFQN,
1329
+ parseFQN
812
1330
  });
813
1331
  //# sourceMappingURL=index.js.map