@objectstack/metadata 4.0.4 → 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.js CHANGED
@@ -174,277 +174,8 @@ export default metadata;
174
174
  }
175
175
  };
176
176
 
177
- // src/objects/sys-metadata.object.ts
178
- import { ObjectSchema, Field } from "@objectstack/spec/data";
179
- var SysMetadataObject = ObjectSchema.create({
180
- namespace: "sys",
181
- name: "metadata",
182
- label: "System Metadata",
183
- pluralLabel: "System Metadata",
184
- icon: "settings",
185
- isSystem: true,
186
- description: "Stores platform and user-scope metadata records (objects, views, flows, etc.)",
187
- fields: {
188
- /** Primary Key (UUID) */
189
- id: Field.text({
190
- label: "ID",
191
- required: true,
192
- readonly: true
193
- }),
194
- /** Machine name — unique identifier used in code references */
195
- name: Field.text({
196
- label: "Name",
197
- required: true,
198
- searchable: true,
199
- maxLength: 255
200
- }),
201
- /** Metadata type (e.g. "object", "view", "flow") */
202
- type: Field.text({
203
- label: "Metadata Type",
204
- required: true,
205
- searchable: true,
206
- maxLength: 100
207
- }),
208
- /** Namespace / module grouping (e.g. "crm", "core") */
209
- namespace: Field.text({
210
- label: "Namespace",
211
- required: false,
212
- defaultValue: "default",
213
- maxLength: 100
214
- }),
215
- /** Package that owns/delivered this metadata */
216
- package_id: Field.text({
217
- label: "Package ID",
218
- required: false,
219
- maxLength: 255
220
- }),
221
- /** Who manages this record: package, platform, or user */
222
- managed_by: Field.select(["package", "platform", "user"], {
223
- label: "Managed By",
224
- required: false
225
- }),
226
- /** Scope: system (code), platform (admin DB), user (personal DB) */
227
- scope: Field.select(["system", "platform", "user"], {
228
- label: "Scope",
229
- required: true,
230
- defaultValue: "platform"
231
- }),
232
- /** JSON payload — the actual metadata configuration */
233
- metadata: Field.textarea({
234
- label: "Metadata",
235
- required: true,
236
- description: "JSON-serialized metadata payload"
237
- }),
238
- /** Parent metadata name for extension/override */
239
- extends: Field.text({
240
- label: "Extends",
241
- required: false,
242
- maxLength: 255
243
- }),
244
- /** Merge strategy when extending parent metadata */
245
- strategy: Field.select(["merge", "replace"], {
246
- label: "Strategy",
247
- required: false,
248
- defaultValue: "merge"
249
- }),
250
- /** Owner user ID (for user-scope items) */
251
- owner: Field.text({
252
- label: "Owner",
253
- required: false,
254
- maxLength: 255
255
- }),
256
- /** Lifecycle state */
257
- state: Field.select(["draft", "active", "archived", "deprecated"], {
258
- label: "State",
259
- required: false,
260
- defaultValue: "active"
261
- }),
262
- /** Tenant ID for multi-tenant isolation */
263
- tenant_id: Field.text({
264
- label: "Tenant ID",
265
- required: false,
266
- maxLength: 255
267
- }),
268
- /** Version number for optimistic concurrency */
269
- version: Field.number({
270
- label: "Version",
271
- required: false,
272
- defaultValue: 1
273
- }),
274
- /** Content checksum for change detection */
275
- checksum: Field.text({
276
- label: "Checksum",
277
- required: false,
278
- maxLength: 64
279
- }),
280
- /** Origin of this metadata record */
281
- source: Field.select(["filesystem", "database", "api", "migration"], {
282
- label: "Source",
283
- required: false
284
- }),
285
- /** Classification tags (JSON array) */
286
- tags: Field.textarea({
287
- label: "Tags",
288
- required: false,
289
- description: "JSON-serialized array of classification tags"
290
- }),
291
- /** Audit fields */
292
- created_by: Field.text({
293
- label: "Created By",
294
- required: false,
295
- readonly: true,
296
- maxLength: 255
297
- }),
298
- created_at: Field.datetime({
299
- label: "Created At",
300
- required: false,
301
- readonly: true
302
- }),
303
- updated_by: Field.text({
304
- label: "Updated By",
305
- required: false,
306
- maxLength: 255
307
- }),
308
- updated_at: Field.datetime({
309
- label: "Updated At",
310
- required: false
311
- })
312
- },
313
- indexes: [
314
- { fields: ["type", "name"], unique: true },
315
- { fields: ["type", "scope"] },
316
- { fields: ["tenant_id"] },
317
- { fields: ["state"] },
318
- { fields: ["namespace"] }
319
- ],
320
- enable: {
321
- trackHistory: true,
322
- searchable: false,
323
- apiEnabled: true,
324
- apiMethods: ["get", "list", "create", "update", "delete"],
325
- trash: false
326
- }
327
- });
328
-
329
- // src/objects/sys-metadata-history.object.ts
330
- import { ObjectSchema as ObjectSchema2, Field as Field2 } from "@objectstack/spec/data";
331
- var SysMetadataHistoryObject = ObjectSchema2.create({
332
- namespace: "sys",
333
- name: "metadata_history",
334
- label: "Metadata History",
335
- pluralLabel: "Metadata History",
336
- icon: "history",
337
- isSystem: true,
338
- description: "Version history and audit trail for metadata changes",
339
- fields: {
340
- /** Primary Key (UUID) */
341
- id: Field2.text({
342
- label: "ID",
343
- required: true,
344
- readonly: true
345
- }),
346
- /** Foreign key to sys_metadata.id */
347
- metadata_id: Field2.text({
348
- label: "Metadata ID",
349
- required: true,
350
- readonly: true,
351
- maxLength: 255
352
- }),
353
- /** Machine name (denormalized for easier querying) */
354
- name: Field2.text({
355
- label: "Name",
356
- required: true,
357
- searchable: true,
358
- readonly: true,
359
- maxLength: 255
360
- }),
361
- /** Metadata type (denormalized for easier querying) */
362
- type: Field2.text({
363
- label: "Metadata Type",
364
- required: true,
365
- searchable: true,
366
- readonly: true,
367
- maxLength: 100
368
- }),
369
- /** Version number at this snapshot */
370
- version: Field2.number({
371
- label: "Version",
372
- required: true,
373
- readonly: true
374
- }),
375
- /** Type of operation that created this history entry */
376
- operation_type: Field2.select(["create", "update", "publish", "revert", "delete"], {
377
- label: "Operation Type",
378
- required: true,
379
- readonly: true
380
- }),
381
- /** Historical metadata snapshot (JSON payload) */
382
- metadata: Field2.textarea({
383
- label: "Metadata",
384
- required: true,
385
- readonly: true,
386
- description: "JSON-serialized metadata snapshot at this version"
387
- }),
388
- /** SHA-256 checksum of metadata content */
389
- checksum: Field2.text({
390
- label: "Checksum",
391
- required: true,
392
- readonly: true,
393
- maxLength: 64
394
- }),
395
- /** Checksum of the previous version */
396
- previous_checksum: Field2.text({
397
- label: "Previous Checksum",
398
- required: false,
399
- readonly: true,
400
- maxLength: 64
401
- }),
402
- /** Human-readable description of changes */
403
- change_note: Field2.textarea({
404
- label: "Change Note",
405
- required: false,
406
- readonly: true,
407
- description: "Description of what changed in this version"
408
- }),
409
- /** Tenant ID for multi-tenant isolation */
410
- tenant_id: Field2.text({
411
- label: "Tenant ID",
412
- required: false,
413
- readonly: true,
414
- maxLength: 255
415
- }),
416
- /** User who made this change */
417
- recorded_by: Field2.text({
418
- label: "Recorded By",
419
- required: false,
420
- readonly: true,
421
- maxLength: 255
422
- }),
423
- /** When was this version recorded */
424
- recorded_at: Field2.datetime({
425
- label: "Recorded At",
426
- required: true,
427
- readonly: true
428
- })
429
- },
430
- indexes: [
431
- { fields: ["metadata_id", "version"], unique: true },
432
- { fields: ["metadata_id", "recorded_at"] },
433
- { fields: ["type", "name"] },
434
- { fields: ["recorded_at"] },
435
- { fields: ["operation_type"] },
436
- { fields: ["tenant_id"] }
437
- ],
438
- enable: {
439
- trackHistory: false,
440
- // Don't track history of history records
441
- searchable: false,
442
- apiEnabled: true,
443
- apiMethods: ["get", "list"],
444
- // Read-only via API
445
- trash: false
446
- }
447
- });
177
+ // src/loaders/database-loader.ts
178
+ import { SysMetadataObject, SysMetadataHistoryObject } from "@objectstack/platform-objects/metadata";
448
179
 
