@camstack/addon-model-studio 1.1.328 → 1.1.329

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/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.329] - 2026-10-31
11
+
12
+ ### Changed
13
+
14
+ - Forgetting a node now removes its entries from the model Library 'On nodes' list
15
+
10
16
  ## [1.1.328] - 2026-10-30
11
17
 
12
18
  ### Changed
@@ -44698,14 +44698,24 @@ method(object({
44698
44698
  kind: "mutation",
44699
44699
  auth: "admin"
44700
44700
  });
44701
- method(object({
44702
- nodeId: string().min(1),
44703
- /** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
44704
- removedAt: number()
44705
- }), object({ counts: record(string(), number().int().nonnegative()) }), {
44706
- kind: "mutation",
44707
- auth: "admin"
44708
- });
44701
+ var nodeRemovalHookCapability = {
44702
+ name: "node-removal-hook",
44703
+ scope: "system",
44704
+ mode: "collection",
44705
+ internal: true,
44706
+ methods: {
44707
+ /** Remove or re-point everything this addon keeps for the forgotten node. Idempotent. */
44708
+ onNodeRemoved: method(object({
44709
+ nodeId: string().min(1),
44710
+ /** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
44711
+ removedAt: number()
44712
+ }), object({ counts: record(string(), number().int().nonnegative()) }), {
44713
+ kind: "mutation",
44714
+ auth: "admin"
44715
+ }) },
44716
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
44717
+ mount: { kind: "skip" }
44718
+ };
44709
44719
  /**
44710
44720
  * `device-removal-hook` — the reliable way an addon learns a device was DELETED,
44711
44721
  * so it can delete what it keeps for that device (D865).
@@ -87880,6 +87890,41 @@ function stripModelIdFromInferenceDevices(devices, modelId) {
87880
87890
  };
87881
87891
  }
87882
87892
  //#endregion
87893
+ //#region src/node-removal.ts
87894
+ /** `map` without `nodeId`'s entries, and how many (model, node) entries went. */
87895
+ function clearNodeAvailability(map, nodeId) {
87896
+ const next = {};
87897
+ let cleared = 0;
87898
+ for (const [modelId, perNode] of Object.entries(map)) {
87899
+ if (!(nodeId in perNode)) {
87900
+ next[modelId] = perNode;
87901
+ continue;
87902
+ }
87903
+ cleared += 1;
87904
+ const { [nodeId]: _gone, ...rest } = perNode;
87905
+ if (Object.keys(rest).length > 0) next[modelId] = rest;
87906
+ }
87907
+ return {
87908
+ next,
87909
+ cleared
87910
+ };
87911
+ }
87912
+ function createNodeRemovalHook(deps) {
87913
+ return { onNodeRemoved: async ({ nodeId }) => {
87914
+ let cleared = 0;
87915
+ await deps.availability().update((prev) => {
87916
+ const result = clearNodeAvailability(prev, nodeId);
87917
+ cleared = result.cleared;
87918
+ return cleared === 0 ? prev : result.next;
87919
+ });
87920
+ deps.logger.info("cleared model availability of a forgotten node", {
87921
+ tags: { nodeId },
87922
+ meta: { cleared }
87923
+ });
87924
+ return { counts: { "model-availability": cleared } };
87925
+ } };
87926
+ }
87927
+ //#endregion
87883
87928
  //#region src/plus-catalog/plus-catalog-import.ts
87884
87929
  /**
87885
87930
  * Variant-selector axes for an imported plus catalog / plus catalog detector. Family
@@ -89321,13 +89366,22 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
89321
89366
  capability: addonPagesSourceCapability,
89322
89367
  provider: pagesProvider
89323
89368
  }];
89324
- if (isHub) providers.push({
89325
- capability: customModelRegistryCapability,
89326
- provider: {
89327
- listModels: async () => state.get(),
89328
- importModelBundle: async (input) => this.importModelBundle_(input)
89329
- }
89330
- });
89369
+ if (isHub) {
89370
+ providers.push({
89371
+ capability: customModelRegistryCapability,
89372
+ provider: {
89373
+ listModels: async () => state.get(),
89374
+ importModelBundle: async (input) => this.importModelBundle_(input)
89375
+ }
89376
+ });
89377
+ providers.push({
89378
+ capability: nodeRemovalHookCapability,
89379
+ provider: createNodeRemovalHook({
89380
+ availability: () => this.availability_,
89381
+ logger: this.ctx.logger.child("node-removal")
89382
+ })
89383
+ });
89384
+ }
89331
89385
  return {
89332
89386
  providers,
89333
89387
  customActions: isHub ? modelStudioActions : void 0,
@@ -44672,14 +44672,24 @@ method(object({
44672
44672
  kind: "mutation",
44673
44673
  auth: "admin"
44674
44674
  });
44675
- method(object({
44676
- nodeId: string().min(1),
44677
- /** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
44678
- removedAt: number()
44679
- }), object({ counts: record(string(), number().int().nonnegative()) }), {
44680
- kind: "mutation",
44681
- auth: "admin"
44682
- });
44675
+ var nodeRemovalHookCapability = {
44676
+ name: "node-removal-hook",
44677
+ scope: "system",
44678
+ mode: "collection",
44679
+ internal: true,
44680
+ methods: {
44681
+ /** Remove or re-point everything this addon keeps for the forgotten node. Idempotent. */
44682
+ onNodeRemoved: method(object({
44683
+ nodeId: string().min(1),
44684
+ /** Epoch ms the node was forgotten (the tombstone's `removedAt`). */
44685
+ removedAt: number()
44686
+ }), object({ counts: record(string(), number().int().nonnegative()) }), {
44687
+ kind: "mutation",
44688
+ auth: "admin"
44689
+ }) },
44690
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
44691
+ mount: { kind: "skip" }
44692
+ };
44683
44693
  /**
44684
44694
  * `device-removal-hook` — the reliable way an addon learns a device was DELETED,
44685
44695
  * so it can delete what it keeps for that device (D865).
@@ -87854,6 +87864,41 @@ function stripModelIdFromInferenceDevices(devices, modelId) {
87854
87864
  };
87855
87865
  }
87856
87866
  //#endregion
87867
+ //#region src/node-removal.ts
87868
+ /** `map` without `nodeId`'s entries, and how many (model, node) entries went. */
87869
+ function clearNodeAvailability(map, nodeId) {
87870
+ const next = {};
87871
+ let cleared = 0;
87872
+ for (const [modelId, perNode] of Object.entries(map)) {
87873
+ if (!(nodeId in perNode)) {
87874
+ next[modelId] = perNode;
87875
+ continue;
87876
+ }
87877
+ cleared += 1;
87878
+ const { [nodeId]: _gone, ...rest } = perNode;
87879
+ if (Object.keys(rest).length > 0) next[modelId] = rest;
87880
+ }
87881
+ return {
87882
+ next,
87883
+ cleared
87884
+ };
87885
+ }
87886
+ function createNodeRemovalHook(deps) {
87887
+ return { onNodeRemoved: async ({ nodeId }) => {
87888
+ let cleared = 0;
87889
+ await deps.availability().update((prev) => {
87890
+ const result = clearNodeAvailability(prev, nodeId);
87891
+ cleared = result.cleared;
87892
+ return cleared === 0 ? prev : result.next;
87893
+ });
87894
+ deps.logger.info("cleared model availability of a forgotten node", {
87895
+ tags: { nodeId },
87896
+ meta: { cleared }
87897
+ });
87898
+ return { counts: { "model-availability": cleared } };
87899
+ } };
87900
+ }
87901
+ //#endregion
87857
87902
  //#region src/plus-catalog/plus-catalog-import.ts
87858
87903
  /**
87859
87904
  * Variant-selector axes for an imported plus catalog / plus catalog detector. Family
@@ -89295,13 +89340,22 @@ var ModelStudioAddon = class ModelStudioAddon extends BaseAddon {
89295
89340
  capability: addonPagesSourceCapability,
89296
89341
  provider: pagesProvider
89297
89342
  }];
89298
- if (isHub) providers.push({
89299
- capability: customModelRegistryCapability,
89300
- provider: {
89301
- listModels: async () => state.get(),
89302
- importModelBundle: async (input) => this.importModelBundle_(input)
89303
- }
89304
- });
89343
+ if (isHub) {
89344
+ providers.push({
89345
+ capability: customModelRegistryCapability,
89346
+ provider: {
89347
+ listModels: async () => state.get(),
89348
+ importModelBundle: async (input) => this.importModelBundle_(input)
89349
+ }
89350
+ });
89351
+ providers.push({
89352
+ capability: nodeRemovalHookCapability,
89353
+ provider: createNodeRemovalHook({
89354
+ availability: () => this.availability_,
89355
+ logger: this.ctx.logger.child("node-removal")
89356
+ })
89357
+ });
89358
+ }
89305
89359
  return {
89306
89360
  providers,
89307
89361
  customActions: isHub ? modelStudioActions : void 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-model-studio",
3
- "version": "1.1.328",
3
+ "version": "1.1.329",
4
4
  "description": "Custom detection model registry, conversion & distribution for CamStack",
5
5
  "keywords": [
6
6
  "camstack",
@@ -53,6 +53,9 @@
53
53
  {
54
54
  "name": "model-convert"
55
55
  },
56
+ {
57
+ "name": "node-removal-hook"
58
+ },
56
59
  {
57
60
  "name": "addon-pages-source"
58
61
  }