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