449
180
  // src/utils/metadata-history-utils.ts
450
181
  async function calculateChecksum(metadata) {
@@ -544,6 +275,308 @@ function generateDiffSummary(diff) {
544
275
  return summary.join(", ");
545
276
  }
546
277
 
278
+ // src/projection/metadata-projector.ts
279
+ import { StorageNameMapping } from "@objectstack/spec/system";
280
+ var MetadataProjector = class {
281
+ constructor(options) {
282
+ // Map of metadata types to their target table names
283
+ this.typeTableMap = {
284
+ object: "sys_object",
285
+ view: "sys_view",
286
+ agent: "sys_agent",
287
+ tool: "sys_tool",
288
+ flow: "sys_flow"
289
+ // Add more as needed: dashboard, app, action, workflow, etc.
290
+ };
291
+ if (!options.driver && !options.engine) {
292
+ throw new Error("MetadataProjector requires either a driver or engine");
293
+ }
294
+ this.driver = options.driver;
295
+ this.engine = options.engine;
296
+ this.scope = {
297
+ organizationId: options.organizationId,
298
+ projectId: options.projectId
299
+ };
300
+ }
301
+ /**
302
+ * Project metadata to type-specific table
303
+ */
304
+ async project(type, name, data) {
305
+ const targetTable = this.typeTableMap[type];
306
+ if (!targetTable) {
307
+ return;
308
+ }
309
+ const projectedData = this.transformToProjection(type, name, data);
310
+ if (!projectedData) {
311
+ return;
312
+ }
313
+ try {
314
+ const projId = this.scope.projectId ?? null;
315
+ const existing = await this._findOne(targetTable, {
316
+ where: { name, project_id: projId }
317
+ });
318
+ if (existing) {
319
+ await this._update(targetTable, existing.id, projectedData);
320
+ } else {
321
+ const id = this.generateId();
322
+ await this._create(targetTable, {
323
+ id,
324
+ ...projectedData
325
+ });
326
+ }
327
+ } catch (error) {
328
+ console.error(`Failed to project ${type}/${name} to ${targetTable}:`, error);
329
+ }
330
+ }
331
+ /**
332
+ * Delete projection from type-specific table
333
+ */
334
+ async deleteProjection(type, name) {
335
+ const targetTable = this.typeTableMap[type];
336
+ if (!targetTable) {
337
+ return;
338
+ }
339
+ try {
340
+ const projId = this.scope.projectId ?? null;
341
+ const existing = await this._findOne(targetTable, {
342
+ where: { name, project_id: projId }
343
+ });
344
+ if (existing) {
345
+ await this._delete(targetTable, existing.id);
346
+ }
347
+ } catch (error) {
348
+ console.error(`Failed to delete projection ${type}/${name} from ${targetTable}:`, error);
349
+ }
350
+ }
351
+ /**
352
+ * Transform metadata into projection record
353
+ */
354
+ transformToProjection(type, name, data) {
355
+ const now = (/* @__PURE__ */ new Date()).toISOString();
356
+ switch (type) {
357
+ case "object":
358
+ return this.projectObject(name, data, now);
359
+ case "view":
360
+ return this.projectView(name, data, now);
361
+ case "agent":
362
+ return this.projectAgent(name, data, now);
363
+ case "tool":
364
+ return this.projectTool(name, data, now);
365
+ case "flow":
366
+ return this.projectFlow(name, data, now);
367
+ default:
368
+ return null;
369
+ }
370
+ }
371
+ /**
372
+ * Project object metadata to sys_object
373
+ */
374
+ projectObject(name, data, now) {
375
+ return {
376
+ name,
377
+ project_id: this.scope.projectId ?? null,
378
+ label: data.label || name,
379
+ plural_label: data.pluralLabel || data.label || name,
380
+ description: data.description || "",
381
+ icon: data.icon || "database",
382
+ namespace: data.namespace || "default",
383
+ tags: Array.isArray(data.tags) ? data.tags.join(",") : data.tags || "",
384
+ active: data.active !== false,
385
+ is_system: data.isSystem || false,
386
+ abstract: data.abstract || false,
387
+ datasource: data.datasource || "default",
388
+ table_name: data.name ? StorageNameMapping.resolveTableName({ name: data.name }) : name,
389
+ // Serialize complex structures as JSON
390
+ fields_json: data.fields ? JSON.stringify(data.fields) : null,
391
+ indexes_json: data.indexes ? JSON.stringify(data.indexes) : null,
392
+ validations_json: data.validations ? JSON.stringify(data.validations) : null,
393
+ state_machines_json: data.stateMachines ? JSON.stringify(data.stateMachines) : null,
394
+ capabilities_json: data.enable ? JSON.stringify(data.enable) : null,
395
+ // Denormalized fields
396
+ field_count: data.fields ? Object.keys(data.fields).length : 0,
397
+ display_name_field: data.displayNameField || null,
398
+ title_format: data.titleFormat || null,
399
+ compact_layout: Array.isArray(data.compactLayout) ? data.compactLayout.join(",") : data.compactLayout || null,
400
+ // Capabilities (denormalized for easier querying)
401
+ track_history: data.enable?.trackHistory || false,
402
+ searchable: data.enable?.searchable !== false,
403
+ api_enabled: data.enable?.apiEnabled !== false,
404
+ files: data.enable?.files || false,
405
+ feeds: data.enable?.feeds || false,
406
+ activities: data.enable?.activities || false,
407
+ trash: data.enable?.trash !== false,
408
+ mru: data.enable?.mru !== false,
409
+ clone: data.enable?.clone !== false,
410
+ // Package management
411
+ package_id: data.packageId || null,
412
+ managed_by: data.managedBy || "user",
413
+ // Audit
414
+ created_by: data.createdBy || null,
415
+ created_at: data.createdAt || now,
416
+ updated_by: data.updatedBy || null,
417
+ updated_at: now
418
+ };
419
+ }
420
+ /**
421
+ * Project view metadata to sys_view
422
+ */
423
+ projectView(name, data, now) {
424
+ return {
425
+ name,
426
+ project_id: this.scope.projectId ?? null,
427
+ label: data.label || name,
428
+ description: data.description || "",
429
+ object_name: data.object || "",
430
+ view_type: data.type || "grid",
431
+ // Serialize configurations as JSON
432
+ columns_json: data.columns ? JSON.stringify(data.columns) : null,
433
+ filters_json: data.filters ? JSON.stringify(data.filters) : null,
434
+ sort_json: data.sort ? JSON.stringify(data.sort) : null,
435
+ config_json: data.config ? JSON.stringify(data.config) : null,
436
+ // Display options
437
+ page_size: data.pageSize || 25,
438
+ show_search: data.showSearch !== false,
439
+ show_filters: data.showFilters !== false,
440
+ // Classification
441
+ namespace: data.namespace || "default",
442
+ // Package management
443
+ package_id: data.packageId || null,
444
+ managed_by: data.managedBy || "user",
445
+ // Audit
446
+ created_by: data.createdBy || null,
447
+ created_at: data.createdAt || now,
448
+ updated_by: data.updatedBy || null,
449
+ updated_at: now
450
+ };
451
+ }
452
+ /**
453
+ * Project agent metadata to sys_agent
454
+ */
455
+ projectAgent(name, data, now) {
456
+ return {
457
+ name,
458
+ project_id: this.scope.projectId ?? null,
459
+ label: data.label || name,
460
+ description: data.description || "",
461
+ agent_type: data.type || "conversational",
462
+ // Model configuration
463
+ model: data.model || null,
464
+ temperature: data.temperature ?? 0.7,
465
+ max_tokens: data.maxTokens || null,
466
+ top_p: data.topP || null,
467
+ // System prompt
468
+ system_prompt: data.systemPrompt || null,
469
+ // Tools and skills as JSON
470
+ tools_json: data.tools ? JSON.stringify(data.tools) : null,
471
+ skills_json: data.skills ? JSON.stringify(data.skills) : null,
472
+ // Memory
473
+ memory_enabled: data.memoryEnabled || false,
474
+ memory_window: data.memoryWindow || 10,
475
+ // Classification
476
+ namespace: data.namespace || "default",
477
+ // Package management
478
+ package_id: data.packageId || null,
479
+ managed_by: data.managedBy || "user",
480
+ // Audit
481
+ created_by: data.createdBy || null,
482
+ created_at: data.createdAt || now,
483
+ updated_by: data.updatedBy || null,
484
+ updated_at: now
485
+ };
486
+ }
487
+ /**
488
+ * Project tool metadata to sys_tool
489
+ */
490
+ projectTool(name, data, now) {
491
+ return {
492
+ name,
493
+ project_id: this.scope.projectId ?? null,
494
+ label: data.label || name,
495
+ description: data.description || "",
496
+ // Parameters and implementation
497
+ parameters_json: data.parameters ? JSON.stringify(data.parameters) : null,
498
+ handler_code: data.handler || null,
499
+ // Classification
500
+ namespace: data.namespace || "default",
501
+ // Package management
502
+ package_id: data.packageId || null,
503
+ managed_by: data.managedBy || "user",
504
+ // Audit
505
+ created_by: data.createdBy || null,
506
+ created_at: data.createdAt || now,
507
+ updated_by: data.updatedBy || null,
508
+ updated_at: now
509
+ };
510
+ }
511
+ /**
512
+ * Project flow metadata to sys_flow
513
+ */
514
+ projectFlow(name, data, now) {
515
+ return {
516
+ name,
517
+ project_id: this.scope.projectId ?? null,
518
+ label: data.label || name,
519
+ description: data.description || "",
520
+ flow_type: data.type || "autolaunched",
521
+ // Flow definition
522
+ nodes_json: data.nodes ? JSON.stringify(data.nodes) : null,
523
+ edges_json: data.edges ? JSON.stringify(data.edges) : null,
524
+ variables_json: data.variables ? JSON.stringify(data.variables) : null,
525
+ // Trigger configuration
526
+ trigger_type: data.triggerType || null,
527
+ trigger_object: data.triggerObject || null,
528
+ // Status
529
+ active: data.active || false,
530
+ // Classification
531
+ namespace: data.namespace || "default",
532
+ // Package management
533
+ package_id: data.packageId || null,
534
+ managed_by: data.managedBy || "user",
535
+ // Audit
536
+ created_by: data.createdBy || null,
537
+ created_at: data.createdAt || now,
538
+ updated_by: data.updatedBy || null,
539
+ updated_at: now
540
+ };
541
+ }
542
+ // ==========================================
543
+ // Internal CRUD helpers (driver vs engine)
544
+ // ==========================================
545
+ async _findOne(table, query) {
546
+ if (this.engine) {
547
+ return this.engine.findOne(table, query);
548
+ }
549
+ return this.driver.findOne(table, { object: table, ...query });
550
+ }
551
+ async _create(table, data) {
552
+ if (this.engine) {
553
+ return this.engine.insert(table, data);
554
+ }
555
+ return this.driver.create(table, data);
556
+ }
557
+ async _update(table, id, data) {
558
+ if (this.engine) {
559
+ return this.engine.update(table, { id, ...data });
560
+ }
561
+ return this.driver.update(table, id, data);
562
+ }
563
+ async _delete(table, id) {
564
+ if (this.engine) {
565
+ return this.engine.delete(table, { where: { id } });
566
+ }
567
+ return this.driver.delete(table, id);
568
+ }
569
+ /**
570
+ * Generate a simple unique ID
571
+ */
572
+ generateId() {
573
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
574
+ return globalThis.crypto.randomUUID();
575
+ }
576
+ return `proj_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
577
+ }
578
+ };
579
+
547
580
  // src/loaders/database-loader.ts
548
581
  var DatabaseLoader = class {
549
582
  constructor(options) {
@@ -559,11 +592,64 @@ var DatabaseLoader = class {
559
592
  };
560
593
  this.schemaReady = false;
561
594
  this.historySchemaReady = false;
595
+ if (!options.driver && !options.engine) {
596
+ throw new Error("DatabaseLoader requires either a driver or engine");
597
+ }
562
598
  this.driver = options.driver;
599
+ this.engine = options.engine;
563
600
  this.tableName = options.tableName ?? "sys_metadata";
564
601
  this.historyTableName = options.historyTableName ?? "sys_metadata_history";
565
- this.tenantId = options.tenantId;
602
+ this.organizationId = options.organizationId;
603
+ this.projectId = options.projectId;
566
604
  this.trackHistory = options.trackHistory !== false;
605
+ this.enableProjection = options.enableProjection !== false;
606
+ if (this.enableProjection) {
607
+ this.projector = new MetadataProjector({
608
+ driver: this.driver,
609
+ engine: this.engine,
610
+ organizationId: this.organizationId,
611
+ projectId: this.projectId
612
+ });
613
+ }
614
+ }
615
+ // ==========================================
616
+ // Internal CRUD helpers (driver vs engine)
617
+ // ==========================================
618
+ async _find(table, query) {
619
+ if (this.engine) {
620
+ return this.engine.find(table, query);
621
+ }
622
+ return this.driver.find(table, { object: table, ...query });
623
+ }
624
+ async _findOne(table, query) {
625
+ if (this.engine) {
626
+ return this.engine.findOne(table, query);
627
+ }
628
+ return this.driver.findOne(table, { object: table, ...query });
629
+ }
630
+ async _count(table, query) {
631
+ if (this.engine) {
632
+ return this.engine.count(table, query);
633
+ }
634
+ return this.driver.count(table, { object: table, ...query });
635
+ }
636
+ async _create(table, data) {
637
+ if (this.engine) {
638
+ return this.engine.insert(table, data);
639
+ }
640
+ return this.driver.create(table, data);
641
+ }
642
+ async _update(table, id, data) {
643
+ if (this.engine) {
644
+ return this.engine.update(table, { id, ...data });
645
+ }
646
+ return this.driver.update(table, id, data);
647
+ }
648
+ async _delete(table, id) {
649
+ if (this.engine) {
650
+ return this.engine.delete(table, { where: { id } });
651
+ }
652
+ return this.driver.delete(table, id);
567
653
  }
568
654
  /**
569
655
  * Ensure the metadata table exists.
@@ -572,6 +658,10 @@ var DatabaseLoader = class {
572
658
  */
573
659
  async ensureSchema() {
574
660
  if (this.schemaReady) return;
661
+ if (this.engine) {
662
+ this.schemaReady = true;
663
+ return;
664
+ }
575
665
  try {
576
666
  await this.driver.syncSchema(this.tableName, {
577
667
  ...SysMetadataObject,
@@ -588,6 +678,10 @@ var DatabaseLoader = class {
588
678
  */
589
679
  async ensureHistorySchema() {
590
680
  if (!this.trackHistory || this.historySchemaReady) return;
681
+ if (this.engine) {
682
+ this.historySchemaReady = true;
683
+ return;
684
+ }
591
685
  try {
592
686
  await this.driver.syncSchema(this.historyTableName, {
593
687
  ...SysMetadataHistoryObject,
@@ -600,16 +694,18 @@ var DatabaseLoader = class {
600
694
  }
601
695
  /**
602
696
  * Build base filter conditions for queries.
603
- * Always includes tenantId when configured.
697
+ * Filters by organizationId when configured; project_id when projectId is set,
698
+ * or null (platform-global) when not set.
604
699
  */
605
700
  baseFilter(type, name) {
606
701
  const filter = { type };
607
702
  if (name !== void 0) {
608
703
  filter.name = name;
609
704
  }
610
- if (this.tenantId) {
611
- filter.tenant_id = this.tenantId;
705
+ if (this.organizationId) {
706
+ filter.organization_id = this.organizationId;
612
707
  }
708
+ filter.project_id = this.projectId ?? null;
613
709
  return filter;
614
710
  }
615
711
  /**
@@ -648,10 +744,11 @@ var DatabaseLoader = class {
648
744
  changeNote,
649
745
  recordedBy,
650
746
  recordedAt: now,
651
- ...this.tenantId ? { tenantId: this.tenantId } : {}
747
+ ...this.organizationId ? { organizationId: this.organizationId } : {},
748
+ ...this.projectId !== void 0 ? { projectId: this.projectId } : {}
652
749
  };
653
750
  try {
654
- await this.driver.create(this.historyTableName, {
751
+ await this._create(this.historyTableName, {
655
752
  id: historyRecord.id,
656
753
  metadata_id: historyRecord.metadataId,
657
754
  name: historyRecord.name,
@@ -664,7 +761,8 @@ var DatabaseLoader = class {
664
761
  change_note: historyRecord.changeNote,
665
762
  recorded_by: historyRecord.recordedBy,
666
763
  recorded_at: historyRecord.recordedAt,
667
- ...this.tenantId ? { tenant_id: this.tenantId } : {}
764
+ ...this.organizationId ? { organization_id: this.organizationId } : {},
765
+ ...this.projectId !== void 0 ? { project_id: this.projectId } : {}
668
766
  });
669
767
  } catch (error) {
670
768
  console.error(`Failed to create history record for ${type}/${name}:`, error);
@@ -696,7 +794,8 @@ var DatabaseLoader = class {
696
794
  strategy: row.strategy ?? "merge",
697
795
  owner: row.owner,
698
796
  state: row.state ?? "active",
699
- tenantId: row.tenant_id,
797
+ organizationId: row.organization_id,
798
+ projectId: row.project_id,
700
799
  version: row.version ?? 1,
701
800
  checksum: row.checksum,
702
801
  source: row.source,
@@ -714,8 +813,7 @@ var DatabaseLoader = class {
714
813
  const startTime = Date.now();
715
814
  await this.ensureSchema();
716
815
  try {
717
- const row = await this.driver.findOne(this.tableName, {
718
- object: this.tableName,
816
+ const row = await this._findOne(this.tableName, {
719
817
  where: this.baseFilter(type, name)
720
818
  });
721
819
  if (!row) {
@@ -743,8 +841,7 @@ var DatabaseLoader = class {
743
841
  async loadMany(type, _options) {
744
842
  await this.ensureSchema();
745
843
  try {
746
- const rows = await this.driver.find(this.tableName, {
747
- object: this.tableName,
844
+ const rows = await this._find(this.tableName, {
748
845
  where: this.baseFilter(type)
749
846
  });
750
847
  return rows.map((row) => this.rowToData(row)).filter((data) => data !== null);
@@ -755,8 +852,7 @@ var DatabaseLoader = class {
755
852
  async exists(type, name) {
756
853
  await this.ensureSchema();
757
854
  try {
758
- const count = await this.driver.count(this.tableName, {
759
- object: this.tableName,
855
+ const count = await this._count(this.tableName, {
760
856
  where: this.baseFilter(type, name)
761
857
  });
762
858
  return count > 0;
@@ -767,8 +863,7 @@ var DatabaseLoader = class {
767
863
  async stat(type, name) {
768
864
  await this.ensureSchema();
769
865
  try {
770
- const row = await this.driver.findOne(this.tableName, {
771
- object: this.tableName,
866
+ const row = await this._findOne(this.tableName, {
772
867
  where: this.baseFilter(type, name)
773
868
  });
774
869
  if (!row) return null;
@@ -787,8 +882,7 @@ var DatabaseLoader = class {
787
882
  async list(type) {
788
883
  await this.ensureSchema();
789
884
  try {
790
- const rows = await this.driver.find(this.tableName, {
791
- object: this.tableName,
885
+ const rows = await this._find(this.tableName, {
792
886
  where: this.baseFilter(type),
793
887
  fields: ["name"]
794
888
  });
@@ -804,8 +898,7 @@ var DatabaseLoader = class {
804
898
  async getHistoryRecord(type, name, version) {
805
899
  if (!this.trackHistory) return null;
806
900
  await this.ensureHistorySchema();
807
- const metadataRow = await this.driver.findOne(this.tableName, {
808
- object: this.tableName,
901
+ const metadataRow = await this._findOne(this.tableName, {
809
902
  where: this.baseFilter(type, name)
810
903
  });
811
904
  if (!metadataRow) return null;
@@ -813,11 +906,11 @@ var DatabaseLoader = class {
813
906
  metadata_id: metadataRow.id,
814
907
  version
815
908
  };
816
- if (this.tenantId) {
817
- filter.tenant_id = this.tenantId;
909
+ if (this.organizationId) {
910
+ filter.organization_id = this.organizationId;
818
911
  }
819
- const row = await this.driver.findOne(this.historyTableName, {
820
- object: this.historyTableName,
912
+ filter.project_id = this.projectId ?? null;
913
+ const row = await this._findOne(this.historyTableName, {
821
914
  where: filter
822
915
  });
823
916
  if (!row) return null;
@@ -832,11 +925,80 @@ var DatabaseLoader = class {
832
925
  checksum: row.checksum,
833
926
  previousChecksum: row.previous_checksum,
834
927
  changeNote: row.change_note,
835
- tenantId: row.tenant_id,
928
+ organizationId: row.organization_id,
929
+ projectId: row.project_id,
836
930
  recordedBy: row.recorded_by,
837
931
  recordedAt: row.recorded_at
838
932
  };
839
933
  }
934
+ /**
935
+ * Query history records with pagination and filtering.
936
+ * Encapsulates history table queries so MetadataManager doesn't need
937
+ * direct driver access.
938
+ */
939
+ async queryHistory(type, name, options) {
940
+ if (!this.trackHistory) {
941
+ return { records: [], total: 0, hasMore: false };
942
+ }
943
+ await this.ensureSchema();
944
+ await this.ensureHistorySchema();
945
+ const filter = { type, name };
946
+ if (this.organizationId) filter.organization_id = this.organizationId;
947
+ filter.project_id = this.projectId ?? null;
948
+ const metadataRecord = await this._findOne(this.tableName, { where: filter });
949
+ if (!metadataRecord) {
950
+ return { records: [], total: 0, hasMore: false };
951
+ }
952
+ const historyFilter = {
953
+ metadata_id: metadataRecord.id
954
+ };
955
+ if (this.organizationId) historyFilter.organization_id = this.organizationId;
956
+ historyFilter.project_id = this.projectId ?? null;
957
+ if (options?.operationType) historyFilter.operation_type = options.operationType;
958
+ if (options?.since) historyFilter.recorded_at = { $gte: options.since };
959
+ if (options?.until) {
960
+ if (historyFilter.recorded_at) {
961
+ historyFilter.recorded_at.$lte = options.until;
962
+ } else {
963
+ historyFilter.recorded_at = { $lte: options.until };
964
+ }
965
+ }
966
+ const limit = options?.limit ?? 50;
967
+ const offset = options?.offset ?? 0;
968
+ const historyRecords = await this._find(this.historyTableName, {
969
+ where: historyFilter,
970
+ orderBy: [
971
+ { field: "recorded_at", order: "desc" },
972
+ { field: "version", order: "desc" }
973
+ ],
974
+ limit: limit + 1,
975
+ offset
976
+ });
977
+ const hasMore = historyRecords.length > limit;
978
+ const records = historyRecords.slice(0, limit);
979
+ const total = await this._count(this.historyTableName, { where: historyFilter });
980
+ const includeMetadata = options?.includeMetadata !== false;
981
+ const result = records.map((row) => {
982
+ const parsedMetadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata;
983
+ return {
984
+ id: row.id,
985
+ metadataId: row.metadata_id,
986
+ name: row.name,
987
+ type: row.type,
988
+ version: row.version,
989
+ operationType: row.operation_type,
990
+ metadata: includeMetadata ? parsedMetadata : null,
991
+ checksum: row.checksum,
992
+ previousChecksum: row.previous_checksum,
993
+ changeNote: row.change_note,
994
+ organizationId: row.organization_id,
995
+ projectId: row.project_id,
996
+ recordedBy: row.recorded_by,
997
+ recordedAt: row.recorded_at
998
+ };
999
+ });
1000
+ return { records: result, total, hasMore };
1001
+ }
840
1002
  /**
841
1003
  * Perform a rollback: persist `restoredData` as the new current state and record a
842
1004
  * single 'revert' history entry (instead of the usual 'update' entry that `save()`
@@ -849,8 +1011,7 @@ var DatabaseLoader = class {
849
1011
  const now = (/* @__PURE__ */ new Date()).toISOString();
850
1012
  const metadataJson = JSON.stringify(restoredData);
851
1013
  const newChecksum = await calculateChecksum(restoredData);
852
- const existing = await this.driver.findOne(this.tableName, {
853
- object: this.tableName,
1014
+ const existing = await this._findOne(this.tableName, {
854
1015
  where: this.baseFilter(type, name)
855
1016
  });
856
1017
  if (!existing) {
@@ -858,7 +1019,7 @@ var DatabaseLoader = class {
858
1019
  }
859
1020
  const previousChecksum = existing.checksum;
860
1021
  const newVersion = (existing.version ?? 0) + 1;
861
- await this.driver.update(this.tableName, existing.id, {
1022
+ await this._update(this.tableName, existing.id, {
862
1023
  metadata: metadataJson,
863
1024
  version: newVersion,
864
1025
  checksum: newChecksum,
@@ -884,8 +1045,7 @@ var DatabaseLoader = class {
884
1045
  const metadataJson = JSON.stringify(data);
885
1046
  const newChecksum = await calculateChecksum(data);
886
1047
  try {
887
- const existing = await this.driver.findOne(this.tableName, {
888
- object: this.tableName,
1048
+ const existing = await this._findOne(this.tableName, {
889
1049
  where: this.baseFilter(type, name)
890
1050
  });
891
1051
  if (existing) {
@@ -899,7 +1059,7 @@ var DatabaseLoader = class {
899
1059
  };
900
1060
  }
901
1061
  const version = (existing.version ?? 0) + 1;
902
- await this.driver.update(this.tableName, existing.id, {
1062
+ await this._update(this.tableName, existing.id, {
903
1063
  metadata: metadataJson,
904
1064
  version,
905
1065
  checksum: newChecksum,
@@ -915,6 +1075,9 @@ var DatabaseLoader = class {
915
1075
  "update",
916
1076
  previousChecksum
917
1077
  );
1078
+ if (this.projector) {
1079
+ await this.projector.project(type, name, data);
1080
+ }
918
1081
  return {
919
1082
  success: true,
920
1083
  path: `datasource://${this.tableName}/${type}/${name}`,
@@ -923,7 +1086,7 @@ var DatabaseLoader = class {
923
1086
  };
924
1087
  } else {
925
1088
  const id = generateId();
926
- await this.driver.create(this.tableName, {
1089
+ await this._create(this.tableName, {
927
1090
  id,
928
1091
  name,
929
1092
  type,
@@ -935,7 +1098,8 @@ var DatabaseLoader = class {
935
1098
  state: "active",
936
1099
  version: 1,
937
1100
  source: "database",
938
- ...this.tenantId ? { tenant_id: this.tenantId } : {},
1101
+ ...this.organizationId ? { organization_id: this.organizationId } : {},
1102
+ ...this.projectId !== void 0 ? { project_id: this.projectId } : { project_id: null },
939
1103
  created_at: now,
940
1104
  updated_at: now
941
1105
  });
@@ -947,6 +1111,9 @@ var DatabaseLoader = class {
947
1111
  data,
948
1112
  "create"
949
1113
  );
1114
+ if (this.projector) {
1115
+ await this.projector.project(type, name, data);
1116
+ }
950
1117
  return {
951
1118
  success: true,
952
1119
  path: `datasource://${this.tableName}/${type}/${name}`,
@@ -965,14 +1132,16 @@ var DatabaseLoader = class {
965
1132
  */
966
1133
  async delete(type, name) {
967
1134
  await this.ensureSchema();
968
- const existing = await this.driver.findOne(this.tableName, {
969
- object: this.tableName,
1135
+ const existing = await this._findOne(this.tableName, {
970
1136
  where: this.baseFilter(type, name)
971
1137
  });
972
1138
  if (!existing) {
973
1139
  return;
974
1140
  }
975
- await this.driver.delete(this.tableName, existing.id);
1141
+ await this._delete(this.tableName, existing.id);
1142
+ if (this.projector) {
1143
+ await this.projector.deleteProjection(type, name);
1144
+ }
976
1145
  }
977
1146
  };
978
1147
  function generateId() {
@@ -1029,16 +1198,55 @@ var MetadataManager = class {
1029
1198
  * Can be called at any time to enable database storage (e.g. after kernel resolves the driver).
1030
1199
  *
1031
1200
  * @param driver - An IDataDriver instance for database operations
1032
- */
1033
- setDatabaseDriver(driver) {
1201
+ * @param organizationId - Organization ID for multi-tenant isolation
1202
+ * @param projectId - Project ID (undefined = platform-global)
1203
+ */
1204
+ setDatabaseDriver(driver, organizationId, projectId) {
1205
+ if (projectId !== void 0) {
1206
+ this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
1207
+ organizationId,
1208
+ projectId
1209
+ });
1210
+ return;
1211
+ }
1034
1212
  const tableName = this.config.tableName ?? "sys_metadata";
1035
1213
  const dbLoader = new DatabaseLoader({
1036
1214
  driver,
1037
- tableName
1215
+ tableName,
1216
+ organizationId,
1217
+ projectId
1038
1218
  });
1039
1219
  this.registerLoader(dbLoader);
1040
1220
  this.logger.info("DatabaseLoader configured", { datasource: this.config.datasource, tableName });
1041
1221
  }
1222
+ /**
1223
+ * Configure and register a DatabaseLoader backed by an IDataEngine (ObjectQL).
1224
+ * The engine handles datasource routing automatically — sys_metadata will
1225
+ * be routed to the correct driver via the standard namespace mapping.
1226
+ * No manual driver resolution needed.
1227
+ *
1228
+ * @param engine - An IDataEngine instance (typically the ObjectQL service)
1229
+ * @param organizationId - Organization ID for multi-tenant isolation
1230
+ * @param projectId - Project ID (undefined = platform-global)
1231
+ */
1232
+ setDataEngine(engine, organizationId, projectId) {
1233
+ if (projectId !== void 0) {
1234
+ this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
1235
+ organizationId,
1236
+ projectId
1237
+ });
1238
+ return;
1239
+ }
1240
+ const tableName = this.config.tableName ?? "sys_metadata";
1241
+ const dbLoader = new DatabaseLoader({
1242
+ engine,
1243
+ tableName,
1244
+ organizationId,
1245
+ projectId
1246
+ });
1247
+ this.registerLoader(dbLoader);
1248
+ this.logger.info("DatabaseLoader configured via DataEngine", { tableName });
1249
+ }
1042
1250
  /**
1043
1251
  * Set the realtime service for publishing metadata change events.
1044
1252
  * Should be called after kernel resolves the realtime service.
@@ -1952,84 +2160,14 @@ var MetadataManager = class {
1952
2160
  if (!dbLoader) {
1953
2161
  throw new Error("History tracking requires a database loader to be configured");
1954
2162
  }
1955
- const driver = dbLoader.driver;
1956
- const tableName = dbLoader.tableName;
1957
- const historyTableName = dbLoader.historyTableName;
1958
- const tenantId = dbLoader.tenantId;
1959
- const filter = { type, name };
1960
- if (tenantId) {
1961
- filter.tenant_id = tenantId;
1962
- }
1963
- const metadataRecord = await driver.findOne(tableName, {
1964
- object: tableName,
1965
- where: filter
1966
- });
1967
- if (!metadataRecord) {
1968
- return {
1969
- records: [],
1970
- total: 0,
1971
- hasMore: false
1972
- };
1973
- }
1974
- const historyFilter = {
1975
- metadata_id: metadataRecord.id
1976
- };
1977
- if (tenantId) {
1978
- historyFilter.tenant_id = tenantId;
1979
- }
1980
- if (options?.operationType) {
1981
- historyFilter.operation_type = options.operationType;
1982
- }
1983
- if (options?.since) {
1984
- historyFilter.recorded_at = { $gte: options.since };
1985
- }
1986
- if (options?.until) {
1987
- if (historyFilter.recorded_at) {
1988
- historyFilter.recorded_at.$lte = options.until;
1989
- } else {
1990
- historyFilter.recorded_at = { $lte: options.until };
1991
- }
1992
- }
1993
- const limit = options?.limit ?? 50;
1994
- const offset = options?.offset ?? 0;
1995
- const historyRecords = await driver.find(historyTableName, {
1996
- object: historyTableName,
1997
- where: historyFilter,
1998
- orderBy: [{ field: "recorded_at", order: "desc" }],
1999
- limit: limit + 1,
2000
- // Fetch one extra to determine hasMore
2001
- offset
2002
- });
2003
- const hasMore = historyRecords.length > limit;
2004
- const records = historyRecords.slice(0, limit);
2005
- const total = await driver.count(historyTableName, {
2006
- object: historyTableName,
2007
- where: historyFilter
2008
- });
2009
- const includeMetadata = options?.includeMetadata !== false;
2010
- const historyResult = records.map((row) => {
2011
- const parsedMetadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata;
2012
- return {
2013
- id: row.id,
2014
- metadataId: row.metadata_id,
2015
- name: row.name,
2016
- type: row.type,
2017
- version: row.version,
2018
- operationType: row.operation_type,
2019
- metadata: includeMetadata ? parsedMetadata : null,
2020
- checksum: row.checksum,
2021
- previousChecksum: row.previous_checksum,
2022
- changeNote: row.change_note,
2023
- tenantId: row.tenant_id,
2024
- recordedBy: row.recorded_by,
2025
- recordedAt: row.recorded_at
2026
- };
2163
+ return dbLoader.queryHistory(type, name, {
2164
+ operationType: options?.operationType,
2165
+ since: options?.since,
2166
+ until: options?.until,
2167
+ limit: options?.limit,
2168
+ offset: options?.offset,
2169
+ includeMetadata: options?.includeMetadata
2027
2170
  });
2028
- return {
2029
- records: historyResult,
2030
- total,
2031
- hasMore
2032
- };
2033
2171
  }
2034
2172
  /**
2035
2173
  * Rollback a metadata item to a specific version.
@@ -2099,6 +2237,10 @@ var MetadataManager = class {
2099
2237
  }
2100
2238
  };
2101
2239
 
2240
+ // src/plugin.ts
2241
+ import { readFile as readFile2 } from "fs/promises";
2242
+ import { createHash as createHash2 } from "crypto";
2243
+
2102
2244
  // src/node-metadata-manager.ts
2103
2245
  import * as path2 from "path";
2104
2246
  import { watch as chokidarWatch } from "chokidar";
@@ -2215,7 +2357,7 @@ var FilesystemLoader = class {
2215
2357
  );
2216
2358
  for (const pattern of globPatterns) {
2217
2359
  const files = await glob(pattern, {
2218
- ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"],
2360
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/*[*]*"],
2219
2361
  nodir: true
2220
2362
  });
2221
2363
  for (const file of files) {
@@ -2504,124 +2646,6 @@ var NodeMetadataManager = class extends MetadataManager {
2504
2646
  }
2505
2647
  };
2506
2648
 
2507
- // src/plugin.ts
2508
- import { DEFAULT_METADATA_TYPE_REGISTRY } from "@objectstack/spec/kernel";
2509
- var MetadataPlugin = class {
2510
- constructor(options = {}) {
2511
- this.name = "com.objectstack.metadata";
2512
- this.type = "standard";
2513
- this.version = "1.0.0";
2514
- this.init = async (ctx) => {
2515
- ctx.logger.info("Initializing Metadata Manager", {
2516
- root: this.options.rootDir || process.cwd(),
2517
- watch: this.options.watch
2518
- });
2519
- ctx.registerService("metadata", this.manager);
2520
- console.log("[MetadataPlugin] Registered metadata service, has getRegisteredTypes:", typeof this.manager.getRegisteredTypes);
2521
- try {
2522
- ctx.getService("manifest").register({
2523
- id: "com.objectstack.metadata",
2524
- name: "Metadata",
2525
- version: "1.0.0",
2526
- type: "plugin",
2527
- namespace: "sys",
2528
- objects: [SysMetadataObject]
2529
- });
2530
- } catch {
2531
- }
2532
- ctx.logger.info("MetadataPlugin providing metadata service (primary mode)", {
2533
- mode: "file-system",
2534
- features: ["watch", "persistence", "multi-format", "query", "overlay", "type-registry"]
2535
- });
2536
- };
2537
- this.start = async (ctx) => {
2538
- ctx.logger.info("Loading metadata from file system...");
2539
- const sortedTypes = [...DEFAULT_METADATA_TYPE_REGISTRY].sort((a, b) => a.loadOrder - b.loadOrder);
2540
- let totalLoaded = 0;
2541
- for (const entry of sortedTypes) {
2542
- try {
2543
- const items = await this.manager.loadMany(entry.type, {
2544
- recursive: true
2545
- });
2546
- if (items.length > 0) {
2547
- for (const item of items) {
2548
- const meta = item;
2549
- if (meta?.name) {
2550
- await this.manager.register(entry.type, meta.name, item);
2551
- }
2552
- }
2553
- ctx.logger.info(`Loaded ${items.length} ${entry.type} from file system`);
2554
- totalLoaded += items.length;
2555
- }
2556
- } catch (e) {
2557
- ctx.logger.debug(`No ${entry.type} metadata found`, { error: e.message });
2558
- }
2559
- }
2560
- ctx.logger.info("Metadata loading complete", {
2561
- totalItems: totalLoaded,
2562
- registeredTypes: sortedTypes.length
2563
- });
2564
- let driverBridged = false;
2565
- try {
2566
- const ql = ctx.getService("objectql");
2567
- if (ql) {
2568
- const tableName = this.manager["config"]?.tableName ?? "sys_metadata";
2569
- const driver = ql.getDriverForObject?.(tableName);
2570
- if (driver) {
2571
- ctx.logger.info("[MetadataPlugin] Bridging driver to MetadataManager via ObjectQL routing", {
2572
- tableName,
2573
- driver: driver.name
2574
- });
2575
- this.manager.setDatabaseDriver(driver);
2576
- driverBridged = true;
2577
- } else {
2578
- ctx.logger.debug("[MetadataPlugin] ObjectQL could not resolve driver for metadata table", { tableName });
2579
- }
2580
- }
2581
- } catch {
2582
- }
2583
- if (!driverBridged) {
2584
- try {
2585
- const services = ctx.getServices();
2586
- for (const [serviceName, service] of services) {
2587
- if (serviceName.startsWith("driver.") && service) {
2588
- ctx.logger.info("[MetadataPlugin] Bridging driver to MetadataManager (fallback: first driver)", {
2589
- driverService: serviceName
2590
- });
2591
- this.manager.setDatabaseDriver(service);
2592
- break;
2593
- }
2594
- }
2595
- } catch (e) {
2596
- ctx.logger.debug("[MetadataPlugin] No driver service found", { error: e.message });
2597
- }
2598
- }
2599
- try {
2600
- const realtimeService = ctx.getService("realtime");
2601
- if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
2602
- ctx.logger.info("[MetadataPlugin] Bridging realtime service to MetadataManager for event publishing");
2603
- this.manager.setRealtimeService(realtimeService);
2604
- }
2605
- } catch (e) {
2606
- ctx.logger.debug("[MetadataPlugin] No realtime service found \u2014 metadata events will not be published", {
2607
- error: e.message
2608
- });
2609
- }
2610
- };
2611
- this.options = {
2612
- watch: true,
2613
- ...options
2614
- };
2615
- const rootDir = this.options.rootDir || process.cwd();
2616
- this.manager = new NodeMetadataManager({
2617
- rootDir,
2618
- watch: this.options.watch ?? true,
2619
- formats: ["yaml", "json", "typescript", "javascript"]
2620
- });
2621
- this.manager.setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY);
2622
- }
2623
- };
2624
-
2625
2649
  // src/loaders/memory-loader.ts
2626
2650
  var MemoryLoader = class {
2627
2651
  constructor() {
@@ -2700,6 +2724,222 @@ var MemoryLoader = class {
2700
2724
  }
2701
2725
  };
2702
2726
 
2727
+ // src/plugin.ts
2728
+ import { DEFAULT_METADATA_TYPE_REGISTRY } from "@objectstack/spec/kernel";
2729
+ import {
2730
+ SysAgent,
2731
+ SysFlow,
2732
+ SysObject,
2733
+ SysTool,
2734
+ SysView
2735
+ } from "@objectstack/platform-objects/metadata";
2736
+ var queryableMetadataObjects = [SysObject, SysView, SysFlow, SysAgent, SysTool];
2737
+ var ARTIFACT_FIELD_TO_TYPE = {
2738
+ objects: "object",
2739
+ objectExtensions: "object_extension",
2740
+ apps: "app",
2741
+ views: "view",
2742
+ pages: "page",
2743
+ dashboards: "dashboard",
2744
+ reports: "report",
2745
+ actions: "action",
2746
+ themes: "theme",
2747
+ workflows: "workflow",
2748
+ approvals: "approval",
2749
+ flows: "flow",
2750
+ roles: "role",
2751
+ permissions: "permission",
2752
+ sharingRules: "sharing_rule",
2753
+ policies: "policy",
2754
+ apis: "api",
2755
+ webhooks: "webhook",
2756
+ agents: "agent",
2757
+ skills: "skill",
2758
+ ragPipelines: "rag_pipeline",
2759
+ hooks: "hook",
2760
+ mappings: "mapping",
2761
+ analyticsCubes: "analytics_cube",
2762
+ connectors: "connector",
2763
+ data: "dataset"
2764
+ };
2765
+ var MetadataPlugin = class {
2766
+ constructor(options = {}) {
2767
+ this.name = "com.objectstack.metadata";
2768
+ this.type = "standard";
2769
+ this.version = "1.0.0";
2770
+ this.init = async (ctx) => {
2771
+ ctx.logger.info("Initializing Metadata Manager", {
2772
+ root: this.options.rootDir || process.cwd(),
2773
+ watch: this.options.watch,
2774
+ artifactSource: this.options.artifactSource?.mode
2775
+ });
2776
+ ctx.registerService("metadata", this.manager);
2777
+ console.log("[MetadataPlugin] Registered metadata service, has getRegisteredTypes:", typeof this.manager.getRegisteredTypes);
2778
+ const registerSysObjects = this.options.registerSystemObjects !== false;
2779
+ if (registerSysObjects) {
2780
+ try {
2781
+ const manifestService = ctx.getService("manifest");
2782
+ manifestService.register({
2783
+ id: "com.objectstack.metadata-objects",
2784
+ name: "Metadata Platform Objects",
2785
+ version: "1.0.0",
2786
+ type: "plugin",
2787
+ scope: "system",
2788
+ defaultDatasource: "cloud",
2789
+ objects: queryableMetadataObjects
2790
+ });
2791
+ ctx.logger.info("Registered system metadata objects", {
2792
+ queryable: queryableMetadataObjects.map((object) => object.name)
2793
+ });
2794
+ } catch {
2795
+ }
2796
+ }
2797
+ ctx.logger.info("MetadataPlugin providing metadata service (primary mode)", {
2798
+ mode: this.options.artifactSource?.mode ?? "file-system",
2799
+ features: ["watch", "multi-format", "query", "overlay", "type-registry"]
2800
+ });
2801
+ };
2802
+ this.start = async (ctx) => {
2803
+ const src = this.options.artifactSource;
2804
+ if (src?.mode === "local-file") {
2805
+ await this._loadFromLocalFile(ctx, src.path);
2806
+ } else if (src?.mode === "artifact-api") {
2807
+ ctx.logger.warn("[MetadataPlugin] artifact-api source is not yet implemented; falling back to file-system scan");
2808
+ await this._loadFromFileSystem(ctx);
2809
+ } else {
2810
+ await this._loadFromFileSystem(ctx);
2811
+ }
2812
+ try {
2813
+ const realtimeService = ctx.getService("realtime");
2814
+ if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
2815
+ ctx.logger.info("[MetadataPlugin] Bridging realtime service to MetadataManager for event publishing");
2816
+ this.manager.setRealtimeService(realtimeService);
2817
+ }
2818
+ } catch (e) {
2819
+ ctx.logger.debug("[MetadataPlugin] No realtime service found \u2014 metadata events will not be published", {
2820
+ error: e.message
2821
+ });
2822
+ }
2823
+ };
2824
+ this.options = {
2825
+ watch: true,
2826
+ ...options
2827
+ };
2828
+ const rootDir = this.options.rootDir || process.cwd();
2829
+ this.manager = new NodeMetadataManager({
2830
+ rootDir,
2831
+ watch: this.options.watch ?? true,
2832
+ formats: ["yaml", "json", "typescript", "javascript"]
2833
+ });
2834
+ this.manager.setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY);
2835
+ }
2836
+ async _loadFromLocalFile(ctx, filePath) {
2837
+ const isUrl = /^https?:\/\//i.test(filePath);
2838
+ ctx.logger.info(
2839
+ `[MetadataPlugin] Loading metadata from ${isUrl ? "remote URL" : "local artifact file"}`,
2840
+ { path: filePath }
2841
+ );
2842
+ let raw;
2843
+ try {
2844
+ let content;
2845
+ if (isUrl) {
2846
+ const controller = new AbortController();
2847
+ const timer = setTimeout(() => controller.abort(), 15e3);
2848
+ try {
2849
+ const res = await fetch(filePath, {
2850
+ redirect: "follow",
2851
+ signal: controller.signal,
2852
+ headers: { Accept: "application/json, */*;q=0.5" }
2853
+ });
2854
+ if (!res.ok) {
2855
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
2856
+ }
2857
+ content = await res.text();
2858
+ } finally {
2859
+ clearTimeout(timer);
2860
+ }
2861
+ } else {
2862
+ content = await readFile2(filePath, "utf8");
2863
+ }
2864
+ raw = JSON.parse(content);
2865
+ } catch (e) {
2866
+ throw new Error(`[MetadataPlugin] Cannot read artifact ${isUrl ? "URL" : "file"} at "${filePath}": ${e.message}`);
2867
+ }
2868
+ const { ProjectArtifactSchema } = await import("@objectstack/spec/cloud");
2869
+ const { ObjectStackDefinitionSchema } = await import("@objectstack/spec");
2870
+ let metadata;
2871
+ const obj = raw;
2872
+ if (obj?.schemaVersion && obj?.commitId && obj?.metadata !== void 0) {
2873
+ const artifact = ProjectArtifactSchema.parse(obj);
2874
+ metadata = artifact.metadata;
2875
+ } else {
2876
+ const def = ObjectStackDefinitionSchema.parse(obj);
2877
+ const canonical = JSON.stringify(def, Object.keys(def).sort());
2878
+ const checksum = createHash2("sha256").update(canonical).digest("hex");
2879
+ const projectId = this.options.projectId ?? "proj_local";
2880
+ ProjectArtifactSchema.parse({
2881
+ schemaVersion: "0.1",
2882
+ projectId,
2883
+ commitId: "local-dev",
2884
+ checksum,
2885
+ metadata: def
2886
+ });
2887
+ metadata = def;
2888
+ }
2889
+ const memLoader = new MemoryLoader();
2890
+ const manifestPackageId = metadata?.manifest?.id ?? metadata?.id ?? void 0;
2891
+ let totalRegistered = 0;
2892
+ for (const [field, metaType] of Object.entries(ARTIFACT_FIELD_TO_TYPE)) {
2893
+ const items = metadata[field];
2894
+ if (!Array.isArray(items) || items.length === 0) continue;
2895
+ for (const item of items) {
2896
+ const name = item?.name;
2897
+ if (!name) continue;
2898
+ if (manifestPackageId && item._packageId === void 0) {
2899
+ item._packageId = manifestPackageId;
2900
+ }
2901
+ await memLoader.save(metaType, name, item);
2902
+ await this.manager.register(metaType, name, item);
2903
+ totalRegistered++;
2904
+ }
2905
+ }
2906
+ this.manager.registerLoader(memLoader);
2907
+ ctx.logger.info("[MetadataPlugin] Artifact metadata loaded", {
2908
+ path: filePath,
2909
+ totalRegistered
2910
+ });
2911
+ }
2912
+ async _loadFromFileSystem(ctx) {
2913
+ ctx.logger.info("Loading metadata from file system...");
2914
+ const sortedTypes = [...DEFAULT_METADATA_TYPE_REGISTRY].sort((a, b) => a.loadOrder - b.loadOrder);
2915
+ let totalLoaded = 0;
2916
+ for (const entry of sortedTypes) {
2917
+ try {
2918
+ const items = await this.manager.loadMany(entry.type, {
2919
+ recursive: true,
2920
+ patterns: entry.filePatterns
2921
+ });
2922
+ if (items.length > 0) {
2923
+ for (const item of items) {
2924
+ const meta = item;
2925
+ if (meta?.name) {
2926
+ await this.manager.register(entry.type, meta.name, item);
2927
+ }
2928
+ }
2929
+ ctx.logger.info(`Loaded ${items.length} ${entry.type} from file system`);
2930
+ totalLoaded += items.length;
2931
+ }
2932
+ } catch (e) {
2933
+ ctx.logger.debug(`No ${entry.type} metadata found`, { error: e.message });
2934
+ }
2935
+ }
2936
+ ctx.logger.info("Metadata loading complete", {
2937
+ totalItems: totalLoaded,
2938
+ registeredTypes: sortedTypes.length
2939
+ });
2940
+ }
2941
+ };
2942
+
2703
2943
  // src/loaders/remote-loader.ts
