@objectstack/metadata 4.0.2 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,7 +28,7 @@ It implements the **`IMetadataService`** contract from `@objectstack/spec` and a
28
28
  │ │ (files) │ │ (HTTP) │ │ (test) │ │
29
29
  │ └─────────────┘ └──────────────┘ └───────────────────┘ │
30
30
  │ ┌──────────────────────────────────────────────────────┐ │
31
- │ │ DatabaseLoader (planned — datasource-backed storage) │ │
31
+ │ │ DatabaseLoader (datasource-backed storage) │ │
32
32
  │ └──────────────────────────────────────────────────────┘ │
33
33
  ├─────────────────────────────────────────────────────────────┤
34
34
  │ Serializer Layer │
@@ -61,7 +61,7 @@ Loaders are pluggable data sources that know how to read/write metadata from dif
61
61
  | `FilesystemLoader` | `file:` | ✅ | ✅ | ✅ | Implemented |
62
62
  | `MemoryLoader` | `memory:` | ✅ | ✅ | ❌ | Implemented |
63
63
  | `RemoteLoader` | `http:` | ✅ | ✅ | ❌ | Implemented |
64
- | `DatabaseLoader` | `datasource:` | ✅ | ✅ | | Planned |
64
+ | `DatabaseLoader` | `datasource:` | ✅ | ✅ | | Implemented |
65
65
 
66
66
  ### 3. Serializers
67
67
 
package/dist/index.cjs CHANGED
@@ -1084,6 +1084,16 @@ var MetadataManager = class {
1084
1084
  this.registerLoader(dbLoader);
1085
1085
  this.logger.info("DatabaseLoader configured", { datasource: this.config.datasource, tableName });
1086
1086
  }
1087
+ /**
1088
+ * Set the realtime service for publishing metadata change events.
1089
+ * Should be called after kernel resolves the realtime service.
1090
+ *
1091
+ * @param service - An IRealtimeService instance for event publishing
1092
+ */
1093
+ setRealtimeService(service) {
1094
+ this.realtimeService = service;
1095
+ this.logger.info("RealtimeService configured for metadata events");
1096
+ }
1087
1097
  /**
1088
1098
  * Register a new metadata loader (data source)
1089
1099
  */
