@memberjunction/ng-clustering 5.51.0 → 6.1.0-edge.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.
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 MemberJunction
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@@ -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;
|
|
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"}
|
|
@@ -98,7 +98,9 @@ export class EntityDocumentAvailabilityEngine extends BaseEngine {
|
|
|
98
98
|
if (docs.length === 0) {
|
|
99
99
|
return entityIDsWithVectors;
|
|
100
100
|
}
|
|
101
|
-
|
|
101
|
+
// `Metadata.Provider` is the global default, named explicitly. `new RunView()` reaches the
|
|
102
|
+
// same place but hides that it is a fallback, which is what makes it easy to leave in by accident.
|
|
103
|
+
const rv = RunView.FromMetadataProvider(provider ?? Metadata.Provider);
|
|
102
104
|
const results = await rv.RunViews(docs.map((d) => ({
|
|
103
105
|
EntityName: 'MJ: Entity Record Documents',
|
|
104
106
|
ExtraFilter: `EntityDocumentID = '${d.ID}' AND VectorJSON IS NOT NULL`,
|
|
@@ -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 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"]}
|
|
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"]}
|
package/package.json
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-clustering",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0-edge.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",
|
|
7
7
|
"files": [
|
|
8
8
|
"/dist"
|
|
9
9
|
],
|
|
10
|
-
"scripts": {
|
|
11
|
-
"test": "vitest run",
|
|
12
|
-
"build": "ngc",
|
|
13
|
-
"test:watch": "vitest"
|
|
14
|
-
},
|
|
15
10
|
"keywords": [
|
|
16
11
|
"angular",
|
|
17
12
|
"clustering",
|
|
@@ -24,25 +19,25 @@
|
|
|
24
19
|
"devDependencies": {
|
|
25
20
|
"@angular/compiler": "21.1.3",
|
|
26
21
|
"@angular/compiler-cli": "21.1.3",
|
|
27
|
-
"@memberjunction/ng-test-utils": "
|
|
22
|
+
"@memberjunction/ng-test-utils": "6.1.0-edge.1",
|
|
28
23
|
"vitest": "^4.0.18"
|
|
29
24
|
},
|
|
30
25
|
"peerDependencies": {
|
|
31
|
-
"@angular/common": "21.1.3",
|
|
32
|
-
"@angular/core": "21.1.3",
|
|
33
|
-
"@angular/forms": "21.1.3"
|
|
26
|
+
"@angular/common": "^21.1.3",
|
|
27
|
+
"@angular/core": "^21.1.3",
|
|
28
|
+
"@angular/forms": "^21.1.3"
|
|
34
29
|
},
|
|
35
30
|
"dependencies": {
|
|
36
|
-
"@memberjunction/ng-ui-components": "
|
|
37
|
-
"@memberjunction/ai-engine-base": "
|
|
38
|
-
"@memberjunction/ai-vectors-memory": "
|
|
39
|
-
"@memberjunction/core": "
|
|
40
|
-
"@memberjunction/core-entities": "
|
|
41
|
-
"@memberjunction/global": "
|
|
42
|
-
"@memberjunction/graphql-dataprovider": "
|
|
43
|
-
"@memberjunction/ng-base-types": "
|
|
44
|
-
"@memberjunction/ng-entity-card": "
|
|
45
|
-
"@memberjunction/ng-entity-viewer": "
|
|
31
|
+
"@memberjunction/ng-ui-components": "6.1.0-edge.1",
|
|
32
|
+
"@memberjunction/ai-engine-base": "6.1.0-edge.1",
|
|
33
|
+
"@memberjunction/ai-vectors-memory": "6.1.0-edge.1",
|
|
34
|
+
"@memberjunction/core": "6.1.0-edge.1",
|
|
35
|
+
"@memberjunction/core-entities": "6.1.0-edge.1",
|
|
36
|
+
"@memberjunction/global": "6.1.0-edge.1",
|
|
37
|
+
"@memberjunction/graphql-dataprovider": "6.1.0-edge.1",
|
|
38
|
+
"@memberjunction/ng-base-types": "6.1.0-edge.1",
|
|
39
|
+
"@memberjunction/ng-entity-card": "6.1.0-edge.1",
|
|
40
|
+
"@memberjunction/ng-entity-viewer": "6.1.0-edge.1",
|
|
46
41
|
"rxjs": "^7.8.2",
|
|
47
42
|
"tslib": "^2.8.1",
|
|
48
43
|
"umap-js": "^1.4.0"
|
|
@@ -51,5 +46,12 @@
|
|
|
51
46
|
"repository": {
|
|
52
47
|
"type": "git",
|
|
53
48
|
"url": "https://github.com/MemberJunction/MJ"
|
|
49
|
+
},
|
|
50
|
+
"mjUILayer": "widgets",
|
|
51
|
+
"scripts": {
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:types": "tsc --noEmit -p tsconfig.spec.json",
|
|
54
|
+
"build": "ngc",
|
|
55
|
+
"test:watch": "vitest"
|
|
54
56
|
}
|
|
55
|
-
}
|
|
57
|
+
}
|