@memberjunction/core-entities-server 6.2.0-edge.2 → 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.
- package/README.md +7 -3
- package/dist/custom/FormComponentGuard.d.ts +48 -0
- package/dist/custom/FormComponentGuard.d.ts.map +1 -0
- package/dist/custom/FormComponentGuard.js +236 -0
- package/dist/custom/FormComponentGuard.js.map +1 -0
- package/dist/custom/FormScopeGuard.d.ts +52 -0
- package/dist/custom/FormScopeGuard.d.ts.map +1 -0
- package/dist/custom/FormScopeGuard.js +111 -0
- package/dist/custom/FormScopeGuard.js.map +1 -0
- package/dist/custom/MJAIAgentExampleEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJAIAgentExampleEntityServer.server.js +3 -2
- package/dist/custom/MJAIAgentExampleEntityServer.server.js.map +1 -1
- package/dist/custom/MJAIAgentNoteEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJAIAgentNoteEntityServer.server.js +3 -2
- package/dist/custom/MJAIAgentNoteEntityServer.server.js.map +1 -1
- package/dist/custom/MJComponentEntityServer.server.d.ts +29 -1
- package/dist/custom/MJComponentEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJComponentEntityServer.server.js +62 -3
- package/dist/custom/MJComponentEntityServer.server.js.map +1 -1
- package/dist/custom/MJEntityFormContributionEntityServer.server.d.ts +37 -0
- package/dist/custom/MJEntityFormContributionEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJEntityFormContributionEntityServer.server.js +89 -0
- package/dist/custom/MJEntityFormContributionEntityServer.server.js.map +1 -0
- package/dist/custom/MJEntityFormOverrideEntityServer.server.d.ts +37 -0
- package/dist/custom/MJEntityFormOverrideEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJEntityFormOverrideEntityServer.server.js +89 -0
- package/dist/custom/MJEntityFormOverrideEntityServer.server.js.map +1 -0
- package/dist/custom/MJInteractionEventEntityServer.server.d.ts +23 -0
- package/dist/custom/MJInteractionEventEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJInteractionEventEntityServer.server.js +70 -0
- package/dist/custom/MJInteractionEventEntityServer.server.js.map +1 -0
- package/dist/custom/MJQueryEntityServer.server.d.ts +9 -1
- package/dist/custom/MJQueryEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJQueryEntityServer.server.js +28 -8
- package/dist/custom/MJQueryEntityServer.server.js.map +1 -1
- package/dist/custom/MJRecordChangeEntityServer.server.d.ts +5 -0
- package/dist/custom/MJRecordChangeEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJRecordChangeEntityServer.server.js +30 -1
- package/dist/custom/MJRecordChangeEntityServer.server.js.map +1 -1
- package/dist/custom/MJTagEntityServer.server.d.ts +1 -1
- package/dist/custom/MJTagEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJTagEntityServer.server.js +7 -3
- package/dist/custom/MJTagEntityServer.server.js.map +1 -1
- package/dist/custom/query-extraction/pipeline.js +1 -1
- package/dist/custom/query-extraction/pipeline.js.map +1 -1
- package/dist/custom/query-extraction/resolve.d.ts +5 -2
- package/dist/custom/query-extraction/resolve.d.ts.map +1 -1
- package/dist/custom/query-extraction/resolve.js +6 -3
- package/dist/custom/query-extraction/resolve.js.map +1 -1
- package/dist/custom/query-extraction/sync.d.ts.map +1 -1
- package/dist/custom/query-extraction/sync.js +4 -2
- package/dist/custom/query-extraction/sync.js.map +1 -1
- package/dist/engines/IdentityClaimEngineServer.d.ts +9 -0
- package/dist/engines/IdentityClaimEngineServer.d.ts.map +1 -1
- package/dist/engines/IdentityClaimEngineServer.js +38 -8
- package/dist/engines/IdentityClaimEngineServer.js.map +1 -1
- package/dist/engines/QueryEngineServer.d.ts.map +1 -1
- package/dist/engines/QueryEngineServer.js +15 -16
- package/dist/engines/QueryEngineServer.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +30 -30
package/README.md
CHANGED
|
@@ -93,6 +93,7 @@ Extends the Component entity with automatic vector embedding generation:
|
|
|
93
93
|
|
|
94
94
|
- **Automatic Embeddings**: Generates vector embeddings for FunctionalRequirements and TechnicalDesign fields
|
|
95
95
|
- **Model Tracking**: Stores the AI model ID used for each embedding
|
|
96
|
+
- **Binary Companions**: Also writes `FunctionalRequirementsVectorBinary` / `TechnicalDesignVectorBinary` (float32 bytes), which readers decode much faster than the JSON vectors
|
|
96
97
|
- **Smart Updates**: Only regenerates embeddings when source text changes
|
|
97
98
|
- **Parallel Processing**: Generates multiple embeddings concurrently for performance
|
|
98
99
|
|
|
@@ -108,7 +109,7 @@ Extends the Query entity with automatic Template management similar to AIPromptE
|
|
|
108
109
|
|
|
109
110
|
Extends the agent note entity to keep the in-process vector store in sync with persisted state:
|
|
110
111
|
|
|
111
|
-
- **Vector Store Invariant**: Overrides `Save()` and `Delete()` so that `AIEngine.Instance._noteVectorService` contains an entry for a note iff its persisted `Status='Active'` and
|
|
112
|
+
- **Vector Store Invariant**: Overrides `Save()` and `Delete()` so that `AIEngine.Instance._noteVectorService` contains an entry for a note iff its persisted `Status='Active'` and it has a stored vector (`EmbeddingVectorBinary` or `EmbeddingVector`)
|
|
112
113
|
- **Inline Maintenance**: When a note's Status flips between `Active` and any other value, or when a note is deleted, the corresponding vector store entry is added/removed in the same operation — eliminating the need for an MJAPI restart after note revocation (e.g., during MemoryManagerAgent consolidation or contradiction resolution)
|
|
113
114
|
- **Consolidation Field Persistence**: New `AIAgentNote` columns introduced by `V202604260056__v5.30.x__Memory_Consolidation_Schema.sql` (`ConsolidatedIntoNoteID`, `ConsolidationCount`, `DerivedFromNoteIDs`, `ProtectionTier`, `ImportanceScore`) persist via the standard generated setters — no custom logic needed in this override
|
|
114
115
|
|
|
@@ -232,7 +233,7 @@ The helper:
|
|
|
232
233
|
|
|
233
234
|
### Using Vector Embeddings in Your Entity
|
|
234
235
|
|
|
235
|
-
1. **Add database fields** for storing vectors and model IDs
|
|
236
|
+
1. **Add database fields** for storing vectors and model IDs — a JSON vector column plus a `VARBINARY(MAX)` binary companion (see the [Binary Fields Guide](../../guides/BINARY_FIELDS_GUIDE.md))
|
|
236
237
|
2. **Override EmbedTextLocal** using the helper function
|
|
237
238
|
3. **Call GenerateEmbeddings** in your Save method:
|
|
238
239
|
|
|
@@ -242,13 +243,16 @@ public async Save(): Promise<boolean> {
|
|
|
242
243
|
{
|
|
243
244
|
fieldName: "Content",
|
|
244
245
|
vectorFieldName: "ContentVector",
|
|
245
|
-
modelFieldName: "ContentVectorModelID"
|
|
246
|
+
modelFieldName: "ContentVectorModelID",
|
|
247
|
+
binaryVectorFieldName: "ContentVectorBinary" // optional; set and cleared with the JSON field
|
|
246
248
|
}
|
|
247
249
|
]);
|
|
248
250
|
return await super.Save();
|
|
249
251
|
}
|
|
250
252
|
```
|
|
251
253
|
|
|
254
|
+
The note, example, component, query and tag entity servers all write both the JSON and the binary vector column. Readers should load with `ReadStoredVector(binary, json)` from `@memberjunction/ai-vectors-memory`, which prefers the binary column and falls back to JSON.
|
|
255
|
+
|
|
252
256
|
## Common Use Cases
|
|
253
257
|
|
|
254
258
|
### 1. Automatic Related Entity Management
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type IMetadataProvider, type IRunViewProvider, type UserInfo } from '@memberjunction/core';
|
|
2
|
+
import { type FormScopeOperation, type GuardedComponentField } from '@memberjunction/core-entities';
|
|
3
|
+
/** The guarded columns of a component, current or as stored. */
|
|
4
|
+
export type GuardedComponentValues = Record<GuardedComponentField, unknown>;
|
|
5
|
+
/** What the component guard reads from a `MJ: Components` row. */
|
|
6
|
+
export interface GuardedComponentRow {
|
|
7
|
+
readonly ActiveUser: UserInfo | null;
|
|
8
|
+
/** The component's primary key. */
|
|
9
|
+
readonly ID: string;
|
|
10
|
+
/** The guarded columns as the write would store them. */
|
|
11
|
+
readonly Values: GuardedComponentValues;
|
|
12
|
+
readonly RunViewProvider: IRunViewProvider;
|
|
13
|
+
readonly MetadataProvider: IMetadataProvider;
|
|
14
|
+
}
|
|
15
|
+
/** What the row check reads from a `MJ: Entity Form Contributions` or `MJ: Entity Form Overrides` row. */
|
|
16
|
+
export interface GuardedFormComponentRow {
|
|
17
|
+
readonly IsSaved: boolean;
|
|
18
|
+
readonly ActiveUser: UserInfo | null;
|
|
19
|
+
readonly ID: string;
|
|
20
|
+
readonly ComponentID: string;
|
|
21
|
+
GetFieldByName(name: string): {
|
|
22
|
+
OldValue: unknown;
|
|
23
|
+
} | null | undefined;
|
|
24
|
+
readonly RunViewProvider: IRunViewProvider;
|
|
25
|
+
readonly MetadataProvider: IMetadataProvider;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Why this write to a component is refused, or null when it may proceed.
|
|
29
|
+
*
|
|
30
|
+
* With no caller (a trusted server context) nothing is checked. A create by a caller without the
|
|
31
|
+
* `Manage Form Defaults` grant is checked for a name another component already has. An update or
|
|
32
|
+
* delete reads, in one batch and as the caller: every form and panel row that uses the component,
|
|
33
|
+
* the component's stored guarded columns (update), the other components with the same name
|
|
34
|
+
* (update without the grant), and whether the caller created the component (without the grant).
|
|
35
|
+
* These are read for every update, whatever it changes. The changed columns are found by
|
|
36
|
+
* comparing the write's values with the stored ones. When a read fails, the write is refused.
|
|
37
|
+
*/
|
|
38
|
+
export declare function ComponentGuardRefusal(row: GuardedComponentRow, operation: FormScopeOperation): Promise<string | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Why a form or panel row may not point at its component, or null when it may.
|
|
41
|
+
*
|
|
42
|
+
* Checked when the row is created or its `ComponentID` differs from the value as loaded, for a
|
|
43
|
+
* caller; a trusted server context with no caller is not checked. The rows that use the component
|
|
44
|
+
* and, without the grant, whether the caller created it are read in one batch as the caller and
|
|
45
|
+
* checked with `FormRowComponentRefusal`. When a read fails, the write is refused.
|
|
46
|
+
*/
|
|
47
|
+
export declare function FormRowComponentGuardRefusal(row: GuardedFormComponentRow): Promise<string | null>;
|
|
48
|
+
//# sourceMappingURL=FormComponentGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormComponentGuard.d.ts","sourceRoot":"","sources":["../../src/custom/FormComponentGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EAChB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAQH,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAE7B,MAAM,+BAA+B,CAAC;AAWvC,gEAAgE;AAChE,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAC;AAE5E,kEAAkE;AAClE,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,UAAU,EAAE,QAAQ,GAAG,IAAI,CAAC;IACrC,mCAAmC;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;CAChD;AAED,0GAA0G;AAC1G,MAAM,WAAW,uBAAuB;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,QAAQ,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACvE,QAAQ,CAAC,eAAe,EAAE,gBAAgB,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;CAChD;AAmBD;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,mBAAmB,EAAE,SAAS,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwB3H;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBvG"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { CompositeKey, LogError, RunView, } from '@memberjunction/core';
|
|
2
|
+
import { EscapeSQLString, UUIDsEqual } from '@memberjunction/global';
|
|
3
|
+
import { ComponentNameCollisionRefusal, ComponentWriteRefusal, FormRowComponentRefusal, GUARDED_COMPONENT_FIELDS, UserCanManageFormDefaults, } from '@memberjunction/core-entities';
|
|
4
|
+
/** The entities whose rows draw a component on a form, each through its `ComponentID` column. */
|
|
5
|
+
const FORM_ROW_ENTITIES = ['MJ: Entity Form Contributions', 'MJ: Entity Form Overrides'];
|
|
6
|
+
/**
|
|
7
|
+
* Why this write to a component is refused, or null when it may proceed.
|
|
8
|
+
*
|
|
9
|
+
* With no caller (a trusted server context) nothing is checked. A create by a caller without the
|
|
10
|
+
* `Manage Form Defaults` grant is checked for a name another component already has. An update or
|
|
11
|
+
* delete reads, in one batch and as the caller: every form and panel row that uses the component,
|
|
12
|
+
* the component's stored guarded columns (update), the other components with the same name
|
|
13
|
+
* (update without the grant), and whether the caller created the component (without the grant).
|
|
14
|
+
* These are read for every update, whatever it changes. The changed columns are found by
|
|
15
|
+
* comparing the write's values with the stored ones. When a read fails, the write is refused.
|
|
16
|
+
*/
|
|
17
|
+
export async function ComponentGuardRefusal(row, operation) {
|
|
18
|
+
const caller = guardCaller(row);
|
|
19
|
+
if (!caller)
|
|
20
|
+
return null;
|
|
21
|
+
if (operation === 'create')
|
|
22
|
+
return caller.HoldsGrant ? null : createNameRefusal(row, caller);
|
|
23
|
+
const isUpdate = operation === 'update';
|
|
24
|
+
const batch = await runBatch(caller, {
|
|
25
|
+
...usesViews([row.ID]),
|
|
26
|
+
stored: isUpdate ? storedComponentView(row.ID) : undefined,
|
|
27
|
+
sameName: isUpdate && !caller.HoldsGrant ? sameNameView(row.Values.Name, row.ID) : undefined,
|
|
28
|
+
created: caller.HoldsGrant ? undefined : createdByCallerView(caller, [row.ID]),
|
|
29
|
+
});
|
|
30
|
+
if ('Error' in batch)
|
|
31
|
+
return readRefusal(batch.Error);
|
|
32
|
+
const stored = batch.Results.stored ?? [];
|
|
33
|
+
if (isUpdate && stored.length === 0)
|
|
34
|
+
return readRefusal('the component as stored was not found');
|
|
35
|
+
const changed = isUpdate ? changedFields(row.Values, stored[0]) : [];
|
|
36
|
+
const writeRefusal = ComponentWriteRefusal({
|
|
37
|
+
Operation: operation,
|
|
38
|
+
ChangedFields: changed,
|
|
39
|
+
...ownership(batch.Results, row.ID),
|
|
40
|
+
CallerID: caller.User.ID,
|
|
41
|
+
CallerHoldsGrant: caller.HoldsGrant,
|
|
42
|
+
});
|
|
43
|
+
if (writeRefusal || caller.HoldsGrant || !changed.some((f) => f === 'Name' || f === 'Namespace'))
|
|
44
|
+
return writeRefusal;
|
|
45
|
+
return collisionRefusal(caller, idsOf(batch.Results.sameName));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Why a form or panel row may not point at its component, or null when it may.
|
|
49
|
+
*
|
|
50
|
+
* Checked when the row is created or its `ComponentID` differs from the value as loaded, for a
|
|
51
|
+
* caller; a trusted server context with no caller is not checked. The rows that use the component
|
|
52
|
+
* and, without the grant, whether the caller created it are read in one batch as the caller and
|
|
53
|
+
* checked with `FormRowComponentRefusal`. When a read fails, the write is refused.
|
|
54
|
+
*/
|
|
55
|
+
export async function FormRowComponentGuardRefusal(row) {
|
|
56
|
+
const caller = guardCaller(row);
|
|
57
|
+
if (!caller || !row.ComponentID)
|
|
58
|
+
return null;
|
|
59
|
+
const prior = row.GetFieldByName('ComponentID')?.OldValue;
|
|
60
|
+
if (row.IsSaved && typeof prior === 'string' && UUIDsEqual(prior, row.ComponentID))
|
|
61
|
+
return null;
|
|
62
|
+
const batch = await runBatch(caller, {
|
|
63
|
+
...usesViews([row.ComponentID]),
|
|
64
|
+
created: caller.HoldsGrant ? undefined : createdByCallerView(caller, [row.ComponentID]),
|
|
65
|
+
});
|
|
66
|
+
if ('Error' in batch)
|
|
67
|
+
return readRefusal(batch.Error);
|
|
68
|
+
return FormRowComponentRefusal({
|
|
69
|
+
RowID: row.IsSaved ? row.ID : null,
|
|
70
|
+
...ownership(batch.Results, row.ComponentID),
|
|
71
|
+
CallerID: caller.User.ID,
|
|
72
|
+
CallerHoldsGrant: caller.HoldsGrant,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** The caller and whether they hold the grant, or null with no caller. */
|
|
76
|
+
function guardCaller(row) {
|
|
77
|
+
if (!row.ActiveUser)
|
|
78
|
+
return null;
|
|
79
|
+
return {
|
|
80
|
+
User: row.ActiveUser,
|
|
81
|
+
HoldsGrant: UserCanManageFormDefaults(row.ActiveUser, row.MetadataProvider),
|
|
82
|
+
RunViewProvider: row.RunViewProvider,
|
|
83
|
+
MetadataProvider: row.MetadataProvider,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** The name check for a create: the other components with this name, then whose they are. */
|
|
87
|
+
async function createNameRefusal(row, caller) {
|
|
88
|
+
const batch = await runBatch(caller, { sameName: sameNameView(row.Values.Name, null) });
|
|
89
|
+
if ('Error' in batch)
|
|
90
|
+
return readRefusal(batch.Error);
|
|
91
|
+
return collisionRefusal(caller, idsOf(batch.Results.sameName));
|
|
92
|
+
}
|
|
93
|
+
/** Applies `ComponentNameCollisionRefusal` to these same-named components, reading whose they are. */
|
|
94
|
+
async function collisionRefusal(caller, componentIDs) {
|
|
95
|
+
if (componentIDs.length === 0)
|
|
96
|
+
return null;
|
|
97
|
+
const batch = await runBatch(caller, {
|
|
98
|
+
...usesViews(componentIDs),
|
|
99
|
+
created: createdByCallerView(caller, componentIDs),
|
|
100
|
+
});
|
|
101
|
+
if ('Error' in batch)
|
|
102
|
+
return readRefusal(batch.Error);
|
|
103
|
+
return ComponentNameCollisionRefusal({
|
|
104
|
+
NamesComponent: true,
|
|
105
|
+
Collisions: componentIDs.map((id) => ownership(batch.Results, id)),
|
|
106
|
+
CallerID: caller.User.ID,
|
|
107
|
+
CallerHoldsGrant: false,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** The rows that use one component, and whether the caller created it, from a batch's results. */
|
|
111
|
+
function ownership(results, componentID) {
|
|
112
|
+
const uses = [...(results.contributions ?? []), ...(results.overrides ?? [])].map(asUse);
|
|
113
|
+
const createdKey = recordChangeKey(componentID).toLowerCase();
|
|
114
|
+
return {
|
|
115
|
+
References: uses.filter((use) => UUIDsEqual(use.ComponentID, componentID)),
|
|
116
|
+
CreatedByCaller: (results.created ?? []).some((r) => String(r.RecordID ?? '').toLowerCase() === createdKey),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** One view per form entity: the rows that use any of these components. */
|
|
120
|
+
function usesViews(componentIDs) {
|
|
121
|
+
const filter = componentIDs.length === 1
|
|
122
|
+
? `ComponentID='${EscapeSQLString(componentIDs[0])}'`
|
|
123
|
+
: `ComponentID IN (${componentIDs.map((id) => `'${EscapeSQLString(id)}'`).join(',')})`;
|
|
124
|
+
const view = (EntityName) => ({
|
|
125
|
+
EntityName,
|
|
126
|
+
ExtraFilter: filter,
|
|
127
|
+
Fields: ['ID', 'ComponentID', 'Scope', 'UserID'],
|
|
128
|
+
ResultType: 'simple',
|
|
129
|
+
BypassCache: true,
|
|
130
|
+
});
|
|
131
|
+
return { contributions: view(FORM_ROW_ENTITIES[0]), overrides: view(FORM_ROW_ENTITIES[1]) };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The caller's `Create` record changes for these components, with `Source` 'Internal'.
|
|
135
|
+
* `MJ: Components` tracks record changes, so every component created through the platform has one,
|
|
136
|
+
* written by the database provider in the same batch as the insert. `MJRecordChangeEntityServer`
|
|
137
|
+
* refuses an Internal `Create` record change created by a caller, so such a row cannot be forged
|
|
138
|
+
* through the API. Null when the component entity is missing from the metadata, which `runBatch` reports as a
|
|
139
|
+
* failed read.
|
|
140
|
+
*/
|
|
141
|
+
function createdByCallerView(caller, componentIDs) {
|
|
142
|
+
const componentEntity = caller.MetadataProvider.EntityByName('MJ: Components');
|
|
143
|
+
if (!componentEntity)
|
|
144
|
+
return null;
|
|
145
|
+
const recordIDs = componentIDs.map((id) => `'${EscapeSQLString(recordChangeKey(id))}'`).join(',');
|
|
146
|
+
return {
|
|
147
|
+
EntityName: 'MJ: Record Changes',
|
|
148
|
+
ExtraFilter: `EntityID='${EscapeSQLString(componentEntity.ID)}' AND Source='Internal' AND Type='Create' ` +
|
|
149
|
+
`AND UserID='${EscapeSQLString(caller.User.ID)}' AND RecordID IN (${recordIDs})`,
|
|
150
|
+
Fields: ['RecordID'],
|
|
151
|
+
ResultType: 'simple',
|
|
152
|
+
BypassCache: true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** A component's `RecordID` as `MJ: Record Changes` stores it for 'MJ: Components': `ID|<id>`. */
|
|
156
|
+
function recordChangeKey(componentID) {
|
|
157
|
+
return CompositeKey.FromID(componentID).ToURLSegment(); // first-pk-ok: 'MJ: Components' has the single key ID
|
|
158
|
+
}
|
|
159
|
+
/** The component's guarded columns as stored. */
|
|
160
|
+
function storedComponentView(componentID) {
|
|
161
|
+
return {
|
|
162
|
+
EntityName: 'MJ: Components',
|
|
163
|
+
ExtraFilter: `ID='${EscapeSQLString(componentID)}'`,
|
|
164
|
+
Fields: [...GUARDED_COMPONENT_FIELDS],
|
|
165
|
+
ResultType: 'simple',
|
|
166
|
+
BypassCache: true,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The other components a lookup by this name finds. The name is compared trimmed and lower-cased,
|
|
171
|
+
* as `ComponentMetadataEngineServer.FindComponent` compares it, so stored padding or a
|
|
172
|
+
* case-sensitive database cannot hide a match. It is matched in any namespace, because a lookup
|
|
173
|
+
* may be made without one. The new name is trimmed of all whitespace, as the lookup trims it; SQL
|
|
174
|
+
* `LTRIM`/`RTRIM` remove only spaces from the stored name, so a stored name padded with a tab or a
|
|
175
|
+
* line break is not matched. On SQL Server the name is a Unicode literal (`N'...'`), so a name with
|
|
176
|
+
* characters outside the database's code page is compared as stored.
|
|
177
|
+
*/
|
|
178
|
+
function sameNameView(name, excludeID) {
|
|
179
|
+
const literal = EscapeSQLString(String(name ?? '').trim());
|
|
180
|
+
const exclude = excludeID ? ` AND ID<>'${EscapeSQLString(excludeID)}'` : '';
|
|
181
|
+
return {
|
|
182
|
+
EntityName: 'MJ: Components',
|
|
183
|
+
ExtraFilter: {
|
|
184
|
+
default: `LOWER(LTRIM(RTRIM(Name)))=LOWER('${literal}')${exclude}`,
|
|
185
|
+
sqlserver: `LOWER(LTRIM(RTRIM(Name)))=LOWER(N'${literal}')${exclude}`,
|
|
186
|
+
},
|
|
187
|
+
Fields: ['ID'],
|
|
188
|
+
ResultType: 'simple',
|
|
189
|
+
BypassCache: true,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** A form or panel row as a `simple` view returns it. */
|
|
193
|
+
function asUse(row) {
|
|
194
|
+
return {
|
|
195
|
+
ID: String(row.ID ?? ''),
|
|
196
|
+
ComponentID: String(row.ComponentID ?? ''),
|
|
197
|
+
Scope: row.Scope,
|
|
198
|
+
UserID: typeof row.UserID === 'string' ? row.UserID : null,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function idsOf(rows) {
|
|
202
|
+
return (rows ?? []).map((r) => String(r.ID));
|
|
203
|
+
}
|
|
204
|
+
/** The guarded columns whose new value differs from the stored one. */
|
|
205
|
+
function changedFields(values, stored) {
|
|
206
|
+
return GUARDED_COMPONENT_FIELDS.filter((field) => (values[field] ?? null) !== (stored[field] ?? null));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Runs the given views in one `RunViews` call as the caller. A view that could not be built, any
|
|
210
|
+
* view that fails, or a throw is an error.
|
|
211
|
+
*/
|
|
212
|
+
async function runBatch(caller, views) {
|
|
213
|
+
const entries = Object.entries(views);
|
|
214
|
+
if (entries.some(([, view]) => view === null)) {
|
|
215
|
+
return failedRead('the MJ: Components entity is not in the metadata');
|
|
216
|
+
}
|
|
217
|
+
const present = entries.filter((entry) => !!entry[1]);
|
|
218
|
+
try {
|
|
219
|
+
const results = await new RunView(caller.RunViewProvider).RunViews(present.map(([, view]) => view), caller.User);
|
|
220
|
+
const reasons = present.flatMap(([, view], i) => results?.[i]?.Success ? [] : [results?.[i]?.ErrorMessage || `${view.EntityName} could not be read`]);
|
|
221
|
+
if (reasons.length > 0)
|
|
222
|
+
return failedRead(reasons.join('; '));
|
|
223
|
+
return { Results: Object.fromEntries(present.map(([role], i) => [role, results[i].Results ?? []])) };
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
return failedRead(err instanceof Error ? err.message : String(err));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function failedRead(reason) {
|
|
230
|
+
LogError(`Form component guard: a read failed, so the write is refused: ${reason}`);
|
|
231
|
+
return { Error: reason };
|
|
232
|
+
}
|
|
233
|
+
function readRefusal(reason) {
|
|
234
|
+
return `The forms, panels and components this change depends on could not be read, so it is refused: ${reason}`;
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=FormComponentGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormComponentGuard.js","sourceRoot":"","sources":["../../src/custom/FormComponentGuard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BaseEntityResult, type EntitySaveOptions, type IMetadataProvider, type UserInfo, type ValidationResult } from '@memberjunction/core';
|
|
2
|
+
import { type FormScope, type FormScopeOperation, type FormScopeWrite } from '@memberjunction/core-entities';
|
|
3
|
+
/**
|
|
4
|
+
* What the scope guard reads from a `MJ: Entity Form Overrides` or `MJ: Entity Form
|
|
5
|
+
* Contributions` row. Both entities carry the same scope columns, and neither shares a base class
|
|
6
|
+
* with the other, so the two server subclasses hand themselves to these helpers through this shape.
|
|
7
|
+
*/
|
|
8
|
+
export interface GuardedFormScopeRow {
|
|
9
|
+
readonly IsSaved: boolean;
|
|
10
|
+
readonly ActiveUser: UserInfo | null;
|
|
11
|
+
readonly Scope: FormScope;
|
|
12
|
+
readonly UserID: string | null;
|
|
13
|
+
GetFieldByName(name: string): {
|
|
14
|
+
OldValue: unknown;
|
|
15
|
+
} | null | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The write this row is about to make, as the scope rule needs to see it — or null when there is
|
|
19
|
+
* no caller.
|
|
20
|
+
*
|
|
21
|
+
* No caller means a trusted, server-internal context, the same reading `MJUserRoleEntityServer`
|
|
22
|
+
* gives it: there is nobody to check the write against.
|
|
23
|
+
*
|
|
24
|
+
* On an update the prior values come from each field's `OldValue`, the value as last loaded.
|
|
25
|
+
* A delete removes the row as stored, so both sides are the loaded values and an unsaved edit
|
|
26
|
+
* to the scope or owner cannot change the check.
|
|
27
|
+
*/
|
|
28
|
+
export declare function DescribeFormScopeWrite(row: GuardedFormScopeRow, operation: FormScopeOperation, callerHoldsGrant: boolean): FormScopeWrite | null;
|
|
29
|
+
/** Why this row's write is refused, or null when it may proceed. */
|
|
30
|
+
export declare function FormScopeGuardRefusal(row: GuardedFormScopeRow, operation: FormScopeOperation, callerHoldsGrant: boolean): string | null;
|
|
31
|
+
/** Whether the caller holds the grant, read through the row's own provider. */
|
|
32
|
+
export declare function CallerHoldsFormDefaultsGrant(row: GuardedFormScopeRow, provider: IMetadataProvider): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Adds a scope problem, if any, to a `Validate()` result. Runs on create and update: first the
|
|
35
|
+
* canonical-Scope check, then the scope rule.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ApplyFormScopeValidation(row: GuardedFormScopeRow, provider: IMetadataProvider, result: ValidationResult): void;
|
|
38
|
+
/**
|
|
39
|
+
* Why a `ReplayOnly` save is refused, or null.
|
|
40
|
+
*
|
|
41
|
+
* `ReplayOnly` performs the write without calling `Validate()`, so it would skip every check
|
|
42
|
+
* there. The canonical-Scope check runs for every caller. Beyond that, it is a replication
|
|
43
|
+
* facility for trusted sync paths: only a caller who holds the grant may use it, and the
|
|
44
|
+
* ownership half of the rule still applies, so a holder cannot replay a write to someone else's
|
|
45
|
+
* personal item.
|
|
46
|
+
*/
|
|
47
|
+
export declare function FormScopeReplayRefusal(row: GuardedFormScopeRow, provider: IMetadataProvider, options: EntitySaveOptions | undefined): string | null;
|
|
48
|
+
/** Why a delete is refused, or null. `Delete()` never calls `Validate()`, so it is checked here. */
|
|
49
|
+
export declare function FormScopeDeleteRefusal(row: GuardedFormScopeRow, provider: IMetadataProvider): string | null;
|
|
50
|
+
/** A refusal recorded as a result, so `LatestResult.CompleteMessage` carries the reason. */
|
|
51
|
+
export declare function FormScopeRefusalResult(type: 'create' | 'update' | 'delete', message: string): BaseEntityResult;
|
|
52
|
+
//# sourceMappingURL=FormScopeGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormScopeGuard.d.ts","sourceRoot":"","sources":["../../src/custom/FormScopeGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,gBAAgB,EAGhB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAIH,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACtB,MAAM,+BAA+B,CAAC;AAEvC;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,QAAQ,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;CAC1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAClC,GAAG,EAAE,mBAAmB,EACxB,SAAS,EAAE,kBAAkB,EAC7B,gBAAgB,EAAE,OAAO,GAC1B,cAAc,GAAG,IAAI,CAqBvB;AAOD,oEAAoE;AACpE,wBAAgB,qBAAqB,CACjC,GAAG,EAAE,mBAAmB,EACxB,SAAS,EAAE,kBAAkB,EAC7B,gBAAgB,EAAE,OAAO,GAC1B,MAAM,GAAG,IAAI,CAGf;AAED,+EAA+E;AAC/E,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,mBAAmB,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAE3G;AAeD;;;GAGG;AACH,wBAAgB,wBAAwB,CACpC,GAAG,EAAE,mBAAmB,EACxB,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,gBAAgB,GACzB,IAAI,CAMN;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAClC,GAAG,EAAE,mBAAmB,EACxB,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,iBAAiB,GAAG,SAAS,GACvC,MAAM,GAAG,IAAI,CAUf;AAED,oGAAoG;AACpG,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,mBAAmB,EAAE,QAAQ,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAE3G;AAED,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAQ9G"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { BaseEntityResult, ValidationErrorInfo, ValidationErrorType, } from '@memberjunction/core';
|
|
2
|
+
import { FormScopeWriteRefusal, IsCanonicalFormScope, UserCanManageFormDefaults, } from '@memberjunction/core-entities';
|
|
3
|
+
/**
|
|
4
|
+
* The write this row is about to make, as the scope rule needs to see it — or null when there is
|
|
5
|
+
* no caller.
|
|
6
|
+
*
|
|
7
|
+
* No caller means a trusted, server-internal context, the same reading `MJUserRoleEntityServer`
|
|
8
|
+
* gives it: there is nobody to check the write against.
|
|
9
|
+
*
|
|
10
|
+
* On an update the prior values come from each field's `OldValue`, the value as last loaded.
|
|
11
|
+
* A delete removes the row as stored, so both sides are the loaded values and an unsaved edit
|
|
12
|
+
* to the scope or owner cannot change the check.
|
|
13
|
+
*/
|
|
14
|
+
export function DescribeFormScopeWrite(row, operation, callerHoldsGrant) {
|
|
15
|
+
const caller = row.ActiveUser;
|
|
16
|
+
if (!caller)
|
|
17
|
+
return null;
|
|
18
|
+
const who = { CallerID: caller.ID, CallerHoldsGrant: callerHoldsGrant };
|
|
19
|
+
if (operation === 'create') {
|
|
20
|
+
return {
|
|
21
|
+
Operation: operation, PriorScope: null, PriorUserID: null,
|
|
22
|
+
NextScope: row.Scope, NextUserID: row.UserID, ...who,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const priorScope = priorValue(row, 'Scope');
|
|
26
|
+
const priorUserID = priorValue(row, 'UserID');
|
|
27
|
+
const isDelete = operation === 'delete';
|
|
28
|
+
return {
|
|
29
|
+
Operation: operation,
|
|
30
|
+
PriorScope: priorScope,
|
|
31
|
+
PriorUserID: priorUserID,
|
|
32
|
+
NextScope: isDelete ? priorScope : row.Scope,
|
|
33
|
+
NextUserID: isDelete ? priorUserID : row.UserID,
|
|
34
|
+
...who,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** The value a field held when the row was loaded. */
|
|
38
|
+
function priorValue(row, field) {
|
|
39
|
+
return row.GetFieldByName(field)?.OldValue;
|
|
40
|
+
}
|
|
41
|
+
/** Why this row's write is refused, or null when it may proceed. */
|
|
42
|
+
export function FormScopeGuardRefusal(row, operation, callerHoldsGrant) {
|
|
43
|
+
const write = DescribeFormScopeWrite(row, operation, callerHoldsGrant);
|
|
44
|
+
return write ? FormScopeWriteRefusal(write) : null;
|
|
45
|
+
}
|
|
46
|
+
/** Whether the caller holds the grant, read through the row's own provider. */
|
|
47
|
+
export function CallerHoldsFormDefaultsGrant(row, provider) {
|
|
48
|
+
return UserCanManageFormDefaults(row.ActiveUser, provider);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Why the row's Scope may not be stored, or null when it is exactly `User`, `Role` or `Global`.
|
|
52
|
+
*
|
|
53
|
+
* Refused for every caller, a trusted one included: the value-list check trims and case-folds,
|
|
54
|
+
* and SQL Server ignores trailing spaces in the CHECK, so a padded or re-cased value would
|
|
55
|
+
* otherwise be stored.
|
|
56
|
+
*/
|
|
57
|
+
function nonCanonicalScopeRefusal(row) {
|
|
58
|
+
return IsCanonicalFormScope(row.Scope)
|
|
59
|
+
? null
|
|
60
|
+
: `Scope must be exactly 'User', 'Role' or 'Global'; '${row.Scope ?? ''}' is not.`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Adds a scope problem, if any, to a `Validate()` result. Runs on create and update: first the
|
|
64
|
+
* canonical-Scope check, then the scope rule.
|
|
65
|
+
*/
|
|
66
|
+
export function ApplyFormScopeValidation(row, provider, result) {
|
|
67
|
+
const problem = nonCanonicalScopeRefusal(row)
|
|
68
|
+
?? FormScopeGuardRefusal(row, row.IsSaved ? 'update' : 'create', CallerHoldsFormDefaultsGrant(row, provider));
|
|
69
|
+
if (!problem)
|
|
70
|
+
return;
|
|
71
|
+
result.Errors.push(new ValidationErrorInfo('Scope', problem, row.Scope, ValidationErrorType.Failure));
|
|
72
|
+
result.Success = false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Why a `ReplayOnly` save is refused, or null.
|
|
76
|
+
*
|
|
77
|
+
* `ReplayOnly` performs the write without calling `Validate()`, so it would skip every check
|
|
78
|
+
* there. The canonical-Scope check runs for every caller. Beyond that, it is a replication
|
|
79
|
+
* facility for trusted sync paths: only a caller who holds the grant may use it, and the
|
|
80
|
+
* ownership half of the rule still applies, so a holder cannot replay a write to someone else's
|
|
81
|
+
* personal item.
|
|
82
|
+
*/
|
|
83
|
+
export function FormScopeReplayRefusal(row, provider, options) {
|
|
84
|
+
if (!options?.ReplayOnly)
|
|
85
|
+
return null;
|
|
86
|
+
const nonCanonical = nonCanonicalScopeRefusal(row);
|
|
87
|
+
if (nonCanonical)
|
|
88
|
+
return nonCanonical;
|
|
89
|
+
if (!row.ActiveUser)
|
|
90
|
+
return null;
|
|
91
|
+
if (!CallerHoldsFormDefaultsGrant(row, provider)) {
|
|
92
|
+
return 'A ReplayOnly save skips validation, which is where form scope is checked, so it needs the ' +
|
|
93
|
+
'Manage Form Defaults authorization.';
|
|
94
|
+
}
|
|
95
|
+
return FormScopeGuardRefusal(row, row.IsSaved ? 'update' : 'create', true);
|
|
96
|
+
}
|
|
97
|
+
/** Why a delete is refused, or null. `Delete()` never calls `Validate()`, so it is checked here. */
|
|
98
|
+
export function FormScopeDeleteRefusal(row, provider) {
|
|
99
|
+
return FormScopeGuardRefusal(row, 'delete', CallerHoldsFormDefaultsGrant(row, provider));
|
|
100
|
+
}
|
|
101
|
+
/** A refusal recorded as a result, so `LatestResult.CompleteMessage` carries the reason. */
|
|
102
|
+
export function FormScopeRefusalResult(type, message) {
|
|
103
|
+
const result = new BaseEntityResult();
|
|
104
|
+
result.Success = false;
|
|
105
|
+
result.Type = type;
|
|
106
|
+
result.Message = message;
|
|
107
|
+
result.StartedAt = new Date();
|
|
108
|
+
result.EndedAt = new Date();
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=FormScopeGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormScopeGuard.js","sourceRoot":"","sources":["../../src/custom/FormScopeGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,GAKtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACH,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,GAI5B,MAAM,+BAA+B,CAAC;AAevC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CAClC,GAAwB,EACxB,SAA6B,EAC7B,gBAAyB;IAEzB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC;IAC9B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,GAAG,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;IACxE,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO;YACH,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI;YACzD,SAAS,EAAE,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,GAAG;SACvD,CAAC;IACN,CAAC;IACD,MAAM,UAAU,GAAG,UAAU,CAAY,GAAG,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,WAAW,GAAG,UAAU,CAAgB,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,SAAS,KAAK,QAAQ,CAAC;IACxC,OAAO;QACH,SAAS,EAAE,SAAS;QACpB,UAAU,EAAE,UAAU;QACtB,WAAW,EAAE,WAAW;QACxB,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK;QAC5C,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM;QAC/C,GAAG,GAAG;KACT,CAAC;AACN,CAAC;AAED,sDAAsD;AACtD,SAAS,UAAU,CAAI,GAAwB,EAAE,KAAa;IAC1D,OAAO,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,QAAa,CAAC;AACpD,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,qBAAqB,CACjC,GAAwB,EACxB,SAA6B,EAC7B,gBAAyB;IAEzB,MAAM,KAAK,GAAG,sBAAsB,CAAC,GAAG,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;IACvE,OAAO,KAAK,CAAC,CAAC,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,4BAA4B,CAAC,GAAwB,EAAE,QAA2B;IAC9F,OAAO,yBAAyB,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;GAMG;AACH,SAAS,wBAAwB,CAAC,GAAwB;IACtD,OAAO,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC;QAClC,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,sDAAsD,GAAG,CAAC,KAAK,IAAI,EAAE,WAAW,CAAC;AAC3F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACpC,GAAwB,EACxB,QAA2B,EAC3B,MAAwB;IAExB,MAAM,OAAO,GAAG,wBAAwB,CAAC,GAAG,CAAC;WACtC,qBAAqB,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,4BAA4B,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;IAClH,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;IACtG,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAClC,GAAwB,EACxB,QAA2B,EAC3B,OAAsC;IAEtC,IAAI,CAAC,OAAO,EAAE,UAAU;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,YAAY,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC;IACnD,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IACtC,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,CAAC,4BAA4B,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC/C,OAAO,4FAA4F;YAC/F,qCAAqC,CAAC;IAC9C,CAAC;IACD,OAAO,qBAAqB,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC/E,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,sBAAsB,CAAC,GAAwB,EAAE,QAA2B;IACxF,OAAO,qBAAqB,CAAC,GAAG,EAAE,QAAQ,EAAE,4BAA4B,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC7F,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,sBAAsB,CAAC,IAAoC,EAAE,OAAe;IACxF,MAAM,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;IACtC,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC9B,MAAM,CAAC,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;IAC5B,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MJAIAgentExampleEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJAIAgentExampleEntityServer.server.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,mBAAmB,EAAE,iBAAiB,EAAY,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC3H,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAIvE;;;;;;;GAOG;AACH,qBACa,4BAA6B,SAAQ,sBAAsB;IACpE;;OAEG;cACsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI7E,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"MJAIAgentExampleEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJAIAgentExampleEntityServer.server.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,mBAAmB,EAAE,iBAAiB,EAAY,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC3H,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAIvE;;;;;;;GAOG;AACH,qBACa,4BAA6B,SAAQ,sBAAsB;IACpE;;OAEG;cACsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI7E,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAmCnD,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;CAQzE"}
|
|
@@ -30,10 +30,11 @@ let MJAIAgentExampleEntityServer = class MJAIAgentExampleEntityServer extends MJ
|
|
|
30
30
|
const inputField = this.GetFieldByName('ExampleInput');
|
|
31
31
|
const shouldGenerateEmbedding = !this.IsSaved || inputField.Dirty;
|
|
32
32
|
if (shouldGenerateEmbedding && this.ExampleInput && this.ExampleInput.trim().length > 0) {
|
|
33
|
-
await this.GenerateEmbeddingByFieldName("ExampleInput", "EmbeddingVector", "EmbeddingModelID");
|
|
33
|
+
await this.GenerateEmbeddingByFieldName("ExampleInput", "EmbeddingVector", "EmbeddingModelID", "EmbeddingVectorBinary");
|
|
34
34
|
}
|
|
35
35
|
else if (!this.ExampleInput || this.ExampleInput.trim().length === 0) {
|
|
36
36
|
this.EmbeddingVector = null;
|
|
37
|
+
this.EmbeddingVectorBinary = null;
|
|
37
38
|
this.EmbeddingModelID = null;
|
|
38
39
|
}
|
|
39
40
|
// 2. Persist first — only touch the in-memory vector service on a successful save.
|
|
@@ -44,7 +45,7 @@ let MJAIAgentExampleEntityServer = class MJAIAgentExampleEntityServer extends MJ
|
|
|
44
45
|
// AIEngine is registered as deferred — EnsureLoaded blocks until the
|
|
45
46
|
// background load completes so the underlying vector service exists.
|
|
46
47
|
await AIEngine.Instance.EnsureLoaded();
|
|
47
|
-
if (this.Status === 'Active' && this.EmbeddingVector) {
|
|
48
|
+
if (this.Status === 'Active' && (this.EmbeddingVectorBinary || this.EmbeddingVector)) {
|
|
48
49
|
AIEngine.Instance.AddOrUpdateSingleExampleEmbedding(this);
|
|
49
50
|
}
|
|
50
51
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MJAIAgentExampleEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJAIAgentExampleEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,UAAU,EAA0C,QAAQ,EAAyB,MAAM,sBAAsB,CAAC;AAC3H,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD;;;;;;;GAOG;AAEI,IAAM,4BAA4B,GAAlC,MAAM,4BAA6B,SAAQ,sBAAsB;IACpE;;OAEG;IACgB,KAAK,CAAC,cAAc,CAAC,WAAmB;QACvD,OAAO,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACnD,CAAC;IAEQ,KAAK,CAAC,IAAI,CAAC,OAA2B;QAC3C,IAAI,CAAC;YACD,8EAA8E;YAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;YACvD,MAAM,uBAAuB,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC;YAElE,IAAI,uBAAuB,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtF,MAAM,IAAI,CAAC,4BAA4B,CAAC,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"MJAIAgentExampleEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJAIAgentExampleEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,UAAU,EAA0C,QAAQ,EAAyB,MAAM,sBAAsB,CAAC;AAC3H,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD;;;;;;;GAOG;AAEI,IAAM,4BAA4B,GAAlC,MAAM,4BAA6B,SAAQ,sBAAsB;IACpE;;OAEG;IACgB,KAAK,CAAC,cAAc,CAAC,WAAmB;QACvD,OAAO,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACnD,CAAC;IAEQ,KAAK,CAAC,IAAI,CAAC,OAA2B;QAC3C,IAAI,CAAC;YACD,8EAA8E;YAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;YACvD,MAAM,uBAAuB,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC;YAElE,IAAI,uBAAuB,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtF,MAAM,IAAI,CAAC,4BAA4B,CAAC,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,uBAAuB,CAAC,CAAC;YAC5H,CAAC;iBAAM,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACrE,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;gBAC5B,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;gBAClC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;YACjC,CAAC;YAED,mFAAmF;YACnF,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACxC,IAAI,CAAC,KAAK;gBAAE,OAAO,KAAK,CAAC;YAEzB,uEAAuE;YACvE,wEAAwE;YACxE,wEAAwE;YACxE,MAAM,QAAQ,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;YACvC,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,qBAAqB,IAAI,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;gBACnF,QAAQ,CAAC,QAAQ,CAAC,iCAAiC,CAAC,IAAI,CAAC,CAAC;YAC9D,CAAC;iBAAM,CAAC;gBACJ,QAAQ,CAAC,QAAQ,CAAC,4BAA4B,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,OAAO,IAAI,CAAC;QAChB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,QAAQ,CAAC,kCAAkC,EAAE,CAAC,CAAC,CAAC;YAChD,6DAA6D;YAC7D,OAAO,KAAK,CAAC;QACjB,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,MAAM,CAAC,OAA6B;QAC/C,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;YAChB,QAAQ,CAAC,QAAQ,CAAC,4BAA4B,CAAC,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;CACJ,CAAA;AAnDY,4BAA4B;IADxC,aAAa,CAAC,UAAU,EAAE,uBAAuB,CAAC;GACtC,4BAA4B,CAmDxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MJAIAgentNoteEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJAIAgentNoteEntityServer.server.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,mBAAmB,EAAE,iBAAiB,EAAY,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC3H,OAAO,EAAE,mBAAmB,EAA0B,MAAM,+BAA+B,CAAC;AAI5F;;;;;;;;;;;GAWG;AACH,qBACa,yBAA0B,SAAQ,mBAAmB;IAC9D;;OAEG;cACsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI7E,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"MJAIAgentNoteEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJAIAgentNoteEntityServer.server.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,mBAAmB,EAAE,iBAAiB,EAAY,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC3H,OAAO,EAAE,mBAAmB,EAA0B,MAAM,+BAA+B,CAAC;AAI5F;;;;;;;;;;;GAWG;AACH,qBACa,yBAA0B,SAAQ,mBAAmB;IAC9D;;OAEG;cACsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI7E,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IA0CnD,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;CAQzE"}
|
|
@@ -34,10 +34,11 @@ let MJAIAgentNoteEntityServer = class MJAIAgentNoteEntityServer extends MJAIAgen
|
|
|
34
34
|
const noteField = this.GetFieldByName('Note');
|
|
35
35
|
const shouldGenerateEmbedding = !this.IsSaved || noteField.Dirty;
|
|
36
36
|
if (shouldGenerateEmbedding && this.Note && this.Note.trim().length > 0) {
|
|
37
|
-
await this.GenerateEmbeddingByFieldName("Note", "EmbeddingVector", "EmbeddingModelID");
|
|
37
|
+
await this.GenerateEmbeddingByFieldName("Note", "EmbeddingVector", "EmbeddingModelID", "EmbeddingVectorBinary");
|
|
38
38
|
}
|
|
39
39
|
else if (!this.Note || this.Note.trim().length === 0) {
|
|
40
40
|
this.EmbeddingVector = null;
|
|
41
|
+
this.EmbeddingVectorBinary = null;
|
|
41
42
|
this.EmbeddingModelID = null;
|
|
42
43
|
}
|
|
43
44
|
// 2. Persist first — only touch the in-memory vector service on a successful save
|
|
@@ -55,7 +56,7 @@ let MJAIAgentNoteEntityServer = class MJAIAgentNoteEntityServer extends MJAIAgen
|
|
|
55
56
|
// background load completes, the underlying vector service is null and the
|
|
56
57
|
// update would silently no-op. EnsureLoaded blocks until the engine is ready.
|
|
57
58
|
await AIEngine.Instance.EnsureLoaded();
|
|
58
|
-
if (IsInjectableNoteStatus(this.Status) && this.EmbeddingVector) {
|
|
59
|
+
if (IsInjectableNoteStatus(this.Status) && (this.EmbeddingVectorBinary || this.EmbeddingVector)) {
|
|
59
60
|
AIEngine.Instance.AddOrUpdateSingleNoteEmbedding(this);
|
|
60
61
|
}
|
|
61
62
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MJAIAgentNoteEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJAIAgentNoteEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,UAAU,EAA0C,QAAQ,EAAyB,MAAM,sBAAsB,CAAC;AAC3H,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD;;;;;;;;;;;GAWG;AAEI,IAAM,yBAAyB,GAA/B,MAAM,yBAA0B,SAAQ,mBAAmB;IAC9D;;OAEG;IACgB,KAAK,CAAC,cAAc,CAAC,WAAmB;QACvD,OAAO,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACnD,CAAC;IAEQ,KAAK,CAAC,IAAI,CAAC,OAA2B;QAC3C,IAAI,CAAC;YACD,sEAAsE;YACtE,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;YAC9C,MAAM,uBAAuB,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,SAAS,CAAC,KAAK,CAAC;YAEjE,IAAI,uBAAuB,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtE,MAAM,IAAI,CAAC,4BAA4B,CAAC,MAAM,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"MJAIAgentNoteEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJAIAgentNoteEntityServer.server.ts"],"names":[],"mappings":"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"}
|