@objectstack/metadata 4.0.3 → 4.0.5
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/README.md +30 -10
- package/dist/index.cjs +770 -509
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +173 -6151
- package/dist/index.d.ts +173 -6151
- package/dist/index.js +773 -507
- package/dist/index.js.map +1 -1
- package/dist/migrations/migrate-env-id-to-project-id.cjs +84 -0
- package/dist/migrations/migrate-env-id-to-project-id.cjs.map +1 -0
- package/dist/migrations/migrate-env-id-to-project-id.d.cts +37 -0
- package/dist/migrations/migrate-env-id-to-project-id.d.ts +37 -0
- package/dist/migrations/migrate-env-id-to-project-id.js +59 -0
- package/dist/migrations/migrate-env-id-to-project-id.js.map +1 -0
- package/dist/node.cjs +770 -509
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +2 -3
- package/dist/node.d.ts +2 -3
- package/dist/node.js +773 -507
- package/dist/node.js.map +1 -1
- package/package.json +28 -8
package/dist/index.cjs
CHANGED
|
@@ -36,10 +36,11 @@ __export(index_exports, {
|
|
|
36
36
|
MemoryLoader: () => MemoryLoader,
|
|
37
37
|
MetadataManager: () => MetadataManager,
|
|
38
38
|
MetadataPlugin: () => MetadataPlugin,
|
|
39
|
+
MetadataProjector: () => MetadataProjector,
|
|
39
40
|
Migration: () => migration_exports,
|
|
40
41
|
RemoteLoader: () => RemoteLoader,
|
|
41
|
-
SysMetadataHistoryObject: () => SysMetadataHistoryObject,
|
|
42
|
-
SysMetadataObject: () => SysMetadataObject,
|
|
42
|
+
SysMetadataHistoryObject: () => import_metadata3.SysMetadataHistoryObject,
|
|
43
|
+
SysMetadataObject: () => import_metadata3.SysMetadataObject,
|
|
43
44
|
TypeScriptSerializer: () => TypeScriptSerializer,
|
|
44
45
|
YAMLSerializer: () => YAMLSerializer,
|
|
45
46
|
calculateChecksum: () => calculateChecksum,
|
|
@@ -219,277 +220,8 @@ export default metadata;
|
|
|
219
220
|
}
|
|
220
221
|
};
|
|
221
222
|
|
|
222
|
-
// src/
|
|
223
|
-
var
|
|
224
|
-
var SysMetadataObject = import_data.ObjectSchema.create({
|
|
225
|
-
namespace: "sys",
|
|
226
|
-
name: "metadata",
|
|
227
|
-
label: "System Metadata",
|
|
228
|
-
pluralLabel: "System Metadata",
|
|
229
|
-
icon: "settings",
|
|
230
|
-
isSystem: true,
|
|
231
|
-
description: "Stores platform and user-scope metadata records (objects, views, flows, etc.)",
|
|
232
|
-
fields: {
|
|
233
|
-
/** Primary Key (UUID) */
|
|
234
|
-
id: import_data.Field.text({
|
|
235
|
-
label: "ID",
|
|
236
|
-
required: true,
|
|
237
|
-
readonly: true
|
|
238
|
-
}),
|
|
239
|
-
/** Machine name — unique identifier used in code references */
|
|
240
|
-
name: import_data.Field.text({
|
|
241
|
-
label: "Name",
|
|
242
|
-
required: true,
|
|
243
|
-
searchable: true,
|
|
244
|
-
maxLength: 255
|
|
245
|
-
}),
|
|
246
|
-
/** Metadata type (e.g. "object", "view", "flow") */
|
|
247
|
-
type: import_data.Field.text({
|
|
248
|
-
label: "Metadata Type",
|
|
249
|
-
required: true,
|
|
250
|
-
searchable: true,
|
|
251
|
-
maxLength: 100
|
|
252
|
-
}),
|
|
253
|
-
/** Namespace / module grouping (e.g. "crm", "core") */
|
|
254
|
-
namespace: import_data.Field.text({
|
|
255
|
-
label: "Namespace",
|
|
256
|
-
required: false,
|
|
257
|
-
defaultValue: "default",
|
|
258
|
-
maxLength: 100
|
|
259
|
-
}),
|
|
260
|
-
/** Package that owns/delivered this metadata */
|
|
261
|
-
package_id: import_data.Field.text({
|
|
262
|
-
label: "Package ID",
|
|
263
|
-
required: false,
|
|
264
|
-
maxLength: 255
|
|
265
|
-
}),
|
|
266
|
-
/** Who manages this record: package, platform, or user */
|
|
267
|
-
managed_by: import_data.Field.select(["package", "platform", "user"], {
|
|
268
|
-
label: "Managed By",
|
|
269
|
-
required: false
|
|
270
|
-
}),
|
|
271
|
-
/** Scope: system (code), platform (admin DB), user (personal DB) */
|
|
272
|
-
scope: import_data.Field.select(["system", "platform", "user"], {
|
|
273
|
-
label: "Scope",
|
|
274
|
-
required: true,
|
|
275
|
-
defaultValue: "platform"
|
|
276
|
-
}),
|
|
277
|
-
/** JSON payload — the actual metadata configuration */
|
|
278
|
-
metadata: import_data.Field.textarea({
|
|
279
|
-
label: "Metadata",
|
|
280
|
-
required: true,
|
|
281
|
-
description: "JSON-serialized metadata payload"
|
|
282
|
-
}),
|
|
283
|
-
/** Parent metadata name for extension/override */
|
|
284
|
-
extends: import_data.Field.text({
|
|
285
|
-
label: "Extends",
|
|
286
|
-
required: false,
|
|
287
|
-
maxLength: 255
|
|
288
|
-
}),
|
|
289
|
-
/** Merge strategy when extending parent metadata */
|
|
290
|
-
strategy: import_data.Field.select(["merge", "replace"], {
|
|
291
|
-
label: "Strategy",
|
|
292
|
-
required: false,
|
|
293
|
-
defaultValue: "merge"
|
|
294
|
-
}),
|
|
295
|
-
/** Owner user ID (for user-scope items) */
|
|
296
|
-
owner: import_data.Field.text({
|
|
297
|
-
label: "Owner",
|
|
298
|
-
required: false,
|
|
299
|
-
maxLength: 255
|
|
300
|
-
}),
|
|
301
|
-
/** Lifecycle state */
|
|
302
|
-
state: import_data.Field.select(["draft", "active", "archived", "deprecated"], {
|
|
303
|
-
label: "State",
|
|
304
|
-
required: false,
|
|
305
|
-
defaultValue: "active"
|
|
306
|
-
}),
|
|
307
|
-
/** Tenant ID for multi-tenant isolation */
|
|
308
|
-
tenant_id: import_data.Field.text({
|
|
309
|
-
label: "Tenant ID",
|
|
310
|
-
required: false,
|
|
311
|
-
maxLength: 255
|
|
312
|
-
}),
|
|
313
|
-
/** Version number for optimistic concurrency */
|
|
314
|
-
version: import_data.Field.number({
|
|
315
|
-
label: "Version",
|
|
316
|
-
required: false,
|
|
317
|
-
defaultValue: 1
|
|
318
|
-
}),
|
|
319
|
-
/** Content checksum for change detection */
|
|
320
|
-
checksum: import_data.Field.text({
|
|
321
|
-
label: "Checksum",
|
|
322
|
-
required: false,
|
|
323
|
-
maxLength: 64
|
|
324
|
-
}),
|
|
325
|
-
/** Origin of this metadata record */
|
|
326
|
-
source: import_data.Field.select(["filesystem", "database", "api", "migration"], {
|
|
327
|
-
label: "Source",
|
|
328
|
-
required: false
|
|
329
|
-
}),
|
|
330
|
-
/** Classification tags (JSON array) */
|
|
331
|
-
tags: import_data.Field.textarea({
|
|
332
|
-
label: "Tags",
|
|
333
|
-
required: false,
|
|
334
|
-
description: "JSON-serialized array of classification tags"
|
|
335
|
-
}),
|
|
336
|
-
/** Audit fields */
|
|
337
|
-
created_by: import_data.Field.text({
|
|
338
|
-
label: "Created By",
|
|
339
|
-
required: false,
|
|
340
|
-
readonly: true,
|
|
341
|
-
maxLength: 255
|
|
342
|
-
}),
|
|
343
|
-
created_at: import_data.Field.datetime({
|
|
344
|
-
label: "Created At",
|
|
345
|
-
required: false,
|
|
346
|
-
readonly: true
|
|
347
|
-
}),
|
|
348
|
-
updated_by: import_data.Field.text({
|
|
349
|
-
label: "Updated By",
|
|
350
|
-
required: false,
|
|
351
|
-
maxLength: 255
|
|
352
|
-
}),
|
|
353
|
-
updated_at: import_data.Field.datetime({
|
|
354
|
-
label: "Updated At",
|
|
355
|
-
required: false
|
|
356
|
-
})
|
|
357
|
-
},
|
|
358
|
-
indexes: [
|
|
359
|
-
{ fields: ["type", "name"], unique: true },
|
|
360
|
-
{ fields: ["type", "scope"] },
|
|
361
|
-
{ fields: ["tenant_id"] },
|
|
362
|
-
{ fields: ["state"] },
|
|
363
|
-
{ fields: ["namespace"] }
|
|
364
|
-
],
|
|
365
|
-
enable: {
|
|
366
|
-
trackHistory: true,
|
|
367
|
-
searchable: false,
|
|
368
|
-
apiEnabled: true,
|
|
369
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
370
|
-
trash: false
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
// src/objects/sys-metadata-history.object.ts
|
|
375
|
-
var import_data2 = require("@objectstack/spec/data");
|
|
376
|
-
var SysMetadataHistoryObject = import_data2.ObjectSchema.create({
|
|
377
|
-
namespace: "sys",
|
|
378
|
-
name: "metadata_history",
|
|
379
|
-
label: "Metadata History",
|
|
380
|
-
pluralLabel: "Metadata History",
|
|
381
|
-
icon: "history",
|
|
382
|
-
isSystem: true,
|
|
383
|
-
description: "Version history and audit trail for metadata changes",
|
|
384
|
-
fields: {
|
|
385
|
-
/** Primary Key (UUID) */
|
|
386
|
-
id: import_data2.Field.text({
|
|
387
|
-
label: "ID",
|
|
388
|
-
required: true,
|
|
389
|
-
readonly: true
|
|
390
|
-
}),
|
|
391
|
-
/** Foreign key to sys_metadata.id */
|
|
392
|
-
metadata_id: import_data2.Field.text({
|
|
393
|
-
label: "Metadata ID",
|
|
394
|
-
required: true,
|
|
395
|
-
readonly: true,
|
|
396
|
-
maxLength: 255
|
|
397
|
-
}),
|
|
398
|
-
/** Machine name (denormalized for easier querying) */
|
|
399
|
-
name: import_data2.Field.text({
|
|
400
|
-
label: "Name",
|
|
401
|
-
required: true,
|
|
402
|
-
searchable: true,
|
|
403
|
-
readonly: true,
|
|
404
|
-
maxLength: 255
|
|
405
|
-
}),
|
|
406
|
-
/** Metadata type (denormalized for easier querying) */
|
|
407
|
-
type: import_data2.Field.text({
|
|
408
|
-
label: "Metadata Type",
|
|
409
|
-
required: true,
|
|
410
|
-
searchable: true,
|
|
411
|
-
readonly: true,
|
|
412
|
-
maxLength: 100
|
|
413
|
-
}),
|
|
414
|
-
/** Version number at this snapshot */
|
|
415
|
-
version: import_data2.Field.number({
|
|
416
|
-
label: "Version",
|
|
417
|
-
required: true,
|
|
418
|
-
readonly: true
|
|
419
|
-
}),
|
|
420
|
-
/** Type of operation that created this history entry */
|
|
421
|
-
operation_type: import_data2.Field.select(["create", "update", "publish", "revert", "delete"], {
|
|
422
|
-
label: "Operation Type",
|
|
423
|
-
required: true,
|
|
424
|
-
readonly: true
|
|
425
|
-
}),
|
|
426
|
-
/** Historical metadata snapshot (JSON payload) */
|
|
427
|
-
metadata: import_data2.Field.textarea({
|
|
428
|
-
label: "Metadata",
|
|
429
|
-
required: true,
|
|
430
|
-
readonly: true,
|
|
431
|
-
description: "JSON-serialized metadata snapshot at this version"
|
|
432
|
-
}),
|
|
433
|
-
/** SHA-256 checksum of metadata content */
|
|
434
|
-
checksum: import_data2.Field.text({
|
|
435
|
-
label: "Checksum",
|
|
436
|
-
required: true,
|
|
437
|
-
readonly: true,
|
|
438
|
-
maxLength: 64
|
|
439
|
-
}),
|
|
440
|
-
/** Checksum of the previous version */
|
|
441
|
-
previous_checksum: import_data2.Field.text({
|
|
442
|
-
label: "Previous Checksum",
|
|
443
|
-
required: false,
|
|
444
|
-
readonly: true,
|
|
445
|
-
maxLength: 64
|
|
446
|
-
}),
|
|
447
|
-
/** Human-readable description of changes */
|
|
448
|
-
change_note: import_data2.Field.textarea({
|
|
449
|
-
label: "Change Note",
|
|
450
|
-
required: false,
|
|
451
|
-
readonly: true,
|
|
452
|
-
description: "Description of what changed in this version"
|
|
453
|
-
}),
|
|
454
|
-
/** Tenant ID for multi-tenant isolation */
|
|
455
|
-
tenant_id: import_data2.Field.text({
|
|
456
|
-
label: "Tenant ID",
|
|
457
|
-
required: false,
|
|
458
|
-
readonly: true,
|
|
459
|
-
maxLength: 255
|
|
460
|
-
}),
|
|
461
|
-
/** User who made this change */
|
|
462
|
-
recorded_by: import_data2.Field.text({
|
|
463
|
-
label: "Recorded By",
|
|
464
|
-
required: false,
|
|
465
|
-
readonly: true,
|
|
466
|
-
maxLength: 255
|
|
467
|
-
}),
|
|
468
|
-
/** When was this version recorded */
|
|
469
|
-
recorded_at: import_data2.Field.datetime({
|
|
470
|
-
label: "Recorded At",
|
|
471
|
-
required: true,
|
|
472
|
-
readonly: true
|
|
473
|
-
})
|
|
474
|
-
},
|
|
475
|
-
indexes: [
|
|
476
|
-
{ fields: ["metadata_id", "version"], unique: true },
|
|
477
|
-
{ fields: ["metadata_id", "recorded_at"] },
|
|
478
|
-
{ fields: ["type", "name"] },
|
|
479
|
-
{ fields: ["recorded_at"] },
|
|
480
|
-
{ fields: ["operation_type"] },
|
|
481
|
-
{ fields: ["tenant_id"] }
|
|
482
|
-
],
|
|
483
|
-
enable: {
|
|
484
|
-
trackHistory: false,
|
|
485
|
-
// Don't track history of history records
|
|
486
|
-
searchable: false,
|
|
487
|
-
apiEnabled: true,
|
|
488
|
-
apiMethods: ["get", "list"],
|
|
489
|
-
// Read-only via API
|
|
490
|
-
trash: false
|
|
491
|
-
}
|
|
492
|
-
});
|
|
223
|
+
// src/loaders/database-loader.ts
|
|
224
|
+
var import_metadata = require("@objectstack/platform-objects/metadata");
|
|
493
225
|
|
|
494
226
|
// src/utils/metadata-history-utils.ts
|
|
495
227
|
async function calculateChecksum(metadata) {
|
|
@@ -589,6 +321,308 @@ function generateDiffSummary(diff) {
|
|
|
589
321
|
return summary.join(", ");
|
|
590
322
|
}
|
|
591
323
|
|
|
324
|
+
// src/projection/metadata-projector.ts
|
|
325
|
+
var import_system = require("@objectstack/spec/system");
|
|
326
|
+
var MetadataProjector = class {
|
|
327
|
+
constructor(options) {
|
|
328
|
+
// Map of metadata types to their target table names
|
|
329
|
+
this.typeTableMap = {
|
|
330
|
+
object: "sys_object",
|
|
331
|
+
view: "sys_view",
|
|
332
|
+
agent: "sys_agent",
|
|
333
|
+
tool: "sys_tool",
|
|
334
|
+
flow: "sys_flow"
|
|
335
|
+
// Add more as needed: dashboard, app, action, workflow, etc.
|
|
336
|
+
};
|
|
337
|
+
if (!options.driver && !options.engine) {
|
|
338
|
+
throw new Error("MetadataProjector requires either a driver or engine");
|
|
339
|
+
}
|
|
340
|
+
this.driver = options.driver;
|
|
341
|
+
this.engine = options.engine;
|
|
342
|
+
this.scope = {
|
|
343
|
+
organizationId: options.organizationId,
|
|
344
|
+
projectId: options.projectId
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Project metadata to type-specific table
|
|
349
|
+
*/
|
|
350
|
+
async project(type, name, data) {
|
|
351
|
+
const targetTable = this.typeTableMap[type];
|
|
352
|
+
if (!targetTable) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const projectedData = this.transformToProjection(type, name, data);
|
|
356
|
+
if (!projectedData) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
const projId = this.scope.projectId ?? null;
|
|
361
|
+
const existing = await this._findOne(targetTable, {
|
|
362
|
+
where: { name, project_id: projId }
|
|
363
|
+
});
|
|
364
|
+
if (existing) {
|
|
365
|
+
await this._update(targetTable, existing.id, projectedData);
|
|
366
|
+
} else {
|
|
367
|
+
const id = this.generateId();
|
|
368
|
+
await this._create(targetTable, {
|
|
369
|
+
id,
|
|
370
|
+
...projectedData
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error(`Failed to project ${type}/${name} to ${targetTable}:`, error);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Delete projection from type-specific table
|
|
379
|
+
*/
|
|
380
|
+
async deleteProjection(type, name) {
|
|
381
|
+
const targetTable = this.typeTableMap[type];
|
|
382
|
+
if (!targetTable) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const projId = this.scope.projectId ?? null;
|
|
387
|
+
const existing = await this._findOne(targetTable, {
|
|
388
|
+
where: { name, project_id: projId }
|
|
389
|
+
});
|
|
390
|
+
if (existing) {
|
|
391
|
+
await this._delete(targetTable, existing.id);
|
|
392
|
+
}
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error(`Failed to delete projection ${type}/${name} from ${targetTable}:`, error);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Transform metadata into projection record
|
|
399
|
+
*/
|
|
400
|
+
transformToProjection(type, name, data) {
|
|
401
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
402
|
+
switch (type) {
|
|
403
|
+
case "object":
|
|
404
|
+
return this.projectObject(name, data, now);
|
|
405
|
+
case "view":
|
|
406
|
+
return this.projectView(name, data, now);
|
|
407
|
+
case "agent":
|
|
408
|
+
return this.projectAgent(name, data, now);
|
|
409
|
+
case "tool":
|
|
410
|
+
return this.projectTool(name, data, now);
|
|
411
|
+
case "flow":
|
|
412
|
+
return this.projectFlow(name, data, now);
|
|
413
|
+
default:
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Project object metadata to sys_object
|
|
419
|
+
*/
|
|
420
|
+
projectObject(name, data, now) {
|
|
421
|
+
return {
|
|
422
|
+
name,
|
|
423
|
+
project_id: this.scope.projectId ?? null,
|
|
424
|
+
label: data.label || name,
|
|
425
|
+
plural_label: data.pluralLabel || data.label || name,
|
|
426
|
+
description: data.description || "",
|
|
427
|
+
icon: data.icon || "database",
|
|
428
|
+
namespace: data.namespace || "default",
|
|
429
|
+
tags: Array.isArray(data.tags) ? data.tags.join(",") : data.tags || "",
|
|
430
|
+
active: data.active !== false,
|
|
431
|
+
is_system: data.isSystem || false,
|
|
432
|
+
abstract: data.abstract || false,
|
|
433
|
+
datasource: data.datasource || "default",
|
|
434
|
+
table_name: data.name ? import_system.StorageNameMapping.resolveTableName({ name: data.name }) : name,
|
|
435
|
+
// Serialize complex structures as JSON
|
|
436
|
+
fields_json: data.fields ? JSON.stringify(data.fields) : null,
|
|
437
|
+
indexes_json: data.indexes ? JSON.stringify(data.indexes) : null,
|
|
438
|
+
validations_json: data.validations ? JSON.stringify(data.validations) : null,
|
|
439
|
+
state_machines_json: data.stateMachines ? JSON.stringify(data.stateMachines) : null,
|
|
440
|
+
capabilities_json: data.enable ? JSON.stringify(data.enable) : null,
|
|
441
|
+
// Denormalized fields
|
|
442
|
+
field_count: data.fields ? Object.keys(data.fields).length : 0,
|
|
443
|
+
display_name_field: data.displayNameField || null,
|
|
444
|
+
title_format: data.titleFormat || null,
|
|
445
|
+
compact_layout: Array.isArray(data.compactLayout) ? data.compactLayout.join(",") : data.compactLayout || null,
|
|
446
|
+
// Capabilities (denormalized for easier querying)
|
|
447
|
+
track_history: data.enable?.trackHistory || false,
|
|
448
|
+
searchable: data.enable?.searchable !== false,
|
|
449
|
+
api_enabled: data.enable?.apiEnabled !== false,
|
|
450
|
+
files: data.enable?.files || false,
|
|
451
|
+
feeds: data.enable?.feeds || false,
|
|
452
|
+
activities: data.enable?.activities || false,
|
|
453
|
+
trash: data.enable?.trash !== false,
|
|
454
|
+
mru: data.enable?.mru !== false,
|
|
455
|
+
clone: data.enable?.clone !== false,
|
|
456
|
+
// Package management
|
|
457
|
+
package_id: data.packageId || null,
|
|
458
|
+
managed_by: data.managedBy || "user",
|
|
459
|
+
// Audit
|
|
460
|
+
created_by: data.createdBy || null,
|
|
461
|
+
created_at: data.createdAt || now,
|
|
462
|
+
updated_by: data.updatedBy || null,
|
|
463
|
+
updated_at: now
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Project view metadata to sys_view
|
|
468
|
+
*/
|
|
469
|
+
projectView(name, data, now) {
|
|
470
|
+
return {
|
|
471
|
+
name,
|
|
472
|
+
project_id: this.scope.projectId ?? null,
|
|
473
|
+
label: data.label || name,
|
|
474
|
+
description: data.description || "",
|
|
475
|
+
object_name: data.object || "",
|
|
476
|
+
view_type: data.type || "grid",
|
|
477
|
+
// Serialize configurations as JSON
|
|
478
|
+
columns_json: data.columns ? JSON.stringify(data.columns) : null,
|
|
479
|
+
filters_json: data.filters ? JSON.stringify(data.filters) : null,
|
|
480
|
+
sort_json: data.sort ? JSON.stringify(data.sort) : null,
|
|
481
|
+
config_json: data.config ? JSON.stringify(data.config) : null,
|
|
482
|
+
// Display options
|
|
483
|
+
page_size: data.pageSize || 25,
|
|
484
|
+
show_search: data.showSearch !== false,
|
|
485
|
+
show_filters: data.showFilters !== false,
|
|
486
|
+
// Classification
|
|
487
|
+
namespace: data.namespace || "default",
|
|
488
|
+
// Package management
|
|
489
|
+
package_id: data.packageId || null,
|
|
490
|
+
managed_by: data.managedBy || "user",
|
|
491
|
+
// Audit
|
|
492
|
+
created_by: data.createdBy || null,
|
|
493
|
+
created_at: data.createdAt || now,
|
|
494
|
+
updated_by: data.updatedBy || null,
|
|
495
|
+
updated_at: now
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Project agent metadata to sys_agent
|
|
500
|
+
*/
|
|
501
|
+
projectAgent(name, data, now) {
|
|
502
|
+
return {
|
|
503
|
+
name,
|
|
504
|
+
project_id: this.scope.projectId ?? null,
|
|
505
|
+
label: data.label || name,
|
|
506
|
+
description: data.description || "",
|
|
507
|
+
agent_type: data.type || "conversational",
|
|
508
|
+
// Model configuration
|
|
509
|
+
model: data.model || null,
|
|
510
|
+
temperature: data.temperature ?? 0.7,
|
|
511
|
+
max_tokens: data.maxTokens || null,
|
|
512
|
+
top_p: data.topP || null,
|
|
513
|
+
// System prompt
|
|
514
|
+
system_prompt: data.systemPrompt || null,
|
|
515
|
+
// Tools and skills as JSON
|
|
516
|
+
tools_json: data.tools ? JSON.stringify(data.tools) : null,
|
|
517
|
+
skills_json: data.skills ? JSON.stringify(data.skills) : null,
|
|
518
|
+
// Memory
|
|
519
|
+
memory_enabled: data.memoryEnabled || false,
|
|
520
|
+
memory_window: data.memoryWindow || 10,
|
|
521
|
+
// Classification
|
|
522
|
+
namespace: data.namespace || "default",
|
|
523
|
+
// Package management
|
|
524
|
+
package_id: data.packageId || null,
|
|
525
|
+
managed_by: data.managedBy || "user",
|
|
526
|
+
// Audit
|
|
527
|
+
created_by: data.createdBy || null,
|
|
528
|
+
created_at: data.createdAt || now,
|
|
529
|
+
updated_by: data.updatedBy || null,
|
|
530
|
+
updated_at: now
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Project tool metadata to sys_tool
|
|
535
|
+
*/
|
|
536
|
+
projectTool(name, data, now) {
|
|
537
|
+
return {
|
|
538
|
+
name,
|
|
539
|
+
project_id: this.scope.projectId ?? null,
|
|
540
|
+
label: data.label || name,
|
|
541
|
+
description: data.description || "",
|
|
542
|
+
// Parameters and implementation
|
|
543
|
+
parameters_json: data.parameters ? JSON.stringify(data.parameters) : null,
|
|
544
|
+
handler_code: data.handler || null,
|
|
545
|
+
// Classification
|
|
546
|
+
namespace: data.namespace || "default",
|
|
547
|
+
// Package management
|
|
548
|
+
package_id: data.packageId || null,
|
|
549
|
+
managed_by: data.managedBy || "user",
|
|
550
|
+
// Audit
|
|
551
|
+
created_by: data.createdBy || null,
|
|
552
|
+
created_at: data.createdAt || now,
|
|
553
|
+
updated_by: data.updatedBy || null,
|
|
554
|
+
updated_at: now
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Project flow metadata to sys_flow
|
|
559
|
+
*/
|
|
560
|
+
projectFlow(name, data, now) {
|
|
561
|
+
return {
|
|
562
|
+
name,
|
|
563
|
+
project_id: this.scope.projectId ?? null,
|
|
564
|
+
label: data.label || name,
|
|
565
|
+
description: data.description || "",
|
|
566
|
+
flow_type: data.type || "autolaunched",
|
|
567
|
+
// Flow definition
|
|
568
|
+
nodes_json: data.nodes ? JSON.stringify(data.nodes) : null,
|
|
569
|
+
edges_json: data.edges ? JSON.stringify(data.edges) : null,
|
|
570
|
+
variables_json: data.variables ? JSON.stringify(data.variables) : null,
|
|
571
|
+
// Trigger configuration
|
|
572
|
+
trigger_type: data.triggerType || null,
|
|
573
|
+
trigger_object: data.triggerObject || null,
|
|
574
|
+
// Status
|
|
575
|
+
active: data.active || false,
|
|
576
|
+
// Classification
|
|
577
|
+
namespace: data.namespace || "default",
|
|
578
|
+
// Package management
|
|
579
|
+
package_id: data.packageId || null,
|
|
580
|
+
managed_by: data.managedBy || "user",
|
|
581
|
+
// Audit
|
|
582
|
+
created_by: data.createdBy || null,
|
|
583
|
+
created_at: data.createdAt || now,
|
|
584
|
+
updated_by: data.updatedBy || null,
|
|
585
|
+
updated_at: now
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
// ==========================================
|
|
589
|
+
// Internal CRUD helpers (driver vs engine)
|
|
590
|
+
// ==========================================
|
|
591
|
+
async _findOne(table, query) {
|
|
592
|
+
if (this.engine) {
|
|
593
|
+
return this.engine.findOne(table, query);
|
|
594
|
+
}
|
|
595
|
+
return this.driver.findOne(table, { object: table, ...query });
|
|
596
|
+
}
|
|
597
|
+
async _create(table, data) {
|
|
598
|
+
if (this.engine) {
|
|
599
|
+
return this.engine.insert(table, data);
|
|
600
|
+
}
|
|
601
|
+
return this.driver.create(table, data);
|
|
602
|
+
}
|
|
603
|
+
async _update(table, id, data) {
|
|
604
|
+
if (this.engine) {
|
|
605
|
+
return this.engine.update(table, { id, ...data });
|
|
606
|
+
}
|
|
607
|
+
return this.driver.update(table, id, data);
|
|
608
|
+
}
|
|
609
|
+
async _delete(table, id) {
|
|
610
|
+
if (this.engine) {
|
|
611
|
+
return this.engine.delete(table, { where: { id } });
|
|
612
|
+
}
|
|
613
|
+
return this.driver.delete(table, id);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Generate a simple unique ID
|
|
617
|
+
*/
|
|
618
|
+
generateId() {
|
|
619
|
+
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
|
|
620
|
+
return globalThis.crypto.randomUUID();
|
|
621
|
+
}
|
|
622
|
+
return `proj_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
|
|
592
626
|
// src/loaders/database-loader.ts
|
|
593
627
|
var DatabaseLoader = class {
|
|
594
628
|
constructor(options) {
|
|
@@ -604,11 +638,64 @@ var DatabaseLoader = class {
|
|
|
604
638
|
};
|
|
605
639
|
this.schemaReady = false;
|
|
606
640
|
this.historySchemaReady = false;
|
|
641
|
+
if (!options.driver && !options.engine) {
|
|
642
|
+
throw new Error("DatabaseLoader requires either a driver or engine");
|
|
643
|
+
}
|
|
607
644
|
this.driver = options.driver;
|
|
645
|
+
this.engine = options.engine;
|
|
608
646
|
this.tableName = options.tableName ?? "sys_metadata";
|
|
609
647
|
this.historyTableName = options.historyTableName ?? "sys_metadata_history";
|
|
610
|
-
this.
|
|
648
|
+
this.organizationId = options.organizationId;
|
|
649
|
+
this.projectId = options.projectId;
|
|
611
650
|
this.trackHistory = options.trackHistory !== false;
|
|
651
|
+
this.enableProjection = options.enableProjection !== false;
|
|
652
|
+
if (this.enableProjection) {
|
|
653
|
+
this.projector = new MetadataProjector({
|
|
654
|
+
driver: this.driver,
|
|
655
|
+
engine: this.engine,
|
|
656
|
+
organizationId: this.organizationId,
|
|
657
|
+
projectId: this.projectId
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
// ==========================================
|
|
662
|
+
// Internal CRUD helpers (driver vs engine)
|
|
663
|
+
// ==========================================
|
|
664
|
+
async _find(table, query) {
|
|
665
|
+
if (this.engine) {
|
|
666
|
+
return this.engine.find(table, query);
|
|
667
|
+
}
|
|
668
|
+
return this.driver.find(table, { object: table, ...query });
|
|
669
|
+
}
|
|
670
|
+
async _findOne(table, query) {
|
|
671
|
+
if (this.engine) {
|
|
672
|
+
return this.engine.findOne(table, query);
|
|
673
|
+
}
|
|
674
|
+
return this.driver.findOne(table, { object: table, ...query });
|
|
675
|
+
}
|
|
676
|
+
async _count(table, query) {
|
|
677
|
+
if (this.engine) {
|
|
678
|
+
return this.engine.count(table, query);
|
|
679
|
+
}
|
|
680
|
+
return this.driver.count(table, { object: table, ...query });
|
|
681
|
+
}
|
|
682
|
+
async _create(table, data) {
|
|
683
|
+
if (this.engine) {
|
|
684
|
+
return this.engine.insert(table, data);
|
|
685
|
+
}
|
|
686
|
+
return this.driver.create(table, data);
|
|
687
|
+
}
|
|
688
|
+
async _update(table, id, data) {
|
|
689
|
+
if (this.engine) {
|
|
690
|
+
return this.engine.update(table, { id, ...data });
|
|
691
|
+
}
|
|
692
|
+
return this.driver.update(table, id, data);
|
|
693
|
+
}
|
|
694
|
+
async _delete(table, id) {
|
|
695
|
+
if (this.engine) {
|
|
696
|
+
return this.engine.delete(table, { where: { id } });
|
|
697
|
+
}
|
|
698
|
+
return this.driver.delete(table, id);
|
|
612
699
|
}
|
|
613
700
|
/**
|
|
614
701
|
* Ensure the metadata table exists.
|
|
@@ -617,9 +704,13 @@ var DatabaseLoader = class {
|
|
|
617
704
|
*/
|
|
618
705
|
async ensureSchema() {
|
|
619
706
|
if (this.schemaReady) return;
|
|
707
|
+
if (this.engine) {
|
|
708
|
+
this.schemaReady = true;
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
620
711
|
try {
|
|
621
712
|
await this.driver.syncSchema(this.tableName, {
|
|
622
|
-
...SysMetadataObject,
|
|
713
|
+
...import_metadata.SysMetadataObject,
|
|
623
714
|
name: this.tableName
|
|
624
715
|
});
|
|
625
716
|
this.schemaReady = true;
|
|
@@ -633,9 +724,13 @@ var DatabaseLoader = class {
|
|
|
633
724
|
*/
|
|
634
725
|
async ensureHistorySchema() {
|
|
635
726
|
if (!this.trackHistory || this.historySchemaReady) return;
|
|
727
|
+
if (this.engine) {
|
|
728
|
+
this.historySchemaReady = true;
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
636
731
|
try {
|
|
637
732
|
await this.driver.syncSchema(this.historyTableName, {
|
|
638
|
-
...SysMetadataHistoryObject,
|
|
733
|
+
...import_metadata.SysMetadataHistoryObject,
|
|
639
734
|
name: this.historyTableName
|
|
640
735
|
});
|
|
641
736
|
this.historySchemaReady = true;
|
|
@@ -645,16 +740,18 @@ var DatabaseLoader = class {
|
|
|
645
740
|
}
|
|
646
741
|
/**
|
|
647
742
|
* Build base filter conditions for queries.
|
|
648
|
-
*
|
|
743
|
+
* Filters by organizationId when configured; project_id when projectId is set,
|
|
744
|
+
* or null (platform-global) when not set.
|
|
649
745
|
*/
|
|
650
746
|
baseFilter(type, name) {
|
|
651
747
|
const filter = { type };
|
|
652
748
|
if (name !== void 0) {
|
|
653
749
|
filter.name = name;
|
|
654
750
|
}
|
|
655
|
-
if (this.
|
|
656
|
-
filter.
|
|
751
|
+
if (this.organizationId) {
|
|
752
|
+
filter.organization_id = this.organizationId;
|
|
657
753
|
}
|
|
754
|
+
filter.project_id = this.projectId ?? null;
|
|
658
755
|
return filter;
|
|
659
756
|
}
|
|
660
757
|
/**
|
|
@@ -693,10 +790,11 @@ var DatabaseLoader = class {
|
|
|
693
790
|
changeNote,
|
|
694
791
|
recordedBy,
|
|
695
792
|
recordedAt: now,
|
|
696
|
-
...this.
|
|
793
|
+
...this.organizationId ? { organizationId: this.organizationId } : {},
|
|
794
|
+
...this.projectId !== void 0 ? { projectId: this.projectId } : {}
|
|
697
795
|
};
|
|
698
796
|
try {
|
|
699
|
-
await this.
|
|
797
|
+
await this._create(this.historyTableName, {
|
|
700
798
|
id: historyRecord.id,
|
|
701
799
|
metadata_id: historyRecord.metadataId,
|
|
702
800
|
name: historyRecord.name,
|
|
@@ -709,7 +807,8 @@ var DatabaseLoader = class {
|
|
|
709
807
|
change_note: historyRecord.changeNote,
|
|
710
808
|
recorded_by: historyRecord.recordedBy,
|
|
711
809
|
recorded_at: historyRecord.recordedAt,
|
|
712
|
-
...this.
|
|
810
|
+
...this.organizationId ? { organization_id: this.organizationId } : {},
|
|
811
|
+
...this.projectId !== void 0 ? { project_id: this.projectId } : {}
|
|
713
812
|
});
|
|
714
813
|
} catch (error) {
|
|
715
814
|
console.error(`Failed to create history record for ${type}/${name}:`, error);
|
|
@@ -741,7 +840,8 @@ var DatabaseLoader = class {
|
|
|
741
840
|
strategy: row.strategy ?? "merge",
|
|
742
841
|
owner: row.owner,
|
|
743
842
|
state: row.state ?? "active",
|
|
744
|
-
|
|
843
|
+
organizationId: row.organization_id,
|
|
844
|
+
projectId: row.project_id,
|
|
745
845
|
version: row.version ?? 1,
|
|
746
846
|
checksum: row.checksum,
|
|
747
847
|
source: row.source,
|
|
@@ -759,8 +859,7 @@ var DatabaseLoader = class {
|
|
|
759
859
|
const startTime = Date.now();
|
|
760
860
|
await this.ensureSchema();
|
|
761
861
|
try {
|
|
762
|
-
const row = await this.
|
|
763
|
-
object: this.tableName,
|
|
862
|
+
const row = await this._findOne(this.tableName, {
|
|
764
863
|
where: this.baseFilter(type, name)
|
|
765
864
|
});
|
|
766
865
|
if (!row) {
|
|
@@ -788,8 +887,7 @@ var DatabaseLoader = class {
|
|
|
788
887
|
async loadMany(type, _options) {
|
|
789
888
|
await this.ensureSchema();
|
|
790
889
|
try {
|
|
791
|
-
const rows = await this.
|
|
792
|
-
object: this.tableName,
|
|
890
|
+
const rows = await this._find(this.tableName, {
|
|
793
891
|
where: this.baseFilter(type)
|
|
794
892
|
});
|
|
795
893
|
return rows.map((row) => this.rowToData(row)).filter((data) => data !== null);
|
|
@@ -800,8 +898,7 @@ var DatabaseLoader = class {
|
|
|
800
898
|
async exists(type, name) {
|
|
801
899
|
await this.ensureSchema();
|
|
802
900
|
try {
|
|
803
|
-
const count = await this.
|
|
804
|
-
object: this.tableName,
|
|
901
|
+
const count = await this._count(this.tableName, {
|
|
805
902
|
where: this.baseFilter(type, name)
|
|
806
903
|
});
|
|
807
904
|
return count > 0;
|
|
@@ -812,8 +909,7 @@ var DatabaseLoader = class {
|
|
|
812
909
|
async stat(type, name) {
|
|
813
910
|
await this.ensureSchema();
|
|
814
911
|
try {
|
|
815
|
-
const row = await this.
|
|
816
|
-
object: this.tableName,
|
|
912
|
+
const row = await this._findOne(this.tableName, {
|
|
817
913
|
where: this.baseFilter(type, name)
|
|
818
914
|
});
|
|
819
915
|
if (!row) return null;
|
|
@@ -832,8 +928,7 @@ var DatabaseLoader = class {
|
|
|
832
928
|
async list(type) {
|
|
833
929
|
await this.ensureSchema();
|
|
834
930
|
try {
|
|
835
|
-
const rows = await this.
|
|
836
|
-
object: this.tableName,
|
|
931
|
+
const rows = await this._find(this.tableName, {
|
|
837
932
|
where: this.baseFilter(type),
|
|
838
933
|
fields: ["name"]
|
|
839
934
|
});
|
|
@@ -849,8 +944,7 @@ var DatabaseLoader = class {
|
|
|
849
944
|
async getHistoryRecord(type, name, version) {
|
|
850
945
|
if (!this.trackHistory) return null;
|
|
851
946
|
await this.ensureHistorySchema();
|
|
852
|
-
const metadataRow = await this.
|
|
853
|
-
object: this.tableName,
|
|
947
|
+
const metadataRow = await this._findOne(this.tableName, {
|
|
854
948
|
where: this.baseFilter(type, name)
|
|
855
949
|
});
|
|
856
950
|
if (!metadataRow) return null;
|
|
@@ -858,11 +952,11 @@ var DatabaseLoader = class {
|
|
|
858
952
|
metadata_id: metadataRow.id,
|
|
859
953
|
version
|
|
860
954
|
};
|
|
861
|
-
if (this.
|
|
862
|
-
filter.
|
|
955
|
+
if (this.organizationId) {
|
|
956
|
+
filter.organization_id = this.organizationId;
|
|
863
957
|
}
|
|
864
|
-
|
|
865
|
-
|
|
958
|
+
filter.project_id = this.projectId ?? null;
|
|
959
|
+
const row = await this._findOne(this.historyTableName, {
|
|
866
960
|
where: filter
|
|
867
961
|
});
|
|
868
962
|
if (!row) return null;
|
|
@@ -877,11 +971,80 @@ var DatabaseLoader = class {
|
|
|
877
971
|
checksum: row.checksum,
|
|
878
972
|
previousChecksum: row.previous_checksum,
|
|
879
973
|
changeNote: row.change_note,
|
|
880
|
-
|
|
974
|
+
organizationId: row.organization_id,
|
|
975
|
+
projectId: row.project_id,
|
|
881
976
|
recordedBy: row.recorded_by,
|
|
882
977
|
recordedAt: row.recorded_at
|
|
883
978
|
};
|
|
884
979
|
}
|
|
980
|
+
/**
|
|
981
|
+
* Query history records with pagination and filtering.
|
|
982
|
+
* Encapsulates history table queries so MetadataManager doesn't need
|
|
983
|
+
* direct driver access.
|
|
984
|
+
*/
|
|
985
|
+
async queryHistory(type, name, options) {
|
|
986
|
+
if (!this.trackHistory) {
|
|
987
|
+
return { records: [], total: 0, hasMore: false };
|
|
988
|
+
}
|
|
989
|
+
await this.ensureSchema();
|
|
990
|
+
await this.ensureHistorySchema();
|
|
991
|
+
const filter = { type, name };
|
|
992
|
+
if (this.organizationId) filter.organization_id = this.organizationId;
|
|
993
|
+
filter.project_id = this.projectId ?? null;
|
|
994
|
+
const metadataRecord = await this._findOne(this.tableName, { where: filter });
|
|
995
|
+
if (!metadataRecord) {
|
|
996
|
+
return { records: [], total: 0, hasMore: false };
|
|
997
|
+
}
|
|
998
|
+
const historyFilter = {
|
|
999
|
+
metadata_id: metadataRecord.id
|
|
1000
|
+
};
|
|
1001
|
+
if (this.organizationId) historyFilter.organization_id = this.organizationId;
|
|
1002
|
+
historyFilter.project_id = this.projectId ?? null;
|
|
1003
|
+
if (options?.operationType) historyFilter.operation_type = options.operationType;
|
|
1004
|
+
if (options?.since) historyFilter.recorded_at = { $gte: options.since };
|
|
1005
|
+
if (options?.until) {
|
|
1006
|
+
if (historyFilter.recorded_at) {
|
|
1007
|
+
historyFilter.recorded_at.$lte = options.until;
|
|
1008
|
+
} else {
|
|
1009
|
+
historyFilter.recorded_at = { $lte: options.until };
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
const limit = options?.limit ?? 50;
|
|
1013
|
+
const offset = options?.offset ?? 0;
|
|
1014
|
+
const historyRecords = await this._find(this.historyTableName, {
|
|
1015
|
+
where: historyFilter,
|
|
1016
|
+
orderBy: [
|
|
1017
|
+
{ field: "recorded_at", order: "desc" },
|
|
1018
|
+
{ field: "version", order: "desc" }
|
|
1019
|
+
],
|
|
1020
|
+
limit: limit + 1,
|
|
1021
|
+
offset
|
|
1022
|
+
});
|
|
1023
|
+
const hasMore = historyRecords.length > limit;
|
|
1024
|
+
const records = historyRecords.slice(0, limit);
|
|
1025
|
+
const total = await this._count(this.historyTableName, { where: historyFilter });
|
|
1026
|
+
const includeMetadata = options?.includeMetadata !== false;
|
|
1027
|
+
const result = records.map((row) => {
|
|
1028
|
+
const parsedMetadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata;
|
|
1029
|
+
return {
|
|
1030
|
+
id: row.id,
|
|
1031
|
+
metadataId: row.metadata_id,
|
|
1032
|
+
name: row.name,
|
|
1033
|
+
type: row.type,
|
|
1034
|
+
version: row.version,
|
|
1035
|
+
operationType: row.operation_type,
|
|
1036
|
+
metadata: includeMetadata ? parsedMetadata : null,
|
|
1037
|
+
checksum: row.checksum,
|
|
1038
|
+
previousChecksum: row.previous_checksum,
|
|
1039
|
+
changeNote: row.change_note,
|
|
1040
|
+
organizationId: row.organization_id,
|
|
1041
|
+
projectId: row.project_id,
|
|
1042
|
+
recordedBy: row.recorded_by,
|
|
1043
|
+
recordedAt: row.recorded_at
|
|
1044
|
+
};
|
|
1045
|
+
});
|
|
1046
|
+
return { records: result, total, hasMore };
|
|
1047
|
+
}
|
|
885
1048
|
/**
|
|
886
1049
|
* Perform a rollback: persist `restoredData` as the new current state and record a
|
|
887
1050
|
* single 'revert' history entry (instead of the usual 'update' entry that `save()`
|
|
@@ -894,8 +1057,7 @@ var DatabaseLoader = class {
|
|
|
894
1057
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
895
1058
|
const metadataJson = JSON.stringify(restoredData);
|
|
896
1059
|
const newChecksum = await calculateChecksum(restoredData);
|
|
897
|
-
const existing = await this.
|
|
898
|
-
object: this.tableName,
|
|
1060
|
+
const existing = await this._findOne(this.tableName, {
|
|
899
1061
|
where: this.baseFilter(type, name)
|
|
900
1062
|
});
|
|
901
1063
|
if (!existing) {
|
|
@@ -903,7 +1065,7 @@ var DatabaseLoader = class {
|
|
|
903
1065
|
}
|
|
904
1066
|
const previousChecksum = existing.checksum;
|
|
905
1067
|
const newVersion = (existing.version ?? 0) + 1;
|
|
906
|
-
await this.
|
|
1068
|
+
await this._update(this.tableName, existing.id, {
|
|
907
1069
|
metadata: metadataJson,
|
|
908
1070
|
version: newVersion,
|
|
909
1071
|
checksum: newChecksum,
|
|
@@ -929,8 +1091,7 @@ var DatabaseLoader = class {
|
|
|
929
1091
|
const metadataJson = JSON.stringify(data);
|
|
930
1092
|
const newChecksum = await calculateChecksum(data);
|
|
931
1093
|
try {
|
|
932
|
-
const existing = await this.
|
|
933
|
-
object: this.tableName,
|
|
1094
|
+
const existing = await this._findOne(this.tableName, {
|
|
934
1095
|
where: this.baseFilter(type, name)
|
|
935
1096
|
});
|
|
936
1097
|
if (existing) {
|
|
@@ -944,7 +1105,7 @@ var DatabaseLoader = class {
|
|
|
944
1105
|
};
|
|
945
1106
|
}
|
|
946
1107
|
const version = (existing.version ?? 0) + 1;
|
|
947
|
-
await this.
|
|
1108
|
+
await this._update(this.tableName, existing.id, {
|
|
948
1109
|
metadata: metadataJson,
|
|
949
1110
|
version,
|
|
950
1111
|
checksum: newChecksum,
|
|
@@ -960,6 +1121,9 @@ var DatabaseLoader = class {
|
|
|
960
1121
|
"update",
|
|
961
1122
|
previousChecksum
|
|
962
1123
|
);
|
|
1124
|
+
if (this.projector) {
|
|
1125
|
+
await this.projector.project(type, name, data);
|
|
1126
|
+
}
|
|
963
1127
|
return {
|
|
964
1128
|
success: true,
|
|
965
1129
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -968,7 +1132,7 @@ var DatabaseLoader = class {
|
|
|
968
1132
|
};
|
|
969
1133
|
} else {
|
|
970
1134
|
const id = generateId();
|
|
971
|
-
await this.
|
|
1135
|
+
await this._create(this.tableName, {
|
|
972
1136
|
id,
|
|
973
1137
|
name,
|
|
974
1138
|
type,
|
|
@@ -980,7 +1144,8 @@ var DatabaseLoader = class {
|
|
|
980
1144
|
state: "active",
|
|
981
1145
|
version: 1,
|
|
982
1146
|
source: "database",
|
|
983
|
-
...this.
|
|
1147
|
+
...this.organizationId ? { organization_id: this.organizationId } : {},
|
|
1148
|
+
...this.projectId !== void 0 ? { project_id: this.projectId } : { project_id: null },
|
|
984
1149
|
created_at: now,
|
|
985
1150
|
updated_at: now
|
|
986
1151
|
});
|
|
@@ -992,6 +1157,9 @@ var DatabaseLoader = class {
|
|
|
992
1157
|
data,
|
|
993
1158
|
"create"
|
|
994
1159
|
);
|
|
1160
|
+
if (this.projector) {
|
|
1161
|
+
await this.projector.project(type, name, data);
|
|
1162
|
+
}
|
|
995
1163
|
return {
|
|
996
1164
|
success: true,
|
|
997
1165
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -1010,14 +1178,16 @@ var DatabaseLoader = class {
|
|
|
1010
1178
|
*/
|
|
1011
1179
|
async delete(type, name) {
|
|
1012
1180
|
await this.ensureSchema();
|
|
1013
|
-
const existing = await this.
|
|
1014
|
-
object: this.tableName,
|
|
1181
|
+
const existing = await this._findOne(this.tableName, {
|
|
1015
1182
|
where: this.baseFilter(type, name)
|
|
1016
1183
|
});
|
|
1017
1184
|
if (!existing) {
|
|
1018
1185
|
return;
|
|
1019
1186
|
}
|
|
1020
|
-
await this.
|
|
1187
|
+
await this._delete(this.tableName, existing.id);
|
|
1188
|
+
if (this.projector) {
|
|
1189
|
+
await this.projector.deleteProjection(type, name);
|
|
1190
|
+
}
|
|
1021
1191
|
}
|
|
1022
1192
|
};
|
|
1023
1193
|
function generateId() {
|
|
@@ -1074,16 +1244,55 @@ var MetadataManager = class {
|
|
|
1074
1244
|
* Can be called at any time to enable database storage (e.g. after kernel resolves the driver).
|
|
1075
1245
|
*
|
|
1076
1246
|
* @param driver - An IDataDriver instance for database operations
|
|
1077
|
-
|
|
1078
|
-
|
|
1247
|
+
* @param organizationId - Organization ID for multi-tenant isolation
|
|
1248
|
+
* @param projectId - Project ID (undefined = platform-global)
|
|
1249
|
+
*/
|
|
1250
|
+
setDatabaseDriver(driver, organizationId, projectId) {
|
|
1251
|
+
if (projectId !== void 0) {
|
|
1252
|
+
this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
|
|
1253
|
+
organizationId,
|
|
1254
|
+
projectId
|
|
1255
|
+
});
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1079
1258
|
const tableName = this.config.tableName ?? "sys_metadata";
|
|
1080
1259
|
const dbLoader = new DatabaseLoader({
|
|
1081
1260
|
driver,
|
|
1082
|
-
tableName
|
|
1261
|
+
tableName,
|
|
1262
|
+
organizationId,
|
|
1263
|
+
projectId
|
|
1083
1264
|
});
|
|
1084
1265
|
this.registerLoader(dbLoader);
|
|
1085
1266
|
this.logger.info("DatabaseLoader configured", { datasource: this.config.datasource, tableName });
|
|
1086
1267
|
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Configure and register a DatabaseLoader backed by an IDataEngine (ObjectQL).
|
|
1270
|
+
* The engine handles datasource routing automatically — sys_metadata will
|
|
1271
|
+
* be routed to the correct driver via the standard namespace mapping.
|
|
1272
|
+
* No manual driver resolution needed.
|
|
1273
|
+
*
|
|
1274
|
+
* @param engine - An IDataEngine instance (typically the ObjectQL service)
|
|
1275
|
+
* @param organizationId - Organization ID for multi-tenant isolation
|
|
1276
|
+
* @param projectId - Project ID (undefined = platform-global)
|
|
1277
|
+
*/
|
|
1278
|
+
setDataEngine(engine, organizationId, projectId) {
|
|
1279
|
+
if (projectId !== void 0) {
|
|
1280
|
+
this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
|
|
1281
|
+
organizationId,
|
|
1282
|
+
projectId
|
|
1283
|
+
});
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const tableName = this.config.tableName ?? "sys_metadata";
|
|
1287
|
+
const dbLoader = new DatabaseLoader({
|
|
1288
|
+
engine,
|
|
1289
|
+
tableName,
|
|
1290
|
+
organizationId,
|
|
1291
|
+
projectId
|
|
1292
|
+
});
|
|
1293
|
+
this.registerLoader(dbLoader);
|
|
1294
|
+
this.logger.info("DatabaseLoader configured via DataEngine", { tableName });
|
|
1295
|
+
}
|
|
1087
1296
|
/**
|
|
1088
1297
|
* Set the realtime service for publishing metadata change events.
|
|
1089
1298
|
* Should be called after kernel resolves the realtime service.
|
|
@@ -1997,84 +2206,14 @@ var MetadataManager = class {
|
|
|
1997
2206
|
if (!dbLoader) {
|
|
1998
2207
|
throw new Error("History tracking requires a database loader to be configured");
|
|
1999
2208
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
}
|
|
2008
|
-
const metadataRecord = await driver.findOne(tableName, {
|
|
2009
|
-
object: tableName,
|
|
2010
|
-
where: filter
|
|
2011
|
-
});
|
|
2012
|
-
if (!metadataRecord) {
|
|
2013
|
-
return {
|
|
2014
|
-
records: [],
|
|
2015
|
-
total: 0,
|
|
2016
|
-
hasMore: false
|
|
2017
|
-
};
|
|
2018
|
-
}
|
|
2019
|
-
const historyFilter = {
|
|
2020
|
-
metadata_id: metadataRecord.id
|
|
2021
|
-
};
|
|
2022
|
-
if (tenantId) {
|
|
2023
|
-
historyFilter.tenant_id = tenantId;
|
|
2024
|
-
}
|
|
2025
|
-
if (options?.operationType) {
|
|
2026
|
-
historyFilter.operation_type = options.operationType;
|
|
2027
|
-
}
|
|
2028
|
-
if (options?.since) {
|
|
2029
|
-
historyFilter.recorded_at = { $gte: options.since };
|
|
2030
|
-
}
|
|
2031
|
-
if (options?.until) {
|
|
2032
|
-
if (historyFilter.recorded_at) {
|
|
2033
|
-
historyFilter.recorded_at.$lte = options.until;
|
|
2034
|
-
} else {
|
|
2035
|
-
historyFilter.recorded_at = { $lte: options.until };
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
const limit = options?.limit ?? 50;
|
|
2039
|
-
const offset = options?.offset ?? 0;
|
|
2040
|
-
const historyRecords = await driver.find(historyTableName, {
|
|
2041
|
-
object: historyTableName,
|
|
2042
|
-
where: historyFilter,
|
|
2043
|
-
orderBy: [{ field: "recorded_at", order: "desc" }],
|
|
2044
|
-
limit: limit + 1,
|
|
2045
|
-
// Fetch one extra to determine hasMore
|
|
2046
|
-
offset
|
|
2047
|
-
});
|
|
2048
|
-
const hasMore = historyRecords.length > limit;
|
|
2049
|
-
const records = historyRecords.slice(0, limit);
|
|
2050
|
-
const total = await driver.count(historyTableName, {
|
|
2051
|
-
object: historyTableName,
|
|
2052
|
-
where: historyFilter
|
|
2209
|
+
return dbLoader.queryHistory(type, name, {
|
|
2210
|
+
operationType: options?.operationType,
|
|
2211
|
+
since: options?.since,
|
|
2212
|
+
until: options?.until,
|
|
2213
|
+
limit: options?.limit,
|
|
2214
|
+
offset: options?.offset,
|
|
2215
|
+
includeMetadata: options?.includeMetadata
|
|
2053
2216
|
});
|
|
2054
|
-
const includeMetadata = options?.includeMetadata !== false;
|
|
2055
|
-
const historyResult = records.map((row) => {
|
|
2056
|
-
const parsedMetadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata;
|
|
2057
|
-
return {
|
|
2058
|
-
id: row.id,
|
|
2059
|
-
metadataId: row.metadata_id,
|
|
2060
|
-
name: row.name,
|
|
2061
|
-
type: row.type,
|
|
2062
|
-
version: row.version,
|
|
2063
|
-
operationType: row.operation_type,
|
|
2064
|
-
metadata: includeMetadata ? parsedMetadata : null,
|
|
2065
|
-
checksum: row.checksum,
|
|
2066
|
-
previousChecksum: row.previous_checksum,
|
|
2067
|
-
changeNote: row.change_note,
|
|
2068
|
-
tenantId: row.tenant_id,
|
|
2069
|
-
recordedBy: row.recorded_by,
|
|
2070
|
-
recordedAt: row.recorded_at
|
|
2071
|
-
};
|
|
2072
|
-
});
|
|
2073
|
-
return {
|
|
2074
|
-
records: historyResult,
|
|
2075
|
-
total,
|
|
2076
|
-
hasMore
|
|
2077
|
-
};
|
|
2078
2217
|
}
|
|
2079
2218
|
/**
|
|
2080
2219
|
* Rollback a metadata item to a specific version.
|
|
@@ -2144,6 +2283,10 @@ var MetadataManager = class {
|
|
|
2144
2283
|
}
|
|
2145
2284
|
};
|
|
2146
2285
|
|
|
2286
|
+
// src/plugin.ts
|
|
2287
|
+
var import_promises = require("fs/promises");
|
|
2288
|
+
var import_node_crypto2 = require("crypto");
|
|
2289
|
+
|
|
2147
2290
|
// src/node-metadata-manager.ts
|
|
2148
2291
|
var path2 = __toESM(require("path"), 1);
|
|
2149
2292
|
var import_chokidar = require("chokidar");
|
|
@@ -2260,7 +2403,7 @@ var FilesystemLoader = class {
|
|
|
2260
2403
|
);
|
|
2261
2404
|
for (const pattern of globPatterns) {
|
|
2262
2405
|
const files = await (0, import_glob.glob)(pattern, {
|
|
2263
|
-
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"],
|
|
2406
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/*[*]*"],
|
|
2264
2407
|
nodir: true
|
|
2265
2408
|
});
|
|
2266
2409
|
for (const file of files) {
|
|
@@ -2549,105 +2692,6 @@ var NodeMetadataManager = class extends MetadataManager {
|
|
|
2549
2692
|
}
|
|
2550
2693
|
};
|
|
2551
2694
|
|
|
2552
|
-
// src/plugin.ts
|
|
2553
|
-
var import_kernel = require("@objectstack/spec/kernel");
|
|
2554
|
-
var MetadataPlugin = class {
|
|
2555
|
-
constructor(options = {}) {
|
|
2556
|
-
this.name = "com.objectstack.metadata";
|
|
2557
|
-
this.type = "standard";
|
|
2558
|
-
this.version = "1.0.0";
|
|
2559
|
-
this.init = async (ctx) => {
|
|
2560
|
-
ctx.logger.info("Initializing Metadata Manager", {
|
|
2561
|
-
root: this.options.rootDir || process.cwd(),
|
|
2562
|
-
watch: this.options.watch
|
|
2563
|
-
});
|
|
2564
|
-
ctx.registerService("metadata", this.manager);
|
|
2565
|
-
console.log("[MetadataPlugin] Registered metadata service, has getRegisteredTypes:", typeof this.manager.getRegisteredTypes);
|
|
2566
|
-
try {
|
|
2567
|
-
ctx.getService("manifest").register({
|
|
2568
|
-
id: "com.objectstack.metadata",
|
|
2569
|
-
name: "Metadata",
|
|
2570
|
-
version: "1.0.0",
|
|
2571
|
-
type: "plugin",
|
|
2572
|
-
namespace: "sys",
|
|
2573
|
-
objects: [SysMetadataObject]
|
|
2574
|
-
});
|
|
2575
|
-
} catch {
|
|
2576
|
-
}
|
|
2577
|
-
ctx.logger.info("MetadataPlugin providing metadata service (primary mode)", {
|
|
2578
|
-
mode: "file-system",
|
|
2579
|
-
features: ["watch", "persistence", "multi-format", "query", "overlay", "type-registry"]
|
|
2580
|
-
});
|
|
2581
|
-
};
|
|
2582
|
-
this.start = async (ctx) => {
|
|
2583
|
-
ctx.logger.info("Loading metadata from file system...");
|
|
2584
|
-
const sortedTypes = [...import_kernel.DEFAULT_METADATA_TYPE_REGISTRY].sort((a, b) => a.loadOrder - b.loadOrder);
|
|
2585
|
-
let totalLoaded = 0;
|
|
2586
|
-
for (const entry of sortedTypes) {
|
|
2587
|
-
try {
|
|
2588
|
-
const items = await this.manager.loadMany(entry.type, {
|
|
2589
|
-
recursive: true
|
|
2590
|
-
});
|
|
2591
|
-
if (items.length > 0) {
|
|
2592
|
-
for (const item of items) {
|
|
2593
|
-
const meta = item;
|
|
2594
|
-
if (meta?.name) {
|
|
2595
|
-
await this.manager.register(entry.type, meta.name, item);
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2598
|
-
ctx.logger.info(`Loaded ${items.length} ${entry.type} from file system`);
|
|
2599
|
-
totalLoaded += items.length;
|
|
2600
|
-
}
|
|
2601
|
-
} catch (e) {
|
|
2602
|
-
ctx.logger.debug(`No ${entry.type} metadata found`, { error: e.message });
|
|
2603
|
-
}
|
|
2604
|
-
}
|
|
2605
|
-
ctx.logger.info("Metadata loading complete", {
|
|
2606
|
-
totalItems: totalLoaded,
|
|
2607
|
-
registeredTypes: sortedTypes.length
|
|
2608
|
-
});
|
|
2609
|
-
try {
|
|
2610
|
-
const services = ctx.getServices();
|
|
2611
|
-
for (const [serviceName, service] of services) {
|
|
2612
|
-
if (serviceName.startsWith("driver.") && service) {
|
|
2613
|
-
ctx.logger.info("[MetadataPlugin] Bridging driver to MetadataManager for database-backed persistence", {
|
|
2614
|
-
driverService: serviceName
|
|
2615
|
-
});
|
|
2616
|
-
this.manager.setDatabaseDriver(service);
|
|
2617
|
-
break;
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
} catch (e) {
|
|
2621
|
-
ctx.logger.debug("[MetadataPlugin] No driver service found \u2014 database metadata persistence not available", {
|
|
2622
|
-
error: e.message
|
|
2623
|
-
});
|
|
2624
|
-
}
|
|
2625
|
-
try {
|
|
2626
|
-
const realtimeService = ctx.getService("realtime");
|
|
2627
|
-
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
2628
|
-
ctx.logger.info("[MetadataPlugin] Bridging realtime service to MetadataManager for event publishing");
|
|
2629
|
-
this.manager.setRealtimeService(realtimeService);
|
|
2630
|
-
}
|
|
2631
|
-
} catch (e) {
|
|
2632
|
-
ctx.logger.debug("[MetadataPlugin] No realtime service found \u2014 metadata events will not be published", {
|
|
2633
|
-
error: e.message
|
|
2634
|
-
});
|
|
2635
|
-
}
|
|
2636
|
-
};
|
|
2637
|
-
this.options = {
|
|
2638
|
-
watch: true,
|
|
2639
|
-
...options
|
|
2640
|
-
};
|
|
2641
|
-
const rootDir = this.options.rootDir || process.cwd();
|
|
2642
|
-
this.manager = new NodeMetadataManager({
|
|
2643
|
-
rootDir,
|
|
2644
|
-
watch: this.options.watch ?? true,
|
|
2645
|
-
formats: ["yaml", "json", "typescript", "javascript"]
|
|
2646
|
-
});
|
|
2647
|
-
this.manager.setTypeRegistry(import_kernel.DEFAULT_METADATA_TYPE_REGISTRY);
|
|
2648
|
-
}
|
|
2649
|
-
};
|
|
2650
|
-
|
|
2651
2695
|
// src/loaders/memory-loader.ts
|
|
2652
2696
|
var MemoryLoader = class {
|
|
2653
2697
|
constructor() {
|
|
@@ -2726,6 +2770,216 @@ var MemoryLoader = class {
|
|
|
2726
2770
|
}
|
|
2727
2771
|
};
|
|
2728
2772
|
|
|
2773
|
+
// src/plugin.ts
|
|
2774
|
+
var import_kernel = require("@objectstack/spec/kernel");
|
|
2775
|
+
var import_metadata2 = require("@objectstack/platform-objects/metadata");
|
|
2776
|
+
var queryableMetadataObjects = [import_metadata2.SysObject, import_metadata2.SysView, import_metadata2.SysFlow, import_metadata2.SysAgent, import_metadata2.SysTool];
|
|
2777
|
+
var ARTIFACT_FIELD_TO_TYPE = {
|
|
2778
|
+
objects: "object",
|
|
2779
|
+
objectExtensions: "object_extension",
|
|
2780
|
+
apps: "app",
|
|
2781
|
+
views: "view",
|
|
2782
|
+
pages: "page",
|
|
2783
|
+
dashboards: "dashboard",
|
|
2784
|
+
reports: "report",
|
|
2785
|
+
actions: "action",
|
|
2786
|
+
themes: "theme",
|
|
2787
|
+
workflows: "workflow",
|
|
2788
|
+
approvals: "approval",
|
|
2789
|
+
flows: "flow",
|
|
2790
|
+
roles: "role",
|
|
2791
|
+
permissions: "permission",
|
|
2792
|
+
sharingRules: "sharing_rule",
|
|
2793
|
+
policies: "policy",
|
|
2794
|
+
apis: "api",
|
|
2795
|
+
webhooks: "webhook",
|
|
2796
|
+
agents: "agent",
|
|
2797
|
+
skills: "skill",
|
|
2798
|
+
ragPipelines: "rag_pipeline",
|
|
2799
|
+
hooks: "hook",
|
|
2800
|
+
mappings: "mapping",
|
|
2801
|
+
analyticsCubes: "analytics_cube",
|
|
2802
|
+
connectors: "connector",
|
|
2803
|
+
data: "dataset"
|
|
2804
|
+
};
|
|
2805
|
+
var MetadataPlugin = class {
|
|
2806
|
+
constructor(options = {}) {
|
|
2807
|
+
this.name = "com.objectstack.metadata";
|
|
2808
|
+
this.type = "standard";
|
|
2809
|
+
this.version = "1.0.0";
|
|
2810
|
+
this.init = async (ctx) => {
|
|
2811
|
+
ctx.logger.info("Initializing Metadata Manager", {
|
|
2812
|
+
root: this.options.rootDir || process.cwd(),
|
|
2813
|
+
watch: this.options.watch,
|
|
2814
|
+
artifactSource: this.options.artifactSource?.mode
|
|
2815
|
+
});
|
|
2816
|
+
ctx.registerService("metadata", this.manager);
|
|
2817
|
+
console.log("[MetadataPlugin] Registered metadata service, has getRegisteredTypes:", typeof this.manager.getRegisteredTypes);
|
|
2818
|
+
const registerSysObjects = this.options.registerSystemObjects !== false;
|
|
2819
|
+
if (registerSysObjects) {
|
|
2820
|
+
try {
|
|
2821
|
+
const manifestService = ctx.getService("manifest");
|
|
2822
|
+
manifestService.register({
|
|
2823
|
+
id: "com.objectstack.metadata-objects",
|
|
2824
|
+
name: "Metadata Platform Objects",
|
|
2825
|
+
version: "1.0.0",
|
|
2826
|
+
type: "plugin",
|
|
2827
|
+
scope: "system",
|
|
2828
|
+
defaultDatasource: "cloud",
|
|
2829
|
+
objects: queryableMetadataObjects
|
|
2830
|
+
});
|
|
2831
|
+
ctx.logger.info("Registered system metadata objects", {
|
|
2832
|
+
queryable: queryableMetadataObjects.map((object) => object.name)
|
|
2833
|
+
});
|
|
2834
|
+
} catch {
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
ctx.logger.info("MetadataPlugin providing metadata service (primary mode)", {
|
|
2838
|
+
mode: this.options.artifactSource?.mode ?? "file-system",
|
|
2839
|
+
features: ["watch", "multi-format", "query", "overlay", "type-registry"]
|
|
2840
|
+
});
|
|
2841
|
+
};
|
|
2842
|
+
this.start = async (ctx) => {
|
|
2843
|
+
const src = this.options.artifactSource;
|
|
2844
|
+
if (src?.mode === "local-file") {
|
|
2845
|
+
await this._loadFromLocalFile(ctx, src.path);
|
|
2846
|
+
} else if (src?.mode === "artifact-api") {
|
|
2847
|
+
ctx.logger.warn("[MetadataPlugin] artifact-api source is not yet implemented; falling back to file-system scan");
|
|
2848
|
+
await this._loadFromFileSystem(ctx);
|
|
2849
|
+
} else {
|
|
2850
|
+
await this._loadFromFileSystem(ctx);
|
|
2851
|
+
}
|
|
2852
|
+
try {
|
|
2853
|
+
const realtimeService = ctx.getService("realtime");
|
|
2854
|
+
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
2855
|
+
ctx.logger.info("[MetadataPlugin] Bridging realtime service to MetadataManager for event publishing");
|
|
2856
|
+
this.manager.setRealtimeService(realtimeService);
|
|
2857
|
+
}
|
|
2858
|
+
} catch (e) {
|
|
2859
|
+
ctx.logger.debug("[MetadataPlugin] No realtime service found \u2014 metadata events will not be published", {
|
|
2860
|
+
error: e.message
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
};
|
|
2864
|
+
this.options = {
|
|
2865
|
+
watch: true,
|
|
2866
|
+
...options
|
|
2867
|
+
};
|
|
2868
|
+
const rootDir = this.options.rootDir || process.cwd();
|
|
2869
|
+
this.manager = new NodeMetadataManager({
|
|
2870
|
+
rootDir,
|
|
2871
|
+
watch: this.options.watch ?? true,
|
|
2872
|
+
formats: ["yaml", "json", "typescript", "javascript"]
|
|
2873
|
+
});
|
|
2874
|
+
this.manager.setTypeRegistry(import_kernel.DEFAULT_METADATA_TYPE_REGISTRY);
|
|
2875
|
+
}
|
|
2876
|
+
async _loadFromLocalFile(ctx, filePath) {
|
|
2877
|
+
const isUrl = /^https?:\/\//i.test(filePath);
|
|
2878
|
+
ctx.logger.info(
|
|
2879
|
+
`[MetadataPlugin] Loading metadata from ${isUrl ? "remote URL" : "local artifact file"}`,
|
|
2880
|
+
{ path: filePath }
|
|
2881
|
+
);
|
|
2882
|
+
let raw;
|
|
2883
|
+
try {
|
|
2884
|
+
let content;
|
|
2885
|
+
if (isUrl) {
|
|
2886
|
+
const controller = new AbortController();
|
|
2887
|
+
const timer = setTimeout(() => controller.abort(), 15e3);
|
|
2888
|
+
try {
|
|
2889
|
+
const res = await fetch(filePath, {
|
|
2890
|
+
redirect: "follow",
|
|
2891
|
+
signal: controller.signal,
|
|
2892
|
+
headers: { Accept: "application/json, */*;q=0.5" }
|
|
2893
|
+
});
|
|
2894
|
+
if (!res.ok) {
|
|
2895
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
2896
|
+
}
|
|
2897
|
+
content = await res.text();
|
|
2898
|
+
} finally {
|
|
2899
|
+
clearTimeout(timer);
|
|
2900
|
+
}
|
|
2901
|
+
} else {
|
|
2902
|
+
content = await (0, import_promises.readFile)(filePath, "utf8");
|
|
2903
|
+
}
|
|
2904
|
+
raw = JSON.parse(content);
|
|
2905
|
+
} catch (e) {
|
|
2906
|
+
throw new Error(`[MetadataPlugin] Cannot read artifact ${isUrl ? "URL" : "file"} at "${filePath}": ${e.message}`);
|
|
2907
|
+
}
|
|
2908
|
+
const { ProjectArtifactSchema } = await import("@objectstack/spec/cloud");
|
|
2909
|
+
const { ObjectStackDefinitionSchema } = await import("@objectstack/spec");
|
|
2910
|
+
let metadata;
|
|
2911
|
+
const obj = raw;
|
|
2912
|
+
if (obj?.schemaVersion && obj?.commitId && obj?.metadata !== void 0) {
|
|
2913
|
+
const artifact = ProjectArtifactSchema.parse(obj);
|
|
2914
|
+
metadata = artifact.metadata;
|
|
2915
|
+
} else {
|
|
2916
|
+
const def = ObjectStackDefinitionSchema.parse(obj);
|
|
2917
|
+
const canonical = JSON.stringify(def, Object.keys(def).sort());
|
|
2918
|
+
const checksum = (0, import_node_crypto2.createHash)("sha256").update(canonical).digest("hex");
|
|
2919
|
+
const projectId = this.options.projectId ?? "proj_local";
|
|
2920
|
+
ProjectArtifactSchema.parse({
|
|
2921
|
+
schemaVersion: "0.1",
|
|
2922
|
+
projectId,
|
|
2923
|
+
commitId: "local-dev",
|
|
2924
|
+
checksum,
|
|
2925
|
+
metadata: def
|
|
2926
|
+
});
|
|
2927
|
+
metadata = def;
|
|
2928
|
+
}
|
|
2929
|
+
const memLoader = new MemoryLoader();
|
|
2930
|
+
const manifestPackageId = metadata?.manifest?.id ?? metadata?.id ?? void 0;
|
|
2931
|
+
let totalRegistered = 0;
|
|
2932
|
+
for (const [field, metaType] of Object.entries(ARTIFACT_FIELD_TO_TYPE)) {
|
|
2933
|
+
const items = metadata[field];
|
|
2934
|
+
if (!Array.isArray(items) || items.length === 0) continue;
|
|
2935
|
+
for (const item of items) {
|
|
2936
|
+
const name = item?.name;
|
|
2937
|
+
if (!name) continue;
|
|
2938
|
+
if (manifestPackageId && item._packageId === void 0) {
|
|
2939
|
+
item._packageId = manifestPackageId;
|
|
2940
|
+
}
|
|
2941
|
+
await memLoader.save(metaType, name, item);
|
|
2942
|
+
await this.manager.register(metaType, name, item);
|
|
2943
|
+
totalRegistered++;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
this.manager.registerLoader(memLoader);
|
|
2947
|
+
ctx.logger.info("[MetadataPlugin] Artifact metadata loaded", {
|
|
2948
|
+
path: filePath,
|
|
2949
|
+
totalRegistered
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
async _loadFromFileSystem(ctx) {
|
|
2953
|
+
ctx.logger.info("Loading metadata from file system...");
|
|
2954
|
+
const sortedTypes = [...import_kernel.DEFAULT_METADATA_TYPE_REGISTRY].sort((a, b) => a.loadOrder - b.loadOrder);
|
|
2955
|
+
let totalLoaded = 0;
|
|
2956
|
+
for (const entry of sortedTypes) {
|
|
2957
|
+
try {
|
|
2958
|
+
const items = await this.manager.loadMany(entry.type, {
|
|
2959
|
+
recursive: true,
|
|
2960
|
+
patterns: entry.filePatterns
|
|
2961
|
+
});
|
|
2962
|
+
if (items.length > 0) {
|
|
2963
|
+
for (const item of items) {
|
|
2964
|
+
const meta = item;
|
|
2965
|
+
if (meta?.name) {
|
|
2966
|
+
await this.manager.register(entry.type, meta.name, item);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
ctx.logger.info(`Loaded ${items.length} ${entry.type} from file system`);
|
|
2970
|
+
totalLoaded += items.length;
|
|
2971
|
+
}
|
|
2972
|
+
} catch (e) {
|
|
2973
|
+
ctx.logger.debug(`No ${entry.type} metadata found`, { error: e.message });
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
ctx.logger.info("Metadata loading complete", {
|
|
2977
|
+
totalItems: totalLoaded,
|
|
2978
|
+
registeredTypes: sortedTypes.length
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
};
|
|
2982
|
+
|
|
2729
2983
|
// src/loaders/remote-loader.ts
|
|
2730
2984
|
var RemoteLoader = class {
|
|
2731
2985
|
constructor(baseUrl, authToken) {
|
|
@@ -2823,6 +3077,9 @@ var RemoteLoader = class {
|
|
|
2823
3077
|
}
|
|
2824
3078
|
};
|
|
2825
3079
|
|
|
3080
|
+
// src/index.ts
|
|
3081
|
+
var import_metadata3 = require("@objectstack/platform-objects/metadata");
|
|
3082
|
+
|
|
2826
3083
|
// src/routes/history-routes.ts
|
|
2827
3084
|
function registerMetadataHistoryRoutes(app, metadataService) {
|
|
2828
3085
|
app.get("/api/v1/metadata/:type/:name/history", async (c) => {
|
|
@@ -2978,7 +3235,8 @@ var HistoryCleanupManager = class {
|
|
|
2978
3235
|
async runCleanup() {
|
|
2979
3236
|
const driver = this.dbLoader.driver;
|
|
2980
3237
|
const historyTableName = this.dbLoader.historyTableName;
|
|
2981
|
-
const
|
|
3238
|
+
const organizationId = this.dbLoader.organizationId;
|
|
3239
|
+
const projectId = this.dbLoader.projectId;
|
|
2982
3240
|
let deleted = 0;
|
|
2983
3241
|
let errors = 0;
|
|
2984
3242
|
try {
|
|
@@ -2989,8 +3247,11 @@ var HistoryCleanupManager = class {
|
|
|
2989
3247
|
const filter = {
|
|
2990
3248
|
recorded_at: { $lt: cutoffISO }
|
|
2991
3249
|
};
|
|
2992
|
-
if (
|
|
2993
|
-
filter.
|
|
3250
|
+
if (organizationId) {
|
|
3251
|
+
filter.organization_id = organizationId;
|
|
3252
|
+
}
|
|
3253
|
+
if (projectId !== void 0) {
|
|
3254
|
+
filter.project_id = projectId;
|
|
2994
3255
|
}
|
|
2995
3256
|
try {
|
|
2996
3257
|
const result = await this.bulkDeleteByFilter(driver, historyTableName, filter);
|
|
@@ -3002,9 +3263,12 @@ var HistoryCleanupManager = class {
|
|
|
3002
3263
|
}
|
|
3003
3264
|
if (this.policy.maxVersions) {
|
|
3004
3265
|
try {
|
|
3266
|
+
const baseWhere = {};
|
|
3267
|
+
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3268
|
+
if (projectId !== void 0) baseWhere.project_id = projectId;
|
|
3005
3269
|
const metadataIds = await driver.find(historyTableName, {
|
|
3006
3270
|
object: historyTableName,
|
|
3007
|
-
where:
|
|
3271
|
+
where: baseWhere,
|
|
3008
3272
|
fields: ["metadata_id"]
|
|
3009
3273
|
});
|
|
3010
3274
|
const uniqueIds = /* @__PURE__ */ new Set();
|
|
@@ -3014,10 +3278,7 @@ var HistoryCleanupManager = class {
|
|
|
3014
3278
|
}
|
|
3015
3279
|
}
|
|
3016
3280
|
for (const metadataId of uniqueIds) {
|
|
3017
|
-
const filter = { metadata_id: metadataId };
|
|
3018
|
-
if (tenantId) {
|
|
3019
|
-
filter.tenant_id = tenantId;
|
|
3020
|
-
}
|
|
3281
|
+
const filter = { metadata_id: metadataId, ...baseWhere };
|
|
3021
3282
|
try {
|
|
3022
3283
|
const historyRecords = await driver.find(historyTableName, {
|
|
3023
3284
|
object: historyTableName,
|
|
@@ -3091,20 +3352,22 @@ var HistoryCleanupManager = class {
|
|
|
3091
3352
|
async getCleanupStats() {
|
|
3092
3353
|
const driver = this.dbLoader.driver;
|
|
3093
3354
|
const historyTableName = this.dbLoader.historyTableName;
|
|
3094
|
-
const
|
|
3355
|
+
const organizationId = this.dbLoader.organizationId;
|
|
3356
|
+
const projectId = this.dbLoader.projectId;
|
|
3095
3357
|
let recordsByAge = 0;
|
|
3096
3358
|
let recordsByCount = 0;
|
|
3097
3359
|
try {
|
|
3360
|
+
const baseWhere = {};
|
|
3361
|
+
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3362
|
+
if (projectId !== void 0) baseWhere.project_id = projectId;
|
|
3098
3363
|
if (this.policy.maxAgeDays) {
|
|
3099
3364
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
3100
3365
|
cutoffDate.setDate(cutoffDate.getDate() - this.policy.maxAgeDays);
|
|
3101
3366
|
const cutoffISO = cutoffDate.toISOString();
|
|
3102
3367
|
const filter = {
|
|
3103
|
-
recorded_at: { $lt: cutoffISO }
|
|
3368
|
+
recorded_at: { $lt: cutoffISO },
|
|
3369
|
+
...baseWhere
|
|
3104
3370
|
};
|
|
3105
|
-
if (tenantId) {
|
|
3106
|
-
filter.tenant_id = tenantId;
|
|
3107
|
-
}
|
|
3108
3371
|
recordsByAge = await driver.count(historyTableName, {
|
|
3109
3372
|
object: historyTableName,
|
|
3110
3373
|
where: filter
|
|
@@ -3113,7 +3376,7 @@ var HistoryCleanupManager = class {
|
|
|
3113
3376
|
if (this.policy.maxVersions) {
|
|
3114
3377
|
const metadataIds = await driver.find(historyTableName, {
|
|
3115
3378
|
object: historyTableName,
|
|
3116
|
-
where:
|
|
3379
|
+
where: baseWhere,
|
|
3117
3380
|
fields: ["metadata_id"]
|
|
3118
3381
|
});
|
|
3119
3382
|
const uniqueIds = /* @__PURE__ */ new Set();
|
|
@@ -3123,10 +3386,7 @@ var HistoryCleanupManager = class {
|
|
|
3123
3386
|
}
|
|
3124
3387
|
}
|
|
3125
3388
|
for (const metadataId of uniqueIds) {
|
|
3126
|
-
const filter = { metadata_id: metadataId };
|
|
3127
|
-
if (tenantId) {
|
|
3128
|
-
filter.tenant_id = tenantId;
|
|
3129
|
-
}
|
|
3389
|
+
const filter = { metadata_id: metadataId, ...baseWhere };
|
|
3130
3390
|
const count = await driver.count(historyTableName, {
|
|
3131
3391
|
object: historyTableName,
|
|
3132
3392
|
where: filter
|
|
@@ -3210,6 +3470,7 @@ var MigrationExecutor = class {
|
|
|
3210
3470
|
MemoryLoader,
|
|
3211
3471
|
MetadataManager,
|
|
3212
3472
|
MetadataPlugin,
|
|
3473
|
+
MetadataProjector,
|
|
3213
3474
|
Migration,
|
|
3214
3475
|
RemoteLoader,
|
|
3215
3476
|
SysMetadataHistoryObject,
|