@memberjunction/core-entities-server 5.40.2 → 5.41.0
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/dist/custom/MJAIAgentCoAgentEntityServer.server.d.ts +78 -0
- package/dist/custom/MJAIAgentCoAgentEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIAgentCoAgentEntityServer.server.js +224 -0
- package/dist/custom/MJAIAgentCoAgentEntityServer.server.js.map +1 -0
- package/dist/custom/MJAIAgentEntityServer.server.d.ts +50 -0
- package/dist/custom/MJAIAgentEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIAgentEntityServer.server.js +126 -0
- package/dist/custom/MJAIAgentEntityServer.server.js.map +1 -0
- package/dist/custom/MJAIAgentNoteEntityServer.server.d.ts +6 -3
- package/dist/custom/MJAIAgentNoteEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJAIAgentNoteEntityServer.server.js +10 -7
- package/dist/custom/MJAIAgentNoteEntityServer.server.js.map +1 -1
- package/dist/custom/MJAIAgentSessionBridgeEntityServer.server.d.ts +50 -0
- package/dist/custom/MJAIAgentSessionBridgeEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIAgentSessionBridgeEntityServer.server.js +133 -0
- package/dist/custom/MJAIAgentSessionBridgeEntityServer.server.js.map +1 -0
- package/dist/custom/MJAIAgentSessionBridgeParticipantEntityServer.server.d.ts +33 -0
- package/dist/custom/MJAIAgentSessionBridgeParticipantEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIAgentSessionBridgeParticipantEntityServer.server.js +90 -0
- package/dist/custom/MJAIAgentSessionBridgeParticipantEntityServer.server.js.map +1 -0
- package/dist/custom/MJAIBridgeAgentIdentityEntityServer.server.d.ts +49 -0
- package/dist/custom/MJAIBridgeAgentIdentityEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIBridgeAgentIdentityEntityServer.server.js +152 -0
- package/dist/custom/MJAIBridgeAgentIdentityEntityServer.server.js.map +1 -0
- package/dist/custom/MJAIBridgeProviderChannelEntityServer.server.d.ts +33 -0
- package/dist/custom/MJAIBridgeProviderChannelEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIBridgeProviderChannelEntityServer.server.js +90 -0
- package/dist/custom/MJAIBridgeProviderChannelEntityServer.server.js.map +1 -0
- package/dist/custom/MJAIBridgeProviderEntityServer.server.d.ts +52 -0
- package/dist/custom/MJAIBridgeProviderEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIBridgeProviderEntityServer.server.js +154 -0
- package/dist/custom/MJAIBridgeProviderEntityServer.server.js.map +1 -0
- package/dist/custom/MJAIRemoteBrowserProviderEntityServer.server.d.ts +63 -0
- package/dist/custom/MJAIRemoteBrowserProviderEntityServer.server.d.ts.map +1 -0
- package/dist/custom/MJAIRemoteBrowserProviderEntityServer.server.js +212 -0
- package/dist/custom/MJAIRemoteBrowserProviderEntityServer.server.js.map +1 -0
- package/dist/custom/MJDuplicateRunEntityServer.server.d.ts.map +1 -1
- package/dist/custom/MJDuplicateRunEntityServer.server.js +6 -2
- package/dist/custom/MJDuplicateRunEntityServer.server.js.map +1 -1
- package/dist/custom/json-schema-lite.d.ts +36 -0
- package/dist/custom/json-schema-lite.d.ts.map +1 -0
- package/dist/custom/json-schema-lite.js +125 -0
- package/dist/custom/json-schema-lite.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/package.json +24 -24
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ValidationErrorInfo, ValidationResult } from '@memberjunction/core';
|
|
2
|
+
import { MJAIAgentSessionBridgeEntity } from '@memberjunction/core-entities';
|
|
3
|
+
/**
|
|
4
|
+
* Server-side `MJ: AI Agent Session Bridges` entity. The session IS the existing `AIAgentSession`;
|
|
5
|
+
* this row is the transport attachment (lifecycle state machine in §7 of the architecture doc).
|
|
6
|
+
*
|
|
7
|
+
* The single-column value lists (`Direction`, `JoinMethod`, `TurnMode`, `Status`, `CloseReason`)
|
|
8
|
+
* are already enforced by table CHECK constraints + the generated sync validator, so this server
|
|
9
|
+
* focuses on **CROSS-FIELD coherence** the DB cannot express:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Outbound needs a target.** `Direction='Outbound'` (the agent goes *to* a meeting / places
|
|
12
|
+
* a call) requires an `Address` (join URL / phone number) OR an `ExternalConnectionID` to dial.
|
|
13
|
+
* An outbound bridge with neither is unactionable.
|
|
14
|
+
* 2. **Connected implies `ConnectedAt`.** Once `Status='Connected'` the media is flowing, so the
|
|
15
|
+
* connect timestamp must be stamped (mirrors the state machine; downstream metrics read it).
|
|
16
|
+
* Same for terminal states and `DisconnectedAt`.
|
|
17
|
+
* 3. **`CloseReason` only on terminal status.** A close reason on a still-active bridge is
|
|
18
|
+
* contradictory; a terminal bridge should record *why* it closed.
|
|
19
|
+
*
|
|
20
|
+
* All three are PURE intra-row checks → no `RunView`, no DB round trips. Each is a separately
|
|
21
|
+
* exported, unit-tested helper.
|
|
22
|
+
*/
|
|
23
|
+
export declare class MJAIAgentSessionBridgeEntityServer extends MJAIAgentSessionBridgeEntity {
|
|
24
|
+
/** Enable async validation so the cross-field coherence checks run. */
|
|
25
|
+
get DefaultSkipAsyncValidation(): boolean;
|
|
26
|
+
ValidateAsync(): Promise<ValidationResult>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* PURE invariant: an `Outbound` bridge must have somewhere to go — an `Address` (join URL / phone
|
|
30
|
+
* number) or an `ExternalConnectionID` (an already-established connection to attach to). Returns
|
|
31
|
+
* the error, or `null` when valid (Inbound bridges are exempt — the endpoint comes to the agent).
|
|
32
|
+
*/
|
|
33
|
+
export declare function ValidateOutboundHasTarget(direction: 'Inbound' | 'Outbound' | null | undefined, address: string | null | undefined, externalConnectionID: string | null | undefined): ValidationErrorInfo | null;
|
|
34
|
+
/**
|
|
35
|
+
* PURE invariant: the lifecycle timestamps must agree with `Status`.
|
|
36
|
+
* - `Connected` ⇒ `ConnectedAt` is set (media is flowing; the timestamp anchors duration metrics).
|
|
37
|
+
* - terminal (`Disconnected`/`Failed`) ⇒ `DisconnectedAt` is set.
|
|
38
|
+
* Returns all violations (empty array = valid). Earlier non-terminal states (Pending/Scheduled/
|
|
39
|
+
* Connecting/Disconnecting) impose no timestamp requirement.
|
|
40
|
+
*/
|
|
41
|
+
export declare function ValidateStatusTimestamps(status: string | null | undefined, connectedAt: Date | null, disconnectedAt: Date | null): ValidationErrorInfo[];
|
|
42
|
+
/**
|
|
43
|
+
* PURE invariant: `CloseReason` is meaningful only for a terminal bridge.
|
|
44
|
+
* - a non-terminal status with a `CloseReason` set is contradictory (the bridge isn't closed).
|
|
45
|
+
* - a terminal status SHOULD record a reason (returned as a warning, not a hard failure, so the
|
|
46
|
+
* janitor / error paths that haven't yet stamped a reason aren't blocked from persisting).
|
|
47
|
+
* Returns the error/warning, or `null` when coherent.
|
|
48
|
+
*/
|
|
49
|
+
export declare function ValidateCloseReasonCoherence(status: string | null | undefined, closeReason: string | null | undefined): ValidationErrorInfo | null;
|
|
50
|
+
//# sourceMappingURL=MJAIAgentSessionBridgeEntityServer.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MJAIAgentSessionBridgeEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJAIAgentSessionBridgeEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,mBAAmB,EAAuB,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE9G,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAK7E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBACa,kCAAmC,SAAQ,4BAA4B;IAChF,uEAAuE;IACvE,IAAoB,0BAA0B,IAAI,OAAO,CAExD;IAEqB,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC;CA0CnE;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACrC,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,IAAI,GAAG,SAAS,EACpD,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChD,mBAAmB,GAAG,IAAI,CAgB5B;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,WAAW,EAAE,IAAI,GAAG,IAAI,EACxB,cAAc,EAAE,IAAI,GAAG,IAAI,GAC5B,mBAAmB,EAAE,CAuBvB;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CACxC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,mBAAmB,GAAG,IAAI,CAsB5B"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { BaseEntity, ValidationErrorInfo, ValidationErrorType } from '@memberjunction/core';
|
|
8
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
9
|
+
import { MJAIAgentSessionBridgeEntity } from '@memberjunction/core-entities';
|
|
10
|
+
/** Bridge statuses that are terminal (the connection is over). */
|
|
11
|
+
const TERMINAL_BRIDGE_STATUSES = new Set(['Disconnected', 'Failed']);
|
|
12
|
+
/**
|
|
13
|
+
* Server-side `MJ: AI Agent Session Bridges` entity. The session IS the existing `AIAgentSession`;
|
|
14
|
+
* this row is the transport attachment (lifecycle state machine in §7 of the architecture doc).
|
|
15
|
+
*
|
|
16
|
+
* The single-column value lists (`Direction`, `JoinMethod`, `TurnMode`, `Status`, `CloseReason`)
|
|
17
|
+
* are already enforced by table CHECK constraints + the generated sync validator, so this server
|
|
18
|
+
* focuses on **CROSS-FIELD coherence** the DB cannot express:
|
|
19
|
+
*
|
|
20
|
+
* 1. **Outbound needs a target.** `Direction='Outbound'` (the agent goes *to* a meeting / places
|
|
21
|
+
* a call) requires an `Address` (join URL / phone number) OR an `ExternalConnectionID` to dial.
|
|
22
|
+
* An outbound bridge with neither is unactionable.
|
|
23
|
+
* 2. **Connected implies `ConnectedAt`.** Once `Status='Connected'` the media is flowing, so the
|
|
24
|
+
* connect timestamp must be stamped (mirrors the state machine; downstream metrics read it).
|
|
25
|
+
* Same for terminal states and `DisconnectedAt`.
|
|
26
|
+
* 3. **`CloseReason` only on terminal status.** A close reason on a still-active bridge is
|
|
27
|
+
* contradictory; a terminal bridge should record *why* it closed.
|
|
28
|
+
*
|
|
29
|
+
* All three are PURE intra-row checks → no `RunView`, no DB round trips. Each is a separately
|
|
30
|
+
* exported, unit-tested helper.
|
|
31
|
+
*/
|
|
32
|
+
let MJAIAgentSessionBridgeEntityServer = class MJAIAgentSessionBridgeEntityServer extends MJAIAgentSessionBridgeEntity {
|
|
33
|
+
/** Enable async validation so the cross-field coherence checks run. */
|
|
34
|
+
get DefaultSkipAsyncValidation() {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
async ValidateAsync() {
|
|
38
|
+
const result = await super.ValidateAsync();
|
|
39
|
+
if (!result.Success)
|
|
40
|
+
return result;
|
|
41
|
+
// Cheap intra-row checks. Each gates on the fields it actually reads so updates that don't
|
|
42
|
+
// touch the lifecycle don't re-pay the (tiny) cost — and, more importantly, an unrelated
|
|
43
|
+
// edit never resurfaces a pre-existing coherence quirk on a field the caller didn't change.
|
|
44
|
+
const newErrors = [];
|
|
45
|
+
const directionDirty = this.GetFieldByName('Direction')?.Dirty ?? false;
|
|
46
|
+
const addressDirty = this.GetFieldByName('Address')?.Dirty ?? false;
|
|
47
|
+
const extConnDirty = this.GetFieldByName('ExternalConnectionID')?.Dirty ?? false;
|
|
48
|
+
if (!this.IsSaved || directionDirty || addressDirty || extConnDirty) {
|
|
49
|
+
const outboundError = ValidateOutboundHasTarget(this.Direction, this.Address, this.ExternalConnectionID);
|
|
50
|
+
if (outboundError) {
|
|
51
|
+
newErrors.push(outboundError);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const statusDirty = this.GetFieldByName('Status')?.Dirty ?? false;
|
|
55
|
+
const connectedAtDirty = this.GetFieldByName('ConnectedAt')?.Dirty ?? false;
|
|
56
|
+
const disconnectedAtDirty = this.GetFieldByName('DisconnectedAt')?.Dirty ?? false;
|
|
57
|
+
if (!this.IsSaved || statusDirty || connectedAtDirty || disconnectedAtDirty) {
|
|
58
|
+
newErrors.push(...ValidateStatusTimestamps(this.Status, this.ConnectedAt ?? null, this.DisconnectedAt ?? null));
|
|
59
|
+
}
|
|
60
|
+
const closeReasonDirty = this.GetFieldByName('CloseReason')?.Dirty ?? false;
|
|
61
|
+
if (!this.IsSaved || statusDirty || closeReasonDirty) {
|
|
62
|
+
const closeError = ValidateCloseReasonCoherence(this.Status, this.CloseReason ?? null);
|
|
63
|
+
if (closeError) {
|
|
64
|
+
newErrors.push(closeError);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Collect all errors; only hard Failures flip Success (a Warning — e.g. a terminal bridge
|
|
68
|
+
// missing a CloseReason — must not block the janitor/error paths from persisting).
|
|
69
|
+
result.Errors.push(...newErrors);
|
|
70
|
+
if (newErrors.some((e) => e.Type === ValidationErrorType.Failure)) {
|
|
71
|
+
result.Success = false;
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
MJAIAgentSessionBridgeEntityServer = __decorate([
|
|
77
|
+
RegisterClass(BaseEntity, 'MJ: AI Agent Session Bridges')
|
|
78
|
+
], MJAIAgentSessionBridgeEntityServer);
|
|
79
|
+
export { MJAIAgentSessionBridgeEntityServer };
|
|
80
|
+
/**
|
|
81
|
+
* PURE invariant: an `Outbound` bridge must have somewhere to go — an `Address` (join URL / phone
|
|
82
|
+
* number) or an `ExternalConnectionID` (an already-established connection to attach to). Returns
|
|
83
|
+
* the error, or `null` when valid (Inbound bridges are exempt — the endpoint comes to the agent).
|
|
84
|
+
*/
|
|
85
|
+
export function ValidateOutboundHasTarget(direction, address, externalConnectionID) {
|
|
86
|
+
if (direction !== 'Outbound') {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const hasAddress = (address ?? '').trim().length > 0;
|
|
90
|
+
const hasConnection = (externalConnectionID ?? '').trim().length > 0;
|
|
91
|
+
if (hasAddress || hasConnection) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return new ValidationErrorInfo('Address', 'An Outbound bridge must specify an Address (meeting join URL / phone number) or an ' +
|
|
95
|
+
'ExternalConnectionID — the agent has no endpoint to connect to.', address ?? null, ValidationErrorType.Failure);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* PURE invariant: the lifecycle timestamps must agree with `Status`.
|
|
99
|
+
* - `Connected` ⇒ `ConnectedAt` is set (media is flowing; the timestamp anchors duration metrics).
|
|
100
|
+
* - terminal (`Disconnected`/`Failed`) ⇒ `DisconnectedAt` is set.
|
|
101
|
+
* Returns all violations (empty array = valid). Earlier non-terminal states (Pending/Scheduled/
|
|
102
|
+
* Connecting/Disconnecting) impose no timestamp requirement.
|
|
103
|
+
*/
|
|
104
|
+
export function ValidateStatusTimestamps(status, connectedAt, disconnectedAt) {
|
|
105
|
+
const errors = [];
|
|
106
|
+
if (status === 'Connected' && connectedAt == null) {
|
|
107
|
+
errors.push(new ValidationErrorInfo('ConnectedAt', "ConnectedAt must be set once Status is 'Connected' (the bridge's media is flowing).", connectedAt, ValidationErrorType.Failure));
|
|
108
|
+
}
|
|
109
|
+
if (status != null && TERMINAL_BRIDGE_STATUSES.has(status) && disconnectedAt == null) {
|
|
110
|
+
errors.push(new ValidationErrorInfo('DisconnectedAt', `DisconnectedAt must be set once Status is terminal ('${status}').`, disconnectedAt, ValidationErrorType.Failure));
|
|
111
|
+
}
|
|
112
|
+
return errors;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* PURE invariant: `CloseReason` is meaningful only for a terminal bridge.
|
|
116
|
+
* - a non-terminal status with a `CloseReason` set is contradictory (the bridge isn't closed).
|
|
117
|
+
* - a terminal status SHOULD record a reason (returned as a warning, not a hard failure, so the
|
|
118
|
+
* janitor / error paths that haven't yet stamped a reason aren't blocked from persisting).
|
|
119
|
+
* Returns the error/warning, or `null` when coherent.
|
|
120
|
+
*/
|
|
121
|
+
export function ValidateCloseReasonCoherence(status, closeReason) {
|
|
122
|
+
const isTerminal = status != null && TERMINAL_BRIDGE_STATUSES.has(status);
|
|
123
|
+
const hasReason = (closeReason ?? '').trim().length > 0;
|
|
124
|
+
if (hasReason && !isTerminal) {
|
|
125
|
+
return new ValidationErrorInfo('CloseReason', `CloseReason ('${closeReason}') may only be set when Status is terminal (Disconnected/Failed); ` +
|
|
126
|
+
`current Status is '${status ?? 'none'}'.`, closeReason, ValidationErrorType.Failure);
|
|
127
|
+
}
|
|
128
|
+
if (isTerminal && !hasReason) {
|
|
129
|
+
return new ValidationErrorInfo('CloseReason', `Status is terminal ('${status}') but no CloseReason is recorded — set why the bridge closed.`, closeReason ?? null, ValidationErrorType.Warning);
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=MJAIAgentSessionBridgeEntityServer.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MJAIAgentSessionBridgeEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJAIAgentSessionBridgeEntityServer.server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ValidationErrorInfo, ValidationResult } from '@memberjunction/core';
|
|
2
|
+
import { MJAIAgentSessionBridgeParticipantEntity } from '@memberjunction/core-entities';
|
|
3
|
+
/**
|
|
4
|
+
* Server-side `MJ: AI Agent Session Bridge Participants` entity. One row per participant on the
|
|
5
|
+
* meeting/call (the diarization map + multi-party signal intel a facilitator agent reads),
|
|
6
|
+
* including the agent bot itself (`IsAgent = 1`).
|
|
7
|
+
*
|
|
8
|
+
* Invariant: **at most one `IsAgent = 1` participant per bridge.** A bridge is one agent's
|
|
9
|
+
* connection into one room (multiple agents = multiple bridges, §4c), so two bot participants on
|
|
10
|
+
* the same bridge is a bookkeeping error that would confuse diarization (which inbound speaker is
|
|
11
|
+
* "the agent") and the self-echo gate. The DB cannot express "≤ 1 row where IsAgent=1 per
|
|
12
|
+
* SessionBridgeID" as a UNIQUE constraint (it would need a filtered unique index, which CodeGen
|
|
13
|
+
* doesn't manage), so it lives here — a cross-record RunView gated on dirty, self-excluded on
|
|
14
|
+
* update. PURE helper `BuildDuplicateAgentParticipantError`.
|
|
15
|
+
*/
|
|
16
|
+
export declare class MJAIAgentSessionBridgeParticipantEntityServer extends MJAIAgentSessionBridgeParticipantEntity {
|
|
17
|
+
/** Enable async validation so the one-agent-per-bridge check runs. */
|
|
18
|
+
get DefaultSkipAsyncValidation(): boolean;
|
|
19
|
+
ValidateAsync(): Promise<ValidationResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Counts OTHER `IsAgent = 1` participants on the same bridge (excluding this row when updating).
|
|
22
|
+
* A failed query counts as 0 (fail-open; re-checks on next save).
|
|
23
|
+
*/
|
|
24
|
+
private countOtherAgentParticipants;
|
|
25
|
+
/** Escapes a UUID/string for safe inclusion in an ExtraFilter literal. */
|
|
26
|
+
private safeLiteral;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* PURE invariant: a bridge has at most one agent-bot participant. Returns the error when
|
|
30
|
+
* `existingAgentCount` other `IsAgent = 1` rows already exist on the bridge, else `null`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function BuildDuplicateAgentParticipantError(sessionBridgeID: string | null, existingAgentCount: number): ValidationErrorInfo | null;
|
|
33
|
+
//# sourceMappingURL=MJAIAgentSessionBridgeParticipantEntityServer.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MJAIAgentSessionBridgeParticipantEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJAIAgentSessionBridgeParticipantEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0C,mBAAmB,EAAuB,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE1I,OAAO,EAAE,uCAAuC,EAAE,MAAM,+BAA+B,CAAC;AAExF;;;;;;;;;;;;GAYG;AACH,qBACa,6CAA8C,SAAQ,uCAAuC;IACtG,sEAAsE;IACtE,IAAoB,0BAA0B,IAAI,OAAO,CAExD;IAEqB,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAmBhE;;;OAGG;YACW,2BAA2B;IAwBzC,0EAA0E;IAC1E,OAAO,CAAC,WAAW;CAGtB;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CAC/C,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,kBAAkB,EAAE,MAAM,GAC3B,mBAAmB,GAAG,IAAI,CAW5B"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { BaseEntity, RunView, ValidationErrorInfo, ValidationErrorType } from '@memberjunction/core';
|
|
8
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
9
|
+
import { MJAIAgentSessionBridgeParticipantEntity } from '@memberjunction/core-entities';
|
|
10
|
+
/**
|
|
11
|
+
* Server-side `MJ: AI Agent Session Bridge Participants` entity. One row per participant on the
|
|
12
|
+
* meeting/call (the diarization map + multi-party signal intel a facilitator agent reads),
|
|
13
|
+
* including the agent bot itself (`IsAgent = 1`).
|
|
14
|
+
*
|
|
15
|
+
* Invariant: **at most one `IsAgent = 1` participant per bridge.** A bridge is one agent's
|
|
16
|
+
* connection into one room (multiple agents = multiple bridges, §4c), so two bot participants on
|
|
17
|
+
* the same bridge is a bookkeeping error that would confuse diarization (which inbound speaker is
|
|
18
|
+
* "the agent") and the self-echo gate. The DB cannot express "≤ 1 row where IsAgent=1 per
|
|
19
|
+
* SessionBridgeID" as a UNIQUE constraint (it would need a filtered unique index, which CodeGen
|
|
20
|
+
* doesn't manage), so it lives here — a cross-record RunView gated on dirty, self-excluded on
|
|
21
|
+
* update. PURE helper `BuildDuplicateAgentParticipantError`.
|
|
22
|
+
*/
|
|
23
|
+
let MJAIAgentSessionBridgeParticipantEntityServer = class MJAIAgentSessionBridgeParticipantEntityServer extends MJAIAgentSessionBridgeParticipantEntity {
|
|
24
|
+
/** Enable async validation so the one-agent-per-bridge check runs. */
|
|
25
|
+
get DefaultSkipAsyncValidation() {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
async ValidateAsync() {
|
|
29
|
+
const result = await super.ValidateAsync();
|
|
30
|
+
if (!result.Success)
|
|
31
|
+
return result;
|
|
32
|
+
// Only when this row claims to be the agent, and only when new or the relevant fields moved.
|
|
33
|
+
const isAgentDirty = this.GetFieldByName('IsAgent')?.Dirty ?? false;
|
|
34
|
+
const bridgeDirty = this.GetFieldByName('SessionBridgeID')?.Dirty ?? false;
|
|
35
|
+
if (this.IsAgent && (!this.IsSaved || isAgentDirty || bridgeDirty)) {
|
|
36
|
+
const conflict = await this.countOtherAgentParticipants();
|
|
37
|
+
const dupError = BuildDuplicateAgentParticipantError(this.SessionBridgeID, conflict);
|
|
38
|
+
if (dupError) {
|
|
39
|
+
result.Errors.push(dupError);
|
|
40
|
+
result.Success = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Counts OTHER `IsAgent = 1` participants on the same bridge (excluding this row when updating).
|
|
47
|
+
* A failed query counts as 0 (fail-open; re-checks on next save).
|
|
48
|
+
*/
|
|
49
|
+
async countOtherAgentParticipants() {
|
|
50
|
+
if (!this.SessionBridgeID) {
|
|
51
|
+
return 0; // required-field violation is sync Validate()'s job
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const md = this.ProviderToUse;
|
|
55
|
+
const selfExclusion = this.IsSaved ? ` AND ID <> '${this.safeLiteral(this.ID)}'` : '';
|
|
56
|
+
const filter = `SessionBridgeID='${this.safeLiteral(this.SessionBridgeID)}' AND IsAgent=1${selfExclusion}`;
|
|
57
|
+
const rv = RunView.FromMetadataProvider(md);
|
|
58
|
+
const queryResult = await rv.RunView({
|
|
59
|
+
EntityName: 'MJ: AI Agent Session Bridge Participants',
|
|
60
|
+
ExtraFilter: filter,
|
|
61
|
+
Fields: ['ID'],
|
|
62
|
+
ResultType: 'simple',
|
|
63
|
+
}, this.ContextCurrentUser);
|
|
64
|
+
return queryResult.Success ? (queryResult.Results?.length ?? 0) : 0;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Escapes a UUID/string for safe inclusion in an ExtraFilter literal. */
|
|
71
|
+
safeLiteral(value) {
|
|
72
|
+
return value.replace(/'/g, "''");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
MJAIAgentSessionBridgeParticipantEntityServer = __decorate([
|
|
76
|
+
RegisterClass(BaseEntity, 'MJ: AI Agent Session Bridge Participants')
|
|
77
|
+
], MJAIAgentSessionBridgeParticipantEntityServer);
|
|
78
|
+
export { MJAIAgentSessionBridgeParticipantEntityServer };
|
|
79
|
+
/**
|
|
80
|
+
* PURE invariant: a bridge has at most one agent-bot participant. Returns the error when
|
|
81
|
+
* `existingAgentCount` other `IsAgent = 1` rows already exist on the bridge, else `null`.
|
|
82
|
+
*/
|
|
83
|
+
export function BuildDuplicateAgentParticipantError(sessionBridgeID, existingAgentCount) {
|
|
84
|
+
if (existingAgentCount <= 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return new ValidationErrorInfo('IsAgent', `This bridge already has an agent participant (${existingAgentCount} IsAgent row(s) exist). ` +
|
|
88
|
+
'A bridge is a single agent’s connection — only one participant may be the agent bot.', true, ValidationErrorType.Failure);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=MJAIAgentSessionBridgeParticipantEntityServer.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MJAIAgentSessionBridgeParticipantEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJAIAgentSessionBridgeParticipantEntityServer.server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ValidationErrorInfo, ValidationResult } from '@memberjunction/core';
|
|
2
|
+
import { MJAIBridgeAgentIdentityEntity } from '@memberjunction/core-entities';
|
|
3
|
+
/**
|
|
4
|
+
* Server-side `MJ: AI Bridge Agent Identities` entity. An identity is how an agent is reachable on
|
|
5
|
+
* a platform — a calendar mailbox (`Email`), an inbound DID (`PhoneNumber`), or a platform-native
|
|
6
|
+
* account (`AccountID`). Invite watchers and inbound routing resolve a connection to an agent by
|
|
7
|
+
* matching `(ProviderID, IdentityValue)`, so that pair MUST be unique, and the value SHOULD look
|
|
8
|
+
* like its declared `IdentityType`.
|
|
9
|
+
*
|
|
10
|
+
* Invariants (see `/plans/realtime/realtime-bridges-architecture.md` §5):
|
|
11
|
+
* 1. **`(ProviderID, IdentityValue)` is unique** across rows — two agents reachable at the same
|
|
12
|
+
* address on the same provider would make inbound routing ambiguous. Cross-record RunView,
|
|
13
|
+
* gated on dirty, self-excluded on update (the `countRows` pattern). The column-level docs say
|
|
14
|
+
* "Unique per provider"; there is no DB UNIQUE constraint on the pair, so this is the real
|
|
15
|
+
* enforcement (case-insensitive, which a raw SQL UNIQUE would not be on a case-sensitive
|
|
16
|
+
* collation).
|
|
17
|
+
* 2. **`IdentityValue` shape matches `IdentityType`** — an `Email` looks like an email, a
|
|
18
|
+
* `PhoneNumber` is digits with an optional leading `+`. `AccountID` is opaque (no shape check).
|
|
19
|
+
* PURE helper `ValidateIdentityValueFormat`.
|
|
20
|
+
*/
|
|
21
|
+
export declare class MJAIBridgeAgentIdentityEntityServer extends MJAIBridgeAgentIdentityEntity {
|
|
22
|
+
/** Enable async validation so the cross-record uniqueness check runs. */
|
|
23
|
+
get DefaultSkipAsyncValidation(): boolean;
|
|
24
|
+
ValidateAsync(): Promise<ValidationResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Counts OTHER identity rows on the same provider with the same `IdentityValue` (excluding this
|
|
27
|
+
* row when updating). A failed query counts as 0 (fail-open; re-checks on next save).
|
|
28
|
+
*/
|
|
29
|
+
private countOtherIdentitiesWithSameValue;
|
|
30
|
+
/** Escapes a UUID/string for safe inclusion in an ExtraFilter literal. */
|
|
31
|
+
private safeLiteral;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* PURE invariant: `IdentityValue` must look like its `IdentityType`. Returns the error, or `null`
|
|
35
|
+
* when valid (or when not shape-checkable — `AccountID` is opaque, and missing values are the sync
|
|
36
|
+
* validator's job).
|
|
37
|
+
*
|
|
38
|
+
* - `Email`: a single `@` with non-empty local part and a dotted domain.
|
|
39
|
+
* - `PhoneNumber`: an optional leading `+` followed by 7–15 digits (E.164-ish), tolerating spaces,
|
|
40
|
+
* dashes, parentheses, and dots as separators.
|
|
41
|
+
* - `AccountID`: opaque — no shape enforced.
|
|
42
|
+
*/
|
|
43
|
+
export declare function ValidateIdentityValueFormat(identityType: 'AccountID' | 'Email' | 'PhoneNumber' | null | undefined, identityValue: string | null | undefined): ValidationErrorInfo | null;
|
|
44
|
+
/**
|
|
45
|
+
* PURE invariant: `(ProviderID, IdentityValue)` must be unique. Returns the error when
|
|
46
|
+
* `existingCount` other rows already use this value on the provider, else `null`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function BuildDuplicateIdentityError(providerID: string | null, identityValue: string | null, existingCount: number): ValidationErrorInfo | null;
|
|
49
|
+
//# sourceMappingURL=MJAIBridgeAgentIdentityEntityServer.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MJAIBridgeAgentIdentityEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJAIBridgeAgentIdentityEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0C,mBAAmB,EAAuB,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE1I,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAE9E;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,mCAAoC,SAAQ,6BAA6B;IAClF,yEAAyE;IACzE,IAAoB,0BAA0B,IAAI,OAAO,CAExD;IAEqB,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAiChE;;;OAGG;YACW,iCAAiC;IA0B/C,0EAA0E;IAC1E,OAAO,CAAC,WAAW;CAGtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACvC,YAAY,EAAE,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,IAAI,GAAG,SAAS,EACtE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACzC,mBAAmB,GAAG,IAAI,CAuB5B;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACvC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,aAAa,EAAE,MAAM,GACtB,mBAAmB,GAAG,IAAI,CAW5B"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { BaseEntity, RunView, ValidationErrorInfo, ValidationErrorType } from '@memberjunction/core';
|
|
8
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
9
|
+
import { MJAIBridgeAgentIdentityEntity } from '@memberjunction/core-entities';
|
|
10
|
+
/**
|
|
11
|
+
* Server-side `MJ: AI Bridge Agent Identities` entity. An identity is how an agent is reachable on
|
|
12
|
+
* a platform — a calendar mailbox (`Email`), an inbound DID (`PhoneNumber`), or a platform-native
|
|
13
|
+
* account (`AccountID`). Invite watchers and inbound routing resolve a connection to an agent by
|
|
14
|
+
* matching `(ProviderID, IdentityValue)`, so that pair MUST be unique, and the value SHOULD look
|
|
15
|
+
* like its declared `IdentityType`.
|
|
16
|
+
*
|
|
17
|
+
* Invariants (see `/plans/realtime/realtime-bridges-architecture.md` §5):
|
|
18
|
+
* 1. **`(ProviderID, IdentityValue)` is unique** across rows — two agents reachable at the same
|
|
19
|
+
* address on the same provider would make inbound routing ambiguous. Cross-record RunView,
|
|
20
|
+
* gated on dirty, self-excluded on update (the `countRows` pattern). The column-level docs say
|
|
21
|
+
* "Unique per provider"; there is no DB UNIQUE constraint on the pair, so this is the real
|
|
22
|
+
* enforcement (case-insensitive, which a raw SQL UNIQUE would not be on a case-sensitive
|
|
23
|
+
* collation).
|
|
24
|
+
* 2. **`IdentityValue` shape matches `IdentityType`** — an `Email` looks like an email, a
|
|
25
|
+
* `PhoneNumber` is digits with an optional leading `+`. `AccountID` is opaque (no shape check).
|
|
26
|
+
* PURE helper `ValidateIdentityValueFormat`.
|
|
27
|
+
*/
|
|
28
|
+
let MJAIBridgeAgentIdentityEntityServer = class MJAIBridgeAgentIdentityEntityServer extends MJAIBridgeAgentIdentityEntity {
|
|
29
|
+
/** Enable async validation so the cross-record uniqueness check runs. */
|
|
30
|
+
get DefaultSkipAsyncValidation() {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
async ValidateAsync() {
|
|
34
|
+
const result = await super.ValidateAsync();
|
|
35
|
+
if (!result.Success)
|
|
36
|
+
return result;
|
|
37
|
+
// (2) Format check — cheap, only when new or the relevant fields changed.
|
|
38
|
+
const valueDirty = this.GetFieldByName('IdentityValue')?.Dirty ?? false;
|
|
39
|
+
const typeDirty = this.GetFieldByName('IdentityType')?.Dirty ?? false;
|
|
40
|
+
if (!this.IsSaved || valueDirty || typeDirty) {
|
|
41
|
+
const formatError = ValidateIdentityValueFormat(this.IdentityType, this.IdentityValue);
|
|
42
|
+
if (formatError) {
|
|
43
|
+
result.Errors.push(formatError);
|
|
44
|
+
result.Success = false;
|
|
45
|
+
// Fall through is fine, but a malformed value also makes the uniqueness compare
|
|
46
|
+
// meaningless — return early.
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// (1) Uniqueness of (ProviderID, IdentityValue) — cross-record, only when new or
|
|
51
|
+
// ProviderID/IdentityValue changed.
|
|
52
|
+
const providerDirty = this.GetFieldByName('ProviderID')?.Dirty ?? false;
|
|
53
|
+
if (!this.IsSaved || providerDirty || valueDirty) {
|
|
54
|
+
const conflict = await this.countOtherIdentitiesWithSameValue();
|
|
55
|
+
const dupError = BuildDuplicateIdentityError(this.ProviderID, this.IdentityValue, conflict);
|
|
56
|
+
if (dupError) {
|
|
57
|
+
result.Errors.push(dupError);
|
|
58
|
+
result.Success = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Counts OTHER identity rows on the same provider with the same `IdentityValue` (excluding this
|
|
65
|
+
* row when updating). A failed query counts as 0 (fail-open; re-checks on next save).
|
|
66
|
+
*/
|
|
67
|
+
async countOtherIdentitiesWithSameValue() {
|
|
68
|
+
if (!this.ProviderID || !this.IdentityValue) {
|
|
69
|
+
return 0; // required-field violations are sync Validate()'s job
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const md = this.ProviderToUse;
|
|
73
|
+
const selfExclusion = this.IsSaved ? ` AND ID <> '${this.safeLiteral(this.ID)}'` : '';
|
|
74
|
+
const filter = `ProviderID='${this.safeLiteral(this.ProviderID)}' AND ` +
|
|
75
|
+
`IdentityValue='${this.safeLiteral(this.IdentityValue)}'${selfExclusion}`;
|
|
76
|
+
const rv = RunView.FromMetadataProvider(md);
|
|
77
|
+
const result = await rv.RunView({
|
|
78
|
+
EntityName: 'MJ: AI Bridge Agent Identities',
|
|
79
|
+
ExtraFilter: filter,
|
|
80
|
+
Fields: ['ID'],
|
|
81
|
+
ResultType: 'simple',
|
|
82
|
+
}, this.ContextCurrentUser);
|
|
83
|
+
return result.Success ? (result.Results?.length ?? 0) : 0;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Escapes a UUID/string for safe inclusion in an ExtraFilter literal. */
|
|
90
|
+
safeLiteral(value) {
|
|
91
|
+
return value.replace(/'/g, "''");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
MJAIBridgeAgentIdentityEntityServer = __decorate([
|
|
95
|
+
RegisterClass(BaseEntity, 'MJ: AI Bridge Agent Identities')
|
|
96
|
+
], MJAIBridgeAgentIdentityEntityServer);
|
|
97
|
+
export { MJAIBridgeAgentIdentityEntityServer };
|
|
98
|
+
/**
|
|
99
|
+
* PURE invariant: `IdentityValue` must look like its `IdentityType`. Returns the error, or `null`
|
|
100
|
+
* when valid (or when not shape-checkable — `AccountID` is opaque, and missing values are the sync
|
|
101
|
+
* validator's job).
|
|
102
|
+
*
|
|
103
|
+
* - `Email`: a single `@` with non-empty local part and a dotted domain.
|
|
104
|
+
* - `PhoneNumber`: an optional leading `+` followed by 7–15 digits (E.164-ish), tolerating spaces,
|
|
105
|
+
* dashes, parentheses, and dots as separators.
|
|
106
|
+
* - `AccountID`: opaque — no shape enforced.
|
|
107
|
+
*/
|
|
108
|
+
export function ValidateIdentityValueFormat(identityType, identityValue) {
|
|
109
|
+
if (identityType == null || identityValue == null || identityValue.trim().length === 0) {
|
|
110
|
+
return null; // required checks are sync Validate()'s job
|
|
111
|
+
}
|
|
112
|
+
const value = identityValue.trim();
|
|
113
|
+
if (identityType === 'Email') {
|
|
114
|
+
if (!isEmailLike(value)) {
|
|
115
|
+
return buildFormatError(`IdentityValue '${value}' is not a valid email address (required for IdentityType 'Email').`, identityValue);
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
if (identityType === 'PhoneNumber') {
|
|
120
|
+
if (!isPhoneNumberLike(value)) {
|
|
121
|
+
return buildFormatError(`IdentityValue '${value}' is not a valid phone number (required for IdentityType 'PhoneNumber' — ` +
|
|
122
|
+
'an optional leading + and 7–15 digits).', identityValue);
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return null; // AccountID — opaque
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* PURE invariant: `(ProviderID, IdentityValue)` must be unique. Returns the error when
|
|
130
|
+
* `existingCount` other rows already use this value on the provider, else `null`.
|
|
131
|
+
*/
|
|
132
|
+
export function BuildDuplicateIdentityError(providerID, identityValue, existingCount) {
|
|
133
|
+
if (existingCount <= 0) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return new ValidationErrorInfo('IdentityValue', `Identity value '${identityValue}' is already registered on this provider (${existingCount} other row(s)). ` +
|
|
137
|
+
'Each (provider, identity value) pair must be unique so inbound connections route to exactly one agent.', identityValue, ValidationErrorType.Failure);
|
|
138
|
+
}
|
|
139
|
+
/** Loose email shape check: `local@domain.tld`, no spaces, single `@`. */
|
|
140
|
+
function isEmailLike(value) {
|
|
141
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
142
|
+
}
|
|
143
|
+
/** Loose E.164-ish phone check: optional leading `+`, 7–15 digits, common separators ignored. */
|
|
144
|
+
function isPhoneNumberLike(value) {
|
|
145
|
+
const stripped = value.replace(/[\s().-]/g, '');
|
|
146
|
+
return /^\+?\d{7,15}$/.test(stripped);
|
|
147
|
+
}
|
|
148
|
+
/** Builds an `IdentityValue` format failure with the standard source/type. */
|
|
149
|
+
function buildFormatError(message, value) {
|
|
150
|
+
return new ValidationErrorInfo('IdentityValue', message, value, ValidationErrorType.Failure);
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=MJAIBridgeAgentIdentityEntityServer.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MJAIBridgeAgentIdentityEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJAIBridgeAgentIdentityEntityServer.server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ValidationErrorInfo, ValidationResult } from '@memberjunction/core';
|
|
2
|
+
import { MJAIBridgeProviderChannelEntity } from '@memberjunction/core-entities';
|
|
3
|
+
/**
|
|
4
|
+
* Server-side `MJ: AI Bridge Provider Channels` entity — the junction declaring which registered
|
|
5
|
+
* `AIAgentChannel`s a provider contributes (Zoom → Meeting Controls + Native Whiteboard, etc.).
|
|
6
|
+
*
|
|
7
|
+
* The only invariant worth enforcing is uniqueness of `(ProviderID, ChannelID)` — a provider
|
|
8
|
+
* shouldn't list the same channel twice. **The database already enforces this with a UNIQUE
|
|
9
|
+
* constraint (`UQ_AIBridgeProviderChannel (ProviderID, ChannelID)`)**, so this check is
|
|
10
|
+
* **defense-in-depth / friendly-message only**: it turns the raw SQL unique-violation error into a
|
|
11
|
+
* clear, field-attributed message at validation time (before the INSERT/UPDATE is attempted) and
|
|
12
|
+
* is consistent with how the sibling co-agent / identity servers surface their cross-record rules.
|
|
13
|
+
* No format/shape invariants exist for this purely-relational row, so there is nothing else to add.
|
|
14
|
+
*/
|
|
15
|
+
export declare class MJAIBridgeProviderChannelEntityServer extends MJAIBridgeProviderChannelEntity {
|
|
16
|
+
/** Enable async validation so the friendly-message uniqueness check runs. */
|
|
17
|
+
get DefaultSkipAsyncValidation(): boolean;
|
|
18
|
+
ValidateAsync(): Promise<ValidationResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Counts OTHER rows pairing this provider with this channel (excluding this row when updating).
|
|
21
|
+
* A failed query counts as 0 (fail-open; the DB UNIQUE constraint is the hard backstop anyway).
|
|
22
|
+
*/
|
|
23
|
+
private countOtherProviderChannelRows;
|
|
24
|
+
/** Escapes a UUID/string for safe inclusion in an ExtraFilter literal. */
|
|
25
|
+
private safeLiteral;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* PURE invariant: a provider may list a given channel only once. Returns the friendly error when
|
|
29
|
+
* `existingCount` other rows already pair this `(ProviderID, ChannelID)`, else `null`.
|
|
30
|
+
* (Defense-in-depth over the DB `UQ_AIBridgeProviderChannel` UNIQUE constraint.)
|
|
31
|
+
*/
|
|
32
|
+
export declare function BuildDuplicateProviderChannelError(providerID: string | null, channelID: string | null, existingCount: number): ValidationErrorInfo | null;
|
|
33
|
+
//# sourceMappingURL=MJAIBridgeProviderChannelEntityServer.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MJAIBridgeProviderChannelEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJAIBridgeProviderChannelEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0C,mBAAmB,EAAuB,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE1I,OAAO,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AAEhF;;;;;;;;;;;GAWG;AACH,qBACa,qCAAsC,SAAQ,+BAA+B;IACtF,6EAA6E;IAC7E,IAAoB,0BAA0B,IAAI,OAAO,CAExD;IAEqB,aAAa,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAkBhE;;;OAGG;YACW,6BAA6B;IA0B3C,0EAA0E;IAC1E,OAAO,CAAC,WAAW;CAGtB;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAC9C,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,EAAE,MAAM,GACtB,mBAAmB,GAAG,IAAI,CAW5B"}
|