@voyant-travel/relationships 0.119.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +36 -0
- package/dist/action-ledger-capabilities.d.ts +20 -0
- package/dist/action-ledger-capabilities.d.ts.map +1 -0
- package/dist/action-ledger-capabilities.js +16 -0
- package/dist/events.d.ts +23 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +9 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/route-runtime.d.ts +21 -0
- package/dist/route-runtime.d.ts.map +1 -0
- package/dist/route-runtime.js +28 -0
- package/dist/routes/accounts.d.ts +1460 -0
- package/dist/routes/accounts.d.ts.map +1 -0
- package/dist/routes/accounts.js +274 -0
- package/dist/routes/activities.d.ts +299 -0
- package/dist/routes/activities.d.ts.map +1 -0
- package/dist/routes/activities.js +64 -0
- package/dist/routes/custom-fields.d.ts +256 -0
- package/dist/routes/custom-fields.d.ts.map +1 -0
- package/dist/routes/custom-fields.js +47 -0
- package/dist/routes/customer-signals.d.ts +281 -0
- package/dist/routes/customer-signals.d.ts.map +1 -0
- package/dist/routes/customer-signals.js +45 -0
- package/dist/routes/index.d.ts +2945 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +14 -0
- package/dist/routes/person-documents.d.ts +519 -0
- package/dist/routes/person-documents.d.ts.map +1 -0
- package/dist/routes/person-documents.js +240 -0
- package/dist/routes/person-relationships.d.ts +189 -0
- package/dist/routes/person-relationships.d.ts.map +1 -0
- package/dist/routes/person-relationships.js +36 -0
- package/dist/schema-accounts.d.ts +2099 -0
- package/dist/schema-accounts.d.ts.map +1 -0
- package/dist/schema-accounts.js +312 -0
- package/dist/schema-activities.d.ts +821 -0
- package/dist/schema-activities.d.ts.map +1 -0
- package/dist/schema-activities.js +92 -0
- package/dist/schema-relations.d.ts +47 -0
- package/dist/schema-relations.d.ts.map +1 -0
- package/dist/schema-relations.js +70 -0
- package/dist/schema-shared.d.ts +10 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +36 -0
- package/dist/schema-signals.d.ts +324 -0
- package/dist/schema-signals.d.ts.map +1 -0
- package/dist/schema-signals.js +80 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +5 -0
- package/dist/service/accounts-merge.d.ts +63 -0
- package/dist/service/accounts-merge.d.ts.map +1 -0
- package/dist/service/accounts-merge.js +382 -0
- package/dist/service/accounts-organizations.d.ts +97 -0
- package/dist/service/accounts-organizations.d.ts.map +1 -0
- package/dist/service/accounts-organizations.js +70 -0
- package/dist/service/accounts-people.d.ts +1315 -0
- package/dist/service/accounts-people.d.ts.map +1 -0
- package/dist/service/accounts-people.js +409 -0
- package/dist/service/accounts-resolve.d.ts +76 -0
- package/dist/service/accounts-resolve.d.ts.map +1 -0
- package/dist/service/accounts-resolve.js +103 -0
- package/dist/service/accounts-shared.d.ts +68 -0
- package/dist/service/accounts-shared.d.ts.map +1 -0
- package/dist/service/accounts-shared.js +149 -0
- package/dist/service/accounts.d.ts +1465 -0
- package/dist/service/accounts.d.ts.map +1 -0
- package/dist/service/accounts.js +13 -0
- package/dist/service/activities.d.ts +486 -0
- package/dist/service/activities.d.ts.map +1 -0
- package/dist/service/activities.js +114 -0
- package/dist/service/custom-fields.d.ts +118 -0
- package/dist/service/custom-fields.d.ts.map +1 -0
- package/dist/service/custom-fields.js +88 -0
- package/dist/service/customer-signals.d.ts +733 -0
- package/dist/service/customer-signals.d.ts.map +1 -0
- package/dist/service/customer-signals.js +112 -0
- package/dist/service/helpers.d.ts +22 -0
- package/dist/service/helpers.d.ts.map +1 -0
- package/dist/service/helpers.js +39 -0
- package/dist/service/index.d.ts +4434 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/service/index.js +17 -0
- package/dist/service/person-documents.d.ts +1201 -0
- package/dist/service/person-documents.d.ts.map +1 -0
- package/dist/service/person-documents.js +240 -0
- package/dist/service/person-relationships.d.ts +502 -0
- package/dist/service/person-relationships.d.ts.map +1 -0
- package/dist/service/person-relationships.js +121 -0
- package/dist/validation.d.ts +3 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { identityService } from "@voyant-travel/identity/service";
|
|
2
|
+
import { inArray } from "drizzle-orm";
|
|
3
|
+
import { personDirectoryView } from "../schema.js";
|
|
4
|
+
import { isManagedBySource, normalizeContactValue, toNullableTrimmed } from "./helpers.js";
|
|
5
|
+
export const organizationEntityType = "organization";
|
|
6
|
+
export const personEntityType = "person";
|
|
7
|
+
export const personBaseIdentitySource = "relationships.person.base";
|
|
8
|
+
function emptyPersonHydratedFields() {
|
|
9
|
+
return {
|
|
10
|
+
email: null,
|
|
11
|
+
phone: null,
|
|
12
|
+
website: null,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function personBaseFields(data) {
|
|
16
|
+
return {
|
|
17
|
+
organizationId: data.organizationId,
|
|
18
|
+
firstName: data.firstName,
|
|
19
|
+
middleName: data.middleName,
|
|
20
|
+
lastName: data.lastName,
|
|
21
|
+
gender: data.gender,
|
|
22
|
+
jobTitle: data.jobTitle,
|
|
23
|
+
relation: data.relation,
|
|
24
|
+
preferredLanguage: data.preferredLanguage,
|
|
25
|
+
preferredCurrency: data.preferredCurrency,
|
|
26
|
+
ownerId: data.ownerId,
|
|
27
|
+
status: data.status,
|
|
28
|
+
source: data.source,
|
|
29
|
+
sourceRef: data.sourceRef,
|
|
30
|
+
tags: data.tags,
|
|
31
|
+
dateOfBirth: data.dateOfBirth,
|
|
32
|
+
notes: data.notes,
|
|
33
|
+
accessibilityEncrypted: data.accessibilityEncrypted,
|
|
34
|
+
dietaryEncrypted: data.dietaryEncrypted,
|
|
35
|
+
loyaltyEncrypted: data.loyaltyEncrypted,
|
|
36
|
+
insuranceEncrypted: data.insuranceEncrypted,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Reads the per-person `(email, phone, website)` triple from the
|
|
41
|
+
* `person_directory` view (replaces the old projection cache —
|
|
42
|
+
* see #446). The view is computed via indexed `LATERAL` joins on
|
|
43
|
+
* `identity_contact_points`, so callers no longer need a rebuild
|
|
44
|
+
* step after contact-point edits.
|
|
45
|
+
*/
|
|
46
|
+
async function loadPersonDirectoryMap(db, personIds) {
|
|
47
|
+
const ids = [...new Set(personIds)];
|
|
48
|
+
if (ids.length === 0) {
|
|
49
|
+
return new Map();
|
|
50
|
+
}
|
|
51
|
+
const rows = await db
|
|
52
|
+
.select()
|
|
53
|
+
.from(personDirectoryView)
|
|
54
|
+
.where(inArray(personDirectoryView.personId, ids));
|
|
55
|
+
const map = new Map();
|
|
56
|
+
for (const row of rows) {
|
|
57
|
+
map.set(row.personId, {
|
|
58
|
+
email: row.email,
|
|
59
|
+
phone: row.phone,
|
|
60
|
+
website: row.website,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
for (const id of ids) {
|
|
64
|
+
if (!map.has(id)) {
|
|
65
|
+
map.set(id, emptyPersonHydratedFields());
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return map;
|
|
69
|
+
}
|
|
70
|
+
export async function syncPersonIdentity(db, personId, data) {
|
|
71
|
+
const existingContactPoints = await identityService.listContactPointsForEntity(db, personEntityType, personId);
|
|
72
|
+
const existingAddresses = await identityService.listAddressesForEntity(db, personEntityType, personId);
|
|
73
|
+
const managedContactPoints = existingContactPoints.filter((point) => isManagedBySource(point.metadata, personBaseIdentitySource));
|
|
74
|
+
const managedAddress = existingAddresses.find((address) => isManagedBySource(address.metadata, personBaseIdentitySource));
|
|
75
|
+
for (const [kind, rawValue] of Object.entries({
|
|
76
|
+
email: data.email,
|
|
77
|
+
phone: data.phone,
|
|
78
|
+
website: data.website,
|
|
79
|
+
})) {
|
|
80
|
+
const value = toNullableTrimmed(rawValue);
|
|
81
|
+
const existing = managedContactPoints.find((point) => point.kind === kind) ??
|
|
82
|
+
existingContactPoints.find((point) => point.kind === kind && point.isPrimary);
|
|
83
|
+
if (!value) {
|
|
84
|
+
if (existing) {
|
|
85
|
+
await identityService.deleteContactPoint(db, existing.id);
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const payload = {
|
|
90
|
+
entityType: personEntityType,
|
|
91
|
+
entityId: personId,
|
|
92
|
+
kind,
|
|
93
|
+
label: kind === "website" ? "website" : "primary",
|
|
94
|
+
value,
|
|
95
|
+
normalizedValue: normalizeContactValue(kind, value),
|
|
96
|
+
isPrimary: true,
|
|
97
|
+
metadata: { managedBy: personBaseIdentitySource },
|
|
98
|
+
};
|
|
99
|
+
if (existing) {
|
|
100
|
+
await identityService.updateContactPoint(db, existing.id, payload);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
await identityService.createContactPoint(db, payload);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (managedAddress) {
|
|
107
|
+
await identityService.deleteAddress(db, managedAddress.id);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export async function deletePersonIdentity(db, personId) {
|
|
111
|
+
const [contactPoints, addresses] = await Promise.all([
|
|
112
|
+
identityService.listContactPointsForEntity(db, personEntityType, personId),
|
|
113
|
+
identityService.listAddressesForEntity(db, personEntityType, personId),
|
|
114
|
+
]);
|
|
115
|
+
await Promise.all([
|
|
116
|
+
...contactPoints.map((point) => identityService.deleteContactPoint(db, point.id)),
|
|
117
|
+
...addresses.map((address) => identityService.deleteAddress(db, address.id)),
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
export async function hydratePeople(db, rows, options = {}) {
|
|
121
|
+
const emptyHydratedRows = () => rows.map((row) => ({
|
|
122
|
+
...row,
|
|
123
|
+
...emptyPersonHydratedFields(),
|
|
124
|
+
}));
|
|
125
|
+
if (rows.length === 0) {
|
|
126
|
+
return emptyHydratedRows();
|
|
127
|
+
}
|
|
128
|
+
const ids = rows.map((row) => row.id);
|
|
129
|
+
let directoryMap;
|
|
130
|
+
try {
|
|
131
|
+
directoryMap = await loadPersonDirectoryMap(db, ids);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (!options.fallbackOnError) {
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
console.warn("[relationships] person identity hydration failed; returning base people rows", {
|
|
138
|
+
error,
|
|
139
|
+
personCount: rows.length,
|
|
140
|
+
});
|
|
141
|
+
return emptyHydratedRows();
|
|
142
|
+
}
|
|
143
|
+
return rows.map((row) => {
|
|
144
|
+
return {
|
|
145
|
+
...row,
|
|
146
|
+
...(directoryMap.get(row.id) ?? emptyPersonHydratedFields()),
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|