@objectstack/metadata 4.2.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.cjs CHANGED
@@ -64,7 +64,12 @@ function registerMetadataHmrRoutes(app, manager, options = {}) {
64
64
  metadataType: evt.metadataType ?? type,
65
65
  name: evt.name ?? "",
66
66
  path: evt.path,
67
- timestamp: Number.isFinite(ts) ? ts : Date.now()
67
+ timestamp: Number.isFinite(ts) ? ts : Date.now(),
68
+ // Forward the canonical server-side sequence number when the
69
+ // event originated from a MetadataRepository (ADR-0008). Legacy
70
+ // chokidar-driven events have no seq — clients fall back to
71
+ // their local counter in that case.
72
+ ...typeof evt.seq === "number" ? { seq: evt.seq } : {}
68
73
  });
69
74
  });
70
75
  }
@@ -605,7 +610,7 @@ var DatabaseLoader = class {
605
610
  this.tableName = options.tableName ?? "sys_metadata";
606
611
  this.historyTableName = options.historyTableName ?? "sys_metadata_history";
607
612
  this.organizationId = options.organizationId;
608
- this.projectId = options.projectId;
613
+ void options.projectId;
609
614
  this.trackHistory = options.trackHistory !== false;
610
615
  const cacheOpts = options.cache;
611
616
  const cacheEnabled = cacheOpts?.enabled !== false;
@@ -695,6 +700,26 @@ var DatabaseLoader = class {
695
700
  }
696
701
  return this.driver.delete(table, id);
697
702
  }
703
+ /**
704
+ * Compute the next per-org `event_seq` for `sys_metadata_history`.
705
+ * Reads `MAX(event_seq) + 1` for the configured `organization_id`.
706
+ * Legacy path — not transactional, so concurrent writes can collide.
707
+ * The canonical (transactional) producer is `SysMetadataRepository`.
708
+ */
709
+ async nextEventSeq() {
710
+ const where = this.organizationId ? { organization_id: this.organizationId } : {};
711
+ try {
712
+ const rows = await this._find(this.historyTableName, { where });
713
+ let max = 0;
714
+ for (const row of rows) {
715
+ const v = typeof row.event_seq === "number" ? row.event_seq : 0;
716
+ if (v > max) max = v;
717
+ }
718
+ return max + 1;
719
+ } catch {
720
+ return 1;
721
+ }
722
+ }
698
723
  /**
699
724
  * Ensure the metadata table exists.
700
725
  * Uses IDataDriver.syncSchema with the SysMetadataObject definition
@@ -759,8 +784,9 @@ var DatabaseLoader = class {
759
784
  }
760
785
  /**
761
786
  * Build base filter conditions for queries.
762
- * Filters by organizationId when configured; project_id when projectId is set,
763
- * or null (platform-global) when not set.
787
+ * Filters by organizationId when configured. `projectId` is accepted
788
+ * for back-compat but no longer constrains the query — see
789
+ * ADR-0008 §0 (branch/project removal).
764
790
  */
