@memberjunction/ng-clustering 5.39.0 → 5.40.1

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.
Files changed (49) hide show
  1. package/dist/__tests__/cluster-view-type.test.d.ts +2 -0
  2. package/dist/__tests__/cluster-view-type.test.d.ts.map +1 -0
  3. package/dist/__tests__/cluster-view-type.test.js +63 -0
  4. package/dist/__tests__/cluster-view-type.test.js.map +1 -0
  5. package/dist/lib/cluster-config-panel.component.d.ts +39 -1
  6. package/dist/lib/cluster-config-panel.component.d.ts.map +1 -1
  7. package/dist/lib/cluster-config-panel.component.js +287 -88
  8. package/dist/lib/cluster-config-panel.component.js.map +1 -1
  9. package/dist/lib/cluster-scatter.component.d.ts +53 -1
  10. package/dist/lib/cluster-scatter.component.d.ts.map +1 -1
  11. package/dist/lib/cluster-scatter.component.js +385 -135
  12. package/dist/lib/cluster-scatter.component.js.map +1 -1
  13. package/dist/lib/clustering.module.d.ts +7 -4
  14. package/dist/lib/clustering.module.d.ts.map +1 -1
  15. package/dist/lib/clustering.module.js +20 -3
  16. package/dist/lib/clustering.module.js.map +1 -1
  17. package/dist/lib/clustering.service.d.ts +63 -3
  18. package/dist/lib/clustering.service.d.ts.map +1 -1
  19. package/dist/lib/clustering.service.js +169 -48
  20. package/dist/lib/clustering.service.js.map +1 -1
  21. package/dist/lib/clustering.types.d.ts +17 -2
  22. package/dist/lib/clustering.types.d.ts.map +1 -1
  23. package/dist/lib/clustering.types.js +3 -0
  24. package/dist/lib/clustering.types.js.map +1 -1
  25. package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts +31 -0
  26. package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts.map +1 -0
  27. package/dist/lib/view-type/cluster-view-prop-sheet.component.js +185 -0
  28. package/dist/lib/view-type/cluster-view-prop-sheet.component.js.map +1 -0
  29. package/dist/lib/view-type/cluster-view-renderer.component.d.ts +143 -0
  30. package/dist/lib/view-type/cluster-view-renderer.component.d.ts.map +1 -0
  31. package/dist/lib/view-type/cluster-view-renderer.component.js +508 -0
  32. package/dist/lib/view-type/cluster-view-renderer.component.js.map +1 -0
  33. package/dist/lib/view-type/cluster-view-type.d.ts +35 -0
  34. package/dist/lib/view-type/cluster-view-type.d.ts.map +1 -0
  35. package/dist/lib/view-type/cluster-view-type.js +51 -0
  36. package/dist/lib/view-type/cluster-view-type.js.map +1 -0
  37. package/dist/lib/view-type/cluster-view.types.d.ts +31 -0
  38. package/dist/lib/view-type/cluster-view.types.d.ts.map +1 -0
  39. package/dist/lib/view-type/cluster-view.types.js +29 -0
  40. package/dist/lib/view-type/cluster-view.types.js.map +1 -0
  41. package/dist/lib/view-type/entity-document-availability.engine.d.ts +80 -0
  42. package/dist/lib/view-type/entity-document-availability.engine.d.ts.map +1 -0
  43. package/dist/lib/view-type/entity-document-availability.engine.js +149 -0
  44. package/dist/lib/view-type/entity-document-availability.engine.js.map +1 -0
  45. package/dist/public-api.d.ts +5 -0
  46. package/dist/public-api.d.ts.map +1 -1
  47. package/dist/public-api.js +6 -0
  48. package/dist/public-api.js.map +1 -1
  49. package/package.json +11 -3
