@proteos/sdk 0.51.1 → 0.53.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/{chunk-IRFFXJMB.js → chunk-HEUXXELG.js} +311 -17
- package/dist/chunk-HEUXXELG.js.map +1 -0
- package/dist/{chunk-RKFXHDVU.cjs → chunk-SWCPI6SL.cjs} +326 -16
- package/dist/chunk-SWCPI6SL.cjs.map +1 -0
- package/dist/index.cjs +209 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +213 -12
- package/dist/index.d.ts +213 -12
- package/dist/index.js +49 -2
- package/dist/index.js.map +1 -1
- package/dist/meta/index.cjs +123 -59
- package/dist/meta/index.d.cts +1 -1
- package/dist/meta/index.d.ts +1 -1
- package/dist/meta/index.js +1 -1
- package/dist/{types-C9flkAj-.d.cts → types-D5P6yk1L.d.cts} +1288 -69
- package/dist/{types-C9flkAj-.d.ts → types-D5P6yk1L.d.ts} +1288 -69
- package/package.json +3 -3
- package/src/conversation/index.ts +39 -0
- package/src/conversation/types.ts +108 -4
- package/src/data/index.ts +15 -2
- package/src/data/records.ts +63 -0
- package/src/data/types.ts +52 -0
- package/src/index.ts +60 -1
- package/src/meta/entity-extensions.ts +98 -0
- package/src/meta/index.ts +84 -1
- package/src/meta/layout/common-props.ts +8 -0
- package/src/meta/layout/control-registry.json +10 -2
- package/src/meta/layout/elements.ts +15 -10
- package/src/meta/layout/index.ts +1 -0
- package/src/meta/list-extensions.ts +98 -0
- package/src/meta/menu-configuration-extensions.ts +132 -0
- package/src/meta/page-extensions.ts +98 -0
- package/src/meta/types.ts +470 -0
- package/dist/chunk-IRFFXJMB.js.map +0 -1
- package/dist/chunk-RKFXHDVU.cjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteos/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "TypeScript SDK for the Proteos platform",
|
|
6
6
|
"repository": {
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"tsup": "^8.3.0",
|
|
67
67
|
"typescript": "^5.7.0",
|
|
68
68
|
"vitest": "^3.0.0",
|
|
69
|
-
"@proteos/
|
|
70
|
-
"@proteos/
|
|
69
|
+
"@proteos/tsconfig": "0.0.0",
|
|
70
|
+
"@proteos/biome-config": "0.0.0"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"build": "tsup",
|
|
@@ -33,6 +33,7 @@ import type {
|
|
|
33
33
|
CreateGlossaryTermRequest,
|
|
34
34
|
CreateSendingRuleRequest,
|
|
35
35
|
CreateToneProfileSetupRequest,
|
|
36
|
+
CreateTranscriptionFromTextRequest,
|
|
36
37
|
CreateTranscriptionRequest,
|
|
37
38
|
DeleteConnectionQuery,
|
|
38
39
|
DispatchMeetingBotRequest,
|
|
@@ -106,6 +107,7 @@ import type {
|
|
|
106
107
|
UpdatePhoneNumberRequest,
|
|
107
108
|
UpdateSendingRuleRequest,
|
|
108
109
|
UpdateTranscriptionRequest,
|
|
110
|
+
UpsertToneProfileRequest,
|
|
109
111
|
} from './types.js'
|
|
110
112
|
import { type VoiceService, VoiceServiceImpl } from './voice.js'
|
|
111
113
|
|
|
@@ -1121,6 +1123,20 @@ export interface ToneProfileService {
|
|
|
1121
1123
|
getProfile(id: string): Promise<ToneProfile>
|
|
1122
1124
|
/** The single most-specific profile for a drafting context; 404 when none. */
|
|
1123
1125
|
resolve(query: ResolveToneProfileQuery): Promise<ToneProfile>
|
|
1126
|
+
/**
|
|
1127
|
+
* Writes a HAND-AUTHORED profile, addressed by its scope tuple rather than an
|
|
1128
|
+
* id — so re-sending the same scope updates the row it created. The row lands
|
|
1129
|
+
* `source: 'manual'`, which locks that scope against synthesis forever after;
|
|
1130
|
+
* it also takes the scope over from a `model` row already there. Needs no
|
|
1131
|
+
* setup: a setup is the opt-in to AI synthesis, and writing a voice by hand is
|
|
1132
|
+
* the opposite of asking for one.
|
|
1133
|
+
*/
|
|
1134
|
+
upsertProfile(request: UpsertToneProfileRequest): Promise<ToneProfile>
|
|
1135
|
+
/**
|
|
1136
|
+
* Removes one profile row, manual or model. A deleted model row comes back on
|
|
1137
|
+
* the next run; a deleted manual row hands its scope back to synthesis.
|
|
1138
|
+
*/
|
|
1139
|
+
deleteProfile(id: string): Promise<void>
|
|
1124
1140
|
}
|
|
1125
1141
|
|
|
1126
1142
|
class ToneProfileServiceImpl implements ToneProfileService {
|
|
@@ -1170,6 +1186,17 @@ class ToneProfileServiceImpl implements ToneProfileService {
|
|
|
1170
1186
|
query,
|
|
1171
1187
|
)
|
|
1172
1188
|
}
|
|
1189
|
+
|
|
1190
|
+
upsertProfile(request: UpsertToneProfileRequest): Promise<ToneProfile> {
|
|
1191
|
+
return this.client.request('PUT', `${CONVERSATION_BASE_PATH}/tone-profiles`, request)
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
async deleteProfile(id: string): Promise<void> {
|
|
1195
|
+
await this.client.request(
|
|
1196
|
+
'DELETE',
|
|
1197
|
+
`${CONVERSATION_BASE_PATH}/tone-profiles/${encodeURIComponent(id)}`,
|
|
1198
|
+
)
|
|
1199
|
+
}
|
|
1173
1200
|
}
|
|
1174
1201
|
|
|
1175
1202
|
/** Batch transcription of stored audio files + materialization. */
|
|
@@ -1180,6 +1207,14 @@ export interface TranscriptionService {
|
|
|
1180
1207
|
* status is `completed` (or `failed`), then `materialize`.
|
|
1181
1208
|
*/
|
|
1182
1209
|
createFromFile(request: CreateTranscriptionRequest): Promise<Transcription>
|
|
1210
|
+
/**
|
|
1211
|
+
* Lands an ALREADY WRITTEN transcript. Synchronous — no provider runs, and
|
|
1212
|
+
* the entity comes back `completed`, ready to `materialize` straight away.
|
|
1213
|
+
* The door for authoring a transcript (and so a conversation) from text with
|
|
1214
|
+
* nothing to upload. Same endpoint as `createFromFile`; the shape of the body
|
|
1215
|
+
* chooses the mode, and exactly one of the two is allowed.
|
|
1216
|
+
*/
|
|
1217
|
+
createFromText(request: CreateTranscriptionFromTextRequest): Promise<Transcription>
|
|
1183
1218
|
list(query?: ListTranscriptionsQuery): Promise<ListResponse<Transcription>>
|
|
1184
1219
|
get(id: string): Promise<Transcription>
|
|
1185
1220
|
/**
|
|
@@ -1204,6 +1239,10 @@ class TranscriptionServiceImpl implements TranscriptionService {
|
|
|
1204
1239
|
return this.client.request('POST', `${CONVERSATION_BASE_PATH}/transcriptions`, request)
|
|
1205
1240
|
}
|
|
1206
1241
|
|
|
1242
|
+
createFromText(request: CreateTranscriptionFromTextRequest): Promise<Transcription> {
|
|
1243
|
+
return this.client.request('POST', `${CONVERSATION_BASE_PATH}/transcriptions`, request)
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1207
1246
|
list(query: ListTranscriptionsQuery = {}): Promise<ListResponse<Transcription>> {
|
|
1208
1247
|
return this.client.requestWithQuery('GET', `${CONVERSATION_BASE_PATH}/transcriptions`, query)
|
|
1209
1248
|
}
|
|
@@ -914,6 +914,14 @@ export type ToneProfileSetupStatus = 'empty' | 'processing' | 'ready'
|
|
|
914
914
|
/** A tone profile row's tier — derived, most specific wins at read time. */
|
|
915
915
|
export type ToneProfileScope = 'user' | 'channel' | 'group' | 'contact'
|
|
916
916
|
|
|
917
|
+
/**
|
|
918
|
+
* Who wrote a tone profile, and therefore who may change it. `manual` rows are
|
|
919
|
+
* hand-authored and authoritative: synthesis never overwrites or prunes them,
|
|
920
|
+
* and deleting the user's setup (the AI opt-in) leaves them standing. `model`
|
|
921
|
+
* rows belong to the synthesis sweep, which owns them outright.
|
|
922
|
+
*/
|
|
923
|
+
export type ToneProfileSource = 'manual' | 'model'
|
|
924
|
+
|
|
917
925
|
/**
|
|
918
926
|
* The per-user opt-in for tone-of-voice synthesis: only set-up users are
|
|
919
927
|
* swept. Also carries the whole-user synthesis claim (status/started_at).
|
|
@@ -933,7 +941,8 @@ export interface ToneProfileSetup {
|
|
|
933
941
|
}
|
|
934
942
|
|
|
935
943
|
/**
|
|
936
|
-
* One
|
|
944
|
+
* One tone-of-voice instruction row — synthesized or hand-authored (`source`).
|
|
945
|
+
* Rows form a specificity
|
|
937
946
|
* hierarchy — user aggregate (the constant voice), per-channel base, per
|
|
938
947
|
* contact-group, per individual contact — and every row is SELF-CONTAINED: a
|
|
939
948
|
* drafting consumer injects exactly one row's `instructions` verbatim (use
|
|
@@ -951,6 +960,8 @@ export interface ToneProfile {
|
|
|
951
960
|
/** Scopes the row to one individual contact within the group. */
|
|
952
961
|
contact_id?: string
|
|
953
962
|
scope: ToneProfileScope
|
|
963
|
+
/** Who owns this row — see ToneProfileSource. */
|
|
964
|
+
source: ToneProfileSource
|
|
954
965
|
/** COMPLETE markdown instruction set, served verbatim to a drafting model. */
|
|
955
966
|
instructions: string
|
|
956
967
|
/** Short delta vs the tier above — what a human scans; empty on root tiers. */
|
|
@@ -975,6 +986,30 @@ export interface ListToneProfilesQuery extends PaginationQuery {
|
|
|
975
986
|
owned_by_id?: string
|
|
976
987
|
channel?: Channel
|
|
977
988
|
scope?: ToneProfileScope
|
|
989
|
+
/** Narrow to hand-authored (`manual`) or synthesized (`model`) rows. */
|
|
990
|
+
source?: ToneProfileSource
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Writes ONE hand-authored tone profile. The identity is the scope tuple, not
|
|
995
|
+
* an id — re-sending the same scope updates the row it created. The row lands
|
|
996
|
+
* `source: 'manual'`, which locks that scope against synthesis; it also takes
|
|
997
|
+
* the scope over from a `model` row already sitting there.
|
|
998
|
+
*
|
|
999
|
+
* Scope grammar (mirrors the resolve precedence): omit `channel` for the
|
|
1000
|
+
* cross-channel base voice; `contact_group_key` needs a `channel`;
|
|
1001
|
+
* `contact_id` needs both, because resolution reaches a contact row only
|
|
1002
|
+
* through its group.
|
|
1003
|
+
*/
|
|
1004
|
+
export interface UpsertToneProfileRequest {
|
|
1005
|
+
owned_by_id: string
|
|
1006
|
+
channel?: Channel
|
|
1007
|
+
contact_group_key?: string
|
|
1008
|
+
contact_id?: string
|
|
1009
|
+
/** COMPLETE markdown style guide for this tier; never a delta. */
|
|
1010
|
+
instructions: string
|
|
1011
|
+
/** Optional human-facing note on how this tier differs from the one above. */
|
|
1012
|
+
differences?: string
|
|
978
1013
|
}
|
|
979
1014
|
|
|
980
1015
|
/**
|
|
@@ -1229,6 +1264,11 @@ export type ContactAddressKind =
|
|
|
1229
1264
|
| 'email'
|
|
1230
1265
|
| 'phone'
|
|
1231
1266
|
| 'slack'
|
|
1267
|
+
/** LinkedIn's stable profile id (ACoAA… / ACwAA… / AE…) — what sending needs. */
|
|
1268
|
+
| 'linkedin_id'
|
|
1269
|
+
/** The `/in/<slug>` public identifier — what humans and records hold. */
|
|
1270
|
+
| 'linkedin_public_identifier'
|
|
1271
|
+
/** Legacy alias of `linkedin_id` (rows written before the kind split); read-only. */
|
|
1232
1272
|
| 'linkedin'
|
|
1233
1273
|
| 'telegram'
|
|
1234
1274
|
| 'instagram'
|
|
@@ -1466,10 +1506,25 @@ export interface AttachContactAddressRequest {
|
|
|
1466
1506
|
name?: string
|
|
1467
1507
|
}
|
|
1468
1508
|
|
|
1509
|
+
/** How a contact→record edge came to be: a user's explicit link, or the
|
|
1510
|
+
* binding maintained from the record's contact-address attributes (exactly one
|
|
1511
|
+
* per record; never unlinked by hand). */
|
|
1512
|
+
export type ContactRecordLinkSource = 'manual' | 'record'
|
|
1513
|
+
|
|
1514
|
+
/** One canonical address key of a contact (the dedup backbone). */
|
|
1515
|
+
export interface ContactAddressKey {
|
|
1516
|
+
kind: ContactAddressKind
|
|
1517
|
+
value: string
|
|
1518
|
+
scope?: string
|
|
1519
|
+
raw_value?: string
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1469
1522
|
/**
|
|
1470
1523
|
* A directed edge from a contact to a business record (entity_slug +
|
|
1471
1524
|
* record_id). Bare — no role; the relationship's semantics come from the
|
|
1472
|
-
* target entity.
|
|
1525
|
+
* target entity. A `manual` link is immutable once created; a `record`
|
|
1526
|
+
* binding repoints/refreshes as the record's addresses change and carries the
|
|
1527
|
+
* keys the record contributed (`address_keys`).
|
|
1473
1528
|
*/
|
|
1474
1529
|
export interface ContactRecordLink {
|
|
1475
1530
|
id: string
|
|
@@ -1477,8 +1532,12 @@ export interface ContactRecordLink {
|
|
|
1477
1532
|
contact_id: string
|
|
1478
1533
|
entity_slug: string
|
|
1479
1534
|
record_id: string
|
|
1535
|
+
source: ContactRecordLinkSource
|
|
1536
|
+
address_keys?: ContactAddressKey[]
|
|
1480
1537
|
created_at: string
|
|
1481
1538
|
created_by: UserRef
|
|
1539
|
+
updated_at?: string
|
|
1540
|
+
updated_by?: UserRef
|
|
1482
1541
|
}
|
|
1483
1542
|
|
|
1484
1543
|
export interface CreateContactRecordLinkRequest {
|
|
@@ -1491,6 +1550,7 @@ export interface ListContactRecordLinksQuery extends PaginationQuery {
|
|
|
1491
1550
|
contact_id?: string
|
|
1492
1551
|
entity_slug?: string
|
|
1493
1552
|
record_id?: string
|
|
1553
|
+
source?: ContactRecordLinkSource
|
|
1494
1554
|
}
|
|
1495
1555
|
|
|
1496
1556
|
export interface MergeContactsRequest {
|
|
@@ -1595,6 +1655,10 @@ export interface Transcription {
|
|
|
1595
1655
|
updated_by: UserRef
|
|
1596
1656
|
}
|
|
1597
1657
|
|
|
1658
|
+
/**
|
|
1659
|
+
* Transcribes a storage-service audio file. Asynchronous: the row lands
|
|
1660
|
+
* `processing` and the caller polls until `completed`.
|
|
1661
|
+
*/
|
|
1598
1662
|
export interface CreateTranscriptionRequest {
|
|
1599
1663
|
file_id: string
|
|
1600
1664
|
language?: string
|
|
@@ -1602,6 +1666,38 @@ export interface CreateTranscriptionRequest {
|
|
|
1602
1666
|
is_diarized?: boolean
|
|
1603
1667
|
}
|
|
1604
1668
|
|
|
1669
|
+
/**
|
|
1670
|
+
* Lands an ALREADY WRITTEN transcript — no provider runs and the row is
|
|
1671
|
+
* `completed` when the call returns. This is how a transcript, and through
|
|
1672
|
+
* `materialize` a conversation, is authored from text with nothing to upload.
|
|
1673
|
+
* `source_file_id` stays empty, which is how a reader tells the two kinds apart.
|
|
1674
|
+
*
|
|
1675
|
+
* Timings are optional: plain text has none, and materialization then sequences
|
|
1676
|
+
* the turns by their position instead of by `start_ms`.
|
|
1677
|
+
*/
|
|
1678
|
+
/**
|
|
1679
|
+
* One turn of an authored transcript. Distinct from TranscriptTurn (the read
|
|
1680
|
+
* shape, always fully populated) because everything but who-said-what is
|
|
1681
|
+
* derived server-side: an unlabelled speaker becomes "Speaker N", and text with
|
|
1682
|
+
* no timings is sequenced by position at materialization.
|
|
1683
|
+
*/
|
|
1684
|
+
export interface AuthoredTranscriptTurn {
|
|
1685
|
+
/** Speaker index — the same number means the same person. */
|
|
1686
|
+
speaker: number
|
|
1687
|
+
speaker_label?: string
|
|
1688
|
+
text: string
|
|
1689
|
+
start_ms?: number
|
|
1690
|
+
end_ms?: number
|
|
1691
|
+
confidence?: number
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
export interface CreateTranscriptionFromTextRequest {
|
|
1695
|
+
turns: AuthoredTranscriptTurn[]
|
|
1696
|
+
language?: string
|
|
1697
|
+
/** Defaults to whether the turns name more than one speaker. */
|
|
1698
|
+
is_diarized?: boolean
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1605
1701
|
/**
|
|
1606
1702
|
* Edits a COMPLETED transcription in place. `turns`, when present, replace the
|
|
1607
1703
|
* diarized turns wholesale; `speaker_labels` maps diarized speaker indexes
|
|
@@ -1721,9 +1817,17 @@ export type SendingPeriod = 'rolling_24h' | 'rolling_7d' | 'rolling_30d'
|
|
|
1721
1817
|
* ONE kind of act performed through a channel connection — shared by a
|
|
1722
1818
|
* limit's `action` (what it counts), a channel action's `action_type` (what
|
|
1723
1819
|
* was performed) and the eligibility check. `message` is the plain send
|
|
1724
|
-
* (valid on a limit, never on a channel action row).
|
|
1820
|
+
* (valid on a limit, never on a channel action row). `profile_lookup` is the
|
|
1821
|
+
* silent twin of `profile_visit`: one provider read the platform performs to
|
|
1822
|
+
* learn a person's full identity (LinkedIn profile id + public identifier),
|
|
1823
|
+
* ledgered and limited like any other act.
|
|
1725
1824
|
*/
|
|
1726
|
-
export type ChannelActionType =
|
|
1825
|
+
export type ChannelActionType =
|
|
1826
|
+
| 'message'
|
|
1827
|
+
| 'invitation'
|
|
1828
|
+
| 'inmail'
|
|
1829
|
+
| 'profile_visit'
|
|
1830
|
+
| 'profile_lookup'
|
|
1727
1831
|
export type Weekday =
|
|
1728
1832
|
| 'monday'
|
|
1729
1833
|
| 'tuesday'
|
package/src/data/index.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ProteosClient } from '../client.js'
|
|
2
2
|
import { type QueryService, QueryServiceImpl } from './queries.js'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type RecordDuplicateService,
|
|
5
|
+
RecordDuplicateServiceImpl,
|
|
6
|
+
type RecordService,
|
|
7
|
+
RecordServiceImpl,
|
|
8
|
+
} from './records.js'
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Facade for the data-service (records + raw-SQL query API).
|
|
@@ -16,10 +21,13 @@ import { type RecordService, RecordServiceImpl } from './records.js'
|
|
|
16
21
|
export class DataClient {
|
|
17
22
|
readonly records: RecordService
|
|
18
23
|
readonly queries: QueryService
|
|
24
|
+
/** Duplicate pairs of an entity's records (raised by contact binding). */
|
|
25
|
+
readonly recordDuplicates: RecordDuplicateService
|
|
19
26
|
|
|
20
27
|
constructor(client: ProteosClient) {
|
|
21
28
|
this.records = new RecordServiceImpl(client)
|
|
22
29
|
this.queries = new QueryServiceImpl(client)
|
|
30
|
+
this.recordDuplicates = new RecordDuplicateServiceImpl(client)
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
|
|
@@ -33,15 +41,20 @@ export type {
|
|
|
33
41
|
QueryValidateMeta,
|
|
34
42
|
QueryValidateResponse,
|
|
35
43
|
} from './queries.js'
|
|
36
|
-
export type { RecordService } from './records.js'
|
|
44
|
+
export type { RecordDuplicateService, RecordService } from './records.js'
|
|
37
45
|
export type {
|
|
38
46
|
BatchTransactionError,
|
|
39
47
|
BatchTransactionStatus,
|
|
40
48
|
BatchUpsertRecordsResponse,
|
|
41
49
|
BatchUpsertTransaction,
|
|
42
50
|
BatchUpsertTransactionResult,
|
|
51
|
+
ListRecordDuplicatesQuery,
|
|
43
52
|
ListRecordsOptions,
|
|
53
|
+
PublishContactObservationsResponse,
|
|
44
54
|
RecordData,
|
|
55
|
+
RecordDuplicate,
|
|
56
|
+
RecordDuplicateSource,
|
|
57
|
+
RecordDuplicateStatus,
|
|
45
58
|
} from './types.js'
|
|
46
59
|
|
|
47
60
|
// Re-export Zod schemas
|
package/src/data/records.ts
CHANGED
|
@@ -4,8 +4,11 @@ import type { ListResult } from '../types/common.js'
|
|
|
4
4
|
import type {
|
|
5
5
|
BatchUpsertRecordsResponse,
|
|
6
6
|
BatchUpsertTransaction,
|
|
7
|
+
ListRecordDuplicatesQuery,
|
|
7
8
|
ListRecordsOptions,
|
|
9
|
+
PublishContactObservationsResponse,
|
|
8
10
|
RecordData,
|
|
11
|
+
RecordDuplicate,
|
|
9
12
|
} from './types.js'
|
|
10
13
|
|
|
11
14
|
const RECORDS_BASE_PATH = '/data/v1/records'
|
|
@@ -92,6 +95,26 @@ export interface RecordService {
|
|
|
92
95
|
* (bare refs) on this path.
|
|
93
96
|
*/
|
|
94
97
|
getPublic(orgId: string, entitySlug: string, id: string): Promise<RecordData>
|
|
98
|
+
/**
|
|
99
|
+
* Replays ONE page of the entity's records as record_contact_observation
|
|
100
|
+
* events (the backfill after an attribute became contact-address). Requires
|
|
101
|
+
* unscoped write on the entity; loop until `page + 1 >= pages_total`.
|
|
102
|
+
*/
|
|
103
|
+
publishContactObservations(
|
|
104
|
+
entitySlug: string,
|
|
105
|
+
options?: { page?: number; page_size?: number },
|
|
106
|
+
): Promise<PublishContactObservationsResponse>
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Record duplicates — "these two records may be the same thing" pairs of one
|
|
111
|
+
* entity (see `RecordDuplicate`). Reading = entity read, dismissing = entity write.
|
|
112
|
+
*/
|
|
113
|
+
export interface RecordDuplicateService {
|
|
114
|
+
list(entitySlug: string, query?: ListRecordDuplicatesQuery): Promise<ListResult<RecordDuplicate>>
|
|
115
|
+
get(entitySlug: string, id: string): Promise<RecordDuplicate>
|
|
116
|
+
/** Closes a pair as "not a duplicate" (409 `record_duplicate_not_open` when already closed). */
|
|
117
|
+
dismiss(entitySlug: string, id: string): Promise<RecordDuplicate>
|
|
95
118
|
}
|
|
96
119
|
|
|
97
120
|
/**
|
|
@@ -149,6 +172,17 @@ export class RecordServiceImpl implements RecordService {
|
|
|
149
172
|
)
|
|
150
173
|
}
|
|
151
174
|
|
|
175
|
+
async publishContactObservations(
|
|
176
|
+
entitySlug: string,
|
|
177
|
+
options: { page?: number; page_size?: number } = {},
|
|
178
|
+
): Promise<PublishContactObservationsResponse> {
|
|
179
|
+
return this.client.requestWithQuery<PublishContactObservationsResponse>(
|
|
180
|
+
'POST',
|
|
181
|
+
`${RECORDS_BASE_PATH}/${entitySlug}/contact-observations`,
|
|
182
|
+
options,
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
152
186
|
listPublic(
|
|
153
187
|
orgId: string,
|
|
154
188
|
entitySlug: string,
|
|
@@ -180,3 +214,32 @@ export class RecordServiceImpl implements RecordService {
|
|
|
180
214
|
)
|
|
181
215
|
}
|
|
182
216
|
}
|
|
217
|
+
|
|
218
|
+
export class RecordDuplicateServiceImpl implements RecordDuplicateService {
|
|
219
|
+
constructor(private readonly client: ProteosClient) {}
|
|
220
|
+
|
|
221
|
+
async list(
|
|
222
|
+
entitySlug: string,
|
|
223
|
+
query: ListRecordDuplicatesQuery = {},
|
|
224
|
+
): Promise<ListResult<RecordDuplicate>> {
|
|
225
|
+
return this.client.requestWithQuery<ListResult<RecordDuplicate>>(
|
|
226
|
+
'GET',
|
|
227
|
+
`${RECORDS_BASE_PATH}/${entitySlug}/duplicates`,
|
|
228
|
+
query,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async get(entitySlug: string, id: string): Promise<RecordDuplicate> {
|
|
233
|
+
return this.client.request<RecordDuplicate>(
|
|
234
|
+
'GET',
|
|
235
|
+
`${RECORDS_BASE_PATH}/${entitySlug}/duplicates/${encodeURIComponent(id)}`,
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async dismiss(entitySlug: string, id: string): Promise<RecordDuplicate> {
|
|
240
|
+
return this.client.request<RecordDuplicate>(
|
|
241
|
+
'POST',
|
|
242
|
+
`${RECORDS_BASE_PATH}/${entitySlug}/duplicates/${encodeURIComponent(id)}/dismiss`,
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
}
|
package/src/data/types.ts
CHANGED
|
@@ -96,3 +96,55 @@ export const BatchUpsertTransactionResultSchema = z.object({
|
|
|
96
96
|
export const BatchUpsertRecordsResponseSchema = z.object({
|
|
97
97
|
results: z.array(BatchUpsertTransactionResultSchema),
|
|
98
98
|
})
|
|
99
|
+
|
|
100
|
+
// ── Record duplicates ─────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/** How a duplicate pair was detected. `contact_binding` = two records bound to
|
|
103
|
+
* the same conversation contact through their contact-address attributes. */
|
|
104
|
+
export type RecordDuplicateSource = 'contact_binding' | (string & {})
|
|
105
|
+
|
|
106
|
+
export type RecordDuplicateStatus = 'open' | 'dismissed' | 'resolved'
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* "These two records of one entity may be the same thing." Asymmetric:
|
|
110
|
+
* `primary_record_id` is the record to keep, `secondary_record_id` the
|
|
111
|
+
* newcomer. A group of colliding records has ONE primary and one row per other
|
|
112
|
+
* member (four records = three rows). `evidence` is shaped by `source` — for
|
|
113
|
+
* `contact_binding`: `{ contact_id, address_keys: [{kind, value}] }`.
|
|
114
|
+
*/
|
|
115
|
+
export interface RecordDuplicate {
|
|
116
|
+
id: string
|
|
117
|
+
org_id: string
|
|
118
|
+
entity_slug: string
|
|
119
|
+
primary_record_id: string
|
|
120
|
+
secondary_record_id: string
|
|
121
|
+
source: RecordDuplicateSource
|
|
122
|
+
evidence: Record<string, unknown>
|
|
123
|
+
status: RecordDuplicateStatus
|
|
124
|
+
resolved_by?: { type: string; id: string }
|
|
125
|
+
resolved_at?: string
|
|
126
|
+
created_at: string
|
|
127
|
+
created_by: { type: string; id: string }
|
|
128
|
+
updated_at: string
|
|
129
|
+
updated_by: { type: string; id: string }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ListRecordDuplicatesQuery {
|
|
133
|
+
/** Only pairs this record takes part in (either side). */
|
|
134
|
+
record_id?: string
|
|
135
|
+
/** Defaults to `open` on the server. */
|
|
136
|
+
status?: RecordDuplicateStatus
|
|
137
|
+
page?: number
|
|
138
|
+
page_size?: number
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** ONE page of an entity's records replayed as contact observations. */
|
|
142
|
+
export interface PublishContactObservationsResponse {
|
|
143
|
+
entity_slug: string
|
|
144
|
+
page: number
|
|
145
|
+
page_size: number
|
|
146
|
+
pages_total: number
|
|
147
|
+
items_total: number
|
|
148
|
+
published: number
|
|
149
|
+
skipped: number
|
|
150
|
+
}
|