@opencxh/domain 1.164.0 → 1.165.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/entities/communication/types.d.ts +0 -51
- package/dist/entities/mcp/types.d.ts +110 -6
- package/dist/entities/mcp/types.test.d.ts +1 -0
- package/dist/entities/webhook/types.d.ts +13 -0
- package/dist/index.cjs +6 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +198 -187
- package/dist/platform/account.d.ts +91 -0
- package/dist/platform/sync-source.d.ts +442 -0
- package/package.json +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Laag 1 van de integratielagen: **Account** — het credential.
|
|
3
|
+
*
|
|
4
|
+
* Vier lagen, en dit is de onderste:
|
|
5
|
+
* | Laag | Vraag | Woord |
|
|
6
|
+
* |---|---|---|
|
|
7
|
+
* | 1 Credential | mag ik erin? | **Account** (`ManagedAccount`) |
|
|
8
|
+
* | 2 Connector | waar is het, welk protocol? | **Connector** (`McpServerEntity`) |
|
|
9
|
+
* | 3 Capability | wat kan ik ermee? | **Source** (`sync-source`, `memory-source`, …) |
|
|
10
|
+
* | 4 Usage | staat het aan, met welke instellingen? | **Connection** (`SyncConnection`, `Channel`) |
|
|
11
|
+
*
|
|
12
|
+
* `ManagedAccount` is een **platform**-primitive, niet een comms-entiteit — dat het type
|
|
13
|
+
* historisch in `entities/communication/types.ts` stond kwam alleen doordat de *store* daar
|
|
14
|
+
* woont. De store blijft in comms (daar zitten de scope-guard en de OAuth-orchestrator);
|
|
15
|
+
* het type hoort hier, want ai, mail, ftp, s3, google en microsoft hangen er allemaal aan.
|
|
16
|
+
*
|
|
17
|
+
* ## Niet verwarren met een Registration
|
|
18
|
+
*
|
|
19
|
+
* `google_account`, `microsoft_account`, `shopify_account`, `slack_account` en `meta_app` zijn
|
|
20
|
+
* GEEN accounts in deze zin. Dat zijn **registraties**: de clientId/clientSecret waarmee de
|
|
21
|
+
* organisatie zichzelf bij de vendor aanmeldt. Eén registratie draagt N accounts. Ze delen
|
|
22
|
+
* alleen het woord.
|
|
23
|
+
*
|
|
24
|
+
* ## Scope: `userId` is de enige eigendom-as
|
|
25
|
+
*
|
|
26
|
+
* `userId` afwezig/`null` = org-breed, gedeeld. Gezet = persoonlijk, van die ene gebruiker.
|
|
27
|
+
* Er is bewust **geen** `ownerScope` en **geen** `createdBy` op een credential:
|
|
28
|
+
* - `ownerScope` (`personal`/`team`/`org`) is de as van een *resource* — met wie deel ik dit
|
|
29
|
+
* contact, werkitem of geheugen. Een token heeft geen teamvariant.
|
|
30
|
+
* - "wat eist de vendor" (één gedeeld token vs. iedereen zijn eigen) is een eigenschap van de
|
|
31
|
+
* **connector**, niet van de accountrij: zie `credentialScope` in `entities/mcp/types.ts`.
|
|
32
|
+
*/
|
|
33
|
+
export type AuthState = {
|
|
34
|
+
type: "oauth2";
|
|
35
|
+
accessToken: string;
|
|
36
|
+
refreshToken?: string;
|
|
37
|
+
/** Epoch ms */
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
scopes: string[];
|
|
40
|
+
idToken?: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: "password";
|
|
43
|
+
username: string;
|
|
44
|
+
/** Rauw wachtwoord (geen secret-store in repo). Alleen via internal-path. */
|
|
45
|
+
password: string;
|
|
46
|
+
/** Toekomstige secret-store-referentie i.p.v. raw `password`. */
|
|
47
|
+
passwordRef?: string;
|
|
48
|
+
} | {
|
|
49
|
+
type: "certificate";
|
|
50
|
+
certRef: string;
|
|
51
|
+
} | {
|
|
52
|
+
type: "apikey";
|
|
53
|
+
/** Rauwe API-key (geen secret-store in repo). Alleen via internal-path. */
|
|
54
|
+
apiKey: string;
|
|
55
|
+
} | {
|
|
56
|
+
type: "static";
|
|
57
|
+
};
|
|
58
|
+
export type ManagedAccountStatus = "connected" | "disconnected" | "error" | "expired";
|
|
59
|
+
export interface ManagedAccount {
|
|
60
|
+
id: string;
|
|
61
|
+
organizationId: string;
|
|
62
|
+
/**
|
|
63
|
+
* Afwezig/`null` = org-breed gedeeld account. Gezet = persoonlijk account van die gebruiker.
|
|
64
|
+
* De enige eigendom-as op dit niveau — zie de moduledoc.
|
|
65
|
+
*/
|
|
66
|
+
userId?: string;
|
|
67
|
+
providerId: string;
|
|
68
|
+
/** "oauth2" | "imap" | "sip" | "webhook" | "apikey" | provider-eigen string */
|
|
69
|
+
protocol: string;
|
|
70
|
+
displayName: string;
|
|
71
|
+
status: ManagedAccountStatus;
|
|
72
|
+
auth: AuthState;
|
|
73
|
+
/** Provider-specifieke metadata (tenantId, externalUserId, scope-flags). */
|
|
74
|
+
metadata: Record<string, unknown>;
|
|
75
|
+
createdAt?: number;
|
|
76
|
+
updatedAt?: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Public-view zonder tokens — wat `account/list` en `account/get` aan UI/comm-server
|
|
80
|
+
* retourneren. `auth` is gestript tot alleen het type.
|
|
81
|
+
*
|
|
82
|
+
* **Leesasymmetrie, en dit is de valkuil:** de publieke `account/list` filtert op de
|
|
83
|
+
* *huidige* gebruiker, dus een job of cron (die geen sessie heeft) ziet daar niets — ook
|
|
84
|
+
* geen org-brede rijen. Server-side code die accounts moet zien gebruikt altijd de
|
|
85
|
+
* `account-internal/`-route (`listManagedAccountsInternal` / `getManagedAccountInternal`).
|
|
86
|
+
*/
|
|
87
|
+
export type ManagedAccountPublic = Omit<ManagedAccount, "auth"> & {
|
|
88
|
+
authType: AuthState["type"];
|
|
89
|
+
scopes?: string[];
|
|
90
|
+
expiresAt?: number;
|
|
91
|
+
};
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data van buiten naar binnen halen, één keer geregeld.
|
|
3
|
+
*
|
|
4
|
+
* Het platform ingest vandaag op vier onafhankelijke manieren — een cron-sync per provider-app,
|
|
5
|
+
* webhooks, een event-driven verrijkingsjob en losse backfills — en elk van die vier herbouwt
|
|
6
|
+
* zelfstandig dezelfde zes dingen: cursor, batching, locking, idempotentie, foutafhandeling en
|
|
7
|
+
* observability. Dat is te zien: `cursorKey()` staat drie keer los in de repo,
|
|
8
|
+
* `microsoft_sync_state` groeit per sub-sync een kolom, en een sync die stilvalt is een
|
|
9
|
+
* `console.error` die niemand ziet.
|
|
10
|
+
*
|
|
11
|
+
* De splitsing die dat oplost is de industriestandaard (Singer/Airbyte, Nango, Fivetran): **een
|
|
12
|
+
* smal bron-contract met een dikke generieke runtime.** De bron weet hoe je één pagina uit
|
|
13
|
+
* Microsoft Graph of Asana haalt; de runtime weet hoe je cursors checkpoint, batches
|
|
14
|
+
* doorschakelt, backoff doet en er een run-log van bijhoudt. Dit bestand is dat smalle contract.
|
|
15
|
+
*
|
|
16
|
+
* Drie endpoints, in de vorm van `memory-source` en `analytics-source` — dus een providerrol en
|
|
17
|
+
* geen servicebus-sleutel, want dit draait server-side zonder gebruiker erbij:
|
|
18
|
+
*
|
|
19
|
+
* | Rol | Endpoint | Antwoord |
|
|
20
|
+
* |---|---|---|
|
|
21
|
+
* | `sync-source` | `GET /provider/sync/describe` | {@link SyncSourceDescribe} |
|
|
22
|
+
* | `sync-source` | `POST /provider/sync/pull` | {@link SyncPullResponse} |
|
|
23
|
+
* | `sync-target` | `POST /provider/sync/land` | {@link SyncLandResponse} |
|
|
24
|
+
*
|
|
25
|
+
* Bron en doel zijn **losse rollen**: `apps/microsoft` levert records en landt niets,
|
|
26
|
+
* `apps/work` landt records en levert niets. Een app mag beide zijn, maar hoeft dat niet.
|
|
27
|
+
*/
|
|
28
|
+
/** De providerrol-groepen, als constante zodat een typefout niet stil een lege lijst oplevert. */
|
|
29
|
+
export declare const SYNC_SOURCE_PROVIDER_GROUP = "sync-source";
|
|
30
|
+
export declare const SYNC_TARGET_PROVIDER_GROUP = "sync-target";
|
|
31
|
+
/**
|
|
32
|
+
* Continu of eenmalig — en dat is een echt verschil, geen label.
|
|
33
|
+
*
|
|
34
|
+
* `continuous` draait op een tik en moet dus goedkoop kunnen niksdoen (een cursor die zegt
|
|
35
|
+
* "niets nieuws"). `once` wordt door een mens gestart, mag duur zijn, en moet **uitputtend**
|
|
36
|
+
* zijn: een migratie die 95% ophaalt is geen migratie. Die eis is precies waarom niet elke bron
|
|
37
|
+
* beide kan — zie {@link SyncSourceDefinition.transport}.
|
|
38
|
+
*/
|
|
39
|
+
export type SyncMode = "continuous" | "once";
|
|
40
|
+
/**
|
|
41
|
+
* Hoe de records feitelijk binnenkomen.
|
|
42
|
+
*
|
|
43
|
+
* Dit staat in het contract omdat het de verwachtingen bepaalt die de runtime en de UI mogen
|
|
44
|
+
* hebben, niet als documentatie:
|
|
45
|
+
*
|
|
46
|
+
* - **`native`** — de app praat zelf met de API van de leverancier. Kent delta-tokens en
|
|
47
|
+
* pagination, dus geschikt voor `continuous` én `once`.
|
|
48
|
+
* - **`tool`** — de app gaat via de AI-tool/MCP-laag. Prima voor de lange staart aan systemen
|
|
49
|
+
* waarvoor een eigen connector niet loont, maar een MCP-zoektool antwoordt "top-N relevant"
|
|
50
|
+
* en niet "alles sinds X", en het protocol kent geen change-token. Een `tool`-bron die
|
|
51
|
+
* `once` claimt belooft volledigheid die hij niet kan leveren.
|
|
52
|
+
* - **`file`** — een geüpload bestand (CSV). Per definitie `once`, en per definitie volledig.
|
|
53
|
+
*/
|
|
54
|
+
export type SyncTransport = "native" | "tool" | "file";
|
|
55
|
+
/** Eén ding dat een app van buiten kan halen. */
|
|
56
|
+
/**
|
|
57
|
+
* Eén instelling van een koppeling, zoals de bron hem declareert.
|
|
58
|
+
*
|
|
59
|
+
* Bewust geen ui-kit-types hier: `packages/domain` heeft geen UI-afhankelijkheden, en het
|
|
60
|
+
* formulier mapt deze declaratie zelf op zijn eigen velden.
|
|
61
|
+
*/
|
|
62
|
+
export interface SyncSettingsField {
|
|
63
|
+
/** Sleutel in het settings-object dat de bron bij `pull` terugkrijgt. */
|
|
64
|
+
key: string;
|
|
65
|
+
label: string;
|
|
66
|
+
type: "text" | "number" | "boolean" | "select";
|
|
67
|
+
required?: boolean;
|
|
68
|
+
/** Alleen bij `type: "select"`. */
|
|
69
|
+
options?: {
|
|
70
|
+
value: string;
|
|
71
|
+
label: string;
|
|
72
|
+
}[];
|
|
73
|
+
/** Uitleg onder het veld. Voor "leeg = ..."-gevallen, die anders raadwerk zijn. */
|
|
74
|
+
help?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Hint dat deze waarde een resource in een andere app aanwijst (`"work_project"`).
|
|
77
|
+
*
|
|
78
|
+
* Vandaag rendert het formulier hem als tekstveld; de hint staat er zodat een picker later
|
|
79
|
+
* toegevoegd kan worden zonder dat de bron verandert. Een hint en geen belofte.
|
|
80
|
+
*/
|
|
81
|
+
resource?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface SyncSourceDefinition {
|
|
84
|
+
/**
|
|
85
|
+
* Stabiel en app-genamespaced: `microsoft.todo`, `asana.tasks`, `csv.contacts`.
|
|
86
|
+
*
|
|
87
|
+
* Dit is een **opgeslagen verwijzing** — `SyncConnection.sourceId` bewaart hem — dus
|
|
88
|
+
* hernoemen breekt bestaande koppelingen. Punt als scheidingsteken, zoals `MemoryKindId`
|
|
89
|
+
* voor app-eigen kinds.
|
|
90
|
+
*/
|
|
91
|
+
id: string;
|
|
92
|
+
label: string;
|
|
93
|
+
description?: string;
|
|
94
|
+
/**
|
|
95
|
+
* De scope-soorten die deze bron landt: `work_item`, `interaction`, `company`, `contact`.
|
|
96
|
+
*
|
|
97
|
+
* Hetzelfde vocabulaire als `ScopeDescribe.kinds`, en dat is geen cosmetische keuze — de
|
|
98
|
+
* runtime routeert een record naar de bezittende app met `findScopeOwner(kind)`. Een soort
|
|
99
|
+
* die geen scope-eigenaar heeft, kan dus niet geland worden, en dat merk je bij het
|
|
100
|
+
* registreren in plaats van halverwege een run.
|
|
101
|
+
*/
|
|
102
|
+
kinds: string[];
|
|
103
|
+
/** Welke modi deze bron ondersteunt. Leeg is zinloos; minstens één. */
|
|
104
|
+
modes: SyncMode[];
|
|
105
|
+
transport: SyncTransport;
|
|
106
|
+
/**
|
|
107
|
+
* De `providerId` van de {@link ManagedAccount} die deze bron nodig heeft.
|
|
108
|
+
*
|
|
109
|
+
* Afwezig = geen credential nodig (een bestand-upload). Aanwezig betekent dat een koppeling
|
|
110
|
+
* niet werkt zonder gekozen account, en dat de UI dat vóór de eerste run kan zeggen in
|
|
111
|
+
* plaats van een mislukte run te laten zien.
|
|
112
|
+
*/
|
|
113
|
+
accountProviderId?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Instellingen die per koppeling verschillen en dus niet in code horen.
|
|
116
|
+
*
|
|
117
|
+
* Declaratief, zodat een connector-app **geen frontend nodig heeft** om configureerbaar te
|
|
118
|
+
* zijn: het Koppelingen-formulier rendert deze velden. Dat is wat `apps/asana` (nul
|
|
119
|
+
* frontend-bestanden) mogelijk houdt.
|
|
120
|
+
*
|
|
121
|
+
* Plat gehouden met opzet. Een genest schema zou een mapping-DSL worden, en die is eerder
|
|
122
|
+
* afgewezen: datumformaten en enums verschillen per bron, en fouten verschuiven dan van `tsc`
|
|
123
|
+
* naar runtime.
|
|
124
|
+
*/
|
|
125
|
+
settingsSchema?: SyncSettingsField[];
|
|
126
|
+
/**
|
|
127
|
+
* Alleen bij `transport: "tool"`: de catalogus-sleutel waarop de connector-picker
|
|
128
|
+
* voorfiltert (`"asana"`, `"linear"`).
|
|
129
|
+
*
|
|
130
|
+
* Een **hint**, geen resolutie: hij bepaalt welke van de MCP-verbindingen van de
|
|
131
|
+
* organisatie als kandidaat worden voorgesteld. Wat de koppeling daarna bewaart is het
|
|
132
|
+
* connector-**id** ({@link SyncConnection.connectorId}). Zonder hint krijgt de gebruiker
|
|
133
|
+
* simpelweg de volledige lijst.
|
|
134
|
+
*/
|
|
135
|
+
connectorHint?: string;
|
|
136
|
+
/**
|
|
137
|
+
* Alleen bij `transport: "tool"`: deze bron kan alleen draaien op een gedeeld
|
|
138
|
+
* organisatie-credential.
|
|
139
|
+
*
|
|
140
|
+
* Waarom dit bestaat: een onbemande ronde (cron, sync) heeft geen handelende gebruiker, dus
|
|
141
|
+
* een connector met `credentialScope: "per-user"` levert daar per definitie nul tools — en dat
|
|
142
|
+
* zag je vroeger alleen als "de sync doet niets". Met deze vlag weigert het opslaan van een
|
|
143
|
+
* `continuous`-koppeling meteen, met een uitleg.
|
|
144
|
+
*
|
|
145
|
+
* Alleen voor `continuous`: een `once`-koppeling start een mens, en dan is er wél een
|
|
146
|
+
* handelende gebruiker wiens token gebruikt kan worden.
|
|
147
|
+
*/
|
|
148
|
+
requiresCredentialScope?: "shared";
|
|
149
|
+
/**
|
|
150
|
+
* Aanbevolen minimale tijd tussen twee rondes, in ms. De organisatie mag hem overschrijven.
|
|
151
|
+
*
|
|
152
|
+
* Een **interval** en geen cron-expressie, omdat dat is wat er daadwerkelijk uitvoerbaar is:
|
|
153
|
+
* `JobOptions.schedule` staat vast bij registratie, dus een cron-string per koppeling zou
|
|
154
|
+
* nooit geëvalueerd worden — een veld dat belooft wat het niet doet. De runtime tikt op een
|
|
155
|
+
* grof raster en poort daarop.
|
|
156
|
+
*
|
|
157
|
+
* Bij de bron en niet in de runtime, omdat alleen de bron weet wat zijn API verdraagt: een
|
|
158
|
+
* delta-feed mag elke minuut, een lijst-endpoint dat elke keer alles teruggeeft niet.
|
|
159
|
+
*/
|
|
160
|
+
defaultIntervalMs?: number;
|
|
161
|
+
/**
|
|
162
|
+
* Hoe vaak de bron zijn cursor moet negeren en alles opnieuw lezen (ms).
|
|
163
|
+
*
|
|
164
|
+
* De reconcile-sweep uit het Stripe/Shopify-patroon, en in deze codebase al bewezen als
|
|
165
|
+
* `CHAT_FULL_SWEEP_INTERVAL_MS`: cursors rusten op de aanname dat de leverancier een
|
|
166
|
+
* `updatedAt` bumpt voor alles wat telt, en dat houdt voor nieuwe records maar is
|
|
167
|
+
* onbetrouwbaar voor edits en verwijderingen. Afwezig = nooit vegen (juist voor een
|
|
168
|
+
* delta-feed die verwijderingen zelf meldt).
|
|
169
|
+
*/
|
|
170
|
+
fullSweepIntervalMs?: number;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Bare payload van `GET /provider/sync/describe` — **niet** in `ResponseFactory` verpakt
|
|
174
|
+
* (mal: {@link MemorySourceDescription} en `AnalyticsSourceDescription`).
|
|
175
|
+
*/
|
|
176
|
+
export interface SyncSourceDescribe {
|
|
177
|
+
/** De declarerende app (== `manifest.name` == `req.source.app`). */
|
|
178
|
+
source: string;
|
|
179
|
+
sources: SyncSourceDefinition[];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Eén record uit een bronsysteem, klaar om geland te worden.
|
|
183
|
+
*
|
|
184
|
+
* De bron doet de mapping naar de vorm die de doel-app verwacht. Dat is bewust: de runtime kent
|
|
185
|
+
* geen enkel veld van geen enkele leverancier, en zodra hij dat wél zou doen is hij niet meer
|
|
186
|
+
* generiek. Zie {@link SyncRecord.data}.
|
|
187
|
+
*/
|
|
188
|
+
export interface SyncRecord {
|
|
189
|
+
/**
|
|
190
|
+
* Het stabiele id in het bronsysteem, genamespaced door de bron zelf
|
|
191
|
+
* (`asana_task:12345`, `graph-todo:AAMk…`).
|
|
192
|
+
*
|
|
193
|
+
* Dit is de dedupe-as: de doel-app upsert hierop via zijn
|
|
194
|
+
* `upsert_by_external_provider_id`-route, dus dezelfde run twee keer draaien schrijft één
|
|
195
|
+
* rij. "Stabiel" is de hele eis — een id dat per pagina verandert maakt van elke sync een
|
|
196
|
+
* duplicaat-fabriek.
|
|
197
|
+
*/
|
|
198
|
+
externalId: string;
|
|
199
|
+
/**
|
|
200
|
+
* Ids die de **container** aanwijzen en niet deze rij: de lijst, het project, het board.
|
|
201
|
+
*
|
|
202
|
+
* Ze worden wél bewaard (uitgaande dispatch heeft ze nodig) maar mogen nooit meedoen in de
|
|
203
|
+
* dedupe. Dit veld bestaat omdat het al één keer fout is gegaan: Microsoft Graph stuurt
|
|
204
|
+
* naast het taak-id ook `graph-todolist:<id>`, dat voor élke taak in die lijst hetzelfde is,
|
|
205
|
+
* en zoeken daarop klapte de hele lijst op één rij (`task/external-ids.ts`). Daar is het
|
|
206
|
+
* met een prefix-lijst gerepareerd; hier zegt de bron het zelf, zodat die lijst niet hoeft
|
|
207
|
+
* te groeien.
|
|
208
|
+
*/
|
|
209
|
+
containerExternalIds?: string[];
|
|
210
|
+
/** De scope-soort, uit {@link SyncSourceDefinition.kinds}. Bepaalt naar welke app dit gaat. */
|
|
211
|
+
kind: string;
|
|
212
|
+
/**
|
|
213
|
+
* De velden voor de doel-app.
|
|
214
|
+
*
|
|
215
|
+
* **Vaste veldnamen, nooit een map met een extern id als sleutel.** De SDK-client mangelt
|
|
216
|
+
* object-sleutels tussen camel- en snake-case, dus data die ín een sleutel zit komt
|
|
217
|
+
* verminkt aan. Dezelfde les die `chatCursors` een lijst maakte en `ContactSource.sourceId`
|
|
218
|
+
* een veldwaarde.
|
|
219
|
+
*/
|
|
220
|
+
data: Record<string, unknown>;
|
|
221
|
+
/**
|
|
222
|
+
* Dit record is bij de bron verwijderd.
|
|
223
|
+
*
|
|
224
|
+
* Alleen zinvol als de bron dat kán weten: een delta-feed meldt verwijderingen, een
|
|
225
|
+
* lijst-endpoint laat ze simpelweg weg. Afwezigheid uit een lijst is géén verwijdering —
|
|
226
|
+
* dat verschil niet respecteren betekent dat een gefilterde of gepagineerde respons je
|
|
227
|
+
* halve administratie opruimt.
|
|
228
|
+
*/
|
|
229
|
+
deleted?: boolean;
|
|
230
|
+
}
|
|
231
|
+
/** `POST /provider/sync/pull` — haal één pagina op. */
|
|
232
|
+
export interface SyncPullRequest {
|
|
233
|
+
sourceId: string;
|
|
234
|
+
/** Zodat de bron kan loggen en zijn eigen per-koppeling state kan vinden als hij die heeft. */
|
|
235
|
+
connectionId: string;
|
|
236
|
+
/** De {@link ManagedAccount} om mee te praten. Afwezig bij `transport: "file"`. */
|
|
237
|
+
accountId?: string;
|
|
238
|
+
/**
|
|
239
|
+
* Bij `transport: "tool"`: het id van de MCP-connector die deze koppeling mag gebruiken,
|
|
240
|
+
* gekozen tóen de koppeling werd geconfigureerd.
|
|
241
|
+
*
|
|
242
|
+
* Hiermee is de toolnaam een pure string — `mcpToolPrefix(connectorId) + toolnaam` — en is
|
|
243
|
+
* er per pagina géén resolutie-RPC nodig. Eerder zocht een bron zijn connector op
|
|
244
|
+
* catalogus-sleutel op, en dat kon bij twee kandidaten (een org-rij naast een persoonlijke,
|
|
245
|
+
* of twee gebruikers met elk hun eigen) geen winnaar kiezen; een app kon zelfs het prefix
|
|
246
|
+
* van iemands persoonlijke connector krijgen.
|
|
247
|
+
*/
|
|
248
|
+
connectorId?: string;
|
|
249
|
+
/**
|
|
250
|
+
* De instellingen van deze koppeling, volgens {@link SyncSourceDefinition.settingsSchema}.
|
|
251
|
+
*
|
|
252
|
+
* Een **object** hier en een JSON-string in de opslag. Let op: dat is NIET omdat de
|
|
253
|
+
* SDK-client uitgaande sleutels zou mangelen — dat doet hij niet, de verzendkant is
|
|
254
|
+
* `removeUndefined` + `JSON.stringify`. Alleen *antwoorden* worden gecamelCase't. De string in
|
|
255
|
+
* de opslag is er om dezelfde reden als de cursor: één schemaveld dat elke vorm moet kunnen
|
|
256
|
+
* dragen.
|
|
257
|
+
*/
|
|
258
|
+
settings?: Record<string, unknown>;
|
|
259
|
+
/**
|
|
260
|
+
* Opaak, exact zoals de bron hem vorige keer teruggaf. `undefined` = vanaf het begin.
|
|
261
|
+
*
|
|
262
|
+
* **De runtime kijkt er nooit in.** Een Graph `deltaLink`, een tijdstempel, een
|
|
263
|
+
* pagina-token — het maakt niet uit, en juist daarom hoeft er geen kolom per sub-sync te
|
|
264
|
+
* bestaan. Dat `microsoft_sync_state` vandaag `todoDeltaLink` náást `chatCursors` náást
|
|
265
|
+
* `chatFullSweepAt` heeft, is precies wat een opaak veld voorkomt.
|
|
266
|
+
*/
|
|
267
|
+
cursor?: unknown;
|
|
268
|
+
/** Negeer de cursor en lees alles opnieuw — de reconcile-sweep. */
|
|
269
|
+
fullSweep?: boolean;
|
|
270
|
+
/** Maximum aantal records in dit antwoord. De bron mag minder geven, nooit meer. */
|
|
271
|
+
limit: number;
|
|
272
|
+
}
|
|
273
|
+
/** Antwoord op `POST /provider/sync/pull`. Wél in `ResponseFactory` verpakt. */
|
|
274
|
+
export interface SyncPullResponse {
|
|
275
|
+
records: SyncRecord[];
|
|
276
|
+
/**
|
|
277
|
+
* De cursor ná deze pagina. De runtime bewaart hem **pas nadat de records geland zijn**.
|
|
278
|
+
*
|
|
279
|
+
* Dat is de Singer/Airbyte-regel en de reden dat crash-herstel werkt: valt de boel om
|
|
280
|
+
* tussen pull en land, dan wordt de batch herhaald (idempotent, want upsert op
|
|
281
|
+
* `externalId`) in plaats van overgeslagen. Andersom — cursor eerst — verlies je records
|
|
282
|
+
* stil, en stil is hier het probleem.
|
|
283
|
+
*/
|
|
284
|
+
cursor?: unknown;
|
|
285
|
+
/** Geen data meer na deze pagina. De runtime schakelt dan niet door. */
|
|
286
|
+
done: boolean;
|
|
287
|
+
/**
|
|
288
|
+
* De bron is gerate-limit; wacht minstens zo lang voor de volgende poging.
|
|
289
|
+
*
|
|
290
|
+
* In milliseconden, zoals elke andere duur in dit platform. Een bron die dit vult in
|
|
291
|
+
* plaats van te gooien, houdt de run `partial` in plaats van `failed` — het is uitstel,
|
|
292
|
+
* geen fout.
|
|
293
|
+
*/
|
|
294
|
+
retryAfterMs?: number;
|
|
295
|
+
}
|
|
296
|
+
/** `POST /provider/sync/land` — schrijf deze records weg. Alleen app-callers. */
|
|
297
|
+
export interface SyncLandRequest {
|
|
298
|
+
/** Waar ze vandaan komen, voor `source`/attributie op de gelande rij. */
|
|
299
|
+
sourceId: string;
|
|
300
|
+
records: SyncRecord[];
|
|
301
|
+
}
|
|
302
|
+
/** Wat er met één record gebeurde. */
|
|
303
|
+
export interface SyncLandOutcome {
|
|
304
|
+
externalId: string;
|
|
305
|
+
/**
|
|
306
|
+
* `created` en `updated` zijn beide "geschreven"; `unchanged` bestaat apart omdat een
|
|
307
|
+
* idempotente resync die niets veranderde géén SSE-ruis mag maken en niet als werk mag
|
|
308
|
+
* tellen (het patroon uit `lib/upsert-dedup.ts`). `failed` is één rij, niet de batch.
|
|
309
|
+
*/
|
|
310
|
+
result: "created" | "updated" | "unchanged" | "deleted" | "failed";
|
|
311
|
+
/** De scopeKey van de gelande rij, zodat de run-log naar het resultaat kan linken. */
|
|
312
|
+
scopeKey?: string;
|
|
313
|
+
/** Alleen bij `failed`. Kort genoeg voor een tabelregel. */
|
|
314
|
+
error?: string;
|
|
315
|
+
}
|
|
316
|
+
/** Antwoord op `POST /provider/sync/land`. Wél in `ResponseFactory` verpakt. */
|
|
317
|
+
export interface SyncLandResponse {
|
|
318
|
+
outcomes: SyncLandOutcome[];
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Bare payload van `GET /provider/sync/land-describe`: welke soorten deze app kan landen.
|
|
322
|
+
*
|
|
323
|
+
* Apart van `ScopeDescribe`, ook al overlappen de soorten meestal: een app kan een soort
|
|
324
|
+
* autoriseren zonder er een ingest-route voor te hebben. Dat verschil zichtbaar maken is
|
|
325
|
+
* goedkoper dan een run die halverwege een 404 vindt.
|
|
326
|
+
*/
|
|
327
|
+
export interface SyncTargetDescribe {
|
|
328
|
+
source: string;
|
|
329
|
+
kinds: string[];
|
|
330
|
+
}
|
|
331
|
+
/** Hoe gezond een koppeling is. Wat de lijst als badge toont. */
|
|
332
|
+
export type SyncHealth =
|
|
333
|
+
/** Laatste run geslaagd. */
|
|
334
|
+
"ok"
|
|
335
|
+
/** Laatste run deels geslaagd, of gerate-limit. Loopt nog, maar niet schoon. */
|
|
336
|
+
| "degraded"
|
|
337
|
+
/** Laatste run gefaald — meestal een verlopen credential. Vraagt een mens. */
|
|
338
|
+
| "broken"
|
|
339
|
+
/** Nooit gedraaid, of een verplicht account ontbreekt. Nog geen fout. */
|
|
340
|
+
| "unconfigured";
|
|
341
|
+
/**
|
|
342
|
+
* Eén geconfigureerde koppeling: deze bron, met dit account, in deze organisatie.
|
|
343
|
+
*
|
|
344
|
+
* De credential zit hier **niet** in — `accountId` verwijst naar de canonieke
|
|
345
|
+
* {@link ManagedAccount}-store. Een tweede plek waar tokens leven is een tweede plek waar ze
|
|
346
|
+
* verlopen zonder dat iemand het weet.
|
|
347
|
+
*/
|
|
348
|
+
export interface SyncConnection {
|
|
349
|
+
id: string;
|
|
350
|
+
organizationId: string;
|
|
351
|
+
/** → {@link SyncSourceDefinition.id} */
|
|
352
|
+
sourceId: string;
|
|
353
|
+
/** → {@link ManagedAccount.id}. Afwezig bij een bron zonder credential. */
|
|
354
|
+
accountId?: string;
|
|
355
|
+
/**
|
|
356
|
+
* Bij `transport: "tool"`: de gekozen MCP-connector. Expliciet, niet run-time geraden —
|
|
357
|
+
* zo is in de UI te zien welke verbinding deze koppeling gebruikt, en verandert het antwoord
|
|
358
|
+
* niet doordat iemand anders dezelfde catalogus-entry toevoegt.
|
|
359
|
+
*
|
|
360
|
+
* Is de rij weg of uitgezet, dan gaat de koppeling naar `health: "unconfigured"` en faalt de
|
|
361
|
+
* run luid — nooit een stille lege pagina waarop de cursor doorschuift.
|
|
362
|
+
*/
|
|
363
|
+
connectorId?: string;
|
|
364
|
+
/**
|
|
365
|
+
* De ingevulde instellingen, geparsed. In de opslag staat hij als JSON-string, om dezelfde
|
|
366
|
+
* reden als de cursor: één schemaveld dat elke vorm moet kunnen dragen. **Niet** vanwege
|
|
367
|
+
* sleutel-mangeling — de client transformeert uitgaande sleutels niet.
|
|
368
|
+
*/
|
|
369
|
+
settings?: Record<string, unknown>;
|
|
370
|
+
/** Vrije naam; afwezig = het label van de bron. */
|
|
371
|
+
label?: string;
|
|
372
|
+
mode: SyncMode;
|
|
373
|
+
enabled: boolean;
|
|
374
|
+
/**
|
|
375
|
+
* Minimale tijd tussen twee rondes, in ms.
|
|
376
|
+
*
|
|
377
|
+
* Bij het aanmaken gekopieerd uit {@link SyncSourceDefinition.defaultIntervalMs}, zodat de
|
|
378
|
+
* tik hem kan lezen zonder per koppeling de bron-catalogus te bevragen — en zodat het
|
|
379
|
+
* effectieve interval te zien en te wijzigen is in plaats van ergens in code te zitten.
|
|
380
|
+
*/
|
|
381
|
+
intervalMs?: number;
|
|
382
|
+
/** Opaak, van de bron. Zie {@link SyncPullRequest.cursor}. */
|
|
383
|
+
cursor?: unknown;
|
|
384
|
+
/** Epoch ms van de laatste volledige veegronde. */
|
|
385
|
+
lastFullSweepAt?: number;
|
|
386
|
+
health: SyncHealth;
|
|
387
|
+
lastRunAt?: number;
|
|
388
|
+
/** De fout van de laatste gefaalde run, zodat de lijst hem kan tonen zonder een run te lezen. */
|
|
389
|
+
lastError?: string;
|
|
390
|
+
createdBy?: string;
|
|
391
|
+
createdAt?: number;
|
|
392
|
+
updatedAt?: number;
|
|
393
|
+
}
|
|
394
|
+
/** Waardoor een run begon. */
|
|
395
|
+
export type SyncRunTrigger = "cron" | "manual" | "webhook";
|
|
396
|
+
/**
|
|
397
|
+
* `partial` bestaat naast `done` en `failed` omdat "12 van de 500 rijen deden het niet" geen
|
|
398
|
+
* van beide is: de koppeling werkt, de cursor mag opschuiven, en er is toch iets om te melden.
|
|
399
|
+
* Zonder die derde staat wordt het óf een fout die de sync stilzet óf een succes dat de fouten
|
|
400
|
+
* verzwijgt.
|
|
401
|
+
*/
|
|
402
|
+
export type SyncRunStatus = "running" | "done" | "partial" | "failed";
|
|
403
|
+
/**
|
|
404
|
+
* Wat er tijdens één ronde gebeurde.
|
|
405
|
+
*
|
|
406
|
+
* Bewust dezelfde vorm als `PlaybookRun`: het is dezelfde vraag ("wat deed het systeem
|
|
407
|
+
* ongevraagd, en ging het goed?"), dus dezelfde velden en dezelfde schermen.
|
|
408
|
+
*/
|
|
409
|
+
export interface SyncRun {
|
|
410
|
+
id: string;
|
|
411
|
+
organizationId: string;
|
|
412
|
+
connectionId: string;
|
|
413
|
+
sourceId: string;
|
|
414
|
+
trigger: SyncRunTrigger;
|
|
415
|
+
status: SyncRunStatus;
|
|
416
|
+
startedAt: number;
|
|
417
|
+
finishedAt?: number;
|
|
418
|
+
/** Hoeveel pagina's deze run gelezen heeft. Verraadt een bron die niet opschiet. */
|
|
419
|
+
pages: number;
|
|
420
|
+
scanned: number;
|
|
421
|
+
/** `created` + `updated`. Expliciet niet `unchanged`, anders lijkt een lege resync werk. */
|
|
422
|
+
written: number;
|
|
423
|
+
/** `unchanged` — de gezonde uitkomst van een idempotente resync. */
|
|
424
|
+
skipped: number;
|
|
425
|
+
failed: number;
|
|
426
|
+
/** Was dit een veegronde? Verklaart waarom `scanned` ineens veel hoger is. */
|
|
427
|
+
fullSweep?: boolean;
|
|
428
|
+
/** De fout die de hele run stopte. Bij `partial` leeg — die staan in {@link SyncRun.errors}. */
|
|
429
|
+
error?: string;
|
|
430
|
+
/**
|
|
431
|
+
* De eerste N mislukte rijen, met hun `externalId`.
|
|
432
|
+
*
|
|
433
|
+
* Begrensd en niet volledig: een bron waarvan élke rij faalt zou anders een run-rij van
|
|
434
|
+
* megabytes opleveren. Het doel is debuggen ("welke rij, en waarom"), niet boekhouden.
|
|
435
|
+
*/
|
|
436
|
+
errors?: {
|
|
437
|
+
externalId: string;
|
|
438
|
+
message: string;
|
|
439
|
+
}[];
|
|
440
|
+
}
|
|
441
|
+
/** Hoeveel mislukte rijen een {@link SyncRun} onthoudt. */
|
|
442
|
+
export declare const SYNC_RUN_MAX_ERRORS = 20;
|