@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/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/objects/sys-metadata.object.ts
225
- var import_data = require("@objectstack/spec/data");
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.tenantId = options.tenantId;
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
- * Always includes tenantId when configured.
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.tenantId) {
658
- filter.tenant_id = this.tenantId;
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.tenantId ? { tenantId: this.tenantId } : {}
795
+ ...this.organizationId ? { organizationId: this.organizationId } : {},
796
+ ...this.projectId !== void 0 ? { projectId: this.projectId } : {}
699
797
  };
700
798
  try {
701
- await this.driver.create(this.historyTableName, {
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.tenantId ? { tenant_id: this.tenantId } : {}
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
- tenantId: row.tenant_id,
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.driver.findOne(this.tableName, {
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.driver.find(this.tableName, {
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.driver.count(this.tableName, {
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.driver.findOne(this.tableName, {
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.driver.find(this.tableName, {
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.driver.findOne(this.tableName, {
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.tenantId) {
864
- filter.tenant_id = this.tenantId;
957
+ if (this.organizationId) {
958
+ filter.organization_id = this.organizationId;
865
959
  }
866
- const row = await this.driver.findOne(this.historyTableName, {
867
- object: this.historyTableName,
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
- tenantId: row.tenant_id,
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.driver.findOne(this.tableName, {
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.driver.update(this.tableName, existing.id, {
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.driver.findOne(this.tableName, {
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.driver.update(this.tableName, existing.id, {
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.driver.create(this.tableName, {
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.tenantId ? { tenant_id: this.tenantId } : {},
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.driver.findOne(this.tableName, {
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.driver.delete(this.tableName, existing.id);
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
- setDatabaseDriver(driver) {
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
- const driver = dbLoader.driver;
2003
- const tableName = dbLoader.tableName;
2004
- const historyTableName = dbLoader.historyTableName;
2005
- const tenantId = dbLoader.tenantId;
2006
- const filter = { type, name };
2007
- if (tenantId) {
2008
- filter.tenant_id = tenantId;
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 tenantId = this.dbLoader.tenantId;
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 (tenantId) {
2995
- filter.tenant_id = tenantId;
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: tenantId ? { tenant_id: tenantId } : {},
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 tenantId = this.dbLoader.tenantId;
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: tenantId ? { tenant_id: tenantId } : {},
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,