@objectstack/objectql 1.0.10 → 1.0.12
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +17 -0
- package/dist/index.d.mts +713 -33
- package/dist/index.d.ts +713 -33
- package/dist/index.js +585 -67
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +580 -67
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -4
- package/src/engine.test.ts +60 -3
- package/src/engine.ts +115 -8
- package/src/index.ts +9 -1
- package/src/plugin.ts +3 -1
- package/src/protocol.ts +63 -22
- package/src/registry.test.ts +456 -25
- package/src/registry.ts +609 -53
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
|
-
|
|
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
|
-
*
|
|
37
|
-
*
|
|
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
|
|
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
|
|
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} ${
|
|
301
|
+
console.error(`[Registry] Validation failed for ${type} ${baseName}: ${e.message}`);
|
|
51
302
|
}
|
|
52
|
-
|
|
53
|
-
|
|
303
|
+
const storageKey = packageId ? `${packageId}:${baseName}` : baseName;
|
|
304
|
+
if (collection.has(storageKey)) {
|
|
305
|
+
console.warn(`[Registry] Overwriting ${type}: ${storageKey}`);
|
|
54
306
|
}
|
|
55
|
-
collection.set(
|
|
56
|
-
|
|
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 === "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
391
|
+
// Package Management
|
|
105
392
|
// ==========================================
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
113
|
-
|
|
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
|
|
116
|
-
return this.
|
|
435
|
+
static getPackage(id) {
|
|
436
|
+
return this.metadata.get("package")?.get(id);
|
|
117
437
|
}
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
581
|
+
const fields = schema.fields || {};
|
|
582
|
+
const fieldKeys = Object.keys(fields);
|
|
195
583
|
if (request.type === "list") {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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", {
|
|
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", {
|
|
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
|
-
|
|
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
|
-
|
|
1326
|
+
RESERVED_NAMESPACES,
|
|
1327
|
+
SchemaRegistry,
|
|
1328
|
+
computeFQN,
|
|
1329
|
+
parseFQN
|
|
812
1330
|
});
|
|
813
1331
|
//# sourceMappingURL=index.js.map
|