@objectstack/objectql 1.0.11 → 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 +9 -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.mjs
CHANGED
|
@@ -1,30 +1,277 @@
|
|
|
1
1
|
// src/registry.ts
|
|
2
2
|
import { ObjectSchema } from "@objectstack/spec/data";
|
|
3
|
-
import { ManifestSchema } from "@objectstack/spec/kernel";
|
|
3
|
+
import { ManifestSchema, InstalledPackageSchema } from "@objectstack/spec/kernel";
|
|
4
4
|
import { AppSchema } from "@objectstack/spec/ui";
|
|
5
|
+
var RESERVED_NAMESPACES = /* @__PURE__ */ new Set(["base", "system"]);
|
|
6
|
+
var DEFAULT_OWNER_PRIORITY = 100;
|
|
7
|
+
var DEFAULT_EXTENDER_PRIORITY = 200;
|
|
8
|
+
function computeFQN(namespace, shortName) {
|
|
9
|
+
if (!namespace || RESERVED_NAMESPACES.has(namespace)) {
|
|
10
|
+
return shortName;
|
|
11
|
+
}
|
|
12
|
+
return `${namespace}__${shortName}`;
|
|
13
|
+
}
|
|
14
|
+
function parseFQN(fqn) {
|
|
15
|
+
const idx = fqn.indexOf("__");
|
|
16
|
+
if (idx === -1) {
|
|
17
|
+
return { namespace: void 0, shortName: fqn };
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
namespace: fqn.slice(0, idx),
|
|
21
|
+
shortName: fqn.slice(idx + 2)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function mergeObjectDefinitions(base, extension) {
|
|
25
|
+
const merged = { ...base };
|
|
26
|
+
if (extension.fields) {
|
|
27
|
+
merged.fields = { ...base.fields, ...extension.fields };
|
|
28
|
+
}
|
|
29
|
+
if (extension.validations) {
|
|
30
|
+
merged.validations = [...base.validations || [], ...extension.validations];
|
|
31
|
+
}
|
|
32
|
+
if (extension.indexes) {
|
|
33
|
+
merged.indexes = [...base.indexes || [], ...extension.indexes];
|
|
34
|
+
}
|
|
35
|
+
if (extension.label !== void 0) merged.label = extension.label;
|
|
36
|
+
if (extension.pluralLabel !== void 0) merged.pluralLabel = extension.pluralLabel;
|
|
37
|
+
if (extension.description !== void 0) merged.description = extension.description;
|
|
38
|
+
return merged;
|
|
39
|
+
}
|
|
5
40
|
var SchemaRegistry = class {
|
|
41
|
+
static get logLevel() {
|
|
42
|
+
return this._logLevel;
|
|
43
|
+
}
|
|
44
|
+
static set logLevel(level) {
|
|
45
|
+
this._logLevel = level;
|
|
46
|
+
}
|
|
47
|
+
static log(msg) {
|
|
48
|
+
if (this._logLevel === "silent" || this._logLevel === "error" || this._logLevel === "warn") return;
|
|
49
|
+
console.log(msg);
|
|
50
|
+
}
|
|
51
|
+
// ==========================================
|
|
52
|
+
// Namespace Management
|
|
53
|
+
// ==========================================
|
|
54
|
+
/**
|
|
55
|
+
* Register a namespace for a package.
|
|
56
|
+
* Enforces namespace uniqueness within the instance.
|
|
57
|
+
*
|
|
58
|
+
* @throws Error if namespace is already registered to a different package
|
|
59
|
+
*/
|
|
60
|
+
static registerNamespace(namespace, packageId) {
|
|
61
|
+
if (!namespace) return;
|
|
62
|
+
const existing = this.namespaceRegistry.get(namespace);
|
|
63
|
+
if (existing && existing !== packageId) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Namespace "${namespace}" is already registered to package "${existing}". Package "${packageId}" cannot use the same namespace.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
this.namespaceRegistry.set(namespace, packageId);
|
|
69
|
+
this.log(`[Registry] Registered namespace: ${namespace} \u2192 ${packageId}`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Unregister a namespace when a package is uninstalled.
|
|
73
|
+
*/
|
|
74
|
+
static unregisterNamespace(namespace, packageId) {
|
|
75
|
+
const existing = this.namespaceRegistry.get(namespace);
|
|
76
|
+
if (existing === packageId) {
|
|
77
|
+
this.namespaceRegistry.delete(namespace);
|
|
78
|
+
this.log(`[Registry] Unregistered namespace: ${namespace}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the package that owns a namespace.
|
|
83
|
+
*/
|
|
84
|
+
static getNamespaceOwner(namespace) {
|
|
85
|
+
return this.namespaceRegistry.get(namespace);
|
|
86
|
+
}
|
|
87
|
+
// ==========================================
|
|
88
|
+
// Object Registration (Ownership Model)
|
|
89
|
+
// ==========================================
|
|
90
|
+
/**
|
|
91
|
+
* Register an object with ownership semantics.
|
|
92
|
+
*
|
|
93
|
+
* @param schema - The object definition
|
|
94
|
+
* @param packageId - The owning package ID
|
|
95
|
+
* @param namespace - The package namespace (for FQN computation)
|
|
96
|
+
* @param ownership - 'own' (single owner) or 'extend' (additive merge)
|
|
97
|
+
* @param priority - Merge priority (lower applied first, higher wins on conflict)
|
|
98
|
+
*
|
|
99
|
+
* @throws Error if trying to 'own' an object that already has an owner
|
|
100
|
+
*/
|
|
101
|
+
static registerObject(schema, packageId, namespace, ownership = "own", priority = ownership === "own" ? DEFAULT_OWNER_PRIORITY : DEFAULT_EXTENDER_PRIORITY) {
|
|
102
|
+
const shortName = schema.name;
|
|
103
|
+
const fqn = computeFQN(namespace, shortName);
|
|
104
|
+
if (namespace) {
|
|
105
|
+
this.registerNamespace(namespace, packageId);
|
|
106
|
+
}
|
|
107
|
+
let contributors = this.objectContributors.get(fqn);
|
|
108
|
+
if (!contributors) {
|
|
109
|
+
contributors = [];
|
|
110
|
+
this.objectContributors.set(fqn, contributors);
|
|
111
|
+
}
|
|
112
|
+
if (ownership === "own") {
|
|
113
|
+
const existingOwner = contributors.find((c) => c.ownership === "own");
|
|
114
|
+
if (existingOwner && existingOwner.packageId !== packageId) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Object "${fqn}" is already owned by package "${existingOwner.packageId}". Package "${packageId}" cannot claim ownership. Use 'extend' to add fields.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const idx = contributors.findIndex((c) => c.packageId === packageId && c.ownership === "own");
|
|
120
|
+
if (idx !== -1) {
|
|
121
|
+
contributors.splice(idx, 1);
|
|
122
|
+
console.warn(`[Registry] Re-registering owned object: ${fqn} from ${packageId}`);
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
const idx = contributors.findIndex((c) => c.packageId === packageId && c.ownership === "extend");
|
|
126
|
+
if (idx !== -1) {
|
|
127
|
+
contributors.splice(idx, 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const contributor = {
|
|
131
|
+
packageId,
|
|
132
|
+
namespace: namespace || "",
|
|
133
|
+
ownership,
|
|
134
|
+
priority,
|
|
135
|
+
definition: { ...schema, name: fqn }
|
|
136
|
+
// Store with FQN as name
|
|
137
|
+
};
|
|
138
|
+
contributors.push(contributor);
|
|
139
|
+
contributors.sort((a, b) => a.priority - b.priority);
|
|
140
|
+
this.mergedObjectCache.delete(fqn);
|
|
141
|
+
this.log(`[Registry] Registered object: ${fqn} (${ownership}, priority=${priority}) from ${packageId}`);
|
|
142
|
+
return fqn;
|
|
143
|
+
}
|
|
6
144
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @param item The metadata item itself
|
|
10
|
-
* @param keyField The property to use as the unique key (default: 'name')
|
|
145
|
+
* Resolve an object by FQN, merging all contributions.
|
|
146
|
+
* Returns the merged object or undefined if not found.
|
|
11
147
|
*/
|
|
12
|
-
static
|
|
148
|
+
static resolveObject(fqn) {
|
|
149
|
+
const cached = this.mergedObjectCache.get(fqn);
|
|
150
|
+
if (cached) return cached;
|
|
151
|
+
const contributors = this.objectContributors.get(fqn);
|
|
152
|
+
if (!contributors || contributors.length === 0) {
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
const ownerContrib = contributors.find((c) => c.ownership === "own");
|
|
156
|
+
if (!ownerContrib) {
|
|
157
|
+
console.warn(`[Registry] Object "${fqn}" has extenders but no owner. Skipping.`);
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
let merged = { ...ownerContrib.definition };
|
|
161
|
+
for (const contrib of contributors) {
|
|
162
|
+
if (contrib.ownership === "extend") {
|
|
163
|
+
merged = mergeObjectDefinitions(merged, contrib.definition);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
this.mergedObjectCache.set(fqn, merged);
|
|
167
|
+
return merged;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get object by name (FQN or short name with fallback scan).
|
|
171
|
+
* For compatibility, tries exact match first, then scans for suffix match.
|
|
172
|
+
*/
|
|
173
|
+
static getObject(name) {
|
|
174
|
+
const direct = this.resolveObject(name);
|
|
175
|
+
if (direct) return direct;
|
|
176
|
+
for (const fqn of this.objectContributors.keys()) {
|
|
177
|
+
const { shortName } = parseFQN(fqn);
|
|
178
|
+
if (shortName === name) {
|
|
179
|
+
return this.resolveObject(fqn);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return void 0;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get all registered objects (merged).
|
|
186
|
+
*
|
|
187
|
+
* @param packageId - Optional filter: only objects contributed by this package
|
|
188
|
+
*/
|
|
189
|
+
static getAllObjects(packageId) {
|
|
190
|
+
const results = [];
|
|
191
|
+
for (const fqn of this.objectContributors.keys()) {
|
|
192
|
+
if (packageId) {
|
|
193
|
+
const contributors = this.objectContributors.get(fqn);
|
|
194
|
+
const hasContribution = contributors?.some((c) => c.packageId === packageId);
|
|
195
|
+
if (!hasContribution) continue;
|
|
196
|
+
}
|
|
197
|
+
const merged = this.resolveObject(fqn);
|
|
198
|
+
if (merged) {
|
|
199
|
+
merged._packageId = this.getObjectOwner(fqn)?.packageId;
|
|
200
|
+
results.push(merged);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return results;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get all contributors for an object.
|
|
207
|
+
*/
|
|
208
|
+
static getObjectContributors(fqn) {
|
|
209
|
+
return this.objectContributors.get(fqn) || [];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get the owner contributor for an object.
|
|
213
|
+
*/
|
|
214
|
+
static getObjectOwner(fqn) {
|
|
215
|
+
const contributors = this.objectContributors.get(fqn);
|
|
216
|
+
return contributors?.find((c) => c.ownership === "own");
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Unregister all objects contributed by a package.
|
|
220
|
+
*
|
|
221
|
+
* @throws Error if trying to uninstall an owner that has extenders
|
|
222
|
+
*/
|
|
223
|
+
static unregisterObjectsByPackage(packageId, force = false) {
|
|
224
|
+
for (const [fqn, contributors] of this.objectContributors.entries()) {
|
|
225
|
+
const packageContribs = contributors.filter((c) => c.packageId === packageId);
|
|
226
|
+
for (const contrib of packageContribs) {
|
|
227
|
+
if (contrib.ownership === "own" && !force) {
|
|
228
|
+
const otherExtenders = contributors.filter(
|
|
229
|
+
(c) => c.packageId !== packageId && c.ownership === "extend"
|
|
230
|
+
);
|
|
231
|
+
if (otherExtenders.length > 0) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Cannot uninstall package "${packageId}": object "${fqn}" is extended by ${otherExtenders.map((c) => c.packageId).join(", ")}. Uninstall extenders first.`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const idx = contributors.indexOf(contrib);
|
|
238
|
+
if (idx !== -1) {
|
|
239
|
+
contributors.splice(idx, 1);
|
|
240
|
+
this.log(`[Registry] Removed ${contrib.ownership} contribution to ${fqn} from ${packageId}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (contributors.length === 0) {
|
|
244
|
+
this.objectContributors.delete(fqn);
|
|
245
|
+
}
|
|
246
|
+
this.mergedObjectCache.delete(fqn);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// ==========================================
|
|
250
|
+
// Generic Metadata (Non-Object Types)
|
|
251
|
+
// ==========================================
|
|
252
|
+
/**
|
|
253
|
+
* Universal Register Method for non-object metadata.
|
|
254
|
+
*/
|
|
255
|
+
static registerItem(type, item, keyField = "name", packageId) {
|
|
13
256
|
if (!this.metadata.has(type)) {
|
|
14
257
|
this.metadata.set(type, /* @__PURE__ */ new Map());
|
|
15
258
|
}
|
|
16
259
|
const collection = this.metadata.get(type);
|
|
17
|
-
const
|
|
260
|
+
const baseName = String(item[keyField]);
|
|
261
|
+
if (packageId) {
|
|
262
|
+
item._packageId = packageId;
|
|
263
|
+
}
|
|
18
264
|
try {
|
|
19
265
|
this.validate(type, item);
|
|
20
266
|
} catch (e) {
|
|
21
|
-
console.error(`[Registry] Validation failed for ${type} ${
|
|
267
|
+
console.error(`[Registry] Validation failed for ${type} ${baseName}: ${e.message}`);
|
|
22
268
|
}
|
|
23
|
-
|
|
24
|
-
|
|
269
|
+
const storageKey = packageId ? `${packageId}:${baseName}` : baseName;
|
|
270
|
+
if (collection.has(storageKey)) {
|
|
271
|
+
console.warn(`[Registry] Overwriting ${type}: ${storageKey}`);
|
|
25
272
|
}
|
|
26
|
-
collection.set(
|
|
27
|
-
|
|
273
|
+
collection.set(storageKey, item);
|
|
274
|
+
this.log(`[Registry] Registered ${type}: ${storageKey}`);
|
|
28
275
|
}
|
|
29
276
|
/**
|
|
30
277
|
* Validate Metadata against Spec Zod Schemas
|
|
@@ -33,9 +280,12 @@ var SchemaRegistry = class {
|
|
|
33
280
|
if (type === "object") {
|
|
34
281
|
return ObjectSchema.parse(item);
|
|
35
282
|
}
|
|
36
|
-
if (type === "
|
|
283
|
+
if (type === "apps") {
|
|
37
284
|
return AppSchema.parse(item);
|
|
38
285
|
}
|
|
286
|
+
if (type === "package") {
|
|
287
|
+
return InstalledPackageSchema.parse(item);
|
|
288
|
+
}
|
|
39
289
|
if (type === "plugin") {
|
|
40
290
|
return ManifestSchema.parse(item);
|
|
41
291
|
}
|
|
@@ -46,66 +296,198 @@ var SchemaRegistry = class {
|
|
|
46
296
|
*/
|
|
47
297
|
static unregisterItem(type, name) {
|
|
48
298
|
const collection = this.metadata.get(type);
|
|
49
|
-
if (collection
|
|
50
|
-
collection.delete(name);
|
|
51
|
-
console.log(`[Registry] Unregistered ${type}: ${name}`);
|
|
52
|
-
} else {
|
|
299
|
+
if (!collection) {
|
|
53
300
|
console.warn(`[Registry] Attempted to unregister non-existent ${type}: ${name}`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (collection.has(name)) {
|
|
304
|
+
collection.delete(name);
|
|
305
|
+
this.log(`[Registry] Unregistered ${type}: ${name}`);
|
|
306
|
+
return;
|
|
54
307
|
}
|
|
308
|
+
for (const key of collection.keys()) {
|
|
309
|
+
if (key.endsWith(`:${name}`)) {
|
|
310
|
+
collection.delete(key);
|
|
311
|
+
this.log(`[Registry] Unregistered ${type}: ${key}`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
console.warn(`[Registry] Attempted to unregister non-existent ${type}: ${name}`);
|
|
55
316
|
}
|
|
56
317
|
/**
|
|
57
318
|
* Universal Get Method
|
|
58
319
|
*/
|
|
59
320
|
static getItem(type, name) {
|
|
60
|
-
|
|
321
|
+
if (type === "object" || type === "objects") {
|
|
322
|
+
return this.getObject(name);
|
|
323
|
+
}
|
|
324
|
+
const collection = this.metadata.get(type);
|
|
325
|
+
if (!collection) return void 0;
|
|
326
|
+
const direct = collection.get(name);
|
|
327
|
+
if (direct) return direct;
|
|
328
|
+
for (const [key, item] of collection) {
|
|
329
|
+
if (key.endsWith(`:${name}`)) return item;
|
|
330
|
+
}
|
|
331
|
+
return void 0;
|
|
61
332
|
}
|
|
62
333
|
/**
|
|
63
334
|
* Universal List Method
|
|
64
335
|
*/
|
|
65
|
-
static listItems(type) {
|
|
66
|
-
|
|
336
|
+
static listItems(type, packageId) {
|
|
337
|
+
if (type === "object" || type === "objects") {
|
|
338
|
+
return this.getAllObjects(packageId);
|
|
339
|
+
}
|
|
340
|
+
const items = Array.from(this.metadata.get(type)?.values() || []);
|
|
341
|
+
if (packageId) {
|
|
342
|
+
return items.filter((item) => item._packageId === packageId);
|
|
343
|
+
}
|
|
344
|
+
return items;
|
|
67
345
|
}
|
|
68
346
|
/**
|
|
69
347
|
* Get all registered metadata types (Kinds)
|
|
70
348
|
*/
|
|
71
349
|
static getRegisteredTypes() {
|
|
72
|
-
|
|
350
|
+
const types = Array.from(this.metadata.keys());
|
|
351
|
+
if (!types.includes("object") && this.objectContributors.size > 0) {
|
|
352
|
+
types.push("object");
|
|
353
|
+
}
|
|
354
|
+
return types;
|
|
73
355
|
}
|
|
74
356
|
// ==========================================
|
|
75
|
-
//
|
|
357
|
+
// Package Management
|
|
76
358
|
// ==========================================
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
359
|
+
static installPackage(manifest, settings) {
|
|
360
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
361
|
+
const pkg = {
|
|
362
|
+
manifest,
|
|
363
|
+
status: "installed",
|
|
364
|
+
enabled: true,
|
|
365
|
+
installedAt: now,
|
|
366
|
+
updatedAt: now,
|
|
367
|
+
settings
|
|
368
|
+
};
|
|
369
|
+
if (manifest.namespace) {
|
|
370
|
+
this.registerNamespace(manifest.namespace, manifest.id);
|
|
371
|
+
}
|
|
372
|
+
if (!this.metadata.has("package")) {
|
|
373
|
+
this.metadata.set("package", /* @__PURE__ */ new Map());
|
|
374
|
+
}
|
|
375
|
+
const collection = this.metadata.get("package");
|
|
376
|
+
if (collection.has(manifest.id)) {
|
|
377
|
+
console.warn(`[Registry] Overwriting package: ${manifest.id}`);
|
|
378
|
+
}
|
|
379
|
+
collection.set(manifest.id, pkg);
|
|
380
|
+
this.log(`[Registry] Installed package: ${manifest.id} (${manifest.name})`);
|
|
381
|
+
return pkg;
|
|
82
382
|
}
|
|
83
|
-
static
|
|
84
|
-
|
|
383
|
+
static uninstallPackage(id) {
|
|
384
|
+
const pkg = this.getPackage(id);
|
|
385
|
+
if (!pkg) {
|
|
386
|
+
console.warn(`[Registry] Package not found for uninstall: ${id}`);
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
if (pkg.manifest.namespace) {
|
|
390
|
+
this.unregisterNamespace(pkg.manifest.namespace, id);
|
|
391
|
+
}
|
|
392
|
+
this.unregisterObjectsByPackage(id);
|
|
393
|
+
const collection = this.metadata.get("package");
|
|
394
|
+
if (collection) {
|
|
395
|
+
collection.delete(id);
|
|
396
|
+
this.log(`[Registry] Uninstalled package: ${id}`);
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
return false;
|
|
85
400
|
}
|
|
86
|
-
static
|
|
87
|
-
return this.
|
|
401
|
+
static getPackage(id) {
|
|
402
|
+
return this.metadata.get("package")?.get(id);
|
|
88
403
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
404
|
+
static getAllPackages() {
|
|
405
|
+
return this.listItems("package");
|
|
406
|
+
}
|
|
407
|
+
static enablePackage(id) {
|
|
408
|
+
const pkg = this.getPackage(id);
|
|
409
|
+
if (pkg) {
|
|
410
|
+
pkg.enabled = true;
|
|
411
|
+
pkg.status = "installed";
|
|
412
|
+
pkg.statusChangedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
413
|
+
pkg.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
414
|
+
this.log(`[Registry] Enabled package: ${id}`);
|
|
415
|
+
}
|
|
416
|
+
return pkg;
|
|
417
|
+
}
|
|
418
|
+
static disablePackage(id) {
|
|
419
|
+
const pkg = this.getPackage(id);
|
|
420
|
+
if (pkg) {
|
|
421
|
+
pkg.enabled = false;
|
|
422
|
+
pkg.status = "disabled";
|
|
423
|
+
pkg.statusChangedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
424
|
+
pkg.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
425
|
+
this.log(`[Registry] Disabled package: ${id}`);
|
|
426
|
+
}
|
|
427
|
+
return pkg;
|
|
428
|
+
}
|
|
429
|
+
// ==========================================
|
|
430
|
+
// App Helpers
|
|
431
|
+
// ==========================================
|
|
432
|
+
static registerApp(app, packageId) {
|
|
433
|
+
this.registerItem("apps", app, "name", packageId);
|
|
434
|
+
}
|
|
435
|
+
static getApp(name) {
|
|
436
|
+
return this.getItem("apps", name);
|
|
437
|
+
}
|
|
438
|
+
static getAllApps() {
|
|
439
|
+
return this.listItems("apps");
|
|
440
|
+
}
|
|
441
|
+
// ==========================================
|
|
442
|
+
// Plugin Helpers
|
|
443
|
+
// ==========================================
|
|
92
444
|
static registerPlugin(manifest) {
|
|
93
445
|
this.registerItem("plugin", manifest, "id");
|
|
94
446
|
}
|
|
95
447
|
static getAllPlugins() {
|
|
96
448
|
return this.listItems("plugin");
|
|
97
449
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
450
|
+
// ==========================================
|
|
451
|
+
// Kind Helpers
|
|
452
|
+
// ==========================================
|
|
101
453
|
static registerKind(kind) {
|
|
102
454
|
this.registerItem("kind", kind, "id");
|
|
103
455
|
}
|
|
104
456
|
static getAllKinds() {
|
|
105
457
|
return this.listItems("kind");
|
|
106
458
|
}
|
|
459
|
+
// ==========================================
|
|
460
|
+
// Reset (for testing)
|
|
461
|
+
// ==========================================
|
|
462
|
+
/**
|
|
463
|
+
* Clear all registry state. Use only for testing.
|
|
464
|
+
*/
|
|
465
|
+
static reset() {
|
|
466
|
+
this.objectContributors.clear();
|
|
467
|
+
this.mergedObjectCache.clear();
|
|
468
|
+
this.namespaceRegistry.clear();
|
|
469
|
+
this.metadata.clear();
|
|
470
|
+
this.log("[Registry] Reset complete");
|
|
471
|
+
}
|
|
107
472
|
};
|
|
108
|
-
//
|
|
473
|
+
// ==========================================
|
|
474
|
+
// Logging control
|
|
475
|
+
// ==========================================
|
|
476
|
+
/** Controls verbosity of registry console messages. Default: 'info'. */
|
|
477
|
+
SchemaRegistry._logLevel = "info";
|
|
478
|
+
// ==========================================
|
|
479
|
+
// Object-specific storage (Ownership Model)
|
|
480
|
+
// ==========================================
|
|
481
|
+
/** FQN → Contributor[] (all packages that own/extend this object) */
|
|
482
|
+
SchemaRegistry.objectContributors = /* @__PURE__ */ new Map();
|
|
483
|
+
/** FQN → Merged ServiceObject (cached, invalidated on changes) */
|
|
484
|
+
SchemaRegistry.mergedObjectCache = /* @__PURE__ */ new Map();
|
|
485
|
+
/** Namespace → PackageId (ensures namespace uniqueness) */
|
|
486
|
+
SchemaRegistry.namespaceRegistry = /* @__PURE__ */ new Map();
|
|
487
|
+
// ==========================================
|
|
488
|
+
// Generic metadata storage (non-object types)
|
|
489
|
+
// ==========================================
|
|
490
|
+
/** Type → Name/ID → MetadataItem */
|
|
109
491
|
SchemaRegistry.metadata = /* @__PURE__ */ new Map();
|
|
110
492
|
|
|
111
493
|
// src/protocol.ts
|
|
@@ -149,7 +531,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
149
531
|
async getMetaItems(request) {
|
|
150
532
|
return {
|
|
151
533
|
type: request.type,
|
|
152
|
-
items: SchemaRegistry.listItems(request.type)
|
|
534
|
+
items: SchemaRegistry.listItems(request.type, request.packageId)
|
|
153
535
|
};
|
|
154
536
|
}
|
|
155
537
|
async getMetaItem(request) {
|
|
@@ -162,31 +544,57 @@ var ObjectStackProtocolImplementation = class {
|
|
|
162
544
|
async getUiView(request) {
|
|
163
545
|
const schema = SchemaRegistry.getObject(request.object);
|
|
164
546
|
if (!schema) throw new Error(`Object ${request.object} not found`);
|
|
165
|
-
|
|
547
|
+
const fields = schema.fields || {};
|
|
548
|
+
const fieldKeys = Object.keys(fields);
|
|
166
549
|
if (request.type === "list") {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
550
|
+
const priorityFields = ["name", "title", "label", "subject", "email", "status", "type", "category", "created_at"];
|
|
551
|
+
let columns = fieldKeys.filter((k) => priorityFields.includes(k));
|
|
552
|
+
if (columns.length < 5) {
|
|
553
|
+
const remaining = fieldKeys.filter((k) => !columns.includes(k) && k !== "id" && !fields[k].hidden);
|
|
554
|
+
columns = [...columns, ...remaining.slice(0, 5 - columns.length)];
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
list: {
|
|
558
|
+
type: "grid",
|
|
559
|
+
object: request.object,
|
|
560
|
+
label: schema.label || schema.name,
|
|
561
|
+
columns: columns.map((f) => ({
|
|
562
|
+
field: f,
|
|
563
|
+
label: fields[f]?.label || f,
|
|
564
|
+
sortable: true
|
|
565
|
+
})),
|
|
566
|
+
sort: fields["created_at"] ? [{ field: "created_at", order: "desc" }] : void 0,
|
|
567
|
+
searchableFields: columns.slice(0, 3)
|
|
568
|
+
// Make first few textual columns searchable
|
|
569
|
+
}
|
|
174
570
|
};
|
|
175
571
|
} else {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
572
|
+
const formFields = fieldKeys.filter((k) => k !== "id" && k !== "created_at" && k !== "modified_at" && !fields[k].hidden).map((f) => ({
|
|
573
|
+
field: f,
|
|
574
|
+
label: fields[f]?.label,
|
|
575
|
+
required: fields[f]?.required,
|
|
576
|
+
readonly: fields[f]?.readonly,
|
|
577
|
+
type: fields[f]?.type,
|
|
578
|
+
// Default to 2 columns for most, 1 for textareas
|
|
579
|
+
colSpan: fields[f]?.type === "textarea" || fields[f]?.type === "html" ? 2 : 1
|
|
580
|
+
}));
|
|
581
|
+
return {
|
|
582
|
+
form: {
|
|
583
|
+
type: "simple",
|
|
584
|
+
object: request.object,
|
|
585
|
+
label: `Edit ${schema.label || schema.name}`,
|
|
586
|
+
sections: [
|
|
587
|
+
{
|
|
588
|
+
label: "General Information",
|
|
589
|
+
columns: 2,
|
|
590
|
+
collapsible: false,
|
|
591
|
+
collapsed: false,
|
|
592
|
+
fields: formFields
|
|
593
|
+
}
|
|
594
|
+
]
|
|
595
|
+
}
|
|
187
596
|
};
|
|
188
597
|
}
|
|
189
|
-
return view;
|
|
190
598
|
}
|
|
191
599
|
async findData(request) {
|
|
192
600
|
const options = { ...request.query };
|
|
@@ -426,23 +834,98 @@ var ObjectQL = class {
|
|
|
426
834
|
}
|
|
427
835
|
/**
|
|
428
836
|
* Register contribution (Manifest)
|
|
837
|
+
*
|
|
838
|
+
* Installs the manifest as a Package (the unit of installation),
|
|
839
|
+
* then decomposes it into individual metadata items (objects, apps, actions, etc.)
|
|
840
|
+
* and registers each into the SchemaRegistry.
|
|
841
|
+
*
|
|
842
|
+
* Key: Package ≠ App. The manifest is the package. The apps[] array inside
|
|
843
|
+
* the manifest contains UI navigation definitions (AppSchema).
|
|
429
844
|
*/
|
|
430
845
|
registerApp(manifest) {
|
|
431
|
-
const id = manifest.id;
|
|
432
|
-
|
|
846
|
+
const id = manifest.id || manifest.name;
|
|
847
|
+
const namespace = manifest.namespace;
|
|
848
|
+
this.logger.debug("Registering package manifest", { id, namespace });
|
|
849
|
+
SchemaRegistry.installPackage(manifest);
|
|
850
|
+
this.logger.debug("Installed Package", { id: manifest.id, name: manifest.name, namespace });
|
|
433
851
|
if (manifest.objects) {
|
|
434
852
|
if (Array.isArray(manifest.objects)) {
|
|
435
853
|
this.logger.debug("Registering objects from manifest (Array)", { id, objectCount: manifest.objects.length });
|
|
436
854
|
for (const objDef of manifest.objects) {
|
|
437
|
-
SchemaRegistry.registerObject(objDef);
|
|
438
|
-
this.logger.debug("Registered Object", {
|
|
855
|
+
const fqn = SchemaRegistry.registerObject(objDef, id, namespace, "own");
|
|
856
|
+
this.logger.debug("Registered Object", { fqn, from: id });
|
|
439
857
|
}
|
|
440
858
|
} else {
|
|
441
859
|
this.logger.debug("Registering objects from manifest (Map)", { id, objectCount: Object.keys(manifest.objects).length });
|
|
442
860
|
for (const [name, objDef] of Object.entries(manifest.objects)) {
|
|
443
861
|
objDef.name = name;
|
|
444
|
-
SchemaRegistry.registerObject(objDef);
|
|
445
|
-
this.logger.debug("Registered Object", {
|
|
862
|
+
const fqn = SchemaRegistry.registerObject(objDef, id, namespace, "own");
|
|
863
|
+
this.logger.debug("Registered Object", { fqn, from: id });
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (Array.isArray(manifest.objectExtensions) && manifest.objectExtensions.length > 0) {
|
|
868
|
+
this.logger.debug("Registering object extensions", { id, count: manifest.objectExtensions.length });
|
|
869
|
+
for (const ext of manifest.objectExtensions) {
|
|
870
|
+
const targetFqn = ext.extend;
|
|
871
|
+
const priority = ext.priority ?? 200;
|
|
872
|
+
const extDef = {
|
|
873
|
+
name: targetFqn,
|
|
874
|
+
// Use the target FQN as name
|
|
875
|
+
fields: ext.fields,
|
|
876
|
+
label: ext.label,
|
|
877
|
+
pluralLabel: ext.pluralLabel,
|
|
878
|
+
description: ext.description,
|
|
879
|
+
validations: ext.validations,
|
|
880
|
+
indexes: ext.indexes
|
|
881
|
+
};
|
|
882
|
+
SchemaRegistry.registerObject(extDef, id, void 0, "extend", priority);
|
|
883
|
+
this.logger.debug("Registered Object Extension", { target: targetFqn, priority, from: id });
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (Array.isArray(manifest.apps) && manifest.apps.length > 0) {
|
|
887
|
+
this.logger.debug("Registering apps from manifest", { id, count: manifest.apps.length });
|
|
888
|
+
for (const app of manifest.apps) {
|
|
889
|
+
const appName = app.name || app.id;
|
|
890
|
+
if (appName) {
|
|
891
|
+
SchemaRegistry.registerApp(app, id);
|
|
892
|
+
this.logger.debug("Registered App", { app: appName, from: id });
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (manifest.name && manifest.navigation && !manifest.apps?.length) {
|
|
897
|
+
SchemaRegistry.registerApp(manifest, id);
|
|
898
|
+
this.logger.debug("Registered manifest-as-app", { app: manifest.name, from: id });
|
|
899
|
+
}
|
|
900
|
+
const metadataArrayKeys = [
|
|
901
|
+
"actions",
|
|
902
|
+
"dashboards",
|
|
903
|
+
"reports",
|
|
904
|
+
"flows",
|
|
905
|
+
"agents",
|
|
906
|
+
"apis",
|
|
907
|
+
"ragPipelines",
|
|
908
|
+
"profiles",
|
|
909
|
+
"sharingRules"
|
|
910
|
+
];
|
|
911
|
+
for (const key of metadataArrayKeys) {
|
|
912
|
+
const items = manifest[key];
|
|
913
|
+
if (Array.isArray(items) && items.length > 0) {
|
|
914
|
+
this.logger.debug(`Registering ${key} from manifest`, { id, count: items.length });
|
|
915
|
+
for (const item of items) {
|
|
916
|
+
const itemName = item.name || item.id;
|
|
917
|
+
if (itemName) {
|
|
918
|
+
SchemaRegistry.registerItem(key, item, "name", id);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const seedData = manifest.data;
|
|
924
|
+
if (Array.isArray(seedData) && seedData.length > 0) {
|
|
925
|
+
this.logger.debug("Registering seed data datasets", { id, count: seedData.length });
|
|
926
|
+
for (const dataset of seedData) {
|
|
927
|
+
if (dataset.object) {
|
|
928
|
+
SchemaRegistry.registerItem("data", dataset, "object", id);
|
|
446
929
|
}
|
|
447
930
|
}
|
|
448
931
|
}
|
|
@@ -478,6 +961,23 @@ var ObjectQL = class {
|
|
|
478
961
|
getSchema(objectName) {
|
|
479
962
|
return SchemaRegistry.getObject(objectName);
|
|
480
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* Resolve an object name to its Fully Qualified Name (FQN).
|
|
966
|
+
*
|
|
967
|
+
* Short names like 'task' are resolved to FQN like 'todo__task'
|
|
968
|
+
* via SchemaRegistry lookup. If no match is found, the name is
|
|
969
|
+
* returned as-is (for ad-hoc / unregistered objects).
|
|
970
|
+
*
|
|
971
|
+
* This ensures that all driver operations use a consistent key
|
|
972
|
+
* regardless of whether the caller uses the short name or FQN.
|
|
973
|
+
*/
|
|
974
|
+
resolveObjectName(name) {
|
|
975
|
+
const schema = SchemaRegistry.getObject(name);
|
|
976
|
+
if (schema) {
|
|
977
|
+
return schema.name;
|
|
978
|
+
}
|
|
979
|
+
return name;
|
|
980
|
+
}
|
|
481
981
|
/**
|
|
482
982
|
* Helper to get the target driver
|
|
483
983
|
*/
|
|
@@ -561,6 +1061,7 @@ var ObjectQL = class {
|
|
|
561
1061
|
// Data Access Methods (IDataEngine Interface)
|
|
562
1062
|
// ============================================
|
|
563
1063
|
async find(object, query) {
|
|
1064
|
+
object = this.resolveObjectName(object);
|
|
564
1065
|
this.logger.debug("Find operation starting", { object, query });
|
|
565
1066
|
const driver = this.getDriver(object);
|
|
566
1067
|
const ast = this.toQueryAST(object, query);
|
|
@@ -584,6 +1085,7 @@ var ObjectQL = class {
|
|
|
584
1085
|
}
|
|
585
1086
|
}
|
|
586
1087
|
async findOne(objectName, query) {
|
|
1088
|
+
objectName = this.resolveObjectName(objectName);
|
|
587
1089
|
this.logger.debug("FindOne operation", { objectName });
|
|
588
1090
|
const driver = this.getDriver(objectName);
|
|
589
1091
|
const ast = this.toQueryAST(objectName, query);
|
|
@@ -591,6 +1093,7 @@ var ObjectQL = class {
|
|
|
591
1093
|
return driver.findOne(objectName, ast);
|
|
592
1094
|
}
|
|
593
1095
|
async insert(object, data, options) {
|
|
1096
|
+
object = this.resolveObjectName(object);
|
|
594
1097
|
this.logger.debug("Insert operation starting", { object, isBatch: Array.isArray(data) });
|
|
595
1098
|
const driver = this.getDriver(object);
|
|
596
1099
|
const hookContext = {
|
|
@@ -621,6 +1124,7 @@ var ObjectQL = class {
|
|
|
621
1124
|
}
|
|
622
1125
|
}
|
|
623
1126
|
async update(object, data, options) {
|
|
1127
|
+
object = this.resolveObjectName(object);
|
|
624
1128
|
this.logger.debug("Update operation starting", { object });
|
|
625
1129
|
const driver = this.getDriver(object);
|
|
626
1130
|
let id = data.id || data._id;
|
|
@@ -656,6 +1160,7 @@ var ObjectQL = class {
|
|
|
656
1160
|
}
|
|
657
1161
|
}
|
|
658
1162
|
async delete(object, options) {
|
|
1163
|
+
object = this.resolveObjectName(object);
|
|
659
1164
|
this.logger.debug("Delete operation starting", { object });
|
|
660
1165
|
const driver = this.getDriver(object);
|
|
661
1166
|
let id = void 0;
|
|
@@ -691,6 +1196,7 @@ var ObjectQL = class {
|
|
|
691
1196
|
}
|
|
692
1197
|
}
|
|
693
1198
|
async count(object, query) {
|
|
1199
|
+
object = this.resolveObjectName(object);
|
|
694
1200
|
const driver = this.getDriver(object);
|
|
695
1201
|
if (driver.count) {
|
|
696
1202
|
const ast = this.toQueryAST(object, { filter: query?.filter });
|
|
@@ -700,6 +1206,7 @@ var ObjectQL = class {
|
|
|
700
1206
|
return res.length;
|
|
701
1207
|
}
|
|
702
1208
|
async aggregate(object, query) {
|
|
1209
|
+
object = this.resolveObjectName(object);
|
|
703
1210
|
const driver = this.getDriver(object);
|
|
704
1211
|
this.logger.debug(`Aggregate on ${object} using ${driver.name}`, query);
|
|
705
1212
|
throw new Error("Aggregate not yet fully implemented in ObjectQL->Driver mapping");
|
|
@@ -723,7 +1230,8 @@ var ObjectQLPlugin = class {
|
|
|
723
1230
|
this.version = "1.0.0";
|
|
724
1231
|
this.init = async (ctx) => {
|
|
725
1232
|
if (!this.ql) {
|
|
726
|
-
|
|
1233
|
+
const hostCtx = { ...this.hostContext, logger: ctx.logger };
|
|
1234
|
+
this.ql = new ObjectQL(hostCtx);
|
|
727
1235
|
}
|
|
728
1236
|
ctx.registerService("objectql", this.ql);
|
|
729
1237
|
let hasMetadata = false;
|
|
@@ -775,9 +1283,14 @@ var ObjectQLPlugin = class {
|
|
|
775
1283
|
}
|
|
776
1284
|
};
|
|
777
1285
|
export {
|
|
1286
|
+
DEFAULT_EXTENDER_PRIORITY,
|
|
1287
|
+
DEFAULT_OWNER_PRIORITY,
|
|
778
1288
|
ObjectQL,
|
|
779
1289
|
ObjectQLPlugin,
|
|
780
1290
|
ObjectStackProtocolImplementation,
|
|
781
|
-
|
|
1291
|
+
RESERVED_NAMESPACES,
|
|
1292
|
+
SchemaRegistry,
|
|
1293
|
+
computeFQN,
|
|
1294
|
+
parseFQN
|
|
782
1295
|
};
|
|
783
1296
|
//# sourceMappingURL=index.mjs.map
|