@@ -0,0 +1,51 @@
1
+ import { __decorate } from "tslib";
2
+ import { RegisterClass } from '@memberjunction/global';
3
+ import { BaseViewTypeDescriptor } from '@memberjunction/ng-entity-viewer';
4
+ import { ClusterViewRendererComponent } from './cluster-view-renderer.component';
5
+ import { ClusterViewPropSheetComponent } from './cluster-view-prop-sheet.component';
6
+ import { EntityDocumentAvailabilityEngine } from './entity-document-availability.engine';
7
+ /**
8
+ * ClusterViewType
9
+ * ---------------
10
+ * The Cluster **view-type plug-in** descriptor. This is the proof that a feature package
11
+ * (ng-clustering) can contribute a brand-new entity-viewer view type by depending only on the
12
+ * host's contracts (`@memberjunction/ng-entity-viewer`) — the host needs zero changes.
13
+ *
14
+ * - **RendererComponent** → {@link ClusterViewRendererComponent} (wraps `mj-cluster-scatter`).
15
+ * - **PropSheetComponent** → {@link ClusterViewPropSheetComponent} (algorithm/K/2D-3D/etc.).
16
+ * - **IsAvailableFor** → true only for entities that have an active Entity Document (vectors),
17
+ * read synchronously from {@link EntityDocumentAvailabilityEngine}.
18
+ * - **EnsureAvailabilityData** → preloads that engine so the sync predicate has data.
19
+ *
20
+ * Registered with the ClassFactory under `'ClusterViewType'`, matching the `DriverClass` of the
21
+ * seeded `MJ: View Types` "Cluster" row.
22
+ */
23
+ let ClusterViewType = class ClusterViewType extends BaseViewTypeDescriptor {
24
+ constructor() {
25
+ super(...arguments);
26
+ this.Name = 'ClusterViewType';
27
+ this.DisplayName = 'Cluster';
28
+ this.Icon = 'fa-solid fa-diagram-project';
29
+ this.RendererComponent = ClusterViewRendererComponent;
30
+ this.PropSheetComponent = ClusterViewPropSheetComponent;
31
+ }
32
+ IsAvailableFor(entity, _provider) {
33
+ return EntityDocumentAvailabilityEngine.Instance.HasVectorsForEntity(entity);
34
+ }
35
+ async EnsureAvailabilityData(provider) {
36
+ await EntityDocumentAvailabilityEngine.Instance.Config(false, provider?.CurrentUser, provider ?? undefined);
37
+ }
38
+ };
39
+ ClusterViewType = __decorate([
40
+ RegisterClass(BaseViewTypeDescriptor, 'ClusterViewType')
41
+ ], ClusterViewType);
42
+ export { ClusterViewType };
43
+ /**
44
+ * Tree-shaking guard. ng-clustering sets `"sideEffects": false`, so without an explicit
45
+ * reference the bundler can drop this module's `@RegisterClass` side effect. Call this once
46
+ * from app bootstrap (or import the ClusteringModule) to guarantee the descriptor registers.
47
+ */
48
+ export function LoadClusterViewType() {
49
+ // no-op; presence prevents tree-shaking of the @RegisterClass side effect above.
50
+ }
51
+ //# sourceMappingURL=cluster-view-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cluster-view-type.js","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view-type.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AACpF,OAAO,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AAEzF;;;;;;;;;;;;;;;GAeG;AAEI,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,sBAAsB;IAApD;;QACI,SAAI,GAAG,iBAAiB,CAAC;QACzB,gBAAW,GAAG,SAAS,CAAC;QACxB,SAAI,GAAG,6BAA6B,CAAC;QACrC,sBAAiB,GAAkB,4BAA4B,CAAC;QACvD,uBAAkB,GAAkB,6BAA6B,CAAC;IAStF,CAAC;IAPU,cAAc,CAAC,MAAkB,EAAE,SAA6B;QACvE,OAAO,gCAAgC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC/E,CAAC;IAEQ,KAAK,CAAC,sBAAsB,CAAC,QAA4B;QAChE,MAAM,gCAAgC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,IAAI,SAAS,CAAC,CAAC;IAC9G,CAAC;CACF,CAAA;AAdY,eAAe;IAD3B,aAAa,CAAC,sBAAsB,EAAE,iBAAiB,CAAC;GAC5C,eAAe,CAc3B;;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,iFAAiF;AACnF,CAAC","sourcesContent":["import { Type } from '@angular/core';\nimport { EntityInfo, IMetadataProvider } from '@memberjunction/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseViewTypeDescriptor } from '@memberjunction/ng-entity-viewer';\nimport { ClusterViewRendererComponent } from './cluster-view-renderer.component';\nimport { ClusterViewPropSheetComponent } from './cluster-view-prop-sheet.component';\nimport { EntityDocumentAvailabilityEngine } from './entity-document-availability.engine';\n\n/**\n * ClusterViewType\n * ---------------\n * The Cluster **view-type plug-in** descriptor. This is the proof that a feature package\n * (ng-clustering) can contribute a brand-new entity-viewer view type by depending only on the\n * host's contracts (`@memberjunction/ng-entity-viewer`) — the host needs zero changes.\n *\n * - **RendererComponent** → {@link ClusterViewRendererComponent} (wraps `mj-cluster-scatter`).\n * - **PropSheetComponent** → {@link ClusterViewPropSheetComponent} (algorithm/K/2D-3D/etc.).\n * - **IsAvailableFor** → true only for entities that have an active Entity Document (vectors),\n * read synchronously from {@link EntityDocumentAvailabilityEngine}.\n * - **EnsureAvailabilityData** → preloads that engine so the sync predicate has data.\n *\n * Registered with the ClassFactory under `'ClusterViewType'`, matching the `DriverClass` of the\n * seeded `MJ: View Types` \"Cluster\" row.\n */\n@RegisterClass(BaseViewTypeDescriptor, 'ClusterViewType')\nexport class ClusterViewType extends BaseViewTypeDescriptor {\n readonly Name = 'ClusterViewType';\n readonly DisplayName = 'Cluster';\n readonly Icon = 'fa-solid fa-diagram-project';\n readonly RendererComponent: Type<unknown> = ClusterViewRendererComponent;\n override readonly PropSheetComponent: Type<unknown> = ClusterViewPropSheetComponent;\n\n override IsAvailableFor(entity: EntityInfo, _provider?: IMetadataProvider): boolean {\n return EntityDocumentAvailabilityEngine.Instance.HasVectorsForEntity(entity);\n }\n\n override async EnsureAvailabilityData(provider?: IMetadataProvider): Promise<void> {\n await EntityDocumentAvailabilityEngine.Instance.Config(false, provider?.CurrentUser, provider ?? undefined);\n }\n}\n\n/**\n * Tree-shaking guard. ng-clustering sets `\"sideEffects\": false`, so without an explicit\n * reference the bundler can drop this module's `@RegisterClass` side effect. Call this once\n * from app bootstrap (or import the ClusteringModule) to guarantee the descriptor registers.\n */\nexport function LoadClusterViewType(): void {\n // no-op; presence prevents tree-shaking of the @RegisterClass side effect above.\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { ClusterAlgorithm } from '../clustering.types';
2
+ /**
3
+ * Configuration for the Cluster **view type** (the entity-viewer plug-in), persisted per
4
+ * `UserView` in `DisplayState.viewTypeConfigs` keyed by the Cluster `MJ: View Types` row ID.
5
+ *
6
+ * This is the plug-in-owned config shape referenced opaquely by the host as
7
+ * `Record<string, unknown>`; the Cluster renderer + prop-sheet interpret it with this type.
8
+ */
9
+ export interface ClusterViewConfig {
10
+ /** Clustering algorithm. */
11
+ algorithm: ClusterAlgorithm;
12
+ /** Number of clusters for K-Means (ignored for DBSCAN). */
13
+ k: number;
14
+ /** Projected layout dimensionality: 2 (SVG) or 3 (rotatable). */
15
+ dimensions: 2 | 3;
16
+ /** Legend mode: color by assigned cluster (default) or by source entity. */
17
+ colorBy: 'cluster' | 'entity';
18
+ /** Maximum records to fetch for clustering. */
19
+ maxRecords: number;
20
+ /** Whether to name clusters with an LLM (server-side). */
21
+ nameClusters: boolean;
22
+ }
23
+ /** Sensible defaults for a fresh Cluster view. */
24
+ export declare const DEFAULT_CLUSTER_VIEW_CONFIG: ClusterViewConfig;
25
+ /**
26
+ * Normalizes an opaque host config map into a fully-populated {@link ClusterViewConfig},
27
+ * filling any missing keys from {@link DEFAULT_CLUSTER_VIEW_CONFIG}. Tolerant of partial or
28
+ * empty input (e.g. a brand-new view with no saved config).
29
+ */
30
+ export declare function toClusterViewConfig(raw: Record<string, unknown> | null | undefined): ClusterViewConfig;
31
+ //# sourceMappingURL=cluster-view.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cluster-view.types.d.ts","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,2DAA2D;IAC3D,CAAC,EAAE,MAAM,CAAC;IACV,iEAAiE;IACjE,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;IAClB,4EAA4E;IAC5E,OAAO,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC9B,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,kDAAkD;AAClD,eAAO,MAAM,2BAA2B,EAAE,iBAOzC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,CAatG"}
@@ -0,0 +1,29 @@
1
+ /** Sensible defaults for a fresh Cluster view. */
2
+ export const DEFAULT_CLUSTER_VIEW_CONFIG = {
3
+ algorithm: 'kmeans',
4
+ k: 5,
5
+ dimensions: 2,
6
+ colorBy: 'cluster',
7
+ maxRecords: 500,
8
+ nameClusters: true,
9
+ };
10
+ /**
11
+ * Normalizes an opaque host config map into a fully-populated {@link ClusterViewConfig},
12
+ * filling any missing keys from {@link DEFAULT_CLUSTER_VIEW_CONFIG}. Tolerant of partial or
13
+ * empty input (e.g. a brand-new view with no saved config).
14
+ */
15
+ export function toClusterViewConfig(raw) {
16
+ const r = raw ?? {};
17
+ const algorithm = r['algorithm'] === 'dbscan' ? 'dbscan' : 'kmeans';
18
+ const dimensions = r['dimensions'] === 3 ? 3 : 2;
19
+ const colorBy = r['colorBy'] === 'entity' ? 'entity' : 'cluster';
20
+ return {
21
+ algorithm,
22
+ k: typeof r['k'] === 'number' ? r['k'] : DEFAULT_CLUSTER_VIEW_CONFIG.k,
23
+ dimensions,
24
+ colorBy,
25
+ maxRecords: typeof r['maxRecords'] === 'number' ? r['maxRecords'] : DEFAULT_CLUSTER_VIEW_CONFIG.maxRecords,
26
+ nameClusters: typeof r['nameClusters'] === 'boolean' ? r['nameClusters'] : DEFAULT_CLUSTER_VIEW_CONFIG.nameClusters,
27
+ };
28
+ }
29
+ //# sourceMappingURL=cluster-view.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cluster-view.types.js","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view.types.ts"],"names":[],"mappings":"AAwBA,kDAAkD;AAClD,MAAM,CAAC,MAAM,2BAA2B,GAAsB;IAC5D,SAAS,EAAE,QAAQ;IACnB,CAAC,EAAE,CAAC;IACJ,UAAU,EAAE,CAAC;IACb,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,GAAG;IACf,YAAY,EAAE,IAAI;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAA+C;IACjF,MAAM,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC;IACpB,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IACpE,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IACjE,OAAO;QACL,SAAS;QACT,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,GAAG,CAAY,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC;QAClF,UAAU;QACV,OAAO;QACP,UAAU,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,YAAY,CAAY,CAAC,CAAC,CAAC,2BAA2B,CAAC,UAAU;QACtH,YAAY,EAAE,OAAO,CAAC,CAAC,cAAc,CAAC,KAAK,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,cAAc,CAAa,CAAC,CAAC,CAAC,2BAA2B,CAAC,YAAY;KACjI,CAAC;AACJ,CAAC","sourcesContent":["import { ClusterAlgorithm } from '../clustering.types';\n\n/**\n * Configuration for the Cluster **view type** (the entity-viewer plug-in), persisted per\n * `UserView` in `DisplayState.viewTypeConfigs` keyed by the Cluster `MJ: View Types` row ID.\n *\n * This is the plug-in-owned config shape referenced opaquely by the host as\n * `Record<string, unknown>`; the Cluster renderer + prop-sheet interpret it with this type.\n */\nexport interface ClusterViewConfig {\n /** Clustering algorithm. */\n algorithm: ClusterAlgorithm;\n /** Number of clusters for K-Means (ignored for DBSCAN). */\n k: number;\n /** Projected layout dimensionality: 2 (SVG) or 3 (rotatable). */\n dimensions: 2 | 3;\n /** Legend mode: color by assigned cluster (default) or by source entity. */\n colorBy: 'cluster' | 'entity';\n /** Maximum records to fetch for clustering. */\n maxRecords: number;\n /** Whether to name clusters with an LLM (server-side). */\n nameClusters: boolean;\n}\n\n/** Sensible defaults for a fresh Cluster view. */\nexport const DEFAULT_CLUSTER_VIEW_CONFIG: ClusterViewConfig = {\n algorithm: 'kmeans',\n k: 5,\n dimensions: 2,\n colorBy: 'cluster',\n maxRecords: 500,\n nameClusters: true,\n};\n\n/**\n * Normalizes an opaque host config map into a fully-populated {@link ClusterViewConfig},\n * filling any missing keys from {@link DEFAULT_CLUSTER_VIEW_CONFIG}. Tolerant of partial or\n * empty input (e.g. a brand-new view with no saved config).\n */\nexport function toClusterViewConfig(raw: Record<string, unknown> | null | undefined): ClusterViewConfig {\n const r = raw ?? {};\n const algorithm = r['algorithm'] === 'dbscan' ? 'dbscan' : 'kmeans';\n const dimensions = r['dimensions'] === 3 ? 3 : 2;\n const colorBy = r['colorBy'] === 'entity' ? 'entity' : 'cluster';\n return {\n algorithm,\n k: typeof r['k'] === 'number' ? (r['k'] as number) : DEFAULT_CLUSTER_VIEW_CONFIG.k,\n dimensions,\n colorBy,\n maxRecords: typeof r['maxRecords'] === 'number' ? (r['maxRecords'] as number) : DEFAULT_CLUSTER_VIEW_CONFIG.maxRecords,\n nameClusters: typeof r['nameClusters'] === 'boolean' ? (r['nameClusters'] as boolean) : DEFAULT_CLUSTER_VIEW_CONFIG.nameClusters,\n };\n}\n"]}
@@ -0,0 +1,80 @@
1
+ import { BaseEngine, EntityInfo, IMetadataProvider, UserInfo } from '@memberjunction/core';
2
+ import { MJEntityDocumentEntity } from '@memberjunction/core-entities';
3
+ /**
4
+ * EntityDocumentAvailabilityEngine
5
+ * --------------------------------
6
+ * A tiny, browser-safe cache that answers one synchronous question for the
7
+ * {@link ClusterViewType} availability predicate: *does this entity actually have vectors we
8
+ * can cluster?* — meaning it has an active `MJ: Entity Documents` definition **and** at least
9
+ * one populated `MJ: Entity Record Documents` row (vectorized record). An entity that has an
10
+ * Entity Document configured but no records vectorized yet is NOT clusterable, so the Cluster
11
+ * view type must not be offered for it.
12
+ *
13
+ * The descriptor's `IsAvailableFor` must be synchronous, but the answer depends on data — so the
14
+ * descriptor's async `EnsureAvailabilityData` hook calls {@link Config} (awaited once before the
15
+ * predicates run), and `IsAvailableFor` then reads the populated cache via
16
+ * {@link HasVectorsForEntity}.
17
+ *
18
+ * Extends {@link BaseEngine} for caching + entity-event auto-refresh, and honors the per-request
19
+ * provider (multi-provider safe).
20
+ */
21
+ export declare class EntityDocumentAvailabilityEngine extends BaseEngine<EntityDocumentAvailabilityEngine> {
22
+ /** The global singleton instance. Do not construct directly. */
23
+ static get Instance(): EntityDocumentAvailabilityEngine;
24
+ private _entityDocuments;
25
+ /** Normalized entity IDs that have ≥1 populated vector record. */
26
+ private _entityIDsWithVectors;
27
+ private _vectorPresenceLoaded;
28
+ /**
29
+ * In-flight vector-presence probe, shared by all concurrent {@link Config} callers so they
30
+ * await the SAME load instead of each kicking off (or short-circuiting past) a partial one.
31
+ * Cleared when the probe settles. This is the linchpin of the fix for the Cluster view-type
32
+ * race: the switcher's availability hook and the renderer's `resolveEntityDocumentID` both
33
+ * call `Config` near-simultaneously on first open.
34
+ */
35
+ private _vectorPresenceInFlight;
36
+ /**
37
+ * Loads the active Entity Documents and then probes which of their entities actually have
38
+ * populated vectors. Cheap to call repeatedly — the Entity Documents load is cached by
39
+ * BaseEngine, and the vector-presence probe runs once (until `forceRefresh`).
40
+ */
41
+ Config(forceRefresh?: boolean, contextUser?: UserInfo, provider?: IMetadataProvider): Promise<void>;
42
+ /**
43
+ * Ensure the vector-presence cache is populated exactly once (or re-populated on
44
+ * `forceRefresh`), de-duplicating concurrent callers via {@link _vectorPresenceInFlight}.
45
+ *
46
+ * CRITICAL: `_vectorPresenceLoaded` is flipped to `true` only AFTER the probe resolves and
47
+ * the result set is swapped in — never before the `await`. The previous implementation set
48
+ * the flag at the top of `loadVectorPresence`, so a second concurrent `Config` saw "loaded"
49
+ * while the set was still empty and returned an availability answer of "no vectors" — exactly
50
+ * the flip-flop / "nothing shown then no-vectors" symptom on first Cluster open.
51
+ */
52
+ private ensureVectorPresence;
53
+ /**
54
+ * Determine, per active Entity Document, whether it has at least one populated vector record,
55
+ * and return the set of owning entity IDs. Uses a single batched {@link RunView.RunViews} with
56
+ * `MaxRows: 1` per document so it's a lightweight existence probe, not a full fetch. Returns a
57
+ * fresh set that the caller swaps in atomically once fully populated (see {@link ensureVectorPresence}).
58
+ */
59
+ private loadVectorPresence;
60
+ /** All cached active Entity Document rows. */
61
+ get EntityDocuments(): MJEntityDocumentEntity[];
62
+ /**
63
+ * Return the active Entity Document rows for a given entity, matched by EntityID
64
+ * (case-insensitive UUID comparison). Used by the Cluster view-type renderer to
65
+ * resolve a **deterministic** source document for the entity's vectors — picking
66
+ * the first active doc explicitly rather than letting the server choose one
67
+ * non-deterministically.
68
+ *
69
+ * @param entity The entity to find active documents for.
70
+ * @returns The active Entity Document rows for that entity (possibly empty).
71
+ */
72
+ GetActiveEntityDocumentsForEntity(entity: EntityInfo | null): MJEntityDocumentEntity[];
73
+ /**
74
+ * True only when the entity has an active Entity Document **and** at least one populated vector
75
+ * record — i.e. it can actually be clustered. Returns false for entities that have an Entity
76
+ * Document configured but no vectorized records yet. UUID matching is case-insensitive.
77
+ */
78
+ HasVectorsForEntity(entity: EntityInfo | null): boolean;
79
+ }
80
+ //# sourceMappingURL=entity-document-availability.engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-document-availability.engine.d.ts","sourceRoot":"","sources":["../../../src/lib/view-type/entity-document-availability.engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA4B,UAAU,EAAE,iBAAiB,EAAY,QAAQ,EAAW,MAAM,sBAAsB,CAAC;AACxI,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAGvE;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,gCAAiC,SAAQ,UAAU,CAAC,gCAAgC,CAAC;IAChG,gEAAgE;IAChE,WAAkB,QAAQ,IAAI,gCAAgC,CAE7D;IAED,OAAO,CAAC,gBAAgB,CAAgC;IACxD,kEAAkE;IAClE,OAAO,CAAC,qBAAqB,CAAqB;IAClD,OAAO,CAAC,qBAAqB,CAAS;IACtC;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB,CAA8B;IAE7D;;;;OAIG;IACU,MAAM,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAehH;;;;;;;;;OASG;YACW,oBAAoB;IAyBlC;;;;;OAKG;YACW,kBAAkB;IA6BhC,8CAA8C;IAC9C,IAAW,eAAe,IAAI,sBAAsB,EAAE,CAErD;IAED;;;;;;;;;OASG;IACI,iCAAiC,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,sBAAsB,EAAE;IAQ7F;;;;OAIG;IACI,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO;CAM/D"}
@@ -0,0 +1,149 @@
1
+ import { BaseEngine, Metadata, RunView } from '@memberjunction/core';
2
+ import { NormalizeUUID } from '@memberjunction/global';
3
+ /**
4
+ * EntityDocumentAvailabilityEngine
5
+ * --------------------------------
6
+ * A tiny, browser-safe cache that answers one synchronous question for the
7
+ * {@link ClusterViewType} availability predicate: *does this entity actually have vectors we
8
+ * can cluster?* — meaning it has an active `MJ: Entity Documents` definition **and** at least
9
+ * one populated `MJ: Entity Record Documents` row (vectorized record). An entity that has an
10
+ * Entity Document configured but no records vectorized yet is NOT clusterable, so the Cluster
11
+ * view type must not be offered for it.
12
+ *
13
+ * The descriptor's `IsAvailableFor` must be synchronous, but the answer depends on data — so the
14
+ * descriptor's async `EnsureAvailabilityData` hook calls {@link Config} (awaited once before the
15
+ * predicates run), and `IsAvailableFor` then reads the populated cache via
16
+ * {@link HasVectorsForEntity}.
17
+ *
18
+ * Extends {@link BaseEngine} for caching + entity-event auto-refresh, and honors the per-request
19
+ * provider (multi-provider safe).
20
+ */
21
+ export class EntityDocumentAvailabilityEngine extends BaseEngine {
22
+ constructor() {
23
+ super(...arguments);
24
+ this._entityDocuments = [];
25
+ /** Normalized entity IDs that have ≥1 populated vector record. */
26
+ this._entityIDsWithVectors = new Set();
27
+ this._vectorPresenceLoaded = false;
28
+ /**
29
+ * In-flight vector-presence probe, shared by all concurrent {@link Config} callers so they
30
+ * await the SAME load instead of each kicking off (or short-circuiting past) a partial one.
31
+ * Cleared when the probe settles. This is the linchpin of the fix for the Cluster view-type
32
+ * race: the switcher's availability hook and the renderer's `resolveEntityDocumentID` both
33
+ * call `Config` near-simultaneously on first open.
34
+ */
35
+ this._vectorPresenceInFlight = null;
36
+ }
37
+ /** The global singleton instance. Do not construct directly. */
38
+ static get Instance() {
39
+ return super.getInstance();
40
+ }
41
+ /**
42
+ * Loads the active Entity Documents and then probes which of their entities actually have
43
+ * populated vectors. Cheap to call repeatedly — the Entity Documents load is cached by
44
+ * BaseEngine, and the vector-presence probe runs once (until `forceRefresh`).
45
+ */
46
+ async Config(forceRefresh, contextUser, provider) {
47
+ const c = [
48
+ {
49
+ Type: 'entity',
50
+ EntityName: 'MJ: Entity Documents',
51
+ PropertyName: '_entityDocuments',
52
+ Filter: "Status = 'Active'",
53
+ CacheLocal: true,
54
+ },
55
+ ];
56
+ await this.Load(c, provider ?? Metadata.Provider, forceRefresh, contextUser);
57
+ await this.ensureVectorPresence(forceRefresh ?? false, provider, contextUser);
58
+ }
59
+ /**
60
+ * Ensure the vector-presence cache is populated exactly once (or re-populated on
61
+ * `forceRefresh`), de-duplicating concurrent callers via {@link _vectorPresenceInFlight}.
62
+ *
63
+ * CRITICAL: `_vectorPresenceLoaded` is flipped to `true` only AFTER the probe resolves and
64
+ * the result set is swapped in — never before the `await`. The previous implementation set
65
+ * the flag at the top of `loadVectorPresence`, so a second concurrent `Config` saw "loaded"
66
+ * while the set was still empty and returned an availability answer of "no vectors" — exactly
67
+ * the flip-flop / "nothing shown then no-vectors" symptom on first Cluster open.
68
+ */
69
+ async ensureVectorPresence(forceRefresh, provider, contextUser) {
70
+ if (forceRefresh) {
71
+ this._vectorPresenceLoaded = false;
72
+ this._vectorPresenceInFlight = null;
73
+ }
74
+ if (this._vectorPresenceLoaded) {
75
+ return;
76
+ }
77
+ if (!this._vectorPresenceInFlight) {
78
+ this._vectorPresenceInFlight = this.loadVectorPresence(provider, contextUser)
79
+ .then((populated) => {
80
+ this._entityIDsWithVectors = populated;
81
+ this._vectorPresenceLoaded = true;
82
+ })
83
+ .finally(() => {
84
+ this._vectorPresenceInFlight = null;
85
+ });
86
+ }
87
+ await this._vectorPresenceInFlight;
88
+ }
89
+ /**
90
+ * Determine, per active Entity Document, whether it has at least one populated vector record,
91
+ * and return the set of owning entity IDs. Uses a single batched {@link RunView.RunViews} with
92
+ * `MaxRows: 1` per document so it's a lightweight existence probe, not a full fetch. Returns a
93
+ * fresh set that the caller swaps in atomically once fully populated (see {@link ensureVectorPresence}).
94
+ */
95
+ async loadVectorPresence(provider, contextUser) {
96
+ const entityIDsWithVectors = new Set();
97
+ const docs = this._entityDocuments;
98
+ if (docs.length === 0) {
99
+ return entityIDsWithVectors;
100
+ }
101
+ const rv = provider ? RunView.FromMetadataProvider(provider) : new RunView();
102
+ const results = await rv.RunViews(docs.map((d) => ({
103
+ EntityName: 'MJ: Entity Record Documents',
104
+ ExtraFilter: `EntityDocumentID = '${d.ID}' AND VectorJSON IS NOT NULL`,
105
+ Fields: ['ID'],
106
+ MaxRows: 1,
107
+ ResultType: 'simple',
108
+ })), contextUser);
109
+ results.forEach((res, i) => {
110
+ if (res?.Success && (res.Results?.length ?? 0) > 0) {
111
+ entityIDsWithVectors.add(NormalizeUUID(docs[i].EntityID));
112
+ }
113
+ });
114
+ return entityIDsWithVectors;
115
+ }
116
+ /** All cached active Entity Document rows. */
117
+ get EntityDocuments() {
118
+ return this._entityDocuments;
119
+ }
120
+ /**
121
+ * Return the active Entity Document rows for a given entity, matched by EntityID
122
+ * (case-insensitive UUID comparison). Used by the Cluster view-type renderer to
123
+ * resolve a **deterministic** source document for the entity's vectors — picking
124
+ * the first active doc explicitly rather than letting the server choose one
125
+ * non-deterministically.
126
+ *
127
+ * @param entity The entity to find active documents for.
128
+ * @returns The active Entity Document rows for that entity (possibly empty).
129
+ */
130
+ GetActiveEntityDocumentsForEntity(entity) {
131
+ if (!entity) {
132
+ return [];
133
+ }
134
+ const target = NormalizeUUID(entity.ID);
135
+ return this._entityDocuments.filter((d) => NormalizeUUID(d.EntityID) === target);
136
+ }
137
+ /**
138
+ * True only when the entity has an active Entity Document **and** at least one populated vector
139
+ * record — i.e. it can actually be clustered. Returns false for entities that have an Entity
140
+ * Document configured but no vectorized records yet. UUID matching is case-insensitive.
141
+ */
142
+ HasVectorsForEntity(entity) {
143
+ if (!entity) {
144
+ return false;
145
+ }
146
+ return this._entityIDsWithVectors.has(NormalizeUUID(entity.ID));
147
+ }
148
+ }
149
+ //# sourceMappingURL=entity-document-availability.engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-document-availability.engine.js","sourceRoot":"","sources":["../../../src/lib/view-type/entity-document-availability.engine.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseEngine, BaseEnginePropertyConfig, EntityInfo, IMetadataProvider, Metadata, UserInfo, RunView } from '@memberjunction/core';\nimport { MJEntityDocumentEntity } from '@memberjunction/core-entities';\nimport { NormalizeUUID } from '@memberjunction/global';\n\n/**\n * EntityDocumentAvailabilityEngine\n * --------------------------------\n * A tiny, browser-safe cache that answers one synchronous question for the\n * {@link ClusterViewType} availability predicate: *does this entity actually have vectors we\n * can cluster?* — meaning it has an active `MJ: Entity Documents` definition **and** at least\n * one populated `MJ: Entity Record Documents` row (vectorized record). An entity that has an\n * Entity Document configured but no records vectorized yet is NOT clusterable, so the Cluster\n * view type must not be offered for it.\n *\n * The descriptor's `IsAvailableFor` must be synchronous, but the answer depends on data — so the\n * descriptor's async `EnsureAvailabilityData` hook calls {@link Config} (awaited once before the\n * predicates run), and `IsAvailableFor` then reads the populated cache via\n * {@link HasVectorsForEntity}.\n *\n * Extends {@link BaseEngine} for caching + entity-event auto-refresh, and honors the per-request\n * provider (multi-provider safe).\n */\nexport class EntityDocumentAvailabilityEngine extends BaseEngine<EntityDocumentAvailabilityEngine> {\n /** The global singleton instance. Do not construct directly. */\n public static get Instance(): EntityDocumentAvailabilityEngine {\n return super.getInstance<EntityDocumentAvailabilityEngine>();\n }\n\n private _entityDocuments: MJEntityDocumentEntity[] = [];\n /** Normalized entity IDs that have ≥1 populated vector record. */\n private _entityIDsWithVectors = new Set<string>();\n private _vectorPresenceLoaded = false;\n /**\n * In-flight vector-presence probe, shared by all concurrent {@link Config} callers so they\n * await the SAME load instead of each kicking off (or short-circuiting past) a partial one.\n * Cleared when the probe settles. This is the linchpin of the fix for the Cluster view-type\n * race: the switcher's availability hook and the renderer's `resolveEntityDocumentID` both\n * call `Config` near-simultaneously on first open.\n */\n private _vectorPresenceInFlight: Promise<void> | null = null;\n\n /**\n * Loads the active Entity Documents and then probes which of their entities actually have\n * populated vectors. Cheap to call repeatedly — the Entity Documents load is cached by\n * BaseEngine, and the vector-presence probe runs once (until `forceRefresh`).\n */\n public async Config(forceRefresh?: boolean, contextUser?: UserInfo, provider?: IMetadataProvider): Promise<void> {\n const c: Partial<BaseEnginePropertyConfig>[] = [\n {\n Type: 'entity',\n EntityName: 'MJ: Entity Documents',\n PropertyName: '_entityDocuments',\n Filter: \"Status = 'Active'\",\n CacheLocal: true,\n },\n ];\n await this.Load(c, provider ?? Metadata.Provider, forceRefresh, contextUser);\n\n await this.ensureVectorPresence(forceRefresh ?? false, provider, contextUser);\n }\n\n /**\n * Ensure the vector-presence cache is populated exactly once (or re-populated on\n * `forceRefresh`), de-duplicating concurrent callers via {@link _vectorPresenceInFlight}.\n *\n * CRITICAL: `_vectorPresenceLoaded` is flipped to `true` only AFTER the probe resolves and\n * the result set is swapped in — never before the `await`. The previous implementation set\n * the flag at the top of `loadVectorPresence`, so a second concurrent `Config` saw \"loaded\"\n * while the set was still empty and returned an availability answer of \"no vectors\" — exactly\n * the flip-flop / \"nothing shown then no-vectors\" symptom on first Cluster open.\n */\n private async ensureVectorPresence(\n forceRefresh: boolean,\n provider?: IMetadataProvider,\n contextUser?: UserInfo,\n ): Promise<void> {\n if (forceRefresh) {\n this._vectorPresenceLoaded = false;\n this._vectorPresenceInFlight = null;\n }\n if (this._vectorPresenceLoaded) {\n return;\n }\n if (!this._vectorPresenceInFlight) {\n this._vectorPresenceInFlight = this.loadVectorPresence(provider, contextUser)\n .then((populated) => {\n this._entityIDsWithVectors = populated;\n this._vectorPresenceLoaded = true;\n })\n .finally(() => {\n this._vectorPresenceInFlight = null;\n });\n }\n await this._vectorPresenceInFlight;\n }\n\n /**\n * Determine, per active Entity Document, whether it has at least one populated vector record,\n * and return the set of owning entity IDs. Uses a single batched {@link RunView.RunViews} with\n * `MaxRows: 1` per document so it's a lightweight existence probe, not a full fetch. Returns a\n * fresh set that the caller swaps in atomically once fully populated (see {@link ensureVectorPresence}).\n */\n private async loadVectorPresence(provider?: IMetadataProvider, contextUser?: UserInfo): Promise<Set<string>> {\n const entityIDsWithVectors = new Set<string>();\n\n const docs = this._entityDocuments;\n if (docs.length === 0) {\n return entityIDsWithVectors;\n }\n\n const rv = provider ? RunView.FromMetadataProvider(provider) : new RunView();\n const results = await rv.RunViews(\n docs.map((d) => ({\n EntityName: 'MJ: Entity Record Documents',\n ExtraFilter: `EntityDocumentID = '${d.ID}' AND VectorJSON IS NOT NULL`,\n Fields: ['ID'],\n MaxRows: 1,\n ResultType: 'simple' as const,\n })),\n contextUser,\n );\n\n results.forEach((res, i) => {\n if (res?.Success && (res.Results?.length ?? 0) > 0) {\n entityIDsWithVectors.add(NormalizeUUID(docs[i].EntityID));\n }\n });\n\n return entityIDsWithVectors;\n }\n\n /** All cached active Entity Document rows. */\n public get EntityDocuments(): MJEntityDocumentEntity[] {\n return this._entityDocuments;\n }\n\n /**\n * Return the active Entity Document rows for a given entity, matched by EntityID\n * (case-insensitive UUID comparison). Used by the Cluster view-type renderer to\n * resolve a **deterministic** source document for the entity's vectors — picking\n * the first active doc explicitly rather than letting the server choose one\n * non-deterministically.\n *\n * @param entity The entity to find active documents for.\n * @returns The active Entity Document rows for that entity (possibly empty).\n */\n public GetActiveEntityDocumentsForEntity(entity: EntityInfo | null): MJEntityDocumentEntity[] {\n if (!entity) {\n return [];\n }\n const target = NormalizeUUID(entity.ID);\n return this._entityDocuments.filter((d) => NormalizeUUID(d.EntityID) === target);\n }\n\n /**\n * True only when the entity has an active Entity Document **and** at least one populated vector\n * record — i.e. it can actually be clustered. Returns false for entities that have an Entity\n * Document configured but no vectorized records yet. UUID matching is case-insensitive.\n */\n public HasVectorsForEntity(entity: EntityInfo | null): boolean {\n if (!entity) {\n return false;\n }\n return this._entityIDsWithVectors.has(NormalizeUUID(entity.ID));\n }\n}\n"]}
@@ -3,4 +3,9 @@ export * from './lib/cluster-scatter.component';
3
3
  export * from './lib/cluster-config-panel.component';