@@ -1096,7 +1106,9 @@ var MetadataManager = class {
1096
1106
  // ==========================================
1097
1107
  /**
1098
1108
  * Register/save a metadata item by type
1099
- * Stores in-memory registry and persists to writable loaders (if configured)
1109
+ * Stores in-memory registry and persists to database-backed loaders only.
1110
+ * FilesystemLoader (protocol 'file:') is read-only for static metadata and
1111
+ * should not be written to during runtime registration.
1100
1112
  */
1101
1113
  async register(type, name, data) {
1102
1114
  if (!this.registry.has(type)) {
@@ -1104,10 +1116,29 @@ var MetadataManager = class {
1104
1116
  }
1105
1117
  this.registry.get(type).set(name, data);
1106
1118
  for (const loader of this.loaders.values()) {
1107
- if (loader.save) {
1119
+ if (loader.save && loader.contract.protocol === "datasource:" && loader.contract.capabilities.write) {
1108
1120
  await loader.save(type, name, data);
1109
1121
  }
1110
1122
  }
1123
+ if (this.realtimeService) {
1124
+ const event = {
1125
+ type: `metadata.${type}.created`,
1126
+ object: type,
1127
+ payload: {
1128
+ metadataType: type,
1129
+ name,
1130
+ definition: data,
1131
+ packageId: data?.packageId
1132
+ },
1133
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1134
+ };
1135
+ try {
1136
+ await this.realtimeService.publish(event);
1137
+ this.logger.debug(`Published metadata.${type}.created event`, { name });
1138
+ } catch (error) {
1139
+ this.logger.warn(`Failed to publish metadata event`, { type, name, error });
1140
+ }
1141
+ }
1111
1142
  }
1112
1143
  /**
1113
1144
  * Get a metadata item by type and name.
@@ -1148,7 +1179,8 @@ var MetadataManager = class {
1148
1179
  return Array.from(items.values());
1149
1180
  }
1150
1181
  /**
1151
- * Unregister/remove a metadata item by type and name
1182
+ * Unregister/remove a metadata item by type and name.
1183
+ * Deletes from database-backed loaders only (same rationale as register()).
1152
1184
  */
1153
1185
  async unregister(type, name) {
1154
1186
  const typeStore = this.registry.get(type);
@@ -1159,6 +1191,7 @@ var MetadataManager = class {
1159
1191
  }
1160
1192
  }
1161
1193
  for (const loader of this.loaders.values()) {
1194
+ if (loader.contract.protocol !== "datasource:" || !loader.contract.capabilities.write) continue;
1162
1195
  if (typeof loader.delete === "function") {
1163
1196
  try {
1164
1197
  await loader.delete(type, name);
@@ -1167,6 +1200,23 @@ var MetadataManager = class {
1167
1200
  }
1168
1201
  }
1169
1202
  }
1203
+ if (this.realtimeService) {
1204
+ const event = {
1205
+ type: `metadata.${type}.deleted`,
1206
+ object: type,
1207
+ payload: {
1208
+ metadataType: type,
1209
+ name
1210
+ },
1211
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1212
+ };
1213
+ try {
1214
+ await this.realtimeService.publish(event);
1215
+ this.logger.debug(`Published metadata.${type}.deleted event`, { name });
1216
+ } catch (error) {
1217
+ this.logger.warn(`Failed to publish metadata event`, { type, name, error });
1218
+ }
1219
+ }
1170
1220
  }
1171
1221
  /**
1172
1222
  * Check if a metadata item exists
@@ -2512,6 +2562,7 @@ var MetadataPlugin = class {
2512
2562
  watch: this.options.watch
2513
2563
  });
2514
2564
  ctx.registerService("metadata", this.manager);
2565
+ console.log("[MetadataPlugin] Registered metadata service, has getRegisteredTypes:", typeof this.manager.getRegisteredTypes);
2515
2566
  try {
2516
2567
  ctx.getService("manifest").register({
2517
2568
  id: "com.objectstack.metadata",
@@ -2555,6 +2606,52 @@ var MetadataPlugin = class {
2555
2606
  totalItems: totalLoaded,
2556
2607
  registeredTypes: sortedTypes.length
2557
2608
  });
2609
+ let driverBridged = false;
2610
+ try {
2611
+ const ql = ctx.getService("objectql");
2612
+ if (ql) {
2613
+ const tableName = this.manager["config"]?.tableName ?? "sys_metadata";
2614
+ const driver = ql.getDriverForObject?.(tableName);
2615
+ if (driver) {
2616
+ ctx.logger.info("[MetadataPlugin] Bridging driver to MetadataManager via ObjectQL routing", {
2617
+ tableName,
2618
+ driver: driver.name
2619
+ });
2620
+ this.manager.setDatabaseDriver(driver);
2621
+ driverBridged = true;
2622
+ } else {
2623
+ ctx.logger.debug("[MetadataPlugin] ObjectQL could not resolve driver for metadata table", { tableName });
2624
+ }
2625
+ }
2626
+ } catch {
2627
+ }
2628
+ if (!driverBridged) {
2629
+ try {
2630
+ const services = ctx.getServices();
2631
+ for (const [serviceName, service] of services) {
2632
+ if (serviceName.startsWith("driver.") && service) {
2633
+ ctx.logger.info("[MetadataPlugin] Bridging driver to MetadataManager (fallback: first driver)", {
2634
+ driverService: serviceName
2635
+ });
2636
+ this.manager.setDatabaseDriver(service);
2637
+ break;
2638
+ }
2639
+ }
2640
+ } catch (e) {
2641
+ ctx.logger.debug("[MetadataPlugin] No driver service found", { error: e.message });
2642
+ }
2643
+ }
2644
+ try {
2645
+ const realtimeService = ctx.getService("realtime");
2646
+ if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
2647
+ ctx.logger.info("[MetadataPlugin] Bridging realtime service to MetadataManager for event publishing");
2648
+ this.manager.setRealtimeService(realtimeService);
2649
+ }
2650
+ } catch (e) {
2651
+ ctx.logger.debug("[MetadataPlugin] No realtime service found \u2014 metadata events will not be published", {
2652
+ error: e.message
2653
+ });
2654
+ }
2558
2655
  };
2559
2656
  this.options = {
2560
2657
  watch: true,