@opencxh/domain 1.172.1 → 1.172.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entities/activity/blocks.d.ts +30 -30
- package/dist/entities/activity/catalog.d.ts +63 -86
- package/dist/entities/activity/descriptor.d.ts +31 -32
- package/dist/entities/activity/index.d.ts +2 -2
- package/dist/entities/activity/preview.d.ts +7 -7
- package/dist/entities/activity/resolve.d.ts +37 -38
- package/dist/entities/activity/types.d.ts +34 -34
- package/dist/entities/ai-account/types.d.ts +3 -3
- package/dist/entities/ai-account/vendors.d.ts +25 -32
- package/dist/entities/ai-budget/types.d.ts +5 -5
- package/dist/entities/ai-conversation/types.d.ts +12 -12
- package/dist/entities/ai-profile/types.d.ts +14 -21
- package/dist/entities/ai-settings/types.d.ts +12 -13
- package/dist/entities/analytics/index.d.ts +4 -4
- package/dist/entities/analytics/source.d.ts +1 -1
- package/dist/entities/analytics/usage.d.ts +23 -28
- package/dist/entities/artifact/blocks.d.ts +54 -69
- package/dist/entities/artifact/markdown.d.ts +2 -3
- package/dist/entities/artifact/types.d.ts +46 -54
- package/dist/entities/assignment/types.d.ts +74 -88
- package/dist/entities/channel/index.d.ts +1 -1
- package/dist/entities/channel/signature.d.ts +8 -9
- package/dist/entities/communication/index.d.ts +1 -1
- package/dist/entities/contact/index.d.ts +1 -1
- package/dist/entities/custom-field-def/types.d.ts +14 -14
- package/dist/entities/external-identity/types.d.ts +45 -50
- package/dist/entities/interaction/index.d.ts +1 -1
- package/dist/entities/interaction/types.d.ts +44 -47
- package/dist/entities/live-lens/types.d.ts +39 -44
- package/dist/entities/mcp/types.d.ts +53 -57
- package/dist/entities/memory/alias.d.ts +7 -7
- package/dist/entities/memory/browse.d.ts +21 -21
- package/dist/entities/memory/ingest.d.ts +24 -28
- package/dist/entities/memory/item.d.ts +53 -58
- package/dist/entities/memory/kind.d.ts +25 -25
- package/dist/entities/memory/query.d.ts +49 -51
- package/dist/entities/playbook/actor.d.ts +17 -17
- package/dist/entities/playbook/assignment.d.ts +54 -63
- package/dist/entities/playbook/index.d.ts +2 -2
- package/dist/entities/playbook/labels.d.ts +4 -4
- package/dist/entities/playbook/trigger-vars.d.ts +14 -15
- package/dist/entities/playbook/types.d.ts +172 -302
- package/dist/entities/resource-reminder/types.d.ts +8 -9
- package/dist/entities/time-entry/duration.d.ts +11 -11
- package/dist/entities/time-entry/types.d.ts +59 -59
- package/dist/entities/time-entry/work-type.d.ts +21 -24
- package/dist/entities/topic/examples.d.ts +26 -27
- package/dist/entities/topic/scope.d.ts +11 -12
- package/dist/entities/topic/types.d.ts +26 -28
- package/dist/entities/user/types.d.ts +24 -28
- package/dist/entities/webhook/types.d.ts +6 -6
- package/dist/entities/work/keys.d.ts +25 -26
- package/dist/entities/work/ladder.d.ts +48 -52
- package/dist/entities/work/types.d.ts +108 -114
- package/dist/index.cjs +6 -6
- package/dist/index.d.ts +29 -28
- package/dist/index.js +1377 -1242
- package/dist/platform/account.d.ts +34 -38
- package/dist/platform/ai-tools.d.ts +48 -52
- package/dist/platform/api.d.ts +9 -10
- package/dist/platform/author.d.ts +7 -9
- package/dist/platform/capabilities.d.ts +3 -3
- package/dist/platform/communication.d.ts +163 -204
- package/dist/platform/context.d.ts +8 -8
- package/dist/platform/identity.d.ts +16 -19
- package/dist/platform/kernel.d.ts +1 -1
- package/dist/platform/manifest.d.ts +2 -2
- package/dist/platform/media.d.ts +13 -15
- package/dist/platform/permission.d.ts +39 -0
- package/dist/platform/permission.test.d.ts +1 -0
- package/dist/platform/presence.d.ts +30 -30
- package/dist/platform/provider.d.ts +14 -15
- package/dist/platform/resource-source.d.ts +43 -45
- package/dist/platform/resource.d.ts +17 -23
- package/dist/platform/scope.d.ts +35 -61
- package/dist/platform/services.d.ts +4 -4
- package/dist/platform/storage.d.ts +1 -1
- package/dist/platform/sync-source.d.ts +196 -216
- package/dist/platform/transcript-cadence.d.ts +37 -41
- package/dist/platform/ui.d.ts +6 -6
- package/dist/text/endpoint.d.ts +11 -27
- package/package.json +3 -2
|
@@ -1,415 +1,395 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pulling outside data in, settled once.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `console.error` die niemand ziet.
|
|
4
|
+
* The platform ingests in four independent ways today — a cron sync per provider app, webhooks, an
|
|
5
|
+
* event-driven enrichment job and loose backfills — and each of the four rebuilds the same six
|
|
6
|
+
* things: cursor, batching, locking, idempotency, error handling and observability. It shows:
|
|
7
|
+
* `cursorKey()` sits in the repo three times, `microsoft_sync_state` grows a column per sub-sync,
|
|
8
|
+
* and a sync that stalls is a `console.error` nobody sees.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Microsoft Graph
|
|
14
|
-
*
|
|
10
|
+
* The split that fixes that is the industry standard (Singer/Airbyte, Nango, Fivetran): **a narrow
|
|
11
|
+
* source contract with a thick generic runtime.** The source knows how to fetch one page from
|
|
12
|
+
* Microsoft Graph or Asana; the runtime knows how to checkpoint cursors, chain batches, back off
|
|
13
|
+
* and keep a run log. This file is that narrow contract.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Three endpoints, shaped like `memory-source` and `analytics-source` — so a provider role and not
|
|
16
|
+
* a service-bus key, because this runs server-side with no user present:
|
|
18
17
|
*
|
|
19
|
-
* |
|
|
18
|
+
* | Role | Endpoint | Answer |
|
|
20
19
|
* |---|---|---|
|
|
21
20
|
* | `sync-source` | `GET /provider/sync/describe` | {@link SyncSourceDescribe} |
|
|
22
21
|
* | `sync-source` | `POST /provider/sync/pull` | {@link SyncPullResponse} |
|
|
23
22
|
* | `sync-target` | `POST /provider/sync/land` | {@link SyncLandResponse} |
|
|
24
23
|
*
|
|
25
|
-
*
|
|
26
|
-
* `apps/work`
|
|
24
|
+
* Source and target are **separate roles**: `apps/microsoft` delivers records and lands none,
|
|
25
|
+
* `apps/work` lands records and delivers none. An app may be both, but need not be.
|
|
27
26
|
*/
|
|
28
|
-
/**
|
|
27
|
+
/** The provider role groups, as constants so a typo does not silently yield an empty list. */
|
|
29
28
|
export declare const SYNC_SOURCE_PROVIDER_GROUP = "sync-source";
|
|
30
29
|
export declare const SYNC_TARGET_PROVIDER_GROUP = "sync-target";
|
|
31
30
|
/**
|
|
32
|
-
*
|
|
31
|
+
* Continuous or one-off — a real difference, not a label.
|
|
33
32
|
*
|
|
34
|
-
* `continuous`
|
|
35
|
-
* "
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
* `continuous` runs on a tick and so has to be able to do nothing cheaply (a cursor saying
|
|
34
|
+
* "nothing new"). `once` is started by a person, may be expensive, and has to be **exhaustive**: a
|
|
35
|
+
* migration that fetches 95% is not a migration. That requirement is exactly why not every source
|
|
36
|
+
* can do both — see {@link SyncSourceDefinition.transport}.
|
|
38
37
|
*/
|
|
39
38
|
export type SyncMode = "continuous" | "once";
|
|
40
39
|
/**
|
|
41
|
-
*
|
|
40
|
+
* How the records actually arrive.
|
|
42
41
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* This sits in the contract because it sets the expectations the runtime and the UI may have, not
|
|
43
|
+
* as documentation:
|
|
45
44
|
*
|
|
46
|
-
* - **`native`** —
|
|
47
|
-
*
|
|
48
|
-
* - **`tool`** —
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* `once`
|
|
52
|
-
* - **`file`** —
|
|
45
|
+
* - **`native`** — the app talks to the vendor's API itself. Knows delta tokens and pagination,
|
|
46
|
+
* so suitable for `continuous` as well as `once`.
|
|
47
|
+
* - **`tool`** — the app goes through the AI tool/MCP layer. Fine for the long tail of systems
|
|
48
|
+
* where a dedicated connector does not pay off, but an MCP search tool answers "top-N relevant"
|
|
49
|
+
* and not "everything since X", and the protocol has no change token. A `tool` source claiming
|
|
50
|
+
* `once` promises completeness it cannot deliver.
|
|
51
|
+
* - **`file`** — an uploaded file (CSV). `once` by definition, and complete by definition.
|
|
53
52
|
*/
|
|
54
53
|
export type SyncTransport = "native" | "tool" | "file";
|
|
55
|
-
/** Eén ding dat een app van buiten kan halen. */
|
|
56
54
|
/**
|
|
57
|
-
*
|
|
55
|
+
* One setting of a connection, as the source declares it.
|
|
58
56
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
57
|
+
* Deliberately no ui-kit types here: `packages/domain` has no UI dependencies, and the form maps
|
|
58
|
+
* this declaration onto its own fields itself.
|
|
61
59
|
*/
|
|
62
60
|
export interface SyncSettingsField {
|
|
63
|
-
/**
|
|
61
|
+
/** Key in the settings object the source gets back on `pull`. */
|
|
64
62
|
key: string;
|
|
65
63
|
label: string;
|
|
66
64
|
type: "text" | "number" | "boolean" | "select";
|
|
67
65
|
required?: boolean;
|
|
68
|
-
/**
|
|
66
|
+
/** Only with `type: "select"`. */
|
|
69
67
|
options?: {
|
|
70
68
|
value: string;
|
|
71
69
|
label: string;
|
|
72
70
|
}[];
|
|
73
|
-
/**
|
|
71
|
+
/** Explanation under the field. For "empty = ..." cases, which are guesswork otherwise. */
|
|
74
72
|
help?: string;
|
|
75
73
|
/**
|
|
76
|
-
* Hint
|
|
74
|
+
* Hint that this value points at a resource in another app (`"work_project"`).
|
|
77
75
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
76
|
+
* Today the form renders it as a text field; the hint is here so a picker can be added later
|
|
77
|
+
* without the source changing. A hint, not a promise.
|
|
80
78
|
*/
|
|
81
79
|
resource?: string;
|
|
82
80
|
}
|
|
83
81
|
export interface SyncSourceDefinition {
|
|
84
82
|
/**
|
|
85
|
-
*
|
|
83
|
+
* Stable and app-namespaced: `microsoft.todo`, `asana.tasks`, `csv.contacts`.
|
|
86
84
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* voor app-eigen kinds.
|
|
85
|
+
* A **stored reference** — `SyncConnection.sourceId` keeps it — so renaming breaks existing
|
|
86
|
+
* connections. Dot as separator, like `MemoryKindId` for app-owned kinds.
|
|
90
87
|
*/
|
|
91
88
|
id: string;
|
|
92
89
|
label: string;
|
|
93
90
|
description?: string;
|
|
94
91
|
/**
|
|
95
|
-
*
|
|
92
|
+
* The scope kinds this source lands: `work_item`, `interaction`, `company`, `contact`.
|
|
96
93
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* registreren in plaats van halverwege een run.
|
|
94
|
+
* The same vocabulary as `ScopeDescribe.kinds`, and not a cosmetic choice — the runtime routes a
|
|
95
|
+
* record to the owning app with `findScopeOwner(kind)`. A kind without a scope owner cannot be
|
|
96
|
+
* landed, and you notice that while registering instead of halfway a run.
|
|
101
97
|
*/
|
|
102
98
|
kinds: string[];
|
|
103
|
-
/**
|
|
99
|
+
/** Which modes this source supports. Empty is pointless; at least one. */
|
|
104
100
|
modes: SyncMode[];
|
|
105
101
|
transport: SyncTransport;
|
|
106
102
|
/**
|
|
107
|
-
*
|
|
103
|
+
* The `providerId` of the {@link ManagedAccount} this source needs.
|
|
108
104
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
105
|
+
* Absent = no credential needed (a file upload). Present means a connection does not work
|
|
106
|
+
* without a chosen account, and the UI can say so before the first run instead of showing a
|
|
107
|
+
* failed one.
|
|
112
108
|
*/
|
|
113
109
|
accountProviderId?: string;
|
|
114
110
|
/**
|
|
115
|
-
*
|
|
111
|
+
* Settings that differ per connection and therefore do not belong in code.
|
|
116
112
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* frontend-bestanden) mogelijk houdt.
|
|
113
|
+
* Declarative, so a connector app needs **no frontend** to be configurable: the Connections form
|
|
114
|
+
* renders these fields. That is what keeps `apps/asana` (zero frontend files) possible.
|
|
120
115
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* naar runtime.
|
|
116
|
+
* Kept flat on purpose. A nested schema would turn into a mapping DSL, which was rejected
|
|
117
|
+
* earlier: date formats and enums differ per source, and errors then move from `tsc` to runtime.
|
|
124
118
|
*/
|
|
125
119
|
settingsSchema?: SyncSettingsField[];
|
|
126
120
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
121
|
+
* Only with `transport: "tool"`: the catalog key the connector picker pre-filters on
|
|
122
|
+
* (`"asana"`, `"linear"`).
|
|
129
123
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* simpelweg de volledige lijst.
|
|
124
|
+
* A **hint**, not a resolution: it decides which of the organisation's MCP connections are
|
|
125
|
+
* offered as candidates. What the connection stores afterwards is the connector **id**
|
|
126
|
+
* ({@link SyncConnection.connectorId}). Without a hint the user simply gets the full list.
|
|
134
127
|
*/
|
|
135
128
|
connectorHint?: string;
|
|
136
129
|
/**
|
|
137
|
-
*
|
|
138
|
-
* organisatie-credential.
|
|
130
|
+
* Only with `transport: "tool"`: this source can only run on a shared organisation credential.
|
|
139
131
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
132
|
+
* Why this exists: an unattended round (cron, sync) has no acting user, so a connector with
|
|
133
|
+
* `credentialScope: "per-user"` yields zero tools there by definition — which used to show up
|
|
134
|
+
* only as "the sync does nothing". With this flag, saving a `continuous` connection is refused
|
|
135
|
+
* right away, with an explanation.
|
|
144
136
|
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
137
|
+
* Only for `continuous`: a `once` connection is started by a person, and then there *is* an
|
|
138
|
+
* acting user whose token can be used.
|
|
147
139
|
*/
|
|
148
140
|
requiresCredentialScope?: "shared";
|
|
149
141
|
/**
|
|
150
|
-
*
|
|
142
|
+
* Recommended minimum time between two rounds, in ms. The organisation may override it.
|
|
151
143
|
*
|
|
152
|
-
*
|
|
153
|
-
* `JobOptions.schedule`
|
|
154
|
-
*
|
|
155
|
-
*
|
|
144
|
+
* An **interval** and not a cron expression, because that is what is actually executable:
|
|
145
|
+
* `JobOptions.schedule` is fixed at registration, so a per-connection cron string would never be
|
|
146
|
+
* evaluated — a field promising what it does not do. The runtime ticks on a coarse grid and
|
|
147
|
+
* gates on this.
|
|
156
148
|
*
|
|
157
|
-
*
|
|
158
|
-
* delta
|
|
149
|
+
* On the source and not in the runtime, because only the source knows what its API tolerates: a
|
|
150
|
+
* delta feed may run every minute, a list endpoint returning everything every time may not.
|
|
159
151
|
*/
|
|
160
152
|
defaultIntervalMs?: number;
|
|
161
153
|
/**
|
|
162
|
-
*
|
|
154
|
+
* How often the source should ignore its cursor and read everything again (ms).
|
|
163
155
|
*
|
|
164
|
-
*
|
|
165
|
-
* `CHAT_FULL_SWEEP_INTERVAL_MS`: cursors
|
|
166
|
-
* `updatedAt`
|
|
167
|
-
*
|
|
168
|
-
* delta-feed die verwijderingen zelf meldt).
|
|
156
|
+
* The reconcile sweep from the Stripe/Shopify pattern, already proven in this codebase as
|
|
157
|
+
* `CHAT_FULL_SWEEP_INTERVAL_MS`: cursors rest on the assumption that the vendor bumps an
|
|
158
|
+
* `updatedAt` for everything that counts, which holds for new records but is unreliable for
|
|
159
|
+
* edits and deletions. Absent = never sweep (right for a delta feed that reports deletions).
|
|
169
160
|
*/
|
|
170
161
|
fullSweepIntervalMs?: number;
|
|
171
162
|
}
|
|
172
163
|
/**
|
|
173
|
-
* Bare payload
|
|
174
|
-
*
|
|
164
|
+
* Bare payload of `GET /provider/sync/describe` — **not** wrapped in `ResponseFactory` (template:
|
|
165
|
+
* {@link MemorySourceDescription} and `AnalyticsSourceDescription`).
|
|
175
166
|
*/
|
|
176
167
|
export interface SyncSourceDescribe {
|
|
177
|
-
/**
|
|
168
|
+
/** The declaring app (== `manifest.name` == `req.source.app`). */
|
|
178
169
|
source: string;
|
|
179
170
|
sources: SyncSourceDefinition[];
|
|
180
171
|
}
|
|
181
172
|
/**
|
|
182
|
-
*
|
|
173
|
+
* One record from a source system, ready to be landed.
|
|
183
174
|
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
175
|
+
* The source does the mapping into the shape the target app expects. Deliberately so: the runtime
|
|
176
|
+
* knows no field of any vendor, and the moment it did it would stop being generic. See
|
|
177
|
+
* {@link SyncRecord.data}.
|
|
187
178
|
*/
|
|
188
179
|
export interface SyncRecord {
|
|
189
180
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
181
|
+
* The stable id in the source system, namespaced by the source itself (`asana_task:12345`,
|
|
182
|
+
* `graph-todo:AAMk…`).
|
|
192
183
|
*
|
|
193
|
-
*
|
|
194
|
-
* `upsert_by_external_provider_id
|
|
195
|
-
*
|
|
196
|
-
*
|
|
184
|
+
* This is the dedupe axis: the target app upserts on it through its
|
|
185
|
+
* `upsert_by_external_provider_id` route, so running the same run twice writes one row.
|
|
186
|
+
* "Stable" is the whole requirement — an id that changes per page turns every sync into a
|
|
187
|
+
* duplicate factory.
|
|
197
188
|
*/
|
|
198
189
|
externalId: string;
|
|
199
190
|
/**
|
|
200
|
-
* Ids
|
|
191
|
+
* Ids pointing at the **container** and not at this row: the list, the project, the board.
|
|
201
192
|
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* te groeien.
|
|
193
|
+
* They are stored (outgoing dispatch needs them) but must never take part in the dedupe. This
|
|
194
|
+
* field exists because it went wrong once: next to the task id Microsoft Graph also sends
|
|
195
|
+
* `graph-todolist:<id>`, identical for every task in that list, and searching on it collapsed
|
|
196
|
+
* the whole list onto one row (`task/external-ids.ts`). Repaired there with a prefix list; here
|
|
197
|
+
* the source says it itself, so that list does not have to grow.
|
|
208
198
|
*/
|
|
209
199
|
containerExternalIds?: string[];
|
|
210
|
-
/**
|
|
200
|
+
/** The scope kind, from {@link SyncSourceDefinition.kinds}. Decides which app this goes to. */
|
|
211
201
|
kind: string;
|
|
212
202
|
/**
|
|
213
|
-
*
|
|
203
|
+
* The fields for the target app.
|
|
214
204
|
*
|
|
215
|
-
* **
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* bij een browser langs. (En sinds 17-08-2026 mangelt de client helemaal niets meer.)
|
|
220
|
-
*
|
|
221
|
-
* De regel blijft, om de reden die er werkelijk is: de doel-app moet de velden kunnen *typen*.
|
|
222
|
-
* `{ "asana_gid_123": {...} }` is voor `upsertWorkItemFromExternal` niet te lezen zonder eerst
|
|
223
|
-
* te weten wat de bron erin stopte, en dan verschuift elke fout van `tsc` naar runtime.
|
|
205
|
+
* **Fixed field names, never a map keyed by an external id.** The target app has to be able to
|
|
206
|
+
* *type* the fields: `{ "asana_gid_123": {...} }` cannot be read by `upsertWorkItemFromExternal`
|
|
207
|
+
* without first knowing what the source put in there, and then every error moves from `tsc` to
|
|
208
|
+
* runtime.
|
|
224
209
|
*/
|
|
225
210
|
data: Record<string, unknown>;
|
|
226
211
|
/**
|
|
227
|
-
*
|
|
212
|
+
* This record was deleted at the source.
|
|
228
213
|
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* halve administratie opruimt.
|
|
214
|
+
* Only meaningful when the source *can* know: a delta feed reports deletions, a list endpoint
|
|
215
|
+
* simply leaves them out. Absence from a list is **not** a deletion — not respecting that
|
|
216
|
+
* difference means a filtered or paginated response clears out half your administration.
|
|
233
217
|
*/
|
|
234
218
|
deleted?: boolean;
|
|
235
219
|
}
|
|
236
|
-
/** `POST /provider/sync/pull` —
|
|
220
|
+
/** `POST /provider/sync/pull` — fetch one page. */
|
|
237
221
|
export interface SyncPullRequest {
|
|
238
222
|
sourceId: string;
|
|
239
|
-
/**
|
|
223
|
+
/** So the source can log and find its own per-connection state, if it has any. */
|
|
240
224
|
connectionId: string;
|
|
241
|
-
/**
|
|
225
|
+
/** The {@link ManagedAccount} to talk with. Absent with `transport: "file"`. */
|
|
242
226
|
accountId?: string;
|
|
243
227
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
228
|
+
* With `transport: "tool"`: the id of the MCP connector this connection may use, chosen when the
|
|
229
|
+
* connection was configured.
|
|
246
230
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* van iemands persoonlijke connector krijgen.
|
|
231
|
+
* This makes the tool name a pure string — `mcpToolPrefix(connectorId) + tool name` — with no
|
|
232
|
+
* resolution RPC per page. A source used to look its connector up by catalog key, which could
|
|
233
|
+
* not pick a winner with two candidates (an org row next to a personal one, or two users each
|
|
234
|
+
* with their own); an app could even end up with the prefix of somebody's personal connector.
|
|
252
235
|
*/
|
|
253
236
|
connectorId?: string;
|
|
254
237
|
/**
|
|
255
|
-
*
|
|
238
|
+
* The settings of this connection, per {@link SyncSourceDefinition.settingsSchema}.
|
|
256
239
|
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* de opslag is er om dezelfde reden als de cursor: één schemaveld dat elke vorm moet kunnen
|
|
261
|
-
* dragen.
|
|
240
|
+
* An **object** here and a JSON string in storage — for the same reason as the cursor: one
|
|
241
|
+
* schema field that has to be able to carry any shape. Not because of key mangling; the client
|
|
242
|
+
* does not transform outgoing keys.
|
|
262
243
|
*/
|
|
263
244
|
settings?: Record<string, unknown>;
|
|
264
245
|
/**
|
|
265
|
-
*
|
|
246
|
+
* Opaque, exactly as the source returned it last time. `undefined` = from the start.
|
|
266
247
|
*
|
|
267
|
-
* **
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* `chatFullSweepAt`
|
|
248
|
+
* **The runtime never looks inside.** A Graph `deltaLink`, a timestamp, a page token — it does
|
|
249
|
+
* not matter, and that is precisely why no column per sub-sync has to exist. That
|
|
250
|
+
* `microsoft_sync_state` today carries `todoDeltaLink` next to `chatCursors` next to
|
|
251
|
+
* `chatFullSweepAt` is exactly what an opaque field prevents.
|
|
271
252
|
*/
|
|
272
253
|
cursor?: unknown;
|
|
273
|
-
/**
|
|
254
|
+
/** Ignore the cursor and read everything again — the reconcile sweep. */
|
|
274
255
|
fullSweep?: boolean;
|
|
275
|
-
/** Maximum
|
|
256
|
+
/** Maximum number of records in this answer. The source may give fewer, never more. */
|
|
276
257
|
limit: number;
|
|
277
258
|
}
|
|
278
|
-
/**
|
|
259
|
+
/** Answer to `POST /provider/sync/pull`. This one *is* wrapped in `ResponseFactory`. */
|
|
279
260
|
export interface SyncPullResponse {
|
|
280
261
|
records: SyncRecord[];
|
|
281
262
|
/**
|
|
282
|
-
*
|
|
263
|
+
* The cursor after this page. The runtime stores it **only once the records have landed**.
|
|
283
264
|
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
265
|
+
* That is the Singer/Airbyte rule and the reason crash recovery works: if things fall over
|
|
266
|
+
* between pull and land, the batch is repeated (idempotent, because upsert on `externalId`)
|
|
267
|
+
* instead of skipped. The other way round — cursor first — loses records silently, and silent is
|
|
268
|
+
* the problem here.
|
|
288
269
|
*/
|
|
289
270
|
cursor?: unknown;
|
|
290
|
-
/**
|
|
271
|
+
/** No more data after this page. The runtime then does not chain on. */
|
|
291
272
|
done: boolean;
|
|
292
273
|
/**
|
|
293
|
-
*
|
|
274
|
+
* The source is rate-limited; wait at least this long before the next attempt.
|
|
294
275
|
*
|
|
295
|
-
* In
|
|
296
|
-
*
|
|
297
|
-
*
|
|
276
|
+
* In milliseconds, like every other duration in this platform. A source that fills this in
|
|
277
|
+
* instead of throwing keeps the run `partial` rather than `failed` — it is a delay, not an
|
|
278
|
+
* error.
|
|
298
279
|
*/
|
|
299
280
|
retryAfterMs?: number;
|
|
300
281
|
}
|
|
301
|
-
/** `POST /provider/sync/land` —
|
|
282
|
+
/** `POST /provider/sync/land` — write these records. App callers only. */
|
|
302
283
|
export interface SyncLandRequest {
|
|
303
|
-
/**
|
|
284
|
+
/** Where they came from, for `source`/attribution on the landed row. */
|
|
304
285
|
sourceId: string;
|
|
305
286
|
records: SyncRecord[];
|
|
306
287
|
}
|
|
307
|
-
/**
|
|
288
|
+
/** What happened to one record. */
|
|
308
289
|
export interface SyncLandOutcome {
|
|
309
290
|
externalId: string;
|
|
310
291
|
/**
|
|
311
|
-
* `created`
|
|
312
|
-
*
|
|
313
|
-
*
|
|
292
|
+
* `created` and `updated` are both "written"; `unchanged` exists separately because an
|
|
293
|
+
* idempotent resync that changed nothing must not make SSE noise and must not count as work
|
|
294
|
+
* (the pattern from `lib/upsert-dedup.ts`). `failed` is one row, not the batch.
|
|
314
295
|
*/
|
|
315
296
|
result: "created" | "updated" | "unchanged" | "deleted" | "failed";
|
|
316
|
-
/**
|
|
297
|
+
/** The scopeKey of the landed row, so the run log can link to the result. */
|
|
317
298
|
scopeKey?: string;
|
|
318
|
-
/**
|
|
299
|
+
/** Only on `failed`. Short enough for a table row. */
|
|
319
300
|
error?: string;
|
|
320
301
|
}
|
|
321
|
-
/**
|
|
302
|
+
/** Answer to `POST /provider/sync/land`. This one *is* wrapped in `ResponseFactory`. */
|
|
322
303
|
export interface SyncLandResponse {
|
|
323
304
|
outcomes: SyncLandOutcome[];
|
|
324
305
|
}
|
|
325
306
|
/**
|
|
326
|
-
* Bare payload
|
|
307
|
+
* Bare payload of `GET /provider/sync/land-describe`: which kinds this app can land.
|
|
327
308
|
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
309
|
+
* Separate from `ScopeDescribe`, even though the kinds usually overlap: an app can authorize a
|
|
310
|
+
* kind without having an ingest route for it. Making that difference visible is cheaper than a run
|
|
311
|
+
* that finds a 404 halfway.
|
|
331
312
|
*/
|
|
332
313
|
export interface SyncTargetDescribe {
|
|
333
314
|
source: string;
|
|
334
315
|
kinds: string[];
|
|
335
316
|
}
|
|
336
|
-
/**
|
|
317
|
+
/** How healthy a connection is. What the list shows as a badge. */
|
|
337
318
|
export type SyncHealth =
|
|
338
|
-
/**
|
|
319
|
+
/** Last run succeeded. */
|
|
339
320
|
"ok"
|
|
340
|
-
/**
|
|
321
|
+
/** Last run partly succeeded, or was rate-limited. Still running, but not cleanly. */
|
|
341
322
|
| "degraded"
|
|
342
|
-
/**
|
|
323
|
+
/** Last run failed — usually an expired credential. Needs a person. */
|
|
343
324
|
| "broken"
|
|
344
|
-
/**
|
|
325
|
+
/** Never ran, or a required account is missing. Not an error yet. */
|
|
345
326
|
| "unconfigured";
|
|
346
327
|
/**
|
|
347
|
-
*
|
|
328
|
+
* One configured connection: this source, with this account, in this organisation.
|
|
348
329
|
*
|
|
349
|
-
*
|
|
350
|
-
* {@link ManagedAccount}
|
|
351
|
-
*
|
|
330
|
+
* The credential is **not** in here — `accountId` points at the canonical
|
|
331
|
+
* {@link ManagedAccount} store. A second place where tokens live is a second place where they
|
|
332
|
+
* expire without anyone knowing.
|
|
352
333
|
*/
|
|
353
334
|
export interface SyncConnection {
|
|
354
335
|
id: string;
|
|
355
336
|
organizationId: string;
|
|
356
337
|
/** → {@link SyncSourceDefinition.id} */
|
|
357
338
|
sourceId: string;
|
|
358
|
-
/** → {@link ManagedAccount.id}.
|
|
339
|
+
/** → {@link ManagedAccount.id}. Absent for a source without a credential. */
|
|
359
340
|
accountId?: string;
|
|
360
341
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
342
|
+
* With `transport: "tool"`: the chosen MCP connector. Explicit, not guessed at run time — so the
|
|
343
|
+
* UI shows which connection this one uses, and the answer does not change because somebody else
|
|
344
|
+
* adds the same catalog entry.
|
|
364
345
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
346
|
+
* If the row is gone or disabled, the connection goes to `health: "unconfigured"` and the run
|
|
347
|
+
* fails loudly — never a silent empty page the cursor advances over.
|
|
367
348
|
*/
|
|
368
349
|
connectorId?: string;
|
|
369
350
|
/**
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* sleutel-mangeling — de client transformeert uitgaande sleutels niet.
|
|
351
|
+
* The filled-in settings, parsed. In storage it is a JSON string, for the same reason as the
|
|
352
|
+
* cursor: one schema field that has to be able to carry any shape.
|
|
373
353
|
*/
|
|
374
354
|
settings?: Record<string, unknown>;
|
|
375
|
-
/**
|
|
355
|
+
/** Free-form name; absent = the source's label. */
|
|
376
356
|
label?: string;
|
|
377
357
|
mode: SyncMode;
|
|
378
358
|
enabled: boolean;
|
|
379
359
|
/**
|
|
380
|
-
*
|
|
360
|
+
* Minimum time between two rounds, in ms.
|
|
381
361
|
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
362
|
+
* Copied from {@link SyncSourceDefinition.defaultIntervalMs} on creation, so the tick can read
|
|
363
|
+
* it without querying the source catalog per connection — and so the effective interval is
|
|
364
|
+
* visible and changeable instead of buried in code.
|
|
385
365
|
*/
|
|
386
366
|
intervalMs?: number;
|
|
387
|
-
/**
|
|
367
|
+
/** Opaque, from the source. See {@link SyncPullRequest.cursor}. */
|
|
388
368
|
cursor?: unknown;
|
|
389
|
-
/** Epoch ms
|
|
369
|
+
/** Epoch ms of the last full sweep. */
|
|
390
370
|
lastFullSweepAt?: number;
|
|
391
371
|
health: SyncHealth;
|
|
392
372
|
lastRunAt?: number;
|
|
393
|
-
/**
|
|
373
|
+
/** The error of the last failed run, so the list can show it without reading a run. */
|
|
394
374
|
lastError?: string;
|
|
395
375
|
createdBy?: string;
|
|
396
376
|
createdAt?: number;
|
|
397
377
|
updatedAt?: number;
|
|
398
378
|
}
|
|
399
|
-
/**
|
|
379
|
+
/** What started a run. */
|
|
400
380
|
export type SyncRunTrigger = "cron" | "manual" | "webhook";
|
|
401
381
|
/**
|
|
402
|
-
* `partial`
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
382
|
+
* `partial` exists next to `done` and `failed` because "12 of the 500 rows did not make it" is
|
|
383
|
+
* neither: the connection works, the cursor may advance, and there is still something to report.
|
|
384
|
+
* Without that third state it becomes either an error that halts the sync or a success that hides
|
|
385
|
+
* the failures.
|
|
406
386
|
*/
|
|
407
387
|
export type SyncRunStatus = "running" | "done" | "partial" | "failed";
|
|
408
388
|
/**
|
|
409
|
-
*
|
|
389
|
+
* What happened during one round.
|
|
410
390
|
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
391
|
+
* Deliberately the same shape as `PlaybookRun`: it is the same question ("what did the system do
|
|
392
|
+
* unasked, and did it go well?"), so the same fields and the same screens.
|
|
413
393
|
*/
|
|
414
394
|
export interface SyncRun {
|
|
415
395
|
id: string;
|
|
@@ -420,28 +400,28 @@ export interface SyncRun {
|
|
|
420
400
|
status: SyncRunStatus;
|
|
421
401
|
startedAt: number;
|
|
422
402
|
finishedAt?: number;
|
|
423
|
-
/**
|
|
403
|
+
/** How many pages this run read. Gives away a source that is not making progress. */
|
|
424
404
|
pages: number;
|
|
425
405
|
scanned: number;
|
|
426
|
-
/** `created` + `updated`.
|
|
406
|
+
/** `created` + `updated`. Explicitly not `unchanged`, or an empty resync looks like work. */
|
|
427
407
|
written: number;
|
|
428
|
-
/** `unchanged` —
|
|
408
|
+
/** `unchanged` — the healthy outcome of an idempotent resync. */
|
|
429
409
|
skipped: number;
|
|
430
410
|
failed: number;
|
|
431
|
-
/** Was
|
|
411
|
+
/** Was this a sweep? Explains why `scanned` is suddenly much higher. */
|
|
432
412
|
fullSweep?: boolean;
|
|
433
|
-
/**
|
|
413
|
+
/** The error that stopped the whole run. Empty on `partial` — see {@link SyncRun.errors}. */
|
|
434
414
|
error?: string;
|
|
435
415
|
/**
|
|
436
|
-
*
|
|
416
|
+
* The first N failed rows, with their `externalId`.
|
|
437
417
|
*
|
|
438
|
-
*
|
|
439
|
-
* megabytes
|
|
418
|
+
* Bounded and not complete: a source where every row fails would otherwise produce a run row of
|
|
419
|
+
* megabytes. The goal is debugging ("which row, and why"), not bookkeeping.
|
|
440
420
|
*/
|
|
441
421
|
errors?: {
|
|
442
422
|
externalId: string;
|
|
443
423
|
message: string;
|
|
444
424
|
}[];
|
|
445
425
|
}
|
|
446
|
-
/**
|
|
426
|
+
/** How many failed rows a {@link SyncRun} remembers. */
|
|
447
427
|
export declare const SYNC_RUN_MAX_ERRORS = 20;
|