4
4
  export * from './lib/clustering.service';
5
5
  export * from './lib/clustering.types';
6
+ export * from './lib/view-type/cluster-view.types';
7
+ export * from './lib/view-type/entity-document-availability.engine';
8
+ export * from './lib/view-type/cluster-view-renderer.component';
9
+ export * from './lib/view-type/cluster-view-prop-sheet.component';
10
+ export * from './lib/view-type/cluster-view-type';
6
11
  //# sourceMappingURL=public-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAGA,cAAc,yBAAyB,CAAC;AAGxC,cAAc,iCAAiC,CAAC;AAChD,cAAc,sCAAsC,CAAC;AAGrD,cAAc,0BAA0B,CAAC;AAGzC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAGA,cAAc,yBAAyB,CAAC;AAGxC,cAAc,iCAAiC,CAAC;AAChD,cAAc,sCAAsC,CAAC;AAGrD,cAAc,0BAA0B,CAAC;AAGzC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,oCAAoC,CAAC;AACnD,cAAc,qDAAqD,CAAC;AACpE,cAAc,iDAAiD,CAAC;AAChE,cAAc,mDAAmD,CAAC;AAClE,cAAc,mCAAmC,CAAC"}
@@ -8,4 +8,10 @@ export * from './lib/cluster-config-panel.component';
8
8
  export * from './lib/clustering.service';