765
791
  baseFilter(type, name) {
766
792
  const filter = { type };
@@ -770,13 +796,11 @@ var DatabaseLoader = class {
770
796
  if (this.organizationId) {
771
797
  filter.organization_id = this.organizationId;
772
798
  }
773
- filter.project_id = this.projectId ?? null;
774
799
  return filter;
775
800
  }
776
801
  /**
777
802
  * Create a history record for a metadata change.
778
803
  *
779
- * @param metadataId - The metadata record ID
780
804
  * @param type - Metadata type
781
805
  * @param name - Metadata name
782
806
  * @param version - Version number
@@ -786,7 +810,7 @@ var DatabaseLoader = class {
786
810
  * @param changeNote - Optional change description
787
811
  * @param recordedBy - Optional user who made the change
788
812
  */
789
- async createHistoryRecord(metadataId, type, name, version, metadata, operationType, previousChecksum, changeNote, recordedBy) {
813
+ async createHistoryRecord(type, name, version, metadata, operationType, previousChecksum, changeNote, recordedBy) {
790
814
  if (!this.trackHistory) return;
791
815
  await this.ensureHistorySchema();
792
816
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -796,9 +820,9 @@ var DatabaseLoader = class {
796
820
  }
797
821
  const historyId = generateId();
798
822
  const metadataJson = JSON.stringify(metadata);
823
+ const eventSeq = await this.nextEventSeq();
799
824
  const historyRecord = {
800
825
  id: historyId,
801
- metadataId,
802
826
  name,
803
827
  type,
804
828
  version,
@@ -809,13 +833,12 @@ var DatabaseLoader = class {
809
833
  changeNote,
810
834
  recordedBy,
811
835
  recordedAt: now,
812
- ...this.organizationId ? { organizationId: this.organizationId } : {},
813
- ...this.projectId !== void 0 ? { projectId: this.projectId } : {}
836
+ ...this.organizationId ? { organizationId: this.organizationId } : {}
814
837
  };
815
838
  try {
816
839
  await this._create(this.historyTableName, {
817
840
  id: historyRecord.id,
818
- metadata_id: historyRecord.metadataId,
841
+ event_seq: eventSeq,
819
842
  name: historyRecord.name,
820
843
  type: historyRecord.type,
821
844
  version: historyRecord.version,
@@ -826,8 +849,8 @@ var DatabaseLoader = class {
826
849
  change_note: historyRecord.changeNote,
827
850
  recorded_by: historyRecord.recordedBy,
828
851
  recorded_at: historyRecord.recordedAt,
829
- ...this.organizationId ? { organization_id: this.organizationId } : {},
830
- ...this.projectId !== void 0 ? { project_id: this.projectId } : {}
852
+ source: "database-loader",
853
+ ...this.organizationId ? { organization_id: this.organizationId } : {}
831
854
  });
832
855
  } catch (error) {
833
856
  console.error(`Failed to create history record for ${type}/${name}:`, error);
@@ -1003,25 +1026,20 @@ var DatabaseLoader = class {
1003
1026
  async getHistoryRecord(type, name, version) {
1004
1027
  if (!this.trackHistory) return null;
1005
1028
  await this.ensureHistorySchema();
1006
- const metadataRow = await this._findOne(this.tableName, {
1007
- where: this.baseFilter(type, name)
1008
- });
1009
- if (!metadataRow) return null;
1010
1029
  const filter = {
1011
- metadata_id: metadataRow.id,
1030
+ type,
1031
+ name,
1012
1032
  version
1013
1033
  };
1014
1034
  if (this.organizationId) {
1015
1035
  filter.organization_id = this.organizationId;
1016
1036
  }
1017
- filter.project_id = this.projectId ?? null;
1018
1037
  const row = await this._findOne(this.historyTableName, {
1019
1038
  where: filter
1020
1039
  });
1021
1040
  if (!row) return null;
1022
1041
  return {
1023
1042
  id: row.id,
1024
- metadataId: row.metadata_id,
1025
1043
  name: row.name,
1026
1044
  type: row.type,
1027
1045
  version: row.version,
@@ -1031,7 +1049,6 @@ var DatabaseLoader = class {
1031
1049
  previousChecksum: row.previous_checksum,
1032
1050
  changeNote: row.change_note,
1033
1051
  organizationId: row.organization_id,
1034
- projectId: row.project_id,
1035
1052
  recordedBy: row.recorded_by,
1036
1053
  recordedAt: row.recorded_at
1037
1054
  };
@@ -1047,18 +1064,11 @@ var DatabaseLoader = class {
1047
1064
  }
1048
1065
  await this.ensureSchema();
1049
1066
  await this.ensureHistorySchema();
1050
- const filter = { type, name };
1051
- if (this.organizationId) filter.organization_id = this.organizationId;
1052
- filter.project_id = this.projectId ?? null;
1053
- const metadataRecord = await this._findOne(this.tableName, { where: filter });
1054
- if (!metadataRecord) {
1055
- return { records: [], total: 0, hasMore: false };
1056
- }
1057
1067
  const historyFilter = {
1058
- metadata_id: metadataRecord.id
1068
+ type,
1069
+ name
1059
1070
  };
1060
1071
  if (this.organizationId) historyFilter.organization_id = this.organizationId;
1061
- historyFilter.project_id = this.projectId ?? null;
1062
1072
  if (options?.operationType) historyFilter.operation_type = options.operationType;
1063
1073
  if (options?.since) historyFilter.recorded_at = { $gte: options.since };
1064
1074
  if (options?.until) {
@@ -1087,7 +1097,6 @@ var DatabaseLoader = class {
1087
1097
  const parsedMetadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata;
1088
1098
  return {
1089
1099
  id: row.id,
1090
- metadataId: row.metadata_id,
1091
1100
  name: row.name,
1092
1101
  type: row.type,
1093
1102
  version: row.version,
@@ -1097,7 +1106,6 @@ var DatabaseLoader = class {
1097
1106
  previousChecksum: row.previous_checksum,
1098
1107
  changeNote: row.change_note,
1099
1108
  organizationId: row.organization_id,
1100
- projectId: row.project_id,
1101
1109
  recordedBy: row.recorded_by,
1102
1110
  recordedAt: row.recorded_at
1103
1111
  };
@@ -1133,7 +1141,6 @@ var DatabaseLoader = class {
1133
1141
  });
1134
1142
  this.invalidate(type, name);
1135
1143
  await this.createHistoryRecord(
1136
- existing.id,
1137
1144
  type,
1138
1145
  name,
1139
1146
  newVersion,
@@ -1175,7 +1182,6 @@ var DatabaseLoader = class {
1175
1182
  });
1176
1183
  this.invalidate(type, name);
1177
1184
  await this.createHistoryRecord(
1178
- existing.id,
1179
1185
  type,
1180
1186
  name,
1181
1187
  version,
@@ -1204,13 +1210,11 @@ var DatabaseLoader = class {
1204
1210
  version: 1,
1205
1211
  source: "database",
1206
1212
  ...this.organizationId ? { organization_id: this.organizationId } : {},
1207
- ...this.projectId !== void 0 ? { project_id: this.projectId } : { project_id: null },
1208
1213
  created_at: now,
1209
1214
  updated_at: now
1210
1215
  });
1211
1216
  this.invalidate(type, name);
1212
1217
  await this.createHistoryRecord(
1213
- id,
1214
1218
  type,
1215
1219
  name,
1216
1220
  1,
@@ -1277,6 +1281,7 @@ var _MetadataManager = class _MetadataManager {
1277
1281
  // Invalidated on every `register()` / `unregister()` to keep CRUD writes
1278
1282
  // visible to subsequent reads.
1279
1283
  this.listCache = /* @__PURE__ */ new Map();
1284
+ this.repoWatchClosed = false;
1280
1285
  this.config = config;
1281
1286
  this.logger = (0, import_core.createLogger)({ level: "info", format: "pretty" });
1282
1287
  this.serializers = /* @__PURE__ */ new Map();
@@ -2284,6 +2289,110 @@ var _MetadataManager = class _MetadataManager {
2284
2289
  */
2285
2290
  async stopWatching() {
2286
2291
  }
2292
+ // ─── ADR-0008 PR-6: Repository wiring ───────────────────────────────
2293
+ /**
2294
+ * Attach a {@link MetadataRepository} as a supplementary event source.
2295
+ *
2296
+ * The manager subscribes to `repo.watch({})` and re-emits each event
2297
+ * through {@link notifyWatchers} as a legacy `MetadataWatchEvent`.
2298
+ * Each event also invalidates the in-memory registry entry and the
2299
+ * `list()` cache for the affected type so subsequent reads see fresh
2300
+ * data.
2301
+ *
2302
+ * No write-through. `register()` / `unregister()` / `save()` are
2303
+ * untouched in this PR (deferred to ADR-0008 M0 PR-10).
2304
+ *
2305
+ * Call {@link dispose} (or {@link stopRepositoryWatch}) to detach.
2306
+ */
2307
+ setRepository(repo) {
2308
+ if (this.repository === repo) return;
2309
+ if (this.repository) {
2310
+ void this.stopRepositoryWatch();
2311
+ }
2312
+ this.repository = repo;
2313
+ this.repoWatchClosed = false;
2314
+ void this.startRepositoryWatch();
2315
+ }
2316
+ /** Return the attached repository, if any. */
2317
+ getRepository() {
2318
+ return this.repository;
2319
+ }
2320
+ /** Stop the active repo.watch() loop (best-effort). */
2321
+ async stopRepositoryWatch() {
2322
+ this.repoWatchClosed = true;
2323
+ const iter = this.repoWatchIter;
2324
+ this.repoWatchIter = void 0;
2325
+ if (iter && typeof iter.return === "function") {
2326
+ try {
2327
+ await iter.return(void 0);
2328
+ } catch {
2329
+ }
2330
+ }
2331
+ }
2332
+ /**
2333
+ * Best-effort cleanup. Stops the FS watcher (if any), drains the
2334
+ * repository watch loop, and clears registry caches. Safe to call
2335
+ * multiple times.
2336
+ */
2337
+ async dispose() {
2338
+ await this.stopWatching().catch(() => void 0);
2339
+ await this.stopRepositoryWatch().catch(() => void 0);
2340
+ this.listCache.clear();
2341
+ }
2342
+ async startRepositoryWatch() {
2343
+ const repo = this.repository;
2344
+ if (!repo) return;
2345
+ const iterable = repo.watch({});
2346
+ const iter = iterable[Symbol.asyncIterator]();
2347
+ this.repoWatchIter = iter;
2348
+ try {
2349
+ while (!this.repoWatchClosed) {
2350
+ const { value, done } = await iter.next();
2351
+ if (done) break;
2352
+ try {
2353
+ this.applyRepoEvent(value);
2354
+ } catch (err) {
2355
+ this.logger.warn("[MetadataManager] repo event handler failed", {
2356
+ error: err instanceof Error ? err.message : String(err)
2357
+ });
2358
+ }
2359
+ }
2360
+ } catch (err) {
2361
+ if (!this.repoWatchClosed) {
2362
+ this.logger.warn("[MetadataManager] repository watch loop exited unexpectedly", {
2363
+ error: err instanceof Error ? err.message : String(err)
2364
+ });
2365
+ }
2366
+ } finally {
2367
+ if (this.repoWatchIter === iter) this.repoWatchIter = void 0;
2368
+ }
2369
+ }
2370
+ /** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */
2371
+ applyRepoEvent(evt) {
2372
+ const ref = evt.ref;
2373
+ const type = ref.type;
2374
+ const name = ref.name;
2375
+ const typeStore = this.registry.get(type);
2376
+ if (typeStore) {
2377
+ typeStore.delete(name);
2378
+ if (typeStore.size === 0) this.registry.delete(type);
2379
+ }
2380
+ this.listCache.delete(type);
2381
+ const legacyType = evt.op === "create" ? "added" : evt.op === "delete" ? "deleted" : "changed";
2382
+ const legacyEvent = {
2383
+ type: legacyType,
2384
+ metadataType: type,
2385
+ name,
2386
+ path: "",
2387
+ // Repo events carry the hash only; the body is fetched on demand
2388
+ // via manager.get(type, name). HMR consumers don't read `data` so
2389
+ // this is fine for M0. (See ADR-0008 §12 open question 1.)
2390
+ data: void 0,
2391
+ timestamp: evt.ts
2392
+ };
2393
+ legacyEvent.seq = evt.seq;
2394
+ this.notifyWatchers(type, legacyEvent);
2395
+ }
2287
2396
  notifyWatchers(type, event) {
2288
2397
  const callbacks = this.watchCallbacks.get(type);
2289
2398
  if (!callbacks) return;
@@ -2759,7 +2868,13 @@ var NodeMetadataManager = class extends MetadataManager {
2759
2868
  this.watcher = (0, import_chokidar.watch)(rootDir, {
2760
2869
  ignored,
2761
2870
  persistent,
2762
- ignoreInitial: true
2871
+ ignoreInitial: true,
2872
+ // Use polling to avoid `fs.watch` EMFILE on macOS / busy dev hosts.
2873
+ // Recursive watch over a project root would otherwise wire native
2874
+ // watches across the entire tree, easily exhausting the FD pool.
2875
+ usePolling: true,
2876
+ interval: 1e3,
2877
+ binaryInterval: 2e3
2763
2878
  });
2764
2879
  this.watcher.on("add", async (filePath) => {
2765
2880
  await this.handleFileEvent("added", filePath);
@@ -2894,6 +3009,7 @@ var queryableMetadataObjects = [
2894
3009
  import_metadata2.SysMetadataObject,
2895
3010
  import_metadata2.SysMetadataHistoryObject
2896
3011
  ];
3012
+ var REPO_SUBDIR = ".objectstack/metadata";
2897
3013
  var ARTIFACT_FIELD_TO_TYPE = {
2898
3014
  objects: "object",
2899
3015
  objectExtensions: "object_extension",
@@ -2991,6 +3107,31 @@ var MetadataPlugin = class {
2991
3107
  await this._loadFromFileSystem(ctx);
2992
3108
  }
2993
3109
  }
3110
+ const bootstrapMode = this.options.config?.bootstrap ?? "eager";
3111
+ if (bootstrapMode !== "artifact-only") {
3112
+ try {
3113
+ const path3 = await import("path");
3114
+ const { FileSystemRepository } = await import("@objectstack/metadata-fs");
3115
+ const rootDir = this.options.rootDir || process.cwd();
3116
+ const repoRoot = path3.join(rootDir, REPO_SUBDIR);
3117
+ const repo = new FileSystemRepository({
3118
+ root: repoRoot,
3119
+ org: this.options.organizationId ?? "system",
3120
+ disableWatch: this.options.watch === false
3121
+ });
3122
+ await repo.start();
3123
+ this.repository = repo;
3124
+ this.manager.setRepository(repo);
3125
+ ctx.logger.info("[MetadataPlugin] FileSystemRepository attached", {
3126
+ repoRoot,
3127
+ watch: this.options.watch !== false
3128
+ });
3129
+ } catch (e) {
3130
+ ctx.logger.warn("[MetadataPlugin] Failed to attach FileSystemRepository", {
3131
+ error: e?.message
3132
+ });
3133
+ }
3134
+ }
2994
3135
  try {
2995
3136
  const realtimeService = ctx.getService("realtime");
2996
3137
  if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
@@ -3008,12 +3149,12 @@ var MetadataPlugin = class {
3008
3149
  const { registerMetadataHmrRoutes: registerMetadataHmrRoutes2 } = await Promise.resolve().then(() => (init_hmr_routes(), hmr_routes_exports));
3009
3150
  const hub = registerMetadataHmrRoutes2(httpServer.getRawApp(), this.manager);
3010
3151
  hub.setOnPostReload(async (body = {}) => {
3011
- const src2 = this.options.artifactSource;
3012
- if (src2?.mode === "local-file") {
3152
+ const src3 = this.options.artifactSource;
3153
+ if (src3?.mode === "local-file") {
3013
3154
  try {
3014
- await this._loadFromLocalFile(ctx, src2.path, src2.fetchTimeoutMs);
3155
+ await this._loadFromLocalFile(ctx, src3.path, src3.fetchTimeoutMs);
3015
3156
  ctx.logger.info("[MetadataPlugin] artifact reloaded via HMR POST", {
3016
- path: src2.path,
3157
+ path: src3.path,
3017
3158
  reason: body?.reason
3018
3159
  });
3019
3160
  } catch (e) {
@@ -3022,6 +3163,53 @@ var MetadataPlugin = class {
3022
3163
  }
3023
3164
  }
3024
3165
  });
3166
+ const src2 = this.options.artifactSource;
3167
+ const wantArtifactWatch = this.options.artifactWatch ?? src2?.mode === "local-file";
3168
+ if (src2?.mode === "local-file" && wantArtifactWatch && !/^https?:\/\//i.test(src2.path)) {
3169
+ try {
3170
+ const { watch: chokidarWatch2 } = await import("chokidar");
3171
+ const w = chokidarWatch2(src2.path, {
3172
+ ignoreInitial: true,
3173
+ awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 20 },
3174
+ persistent: true,
3175
+ // Use polling to avoid `fs.watch` exhausting the
3176
+ // process file-descriptor limit on macOS (chokidar
3177
+ // recursively wires watches on the parent
3178
+ // directory tree which can trip EMFILE on busy
3179
+ // dev hosts). 500ms polling is fast enough for
3180
+ // HMR (a recompile takes ~400ms anyway).
3181
+ usePolling: true,
3182
+ interval: 500,
3183
+ binaryInterval: 1e3
3184
+ });
3185
+ let pending = false;
3186
+ const reload = async () => {
3187
+ if (pending) return;
3188
+ pending = true;
3189
+ try {
3190
+ await this._loadFromLocalFile(ctx, src2.path, src2.fetchTimeoutMs);
3191
+ hub.broadcastReload("artifact-file-changed", [src2.path]);
3192
+ ctx.logger.info("[MetadataPlugin] artifact auto-reloaded (file watcher)", {
3193
+ path: src2.path
3194
+ });
3195
+ } catch (e) {
3196
+ ctx.logger.warn("[MetadataPlugin] artifact auto-reload failed", { error: e?.message });
3197
+ } finally {
3198
+ pending = false;
3199
+ }
3200
+ };
3201
+ w.on("change", () => {
3202
+ void reload();
3203
+ });
3204
+ w.on("add", () => {
3205
+ void reload();
3206
+ });
3207
+ this.artifactWatcher = { close: () => w.close() };
3208
+ console.log("[MetadataPlugin] artifact file watcher attached", src2.path);
3209
+ } catch (e) {
3210
+ ctx.logger.warn("[MetadataPlugin] artifact watcher failed to start", { error: e?.message });
3211
+ }
3212
+ }
3025
3213
  console.log("[MetadataPlugin] HMR endpoint registered at /api/v1/dev/metadata-events");
3026
3214
  } else {
3027
3215
  console.log("[MetadataPlugin] HTTP server with getRawApp() not available \u2014 skipping HMR endpoint");
@@ -3030,6 +3218,28 @@ var MetadataPlugin = class {
3030
3218
  console.warn("[MetadataPlugin] Failed to register HMR endpoint", e?.message);
3031
3219
  }
3032
3220
  };
3221
+ this.stop = async (ctx) => {
3222
+ if (this.artifactWatcher) {
3223
+ try {
3224
+ await this.artifactWatcher.close();
3225
+ } catch {
3226
+ }
3227
+ this.artifactWatcher = void 0;
3228
+ }
3229
+ try {
3230
+ await this.manager.dispose();
3231
+ } catch (e) {
3232
+ ctx.logger.warn("[MetadataPlugin] manager.dispose() failed", { error: e?.message });
3233
+ }
3234
+ const repo = this.repository;
3235
+ if (repo && typeof repo.close === "function") {
3236
+ try {
3237
+ await repo.close();
3238
+ } catch {
3239
+ }
3240
+ }
3241
+ this.repository = void 0;
3242
+ };
3033
3243
  this.options = {
3034
3244
  watch: true,
3035
3245
  ...options
@@ -3106,7 +3316,12 @@ var MetadataPlugin = class {
3106
3316
  const items = metadata[field];
3107
3317
  if (!Array.isArray(items) || items.length === 0) continue;
3108
3318
  for (const item of items) {
3109
- const name = item?.name;
3319
+ let name = item?.name;
3320
+ if (!name) {
3321
+ if (metaType === "view") {
3322
+ name = item?.list?.data?.object ?? item?.form?.data?.object;
3323
+ }
3324
+ }
3110
3325
  if (!name) continue;
3111
3326
  if (manifestPackageId && item._packageId === void 0) {
3112
3327
  item._packageId = manifestPackageId;
@@ -3415,6 +3630,10 @@ function registerMetadataHistoryRoutes(app, metadataService) {
3415
3630
  }
3416
3631
 
3417
3632
  // src/utils/history-cleanup.ts
3633
+ var import_kernel2 = require("@objectstack/spec/kernel");
3634
+ function executionPinnedTypes() {
3635
+ return import_kernel2.DEFAULT_METADATA_TYPE_REGISTRY.filter((entry) => entry.executionPinned).map((entry) => entry.type);
3636
+ }
3418
3637
  var HistoryCleanupManager = class {
3419
3638
  constructor(policy, dbLoader) {
3420
3639
  this.policy = policy;
@@ -3450,9 +3669,10 @@ var HistoryCleanupManager = class {
3450
3669
  const driver = this.dbLoader.driver;
3451
3670
  const historyTableName = this.dbLoader.historyTableName;
3452
3671
  const organizationId = this.dbLoader.organizationId;
3453
- const projectId = this.dbLoader.projectId;
3454
3672
  let deleted = 0;
3455
3673
  let errors = 0;
3674
+ const pinnedTypes = executionPinnedTypes();
3675
+ const isPinned = (t) => !!t && pinnedTypes.includes(t);
3456
3676
  try {
3457
3677
  if (this.policy.maxAgeDays) {
3458
3678
  const cutoffDate = /* @__PURE__ */ new Date();
@@ -3464,8 +3684,8 @@ var HistoryCleanupManager = class {
3464
3684
  if (organizationId) {
3465
3685
  filter.organization_id = organizationId;
3466
3686
  }
3467
- if (projectId !== void 0) {
3468
- filter.project_id = projectId;
3687
+ if (pinnedTypes.length > 0) {
3688
+ filter.type = { $nin: pinnedTypes };
3469
3689
  }
3470
3690
  try {
3471
3691
  const result = await this.bulkDeleteByFilter(driver, historyTableName, filter);
@@ -3479,20 +3699,22 @@ var HistoryCleanupManager = class {
3479
3699
  try {
3480
3700
  const baseWhere = {};
3481
3701
  if (organizationId) baseWhere.organization_id = organizationId;
3482
- if (projectId !== void 0) baseWhere.project_id = projectId;
3483
- const metadataIds = await driver.find(historyTableName, {
3702
+ const metaItems = await driver.find(historyTableName, {
3484
3703
  object: historyTableName,
3485
3704
  where: baseWhere,
3486
- fields: ["metadata_id"]
3705
+ fields: ["type", "name"]
3487
3706
  });
3488
- const uniqueIds = /* @__PURE__ */ new Set();
3489
- for (const record of metadataIds) {
3490
- if (record.metadata_id) {
3491
- uniqueIds.add(record.metadata_id);
3707
+ const uniqueKeys = /* @__PURE__ */ new Set();
3708
+ for (const record of metaItems) {
3709
+ const t = record.type;
3710
+ const n = record.name;
3711
+ if (t && n && !isPinned(t)) {
3712
+ uniqueKeys.add(`${t}${n}`);
3492
3713
  }
3493
3714
  }
3494
- for (const metadataId of uniqueIds) {
3495
- const filter = { metadata_id: metadataId, ...baseWhere };
3715
+ for (const key of uniqueKeys) {
3716
+ const [type, name] = key.split("");
3717
+ const filter = { type, name, ...baseWhere };
3496
3718
  try {
3497
3719
  const historyRecords = await driver.find(historyTableName, {
3498
3720
  object: historyTableName,
@@ -3567,13 +3789,13 @@ var HistoryCleanupManager = class {
3567
3789
  const driver = this.dbLoader.driver;
3568
3790
  const historyTableName = this.dbLoader.historyTableName;
3569
3791
  const organizationId = this.dbLoader.organizationId;
3570
- const projectId = this.dbLoader.projectId;
3571
3792
  let recordsByAge = 0;
3572
3793
  let recordsByCount = 0;
3794
+ const pinnedTypes = executionPinnedTypes();
3795
+ const isPinned = (t) => !!t && pinnedTypes.includes(t);
3573
3796
  try {
3574
3797
  const baseWhere = {};
3575
3798
  if (organizationId) baseWhere.organization_id = organizationId;
3576
- if (projectId !== void 0) baseWhere.project_id = projectId;
3577
3799
  if (this.policy.maxAgeDays) {
3578
3800
  const cutoffDate = /* @__PURE__ */ new Date();
3579
3801
  cutoffDate.setDate(cutoffDate.getDate() - this.policy.maxAgeDays);
@@ -3582,25 +3804,31 @@ var HistoryCleanupManager = class {
3582
3804
  recorded_at: { $lt: cutoffISO },
3583
3805
  ...baseWhere
3584
3806
  };
3807
+ if (pinnedTypes.length > 0) {
3808
+ filter.type = { $nin: pinnedTypes };
3809
+ }
3585
3810
  recordsByAge = await driver.count(historyTableName, {
3586
3811
  object: historyTableName,
3587
3812
  where: filter
3588
3813
  });
3589
3814
  }
3590
3815
  if (this.policy.maxVersions) {
3591
- const metadataIds = await driver.find(historyTableName, {
3816
+ const metaItems = await driver.find(historyTableName, {
3592
3817
  object: historyTableName,
3593
3818
  where: baseWhere,
3594
- fields: ["metadata_id"]
3819
+ fields: ["type", "name"]
3595
3820
  });
3596
- const uniqueIds = /* @__PURE__ */ new Set();
3597
- for (const record of metadataIds) {
3598
- if (record.metadata_id) {
3599
- uniqueIds.add(record.metadata_id);
3821
+ const uniqueKeys = /* @__PURE__ */ new Set();
3822
+ for (const record of metaItems) {
3823
+ const t = record.type;
3824
+ const n = record.name;
3825
+ if (t && n && !isPinned(t)) {
3826
+ uniqueKeys.add(`${t}${n}`);
3600
3827
  }
3601
3828
  }
3602
- for (const metadataId of uniqueIds) {
3603
- const filter = { metadata_id: metadataId, ...baseWhere };
3829
+ for (const key of uniqueKeys) {
3830
+ const [type, name] = key.split("");
3831
+ const filter = { type, name, ...baseWhere };
3604
3832
  const count = await driver.count(historyTableName, {
3605
3833
  object: historyTableName,
3606
3834
  where: filter