@memberjunction/ng-clustering 6.2.0-edge.1 → 6.2.0-edge.3

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.
@@ -1 +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;IA+BhC,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"}
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;IAgChC,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"}
@@ -103,7 +103,8 @@ export class EntityDocumentAvailabilityEngine extends BaseEngine {
103
103
  const rv = RunView.FromMetadataProvider(provider ?? Metadata.Provider);
104
104
  const results = await rv.RunViews(docs.map((d) => ({
105
105
  EntityName: 'MJ: Entity Record Documents',
106
- ExtraFilter: `EntityDocumentID = '${d.ID}' AND VectorJSON IS NOT NULL`,
106
+ // Either vector column counts — the same rule the clustering source (EntityDocumentVectorSource) applies.
107
+ ExtraFilter: `EntityDocumentID = '${d.ID}' AND (VectorBinary IS NOT NULL OR VectorJSON IS NOT NULL)`,
107
108
  Fields: ['ID'],
108
109
  MaxRows: 1,
109
110
  ResultType: 'simple',
@@ -1 +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 // `Metadata.Provider` is the global default, named explicitly. `new RunView()` reaches the\n // same place but hides that it is a fallback, which is what makes it easy to leave in by accident.\n const rv = RunView.FromMetadataProvider(provider ?? Metadata.Provider);\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"]}
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 // `Metadata.Provider` is the global default, named explicitly. `new RunView()` reaches the\n // same place but hides that it is a fallback, which is what makes it easy to leave in by accident.\n const rv = RunView.FromMetadataProvider(provider ?? Metadata.Provider);\n const results = await rv.RunViews(\n docs.map((d) => ({\n EntityName: 'MJ: Entity Record Documents',\n // Either vector column counts — the same rule the clustering source (EntityDocumentVectorSource) applies.\n ExtraFilter: `EntityDocumentID = '${d.ID}' AND (VectorBinary IS NOT NULL OR 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-clustering",
3
- "version": "6.2.0-edge.1",
3
+ "version": "6.2.0-edge.3",
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",
@@ -19,7 +19,7 @@
19
19
  "devDependencies": {
20
20
  "@angular/compiler": "21.2.22",
21
21
  "@angular/compiler-cli": "21.2.22",
22
- "@memberjunction/ng-test-utils": "6.2.0-edge.1",
22
+ "@memberjunction/ng-test-utils": "6.2.0-edge.3",
23
23
  "vitest": "^4.0.18"
24
24
  },
25
25
  "peerDependencies": {
@@ -28,16 +28,16 @@
28
28
  "@angular/forms": "^21.2.22"
29
29
  },
30
30
  "dependencies": {
31
- "@memberjunction/ng-ui-components": "6.2.0-edge.1",
32
- "@memberjunction/ai-engine-base": "6.2.0-edge.1",
33
- "@memberjunction/ai-vectors-memory": "6.2.0-edge.1",
34
- "@memberjunction/core": "6.2.0-edge.1",
35
- "@memberjunction/core-entities": "6.2.0-edge.1",
36
- "@memberjunction/global": "6.2.0-edge.1",
37
- "@memberjunction/graphql-dataprovider": "6.2.0-edge.1",
38
- "@memberjunction/ng-base-types": "6.2.0-edge.1",
39
- "@memberjunction/ng-entity-card": "6.2.0-edge.1",
40
- "@memberjunction/ng-entity-viewer": "6.2.0-edge.1",
31
+ "@memberjunction/ng-ui-components": "6.2.0-edge.3",
32
+ "@memberjunction/ai-engine-base": "6.2.0-edge.3",
33
+ "@memberjunction/ai-vectors-memory": "6.2.0-edge.3",
34
+ "@memberjunction/core": "6.2.0-edge.3",
35
+ "@memberjunction/core-entities": "6.2.0-edge.3",
36
+ "@memberjunction/global": "6.2.0-edge.3",
37
+ "@memberjunction/graphql-dataprovider": "6.2.0-edge.3",
38
+ "@memberjunction/ng-base-types": "6.2.0-edge.3",
39
+ "@memberjunction/ng-entity-card": "6.2.0-edge.3",
40
+ "@memberjunction/ng-entity-viewer": "6.2.0-edge.3",
41
41
  "rxjs": "^7.8.2",
42
42
  "tslib": "^2.8.1",
43
43
  "umap-js": "^1.4.0"