9
9
  // Types
10
10
  export * from './lib/clustering.types';
11
+ // Cluster view-type plug-in (entity-viewer integration)
12
+ export * from './lib/view-type/cluster-view.types';
13
+ export * from './lib/view-type/entity-document-availability.engine';
14
+ export * from './lib/view-type/cluster-view-renderer.component';
15
+ export * from './lib/view-type/cluster-view-prop-sheet.component';
16
+ export * from './lib/view-type/cluster-view-type';
11
17
  //# sourceMappingURL=public-api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA,sDAAsD;AAEtD,SAAS;AACT,cAAc,yBAAyB,CAAC;AAExC,aAAa;AACb,cAAc,iCAAiC,CAAC;AAChD,cAAc,sCAAsC,CAAC;AAErD,UAAU;AACV,cAAc,0BAA0B,CAAC;AAEzC,QAAQ;AACR,cAAc,wBAAwB,CAAC","sourcesContent":["// Public API Surface of @memberjunction/ng-clustering\n\n// Module\nexport * from './lib/clustering.module';\n\n// Components\nexport * from './lib/cluster-scatter.component';\nexport * from './lib/cluster-config-panel.component';\n\n// Service\nexport * from './lib/clustering.service';\n\n// Types\nexport * from './lib/clustering.types';\n"]}
