@voyantjs/identity 0.1.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.
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BjC,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2B7B,CAAA;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsBjC,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,97 @@
1
+ import { typeId } from "@voyantjs/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_kind").on(table.kind),
52
+ index("idx_identity_contact_points_normalized").on(table.normalizedValue),
53
+ uniqueIndex("uidx_identity_contact_points_entity_kind_value").on(table.entityType, table.entityId, table.kind, table.value),
54
+ ]);
55
+ export const identityAddresses = pgTable("identity_addresses", {
56
+ id: typeId("identity_addresses"),
57
+ entityType: text("entity_type").notNull(),
58
+ entityId: text("entity_id").notNull(),
59
+ label: addressLabelEnum("label").notNull().default("other"),
60
+ fullText: text("full_text"),
61
+ line1: text("line_1"),
62
+ line2: text("line_2"),
63
+ city: text("city"),
64
+ region: text("region"),
65
+ postalCode: text("postal_code"),
66
+ country: text("country"),
67
+ latitude: doublePrecision("latitude"),
68
+ longitude: doublePrecision("longitude"),
69
+ timezone: text("timezone"),
70
+ isPrimary: boolean("is_primary").notNull().default(false),
71
+ notes: text("notes"),
72
+ metadata: jsonb("metadata").$type(),
73
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
74
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
75
+ }, (table) => [
76
+ index("idx_identity_addresses_entity").on(table.entityType, table.entityId),
77
+ index("idx_identity_addresses_label").on(table.label),
78
+ ]);
79
+ export const identityNamedContacts = pgTable("identity_named_contacts", {
80
+ id: typeId("identity_named_contacts"),
81
+ entityType: text("entity_type").notNull(),
82
+ entityId: text("entity_id").notNull(),
83
+ role: namedContactRoleEnum("role").notNull().default("general"),
84
+ name: text("name").notNull(),
85
+ title: text("title"),
86
+ email: text("email"),
87
+ phone: text("phone"),
88
+ isPrimary: boolean("is_primary").notNull().default(false),
89
+ notes: text("notes"),
90
+ metadata: jsonb("metadata").$type(),
91
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
92
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
93
+ }, (table) => [
94
+ index("idx_identity_named_contacts_entity").on(table.entityType, table.entityId),
95
+ index("idx_identity_named_contacts_role").on(table.role),
96
+ index("idx_identity_named_contacts_primary").on(table.isPrimary),
97
+ ]);
@@ -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: "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social" | "other";
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: "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social" | "other";
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: "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social" | "other";
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
+ id: string;
64
+ entityType: string;
65
+ entityId: string;
66
+ kind: "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social" | "other";
67
+ label: string | null;
68
+ normalizedValue: string | null;
69
+ isPrimary: boolean;
70
+ notes: string | null;
71
+ metadata: Record<string, unknown> | null;
72
+ createdAt: Date;
73
+ updatedAt: Date;
74
+ } | null>;
75
+ updateContactPoint(db: PostgresJsDatabase, id: string, data: UpdateContactPointInput): Promise<{
76
+ id: string;
77
+ entityType: string;
78
+ entityId: string;
79
+ kind: "email" | "phone" | "mobile" | "whatsapp" | "website" | "sms" | "fax" | "social" | "other";
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: "other" | "primary" | "billing" | "shipping" | "mailing" | "meeting" | "service" | "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: "other" | "primary" | "billing" | "shipping" | "mailing" | "meeting" | "service" | "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: "other" | "primary" | "billing" | "shipping" | "mailing" | "meeting" | "service" | "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
+ id: string;
162
+ entityType: string;
163
+ entityId: string;
164
+ label: "other" | "primary" | "billing" | "shipping" | "mailing" | "meeting" | "service" | "legal";
165
+ isPrimary: boolean;
166
+ notes: string | null;
167
+ metadata: Record<string, unknown> | null;
168
+ createdAt: Date;
169
+ updatedAt: Date;
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: "other" | "primary" | "billing" | "shipping" | "mailing" | "meeting" | "service" | "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: "other" | "primary" | "legal" | "general" | "reservations" | "operations" | "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: "other" | "primary" | "legal" | "general" | "reservations" | "operations" | "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: "other" | "primary" | "legal" | "general" | "reservations" | "operations" | "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
+ id: string;
257
+ name: string;
258
+ email: string | null;
259
+ phone: string | null;
260
+ entityType: string;
261
+ entityId: string;
262
+ isPrimary: boolean;
263
+ notes: string | null;
264
+ metadata: Record<string, unknown> | null;
265
+ createdAt: Date;
266
+ updatedAt: Date;
267
+ role: "other" | "primary" | "legal" | "general" | "reservations" | "operations" | "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: "other" | "primary" | "legal" | "general" | "reservations" | "operations" | "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;;;;;;;;;;;;;;;;;;;mCA8BvC,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;;;;;;;;;;;;;;;;;;;;;;;;;;+BA6BlC,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,280 @@
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(sql `(${identityContactPoints.value} ILIKE ${term} OR ${identityContactPoints.normalizedValue} ILIKE ${term})`);
59
+ }
60
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
61
+ return paginate(db
62
+ .select()
63
+ .from(identityContactPoints)
64
+ .where(where)
65
+ .limit(query.limit)
66
+ .offset(query.offset)
67
+ .orderBy(desc(identityContactPoints.isPrimary), identityContactPoints.createdAt), db.select({ count: sql `count(*)::int` }).from(identityContactPoints).where(where), query.limit, query.offset);
68
+ },
69
+ async listContactPointsForEntity(db, entityType, entityId) {
70
+ return db
71
+ .select()
72
+ .from(identityContactPoints)
73
+ .where(and(eq(identityContactPoints.entityType, entityType), eq(identityContactPoints.entityId, entityId)))
74
+ .orderBy(desc(identityContactPoints.isPrimary), identityContactPoints.createdAt);
75
+ },
76
+ async getContactPointById(db, id) {
77
+ const [row] = await db
78
+ .select()
79
+ .from(identityContactPoints)
80
+ .where(eq(identityContactPoints.id, id))
81
+ .limit(1);
82
+ return row ?? null;
83
+ },
84
+ async createContactPoint(db, data) {
85
+ if (data.isPrimary) {
86
+ await ensurePrimaryContactPoint(db, {
87
+ entityType: data.entityType,
88
+ entityId: data.entityId,
89
+ kind: data.kind,
90
+ });
91
+ }
92
+ const [row] = await db.insert(identityContactPoints).values(data).returning();
93
+ return row ?? null;
94
+ },
95
+ async updateContactPoint(db, id, data) {
96
+ const existing = await this.getContactPointById(db, id);
97
+ if (!existing)
98
+ return null;
99
+ const nextEntityType = data.entityType ?? existing.entityType;
100
+ const nextEntityId = data.entityId ?? existing.entityId;
101
+ const nextKind = data.kind ?? existing.kind;
102
+ if (data.isPrimary) {
103
+ await ensurePrimaryContactPoint(db, {
104
+ id,
105
+ entityType: nextEntityType,
106
+ entityId: nextEntityId,
107
+ kind: nextKind,
108
+ });
109
+ }
110
+ const [row] = await db
111
+ .update(identityContactPoints)
112
+ .set({ ...data, updatedAt: new Date() })
113
+ .where(eq(identityContactPoints.id, id))
114
+ .returning();
115
+ return row ?? null;
116
+ },
117
+ async deleteContactPoint(db, id) {
118
+ const [row] = await db
119
+ .delete(identityContactPoints)
120
+ .where(eq(identityContactPoints.id, id))
121
+ .returning({ id: identityContactPoints.id });
122
+ return row ?? null;
123
+ },
124
+ async listAddresses(db, query) {
125
+ const conditions = [];
126
+ if (query.entityType)
127
+ conditions.push(eq(identityAddresses.entityType, query.entityType));
128
+ if (query.entityId)
129
+ conditions.push(eq(identityAddresses.entityId, query.entityId));
130
+ if (query.label)
131
+ conditions.push(eq(identityAddresses.label, query.label));
132
+ if (query.isPrimary !== undefined)
133
+ conditions.push(eq(identityAddresses.isPrimary, query.isPrimary));
134
+ if (query.search) {
135
+ const term = `%${query.search}%`;
136
+ conditions.push(sql `(${identityAddresses.fullText} ILIKE ${term} OR ${identityAddresses.line1} ILIKE ${term} OR ${identityAddresses.city} ILIKE ${term})`);
137
+ }
138
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
139
+ return paginate(db
140
+ .select()
141
+ .from(identityAddresses)
142
+ .where(where)
143
+ .limit(query.limit)
144
+ .offset(query.offset)
145
+ .orderBy(desc(identityAddresses.isPrimary), identityAddresses.createdAt), db.select({ count: sql `count(*)::int` }).from(identityAddresses).where(where), query.limit, query.offset);
146
+ },
147
+ async listAddressesForEntity(db, entityType, entityId) {
148
+ return db
149
+ .select()
150
+ .from(identityAddresses)
151
+ .where(and(eq(identityAddresses.entityType, entityType), eq(identityAddresses.entityId, entityId)))
152
+ .orderBy(desc(identityAddresses.isPrimary), identityAddresses.createdAt);
153
+ },
154
+ async getAddressById(db, id) {
155
+ const [row] = await db
156
+ .select()
157
+ .from(identityAddresses)
158
+ .where(eq(identityAddresses.id, id))
159
+ .limit(1);
160
+ return row ?? null;
161
+ },
162
+ async createAddress(db, data) {
163
+ if (data.isPrimary) {
164
+ await ensurePrimaryAddress(db, {
165
+ entityType: data.entityType,
166
+ entityId: data.entityId,
167
+ });
168
+ }
169
+ const [row] = await db.insert(identityAddresses).values(data).returning();
170
+ return row ?? null;
171
+ },
172
+ async updateAddress(db, id, data) {
173
+ const existing = await this.getAddressById(db, id);
174
+ if (!existing)
175
+ return null;
176
+ const nextEntityType = data.entityType ?? existing.entityType;
177
+ const nextEntityId = data.entityId ?? existing.entityId;
178
+ if (data.isPrimary) {
179
+ await ensurePrimaryAddress(db, {
180
+ id,
181
+ entityType: nextEntityType,
182
+ entityId: nextEntityId,
183
+ });
184
+ }
185
+ const [row] = await db
186
+ .update(identityAddresses)
187
+ .set({ ...data, updatedAt: new Date() })
188
+ .where(eq(identityAddresses.id, id))
189
+ .returning();
190
+ return row ?? null;
191
+ },
192
+ async deleteAddress(db, id) {
193
+ const [row] = await db
194
+ .delete(identityAddresses)
195
+ .where(eq(identityAddresses.id, id))
196
+ .returning({ id: identityAddresses.id });
197
+ return row ?? null;
198
+ },
199
+ async listNamedContacts(db, query) {
200
+ const conditions = [];
201
+ if (query.entityType)
202
+ conditions.push(eq(identityNamedContacts.entityType, query.entityType));
203
+ if (query.entityId)
204
+ conditions.push(eq(identityNamedContacts.entityId, query.entityId));
205
+ if (query.role)
206
+ conditions.push(eq(identityNamedContacts.role, query.role));
207
+ if (query.isPrimary !== undefined) {
208
+ conditions.push(eq(identityNamedContacts.isPrimary, query.isPrimary));
209
+ }
210
+ if (query.search) {
211
+ const term = `%${query.search}%`;
212
+ conditions.push(sql `(
213
+ ${identityNamedContacts.name} ILIKE ${term}
214
+ OR ${identityNamedContacts.title} ILIKE ${term}
215
+ OR ${identityNamedContacts.email} ILIKE ${term}
216
+ OR ${identityNamedContacts.phone} ILIKE ${term}
217
+ )`);
218
+ }
219
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
220
+ return paginate(db
221
+ .select()
222
+ .from(identityNamedContacts)
223
+ .where(where)
224
+ .limit(query.limit)
225
+ .offset(query.offset)
226
+ .orderBy(desc(identityNamedContacts.isPrimary), identityNamedContacts.createdAt), db.select({ count: sql `count(*)::int` }).from(identityNamedContacts).where(where), query.limit, query.offset);
227
+ },
228
+ async listNamedContactsForEntity(db, entityType, entityId) {
229
+ return db
230
+ .select()
231
+ .from(identityNamedContacts)
232
+ .where(and(eq(identityNamedContacts.entityType, entityType), eq(identityNamedContacts.entityId, entityId)))
233
+ .orderBy(desc(identityNamedContacts.isPrimary), identityNamedContacts.createdAt);
234
+ },
235
+ async getNamedContactById(db, id) {
236
+ const [row] = await db
237
+ .select()
238
+ .from(identityNamedContacts)
239
+ .where(eq(identityNamedContacts.id, id))
240
+ .limit(1);
241
+ return row ?? null;
242
+ },
243
+ async createNamedContact(db, data) {
244
+ if (data.isPrimary) {
245
+ await ensurePrimaryNamedContact(db, {
246
+ entityType: data.entityType,
247
+ entityId: data.entityId,
248
+ });
249
+ }
250
+ const [row] = await db.insert(identityNamedContacts).values(data).returning();
251
+ return row ?? null;
252
+ },
253
+ async updateNamedContact(db, id, data) {
254
+ const existing = await this.getNamedContactById(db, id);
255
+ if (!existing)
256
+ return null;
257
+ const nextEntityType = data.entityType ?? existing.entityType;
258
+ const nextEntityId = data.entityId ?? existing.entityId;
259
+ if (data.isPrimary) {
260
+ await ensurePrimaryNamedContact(db, {
261
+ id,
262
+ entityType: nextEntityType,
263
+ entityId: nextEntityId,
264
+ });
265
+ }
266
+ const [row] = await db
267
+ .update(identityNamedContacts)
268
+ .set({ ...data, updatedAt: new Date() })
269
+ .where(eq(identityNamedContacts.id, id))
270
+ .returning();
271
+ return row ?? null;
272
+ },
273
+ async deleteNamedContact(db, id) {
274
+ const [row] = await db
275
+ .delete(identityNamedContacts)
276
+ .where(eq(identityNamedContacts.id, id))
277
+ .returning({ id: identityNamedContacts.id });
278
+ return row ?? null;
279
+ },
280
+ };