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