2704
2944
  var RemoteLoader = class {
2705
2945
  constructor(baseUrl, authToken) {
@@ -2797,6 +3037,9 @@ var RemoteLoader = class {
2797
3037
  }
2798
3038
  };
2799
3039
 
3040
+ // src/index.ts
3041
+ import { SysMetadataObject as SysMetadataObject2, SysMetadataHistoryObject as SysMetadataHistoryObject2 } from "@objectstack/platform-objects/metadata";
3042
+
2800
3043
  // src/routes/history-routes.ts
2801
3044
  function registerMetadataHistoryRoutes(app, metadataService) {
2802
3045
  app.get("/api/v1/metadata/:type/:name/history", async (c) => {
@@ -2952,7 +3195,8 @@ var HistoryCleanupManager = class {
2952
3195
  async runCleanup() {
2953
3196
  const driver = this.dbLoader.driver;
2954
3197
  const historyTableName = this.dbLoader.historyTableName;
2955
- const tenantId = this.dbLoader.tenantId;
3198
+ const organizationId = this.dbLoader.organizationId;
3199
+ const projectId = this.dbLoader.projectId;
2956
3200
  let deleted = 0;
2957
3201
  let errors = 0;
2958
3202
  try {
@@ -2963,8 +3207,11 @@ var HistoryCleanupManager = class {
2963
3207
  const filter = {
2964
3208
  recorded_at: { $lt: cutoffISO }
2965
3209
  };
2966
- if (tenantId) {
2967
- filter.tenant_id = tenantId;
3210
+ if (organizationId) {
3211
+ filter.organization_id = organizationId;
3212
+ }
3213
+ if (projectId !== void 0) {
3214
+ filter.project_id = projectId;
2968
3215
  }
2969
3216
  try {
2970
3217
  const result = await this.bulkDeleteByFilter(driver, historyTableName, filter);
@@ -2976,9 +3223,12 @@ var HistoryCleanupManager = class {
2976
3223
  }
2977
3224
  if (this.policy.maxVersions) {
2978
3225
  try {
3226
+ const baseWhere = {};
3227
+ if (organizationId) baseWhere.organization_id = organizationId;
3228
+ if (projectId !== void 0) baseWhere.project_id = projectId;
2979
3229
  const metadataIds = await driver.find(historyTableName, {
2980
3230
  object: historyTableName,
2981
- where: tenantId ? { tenant_id: tenantId } : {},
3231
+ where: baseWhere,
2982
3232
  fields: ["metadata_id"]
2983
3233
  });
2984
3234
  const uniqueIds = /* @__PURE__ */ new Set();
@@ -2988,10 +3238,7 @@ var HistoryCleanupManager = class {
2988
3238
  }
2989
3239
  }
2990
3240
  for (const metadataId of uniqueIds) {
2991
- const filter = { metadata_id: metadataId };
2992
- if (tenantId) {
2993
- filter.tenant_id = tenantId;
2994
- }
3241
+ const filter = { metadata_id: metadataId, ...baseWhere };
2995
3242
  try {
2996
3243
  const historyRecords = await driver.find(historyTableName, {
2997
3244
  object: historyTableName,
@@ -3065,20 +3312,22 @@ var HistoryCleanupManager = class {
3065
3312
  async getCleanupStats() {
3066
3313
  const driver = this.dbLoader.driver;
3067
3314
  const historyTableName = this.dbLoader.historyTableName;
3068
- const tenantId = this.dbLoader.tenantId;
3315
+ const organizationId = this.dbLoader.organizationId;
3316
+ const projectId = this.dbLoader.projectId;
3069
3317
  let recordsByAge = 0;
3070
3318
  let recordsByCount = 0;
3071
3319
  try {
3320
+ const baseWhere = {};
3321
+ if (organizationId) baseWhere.organization_id = organizationId;
3322
+ if (projectId !== void 0) baseWhere.project_id = projectId;
3072
3323
  if (this.policy.maxAgeDays) {
3073
3324
  const cutoffDate = /* @__PURE__ */ new Date();
3074
3325
  cutoffDate.setDate(cutoffDate.getDate() - this.policy.maxAgeDays);
3075
3326
  const cutoffISO = cutoffDate.toISOString();
3076
3327
  const filter = {
3077
- recorded_at: { $lt: cutoffISO }
3328
+ recorded_at: { $lt: cutoffISO },
3329
+ ...baseWhere
3078
3330
  };
3079
- if (tenantId) {
3080
- filter.tenant_id = tenantId;
3081
- }
3082
3331
  recordsByAge = await driver.count(historyTableName, {
3083
3332
  object: historyTableName,
3084
3333
  where: filter
@@ -3087,7 +3336,7 @@ var HistoryCleanupManager = class {
3087
3336
  if (this.policy.maxVersions) {
3088
3337
  const metadataIds = await driver.find(historyTableName, {
3089
3338
  object: historyTableName,
3090
- where: tenantId ? { tenant_id: tenantId } : {},
3339
+ where: baseWhere,
3091
3340
  fields: ["metadata_id"]
3092
3341
  });
3093
3342
  const uniqueIds = /* @__PURE__ */ new Set();
@@ -3097,10 +3346,7 @@ var HistoryCleanupManager = class {
3097
3346
  }
3098
3347
  }
3099
3348
  for (const metadataId of uniqueIds) {
3100
- const filter = { metadata_id: metadataId };
3101
- if (tenantId) {
3102
- filter.tenant_id = tenantId;
3103
- }
3349
+ const filter = { metadata_id: metadataId, ...baseWhere };
3104
3350
  const count = await driver.count(historyTableName, {
3105
3351
  object: historyTableName,
3106
3352
  where: filter
@@ -3184,11 +3430,12 @@ export {
3184
3430
  MemoryLoader,
3185
3431
  MetadataManager,
3186
3432
  MetadataPlugin,
3433
+ MetadataProjector,
3187
3434
  migration_exports as Migration,
3188
3435
  NodeMetadataManager,
3189
3436
  RemoteLoader,
3190
- SysMetadataHistoryObject,
3191
- SysMetadataObject,
3437
+ SysMetadataHistoryObject2 as SysMetadataHistoryObject,
3438
+ SysMetadataObject2 as SysMetadataObject,
3192
3439
  TypeScriptSerializer,
3193
3440
  YAMLSerializer,
3194
3441
  calculateChecksum,