@voyant-travel/identity 0.119.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,oBAAoB,4HAU/B,CAAA;AAEF,eAAO,MAAM,gBAAgB,6HAS3B,CAAA;AAEF,eAAO,MAAM,oBAAoB,gKAW/B,CAAA;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwCjC,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiC7B,CAAA;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCjC,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA;AAC5E,MAAM,MAAM,uBAAuB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA;AAC/E,MAAM,MAAM,eAAe,GAAG,OAAO,iBAAiB,CAAC,YAAY,CAAA;AACnE,MAAM,MAAM,kBAAkB,GAAG,OAAO,iBAAiB,CAAC,YAAY,CAAA;AACtE,MAAM,MAAM,oBAAoB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA;AAC5E,MAAM,MAAM,uBAAuB,GAAG,OAAO,qBAAqB,CAAC,YAAY,CAAA"}
package/dist/schema.js ADDED
@@ -0,0 +1,102 @@
1
+ import { typeId } from "@voyant-travel/db/lib/typeid-column";
2
+ import { boolean, doublePrecision, index, jsonb, pgEnum, pgTable, text, timestamp, uniqueIndex, } from "drizzle-orm/pg-core";
3
+ export const contactPointKindEnum = pgEnum("contact_point_kind", [
4
+ "email",
5
+ "phone",
6
+ "mobile",
7
+ "whatsapp",
8
+ "website",
9
+ "sms",
10
+ "fax",
11
+ "social",
12
+ "other",
13
+ ]);
14
+ export const addressLabelEnum = pgEnum("address_label", [
15
+ "primary",
16
+ "billing",
17
+ "shipping",
18
+ "mailing",
19
+ "meeting",
20
+ "service",
21
+ "legal",
22
+ "other",
23
+ ]);
24
+ export const namedContactRoleEnum = pgEnum("named_contact_role", [
25
+ "general",
26
+ "primary",
27
+ "reservations",
28
+ "operations",
29
+ "front_desk",
30
+ "sales",
31
+ "emergency",
32
+ "accounting",
33
+ "legal",
34
+ "other",
35
+ ]);
36
+ export const identityContactPoints = pgTable("identity_contact_points", {
37
+ id: typeId("identity_contact_points"),
38
+ entityType: text("entity_type").notNull(),
39
+ entityId: text("entity_id").notNull(),
40
+ kind: contactPointKindEnum("kind").notNull(),
41
+ label: text("label"),
42
+ value: text("value").notNull(),
43
+ normalizedValue: text("normalized_value"),
44
+ isPrimary: boolean("is_primary").notNull().default(false),
45
+ notes: text("notes"),
46
+ metadata: jsonb("metadata").$type(),
47
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
48
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
49
+ }, (table) => [
50
+ index("idx_identity_contact_points_entity").on(table.entityType, table.entityId),
51
+ index("idx_identity_contact_points_entity_primary_created").on(table.entityType, table.entityId, table.isPrimary, table.createdAt),
52
+ index("idx_identity_contact_points_entity_kind_primary_created").on(table.entityType, table.entityId, table.kind, table.isPrimary, table.createdAt),
53
+ index("idx_identity_contact_points_kind").on(table.kind),
54
+ index("idx_identity_contact_points_normalized").on(table.normalizedValue),
55
+ uniqueIndex("uidx_identity_contact_points_entity_kind_value").on(table.entityType, table.entityId, table.kind, table.value),
56
+ ]);
57
+ export const identityAddresses = pgTable("identity_addresses", {
58
+ id: typeId("identity_addresses"),
59
+ entityType: text("entity_type").notNull(),
60
+ entityId: text("entity_id").notNull(),
61
+ label: addressLabelEnum("label").notNull().default("other"),
62
+ fullText: text("full_text"),
63
+ line1: text("line_1"),
64
+ line2: text("line_2"),
65
+ city: text("city"),
66
+ region: text("region"),
67
+ postalCode: text("postal_code"),
68
+ country: text("country"),
69
+ latitude: doublePrecision("latitude"),
70
+ longitude: doublePrecision("longitude"),
71
+ timezone: text("timezone"),
72
+ isPrimary: boolean("is_primary").notNull().default(false),
73
+ notes: text("notes"),
74
+ metadata: jsonb("metadata").$type(),
75
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
76
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
77
+ }, (table) => [
78
+ index("idx_identity_addresses_entity").on(table.entityType, table.entityId),
79
+ index("idx_identity_addresses_entity_primary_created").on(table.entityType, table.entityId, table.isPrimary, table.createdAt),
80
+ index("idx_identity_addresses_label").on(table.label),
81
+ ]);
82
+ export const identityNamedContacts = pgTable("identity_named_contacts", {
83
+ id: typeId("identity_named_contacts"),
84
+ entityType: text("entity_type").notNull(),
85
+ entityId: text("entity_id").notNull(),
86
+ role: namedContactRoleEnum("role").notNull().default("general"),
87
+ name: text("name").notNull(),
88
+ title: text("title"),
89
+ email: text("email"),
90
+ phone: text("phone"),
91
+ isPrimary: boolean("is_primary").notNull().default(false),
92
+ notes: text("notes"),
93
+ metadata: jsonb("metadata").$type(),
94
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
95
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
96
+ }, (table) => [
97
+ index("idx_identity_named_contacts_entity").on(table.entityType, table.entityId),
98
+ index("idx_identity_named_contacts_entity_primary_created").on(table.entityType, table.entityId, table.isPrimary, table.createdAt),
99
+ index("idx_identity_named_contacts_entity_role_primary_created").on(table.entityType, table.entityId, table.role, table.isPrimary, table.createdAt),
100
+ index("idx_identity_named_contacts_role").on(table.role),
101
+ index("idx_identity_named_contacts_primary").on(table.isPrimary),
102
+ ]);
@@ -0,0 +1,290 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { z } from "zod";
3
+ import type { addressListQuerySchema, contactPointListQuerySchema, insertAddressSchema, insertContactPointSchema, insertNamedContactSchema, namedContactListQuerySchema, updateAddressSchema, updateContactPointSchema, updateNamedContactSchema } from "./validation.js";
4
+ type ContactPointListQuery = z.infer<typeof contactPointListQuerySchema>;
5
+ type CreateContactPointInput = z.infer<typeof insertContactPointSchema>;
6
+ type UpdateContactPointInput = z.infer<typeof updateContactPointSchema>;
7
+ type AddressListQuery = z.infer<typeof addressListQuerySchema>;
8
+ type CreateAddressInput = z.infer<typeof insertAddressSchema>;
9
+ type UpdateAddressInput = z.infer<typeof updateAddressSchema>;
10
+ type NamedContactListQuery = z.infer<typeof namedContactListQuerySchema>;
11
+ type CreateNamedContactInput = z.infer<typeof insertNamedContactSchema>;
12
+ type UpdateNamedContactInput = z.infer<typeof updateNamedContactSchema>;
13
+ export declare const identityService: {
14
+ listContactPoints(db: PostgresJsDatabase, query: ContactPointListQuery): Promise<{
15
+ data: {
16
+ id: string;
17
+ entityType: string;
18
+ entityId: string;
19
+ kind: "other" | "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social";
20
+ label: string | null;
21
+ value: string;
22
+ normalizedValue: string | null;
23
+ isPrimary: boolean;
24
+ notes: string | null;
25
+ metadata: Record<string, unknown> | null;
26
+ createdAt: Date;
27
+ updatedAt: Date;
28
+ }[];
29
+ total: number;
30
+ limit: number;
31
+ offset: number;
32
+ }>;
33
+ listContactPointsForEntity(db: PostgresJsDatabase, entityType: string, entityId: string): Promise<{
34
+ id: string;
35
+ entityType: string;
36
+ entityId: string;
37
+ kind: "other" | "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social";
38
+ label: string | null;
39
+ value: string;
40
+ normalizedValue: string | null;
41
+ isPrimary: boolean;
42
+ notes: string | null;
43
+ metadata: Record<string, unknown> | null;
44
+ createdAt: Date;
45
+ updatedAt: Date;
46
+ }[]>;
47
+ getContactPointById(db: PostgresJsDatabase, id: string): Promise<{
48
+ id: string;
49
+ entityType: string;
50
+ entityId: string;
51
+ kind: "other" | "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social";
52
+ label: string | null;
53
+ value: string;
54
+ normalizedValue: string | null;
55
+ isPrimary: boolean;
56
+ notes: string | null;
57
+ metadata: Record<string, unknown> | null;
58
+ createdAt: Date;
59
+ updatedAt: Date;
60
+ } | null>;
61
+ createContactPoint(db: PostgresJsDatabase, data: CreateContactPointInput): Promise<{
62
+ value: string;
63
+ metadata: Record<string, unknown> | null;
64
+ id: string;
65
+ createdAt: Date;
66
+ notes: string | null;
67
+ updatedAt: Date;
68
+ entityType: string;
69
+ entityId: string;
70
+ kind: "other" | "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social";
71
+ label: string | null;
72
+ normalizedValue: string | null;
73
+ isPrimary: boolean;
74
+ } | null>;
75
+ updateContactPoint(db: PostgresJsDatabase, id: string, data: UpdateContactPointInput): Promise<{
76
+ id: string;
77
+ entityType: string;
78
+ entityId: string;
79
+ kind: "other" | "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social";
80
+ label: string | null;
81
+ value: string;
82
+ normalizedValue: string | null;
83
+ isPrimary: boolean;
84
+ notes: string | null;
85
+ metadata: Record<string, unknown> | null;
86
+ createdAt: Date;
87
+ updatedAt: Date;
88
+ } | null>;
89
+ deleteContactPoint(db: PostgresJsDatabase, id: string): Promise<{
90
+ id: string;
91
+ } | null>;
92
+ listAddresses(db: PostgresJsDatabase, query: AddressListQuery): Promise<{
93
+ data: {
94
+ id: string;
95
+ entityType: string;
96
+ entityId: string;
97
+ label: "service" | "primary" | "other" | "billing" | "shipping" | "mailing" | "meeting" | "legal";
98
+ fullText: string | null;
99
+ line1: string | null;
100
+ line2: string | null;
101
+ city: string | null;
102
+ region: string | null;
103
+ postalCode: string | null;
104
+ country: string | null;
105
+ latitude: number | null;
106
+ longitude: number | null;
107
+ timezone: string | null;
108
+ isPrimary: boolean;
109
+ notes: string | null;
110
+ metadata: Record<string, unknown> | null;
111
+ createdAt: Date;
112
+ updatedAt: Date;
113
+ }[];
114
+ total: number;
115
+ limit: number;
116
+ offset: number;
117
+ }>;
118
+ listAddressesForEntity(db: PostgresJsDatabase, entityType: string, entityId: string): Promise<{
119
+ id: string;
120
+ entityType: string;
121
+ entityId: string;
122
+ label: "service" | "primary" | "other" | "billing" | "shipping" | "mailing" | "meeting" | "legal";
123
+ fullText: string | null;
124
+ line1: string | null;
125
+ line2: string | null;
126
+ city: string | null;
127
+ region: string | null;
128
+ postalCode: string | null;
129
+ country: string | null;
130
+ latitude: number | null;
131
+ longitude: number | null;
132
+ timezone: string | null;
133
+ isPrimary: boolean;
134
+ notes: string | null;
135
+ metadata: Record<string, unknown> | null;
136
+ createdAt: Date;
137
+ updatedAt: Date;
138
+ }[]>;
139
+ getAddressById(db: PostgresJsDatabase, id: string): Promise<{
140
+ id: string;
141
+ entityType: string;
142
+ entityId: string;
143
+ label: "service" | "primary" | "other" | "billing" | "shipping" | "mailing" | "meeting" | "legal";
144
+ fullText: string | null;
145
+ line1: string | null;
146
+ line2: string | null;
147
+ city: string | null;
148
+ region: string | null;
149
+ postalCode: string | null;
150
+ country: string | null;
151
+ latitude: number | null;
152
+ longitude: number | null;
153
+ timezone: string | null;
154
+ isPrimary: boolean;
155
+ notes: string | null;
156
+ metadata: Record<string, unknown> | null;
157
+ createdAt: Date;
158
+ updatedAt: Date;
159
+ } | null>;
160
+ createAddress(db: PostgresJsDatabase, data: CreateAddressInput): Promise<{
161
+ metadata: Record<string, unknown> | null;
162
+ id: string;
163
+ createdAt: Date;
164
+ notes: string | null;
165
+ updatedAt: Date;
166
+ entityType: string;
167
+ entityId: string;
168
+ label: "service" | "primary" | "other" | "billing" | "shipping" | "mailing" | "meeting" | "legal";
169
+ isPrimary: boolean;
170
+ fullText: string | null;
171
+ line1: string | null;
172
+ line2: string | null;
173
+ city: string | null;
174
+ region: string | null;
175
+ postalCode: string | null;
176
+ country: string | null;
177
+ latitude: number | null;
178
+ longitude: number | null;
179
+ timezone: string | null;
180
+ } | null>;
181
+ updateAddress(db: PostgresJsDatabase, id: string, data: UpdateAddressInput): Promise<{
182
+ id: string;
183
+ entityType: string;
184
+ entityId: string;
185
+ label: "service" | "primary" | "other" | "billing" | "shipping" | "mailing" | "meeting" | "legal";
186
+ fullText: string | null;
187
+ line1: string | null;
188
+ line2: string | null;
189
+ city: string | null;
190
+ region: string | null;
191
+ postalCode: string | null;
192
+ country: string | null;
193
+ latitude: number | null;
194
+ longitude: number | null;
195
+ timezone: string | null;
196
+ isPrimary: boolean;
197
+ notes: string | null;
198
+ metadata: Record<string, unknown> | null;
199
+ createdAt: Date;
200
+ updatedAt: Date;
201
+ } | null>;
202
+ deleteAddress(db: PostgresJsDatabase, id: string): Promise<{
203
+ id: string;
204
+ } | null>;
205
+ listNamedContacts(db: PostgresJsDatabase, query: NamedContactListQuery): Promise<{
206
+ data: {
207
+ id: string;
208
+ entityType: string;
209
+ entityId: string;
210
+ role: "primary" | "other" | "operations" | "legal" | "general" | "reservations" | "front_desk" | "sales" | "emergency" | "accounting";
211
+ name: string;
212
+ title: string | null;
213
+ email: string | null;
214
+ phone: string | null;
215
+ isPrimary: boolean;
216
+ notes: string | null;
217
+ metadata: Record<string, unknown> | null;
218
+ createdAt: Date;
219
+ updatedAt: Date;
220
+ }[];
221
+ total: number;
222
+ limit: number;
223
+ offset: number;
224
+ }>;
225
+ listNamedContactsForEntity(db: PostgresJsDatabase, entityType: string, entityId: string): Promise<{
226
+ id: string;
227
+ entityType: string;
228
+ entityId: string;
229
+ role: "primary" | "other" | "operations" | "legal" | "general" | "reservations" | "front_desk" | "sales" | "emergency" | "accounting";
230
+ name: string;
231
+ title: string | null;
232
+ email: string | null;
233
+ phone: string | null;
234
+ isPrimary: boolean;
235
+ notes: string | null;
236
+ metadata: Record<string, unknown> | null;
237
+ createdAt: Date;
238
+ updatedAt: Date;
239
+ }[]>;
240
+ getNamedContactById(db: PostgresJsDatabase, id: string): Promise<{
241
+ id: string;
242
+ entityType: string;
243
+ entityId: string;
244
+ role: "primary" | "other" | "operations" | "legal" | "general" | "reservations" | "front_desk" | "sales" | "emergency" | "accounting";
245
+ name: string;
246
+ title: string | null;
247
+ email: string | null;
248
+ phone: string | null;
249
+ isPrimary: boolean;
250
+ notes: string | null;
251
+ metadata: Record<string, unknown> | null;
252
+ createdAt: Date;
253
+ updatedAt: Date;
254
+ } | null>;
255
+ createNamedContact(db: PostgresJsDatabase, data: CreateNamedContactInput): Promise<{
256
+ metadata: Record<string, unknown> | null;
257
+ id: string;
258
+ name: string;
259
+ createdAt: Date;
260
+ notes: string | null;
261
+ email: string | null;
262
+ updatedAt: Date;
263
+ phone: string | null;
264
+ entityType: string;
265
+ entityId: string;
266
+ isPrimary: boolean;
267
+ role: "primary" | "other" | "operations" | "legal" | "general" | "reservations" | "front_desk" | "sales" | "emergency" | "accounting";
268
+ title: string | null;
269
+ } | null>;
270
+ updateNamedContact(db: PostgresJsDatabase, id: string, data: UpdateNamedContactInput): Promise<{
271
+ id: string;
272
+ entityType: string;
273
+ entityId: string;
274
+ role: "primary" | "other" | "operations" | "legal" | "general" | "reservations" | "front_desk" | "sales" | "emergency" | "accounting";
275
+ name: string;
276
+ title: string | null;
277
+ email: string | null;
278
+ phone: string | null;
279
+ isPrimary: boolean;
280
+ notes: string | null;
281
+ metadata: Record<string, unknown> | null;
282
+ createdAt: Date;
283
+ updatedAt: Date;
284
+ } | null>;
285
+ deleteNamedContact(db: PostgresJsDatabase, id: string): Promise<{
286
+ id: string;
287
+ } | null>;
288
+ };
289
+ export {};
290
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAG5B,OAAO,KAAK,EACV,sBAAsB,EACtB,2BAA2B,EAC3B,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,2BAA2B,EAC3B,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,iBAAiB,CAAA;AAExB,KAAK,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AACxE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACvE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACvE,KAAK,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAC9D,KAAK,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAC7D,KAAK,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAC7D,KAAK,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AACxE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACvE,KAAK,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AAgEvE,eAAO,MAAM,eAAe;0BACE,kBAAkB,SAAS,qBAAqB;;;;;;;;;;;;;;;;;;;mCA+BvC,kBAAkB,cAAc,MAAM,YAAY,MAAM;;;;;;;;;;;;;;4BAa/D,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;2BAS/B,kBAAkB,QAAQ,uBAAuB;;;;;;;;;;;;;;2BAajD,kBAAkB,MAAM,MAAM,QAAQ,uBAAuB;;;;;;;;;;;;;;2BAyB7D,kBAAkB,MAAM,MAAM;;;sBAQnC,kBAAkB,SAAS,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;+BA8BlC,kBAAkB,cAAc,MAAM,YAAY,MAAM;;;;;;;;;;;;;;;;;;;;;uBAUhE,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;sBAS/B,kBAAkB,QAAQ,kBAAkB;;;;;;;;;;;;;;;;;;;;;sBAY5C,kBAAkB,MAAM,MAAM,QAAQ,kBAAkB;;;;;;;;;;;;;;;;;;;;;sBAuBxD,kBAAkB,MAAM,MAAM;;;0BAQ1B,kBAAkB,SAAS,qBAAqB;;;;;;;;;;;;;;;;;;;;mCAoCvC,kBAAkB,cAAc,MAAM,YAAY,MAAM;;;;;;;;;;;;;;;4BAa/D,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;2BAS/B,kBAAkB,QAAQ,uBAAuB;;;;;;;;;;;;;;;2BAYjD,kBAAkB,MAAM,MAAM,QAAQ,uBAAuB;;;;;;;;;;;;;;;2BAuB7D,kBAAkB,MAAM,MAAM;;;CAO5D,CAAA"}
@@ -0,0 +1,284 @@
1
+ import { and, desc, eq, ne, sql } from "drizzle-orm";
2
+ import { identityAddresses, identityContactPoints, identityNamedContacts } from "./schema.js";
3
+ async function paginate(rowsQuery, countQuery, limit, offset) {
4
+ const [data, countResult] = await Promise.all([rowsQuery, countQuery]);
5
+ return { data, total: countResult[0]?.count ?? 0, limit, offset };
6
+ }
7
+ async function ensurePrimaryContactPoint(db, params) {
8
+ const conditions = [
9
+ eq(identityContactPoints.entityType, params.entityType),
10
+ eq(identityContactPoints.entityId, params.entityId),
11
+ eq(identityContactPoints.kind, params.kind),
12
+ ];
13
+ if (params.id)
14
+ conditions.push(ne(identityContactPoints.id, params.id));
15
+ await db
16
+ .update(identityContactPoints)
17
+ .set({ isPrimary: false, updatedAt: new Date() })
18
+ .where(and(...conditions));
19
+ }
20
+ async function ensurePrimaryAddress(db, params) {
21
+ const conditions = [
22
+ eq(identityAddresses.entityType, params.entityType),
23
+ eq(identityAddresses.entityId, params.entityId),
24
+ ];
25
+ if (params.id)
26
+ conditions.push(ne(identityAddresses.id, params.id));
27
+ await db
28
+ .update(identityAddresses)
29
+ .set({ isPrimary: false, updatedAt: new Date() })
30
+ .where(and(...conditions));
31
+ }
32
+ async function ensurePrimaryNamedContact(db, params) {
33
+ const conditions = [
34
+ eq(identityNamedContacts.entityType, params.entityType),
35
+ eq(identityNamedContacts.entityId, params.entityId),
36
+ ];
37
+ if (params.id)
38
+ conditions.push(ne(identityNamedContacts.id, params.id));
39
+ await db
40
+ .update(identityNamedContacts)
41
+ .set({ isPrimary: false, updatedAt: new Date() })
42
+ .where(and(...conditions));
43
+ }
44
+ export const identityService = {
45
+ async listContactPoints(db, query) {
46
+ const conditions = [];
47
+ if (query.entityType)
48
+ conditions.push(eq(identityContactPoints.entityType, query.entityType));
49
+ if (query.entityId)
50
+ conditions.push(eq(identityContactPoints.entityId, query.entityId));
51
+ if (query.kind)
52
+ conditions.push(eq(identityContactPoints.kind, query.kind));
53
+ if (query.isPrimary !== undefined) {
54
+ conditions.push(eq(identityContactPoints.isPrimary, query.isPrimary));
55
+ }
56
+ if (query.search) {
57
+ const term = `%${query.search}%`;
58
+ conditions.push(
59
+ // agent-quality: raw-sql reviewed -- owner: identity; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
60
+ sql `(${identityContactPoints.value} ILIKE ${term} OR ${identityContactPoints.normalizedValue} ILIKE ${term})`);
61
+ }
62
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
63
+ return paginate(db
64
+ .select()
65
+ .from(identityContactPoints)
66
+ .where(where)
67
+ .limit(query.limit)
68
+ .offset(query.offset)
69
+ .orderBy(desc(identityContactPoints.isPrimary), identityContactPoints.createdAt), db.select({ count: sql `count(*)::int` }).from(identityContactPoints).where(where), query.limit, query.offset);
70
+ },
71
+ async listContactPointsForEntity(db, entityType, entityId) {
72
+ return db
73
+ .select()
74
+ .from(identityContactPoints)
75
+ .where(and(eq(identityContactPoints.entityType, entityType), eq(identityContactPoints.entityId, entityId)))
76
+ .orderBy(desc(identityContactPoints.isPrimary), identityContactPoints.createdAt);
77
+ },
78
+ async getContactPointById(db, id) {
79
+ const [row] = await db
80
+ .select()
81
+ .from(identityContactPoints)
82
+ .where(eq(identityContactPoints.id, id))
83
+ .limit(1);
84
+ return row ?? null;
85
+ },
86
+ async createContactPoint(db, data) {
87
+ if (data.isPrimary) {
88
+ await ensurePrimaryContactPoint(db, {
89
+ entityType: data.entityType,
90
+ entityId: data.entityId,
91
+ kind: data.kind,
92
+ });
93
+ }
94
+ const [row] = await db.insert(identityContactPoints).values(data).returning();
95
+ return row ?? null;
96
+ },
97
+ async updateContactPoint(db, id, data) {
98
+ const existing = await this.getContactPointById(db, id);
99
+ if (!existing)
100
+ return null;
101
+ const nextEntityType = data.entityType ?? existing.entityType;
102
+ const nextEntityId = data.entityId ?? existing.entityId;
103
+ const nextKind = data.kind ?? existing.kind;
104
+ if (data.isPrimary) {
105
+ await ensurePrimaryContactPoint(db, {
106
+ id,
107
+ entityType: nextEntityType,
108
+ entityId: nextEntityId,
109
+ kind: nextKind,
110
+ });
111
+ }
112
+ const [row] = await db
113
+ .update(identityContactPoints)
114
+ .set({ ...data, updatedAt: new Date() })
115
+ .where(eq(identityContactPoints.id, id))
116
+ .returning();
117
+ return row ?? null;
118
+ },
119
+ async deleteContactPoint(db, id) {
120
+ const [row] = await db
121
+ .delete(identityContactPoints)
122
+ .where(eq(identityContactPoints.id, id))
123
+ .returning({ id: identityContactPoints.id });
124
+ return row ?? null;
125
+ },
126
+ async listAddresses(db, query) {
127
+ const conditions = [];
128
+ if (query.entityType)
129
+ conditions.push(eq(identityAddresses.entityType, query.entityType));
130
+ if (query.entityId)
131
+ conditions.push(eq(identityAddresses.entityId, query.entityId));
132
+ if (query.label)
133
+ conditions.push(eq(identityAddresses.label, query.label));
134
+ if (query.isPrimary !== undefined)
135
+ conditions.push(eq(identityAddresses.isPrimary, query.isPrimary));
136
+ if (query.search) {
137
+ const term = `%${query.search}%`;
138
+ conditions.push(
139
+ // agent-quality: raw-sql reviewed -- owner: identity; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
140
+ sql `(${identityAddresses.fullText} ILIKE ${term} OR ${identityAddresses.line1} ILIKE ${term} OR ${identityAddresses.city} ILIKE ${term})`);
141
+ }
142
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
143
+ return paginate(db
144
+ .select()
145
+ .from(identityAddresses)
146
+ .where(where)
147
+ .limit(query.limit)
148
+ .offset(query.offset)
149
+ .orderBy(desc(identityAddresses.isPrimary), identityAddresses.createdAt), db.select({ count: sql `count(*)::int` }).from(identityAddresses).where(where), query.limit, query.offset);
150
+ },
151
+ async listAddressesForEntity(db, entityType, entityId) {
152
+ return db
153
+ .select()
154
+ .from(identityAddresses)
155
+ .where(and(eq(identityAddresses.entityType, entityType), eq(identityAddresses.entityId, entityId)))
156
+ .orderBy(desc(identityAddresses.isPrimary), identityAddresses.createdAt);
157
+ },
158
+ async getAddressById(db, id) {
159
+ const [row] = await db
160
+ .select()
161
+ .from(identityAddresses)
162
+ .where(eq(identityAddresses.id, id))
163
+ .limit(1);
164
+ return row ?? null;
165
+ },
166
+ async createAddress(db, data) {
167
+ if (data.isPrimary) {
168
+ await ensurePrimaryAddress(db, {
169
+ entityType: data.entityType,
170
+ entityId: data.entityId,
171
+ });
172
+ }
173
+ const [row] = await db.insert(identityAddresses).values(data).returning();
174
+ return row ?? null;
175
+ },
176
+ async updateAddress(db, id, data) {
177
+ const existing = await this.getAddressById(db, id);
178
+ if (!existing)
179
+ return null;
180
+ const nextEntityType = data.entityType ?? existing.entityType;
181
+ const nextEntityId = data.entityId ?? existing.entityId;
182
+ if (data.isPrimary) {
183
+ await ensurePrimaryAddress(db, {
184
+ id,
185
+ entityType: nextEntityType,
186
+ entityId: nextEntityId,
187
+ });
188
+ }
189
+ const [row] = await db
190
+ .update(identityAddresses)
191
+ .set({ ...data, updatedAt: new Date() })
192
+ .where(eq(identityAddresses.id, id))
193
+ .returning();
194
+ return row ?? null;
195
+ },
196
+ async deleteAddress(db, id) {
197
+ const [row] = await db
198
+ .delete(identityAddresses)
199
+ .where(eq(identityAddresses.id, id))
200
+ .returning({ id: identityAddresses.id });
201
+ return row ?? null;
202
+ },
203
+ async listNamedContacts(db, query) {
204
+ const conditions = [];
205
+ if (query.entityType)
206
+ conditions.push(eq(identityNamedContacts.entityType, query.entityType));
207
+ if (query.entityId)
208
+ conditions.push(eq(identityNamedContacts.entityId, query.entityId));
209
+ if (query.role)
210
+ conditions.push(eq(identityNamedContacts.role, query.role));
211
+ if (query.isPrimary !== undefined) {
212
+ conditions.push(eq(identityNamedContacts.isPrimary, query.isPrimary));
213
+ }
214
+ if (query.search) {
215
+ const term = `%${query.search}%`;
216
+ conditions.push(sql `(
217
+ ${identityNamedContacts.name} ILIKE ${term}
218
+ OR ${identityNamedContacts.title} ILIKE ${term}
219
+ OR ${identityNamedContacts.email} ILIKE ${term}
220
+ OR ${identityNamedContacts.phone} ILIKE ${term}
221
+ )`);
222
+ }
223
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
224
+ return paginate(db
225
+ .select()
226
+ .from(identityNamedContacts)
227
+ .where(where)
228
+ .limit(query.limit)
229
+ .offset(query.offset)
230
+ .orderBy(desc(identityNamedContacts.isPrimary), identityNamedContacts.createdAt), db.select({ count: sql `count(*)::int` }).from(identityNamedContacts).where(where), query.limit, query.offset);
231
+ },
232
+ async listNamedContactsForEntity(db, entityType, entityId) {
233
+ return db
234
+ .select()
235
+ .from(identityNamedContacts)
236
+ .where(and(eq(identityNamedContacts.entityType, entityType), eq(identityNamedContacts.entityId, entityId)))
237
+ .orderBy(desc(identityNamedContacts.isPrimary), identityNamedContacts.createdAt);
238
+ },
239
+ async getNamedContactById(db, id) {
240
+ const [row] = await db
241
+ .select()
242
+ .from(identityNamedContacts)
243
+ .where(eq(identityNamedContacts.id, id))
244
+ .limit(1);
245
+ return row ?? null;
246
+ },
247
+ async createNamedContact(db, data) {
248
+ if (data.isPrimary) {
249
+ await ensurePrimaryNamedContact(db, {
250
+ entityType: data.entityType,
251
+ entityId: data.entityId,
252
+ });
253
+ }
254
+ const [row] = await db.insert(identityNamedContacts).values(data).returning();
255
+ return row ?? null;
256
+ },
257
+ async updateNamedContact(db, id, data) {
258
+ const existing = await this.getNamedContactById(db, id);
259
+ if (!existing)
260
+ return null;
261
+ const nextEntityType = data.entityType ?? existing.entityType;
262
+ const nextEntityId = data.entityId ?? existing.entityId;
263
+ if (data.isPrimary) {
264
+ await ensurePrimaryNamedContact(db, {
265
+ id,
266
+ entityType: nextEntityType,
267
+ entityId: nextEntityId,
268
+ });
269
+ }
270
+ const [row] = await db
271
+ .update(identityNamedContacts)
272
+ .set({ ...data, updatedAt: new Date() })
273
+ .where(eq(identityNamedContacts.id, id))
274
+ .returning();
275
+ return row ?? null;
276
+ },
277
+ async deleteNamedContact(db, id) {
278
+ const [row] = await db
279
+ .delete(identityNamedContacts)
280
+ .where(eq(identityNamedContacts.id, id))
281
+ .returning({ id: identityNamedContacts.id });
282
+ return row ?? null;
283
+ },
284
+ };