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