1
+ {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA,sDAAsD;AAEtD,SAAS;AACT,cAAc,yBAAyB,CAAC;AAExC,aAAa;AACb,cAAc,iCAAiC,CAAC;AAChD,cAAc,sCAAsC,CAAC;AAErD,UAAU;AACV,cAAc,0BAA0B,CAAC;AAEzC,QAAQ;AACR,cAAc,wBAAwB,CAAC;AAEvC,wDAAwD;AACxD,cAAc,oCAAoC,CAAC;AACnD,cAAc,qDAAqD,CAAC;AACpE,cAAc,iDAAiD,CAAC;AAChE,cAAc,mDAAmD,CAAC;AAClE,cAAc,mCAAmC,CAAC","sourcesContent":["// Public API Surface of @memberjunction/ng-clustering\n\n// Module\nexport * from './lib/clustering.module';\n\n// Components\nexport * from './lib/cluster-scatter.component';\nexport * from './lib/cluster-config-panel.component';\n\n// Service\nexport * from './lib/clustering.service';\n\n// Types\nexport * from './lib/clustering.types';\n\n// Cluster view-type plug-in (entity-viewer integration)\nexport * from './lib/view-type/cluster-view.types';\nexport * from './lib/view-type/entity-document-availability.engine';\nexport * from './lib/view-type/cluster-view-renderer.component';\nexport * from './lib/view-type/cluster-view-prop-sheet.component';\nexport * from './lib/view-type/cluster-view-type';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-clustering",
3
- "version": "5.39.0",
3
+ "version": "5.40.1",
4
4
  "description": "MemberJunction: Reusable Angular clustering visualization components with scatter plot, config panel, and clustering service",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -32,8 +32,16 @@
32
32
  "@angular/forms": "21.1.3"
33
33
  },
34
34
  "dependencies": {
35
- "@memberjunction/ai-vectors-memory": "5.39.0",
36
- "@memberjunction/ng-entity-card": "5.39.0",
35
+ "@memberjunction/ai-engine-base": "5.40.1",
36
+ "@memberjunction/ai-vectors-memory": "5.40.1",
37
+ "@memberjunction/core": "5.40.1",
38
+ "@memberjunction/core-entities": "5.40.1",
39
+ "@memberjunction/global": "5.40.1",
40
+ "@memberjunction/graphql-dataprovider": "5.40.1",
41
+ "@memberjunction/ng-base-types": "5.40.1",
42
+ "@memberjunction/ng-entity-card": "5.40.1",
43
+ "@memberjunction/ng-entity-viewer": "5.40.1",
44
+ "rxjs": "^7.8.2",
37
45
  "tslib": "^2.8.1",
38
46
  "umap-js": "^1.4.0"
39
47
  },