@sixb/sqlite 0.1.1 → 0.1.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/README.md +18 -13
- package/dist/action-run-storage.d.ts +5 -2
- package/dist/action-run-storage.d.ts.map +1 -1
- package/dist/agents/agent-storage.d.ts +4 -2
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +3 -10
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +3 -2
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts +22 -0
- package/dist/ai-usage-storage.d.ts.map +1 -0
- package/dist/auth-storage/service-accounts.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts +1 -10
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/connector-connection-storage.d.ts +9 -0
- package/dist/connector-connection-storage.d.ts.map +1 -0
- package/dist/execution-storage.d.ts +14 -0
- package/dist/execution-storage.d.ts.map +1 -0
- package/dist/index.d.ts +13 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3862 -1042
- package/dist/index.js.map +30 -27
- package/dist/latest-run-query.d.ts.map +1 -1
- package/dist/migrations.d.ts +0 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts +50 -8
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +4 -5
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts +0 -2
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/outbox.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +3 -3
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/pipeline-run-storage.d.ts +7 -2
- package/dist/pipeline-run-storage.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts +9 -2
- package/dist/projection-run-storage.d.ts.map +1 -1
- package/dist/run-list-query.d.ts +1 -1
- package/dist/run-list-query.d.ts.map +1 -1
- package/dist/sync-run-storage.d.ts +7 -2
- package/dist/sync-run-storage.d.ts.map +1 -1
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/transactions.d.ts +3 -0
- package/dist/transactions.d.ts.map +1 -1
- package/dist/webhook-run-storage.d.ts +9 -2
- package/dist/webhook-run-storage.d.ts.map +1 -1
- package/dist/workflow-run-storage.d.ts +7 -3
- package/dist/workflow-run-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/action-run-storage.ts +51 -74
- package/src/agents/agent-storage.ts +5 -3
- package/src/agents/rows.ts +8 -40
- package/src/agents/runs.ts +27 -21
- package/src/ai-usage-storage.ts +324 -0
- package/src/auth-storage/service-accounts.ts +14 -21
- package/src/auth-storage/shared.ts +0 -22
- package/src/connector-connection-storage.ts +784 -0
- package/src/execution-storage.ts +158 -0
- package/src/index.ts +63 -22
- package/src/latest-run-query.ts +7 -1
- package/src/migrations/004-executions.sql +142 -0
- package/src/migrations/005-workflow-executions.sql +7 -0
- package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
- package/src/migrations/007-split-overrides.sql +154 -0
- package/src/migrations/008-action-executions.sql +4 -0
- package/src/migrations/009-agent-executions.sql +115 -0
- package/src/migrations/010-ai-usage-accounting-foundation.sql +74 -0
- package/src/migrations/011-sync-failure-record.sql +22 -0
- package/src/migrations/012-pipeline-failure-record.sql +45 -0
- package/src/migrations/013-workflow-failure-record.sql +45 -0
- package/src/migrations/014-agent-failure-record.sql +59 -0
- package/src/migrations/015-projection-failure-record.sql +20 -0
- package/src/migrations/016-webhook-run-failure-record.sql +19 -0
- package/src/migrations/017-action-failure-record.sql +65 -0
- package/src/migrations/018-ontology-outbox-failure-record.sql +80 -0
- package/src/migrations/019-webhook-delivery-failure-record.sql +62 -0
- package/src/migrations/020-drop-run-usage-projections.sql +7 -0
- package/src/migrations/021-sync-pipeline-executions.sql +101 -0
- package/src/migrations/022-projection-executions.sql +244 -0
- package/src/migrations/023-webhook-executions.sql +51 -0
- package/src/migrations/024-ontology-commit-executions.sql +58 -0
- package/src/migrations/025-connector-connections.sql +257 -0
- package/src/migrations.ts +119 -4
- package/src/ontology-storage/materialization-session.ts +317 -143
- package/src/ontology-storage/materialization-state.ts +321 -112
- package/src/ontology-storage/materialization-writer.ts +142 -67
- package/src/ontology-storage/materializations.ts +67 -141
- package/src/ontology-storage/outbox.ts +10 -3
- package/src/ontology-storage/shared.ts +10 -4
- package/src/ontology-storage/sources.ts +4 -1
- package/src/pipeline-run-storage.ts +128 -43
- package/src/projection-run-storage.ts +209 -97
- package/src/run-list-query.ts +9 -4
- package/src/sync-run-storage.ts +118 -32
- package/src/testing.ts +20 -0
- package/src/transactions.ts +10 -3
- package/src/webhook-run-storage.ts +118 -74
- package/src/workflow-run-storage.ts +117 -131
- package/dist/validation.d.ts +0 -5
- package/dist/validation.d.ts.map +0 -1
- package/dist/webhook-delivery-storage.d.ts +0 -27
- package/dist/webhook-delivery-storage.d.ts.map +0 -1
- package/src/validation.ts +0 -30
- package/src/webhook-delivery-storage.ts +0 -272
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
import type { Database, SQLQueryBindings } from "bun:sqlite"
|
|
2
|
+
import {
|
|
3
|
+
type ConnectorConnectionPersistence,
|
|
4
|
+
type ConnectorConnectionPersistenceBackend,
|
|
5
|
+
type ConnectorConnectionPersistenceScope,
|
|
6
|
+
DurableConnectorConnectionStorage,
|
|
7
|
+
} from "@sixb/core/internal/connector-connection-storage-provider"
|
|
8
|
+
import type {
|
|
9
|
+
ConnectorAuthorizationAttemptRecord,
|
|
10
|
+
ConnectorAuthorizationRecord,
|
|
11
|
+
ConnectorConnectionRecord,
|
|
12
|
+
ConnectorConnectionRunRecord,
|
|
13
|
+
ConnectorCredentialMutation,
|
|
14
|
+
} from "@sixb/core/storage"
|
|
15
|
+
import { isUniqueConstraintError } from "./storage-errors"
|
|
16
|
+
import { runImmediateTransactionAsync, type SqliteStoreConnection } from "./transactions"
|
|
17
|
+
|
|
18
|
+
export class SqliteConnectorConnectionStorage extends DurableConnectorConnectionStorage {
|
|
19
|
+
private readonly clock: ConnectorConnectionTestClock
|
|
20
|
+
|
|
21
|
+
constructor(connection: SqliteStoreConnection) {
|
|
22
|
+
const clock = { offsetMs: 0 }
|
|
23
|
+
super(new SqliteConnectorConnectionPersistenceBackend(connection.db, clock))
|
|
24
|
+
this.clock = clock
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Internal deterministic clock control used only by the provider contract suite. */
|
|
28
|
+
advanceTimeForTesting(durationMs: number): void {
|
|
29
|
+
this.clock.offsetMs += durationMs
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class SqliteConnectorConnectionPersistenceBackend implements ConnectorConnectionPersistenceBackend {
|
|
34
|
+
constructor(
|
|
35
|
+
private readonly db: Database,
|
|
36
|
+
private readonly clock: ConnectorConnectionTestClock
|
|
37
|
+
) {}
|
|
38
|
+
|
|
39
|
+
read<T>(
|
|
40
|
+
scope: ConnectorConnectionPersistenceScope,
|
|
41
|
+
run: (persistence: ConnectorConnectionPersistence) => Promise<T>
|
|
42
|
+
): Promise<T> {
|
|
43
|
+
return run(new SqliteConnectorConnectionPersistence(this.db, scope, this.clock))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
transaction<T>(
|
|
47
|
+
scope: ConnectorConnectionPersistenceScope,
|
|
48
|
+
run: (persistence: ConnectorConnectionPersistence) => Promise<T>
|
|
49
|
+
): Promise<T> {
|
|
50
|
+
return runImmediateTransactionAsync(this.db, () =>
|
|
51
|
+
run(new SqliteConnectorConnectionPersistence(this.db, scope, this.clock))
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class SqliteConnectorConnectionPersistence implements ConnectorConnectionPersistence {
|
|
57
|
+
constructor(
|
|
58
|
+
private readonly db: Database,
|
|
59
|
+
private readonly scope: ConnectorConnectionPersistenceScope,
|
|
60
|
+
private readonly clock: ConnectorConnectionTestClock
|
|
61
|
+
) {}
|
|
62
|
+
|
|
63
|
+
async now(): Promise<Date> {
|
|
64
|
+
const row = this.db.query("SELECT strftime('%Y-%m-%dT%H:%M:%fZ', 'now') AS now").get() as {
|
|
65
|
+
readonly now: string
|
|
66
|
+
}
|
|
67
|
+
return new Date(new Date(row.now).getTime() + this.clock.offsetMs)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async insertAuthorizationAttempt(record: ConnectorAuthorizationAttemptRecord): Promise<boolean> {
|
|
71
|
+
try {
|
|
72
|
+
this.db
|
|
73
|
+
.query(
|
|
74
|
+
`
|
|
75
|
+
INSERT INTO connector_authorization_attempts (
|
|
76
|
+
project_id, connector_id, id, slot, initiated_by_execution_id,
|
|
77
|
+
state_hash, code_verifier, redirect_uri, connection_run_id, return_to,
|
|
78
|
+
callback_binding_hash, reauthorization_id, reauthorization_revision,
|
|
79
|
+
reauthorization_connection_ids, created_at, expires_at
|
|
80
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
81
|
+
`
|
|
82
|
+
)
|
|
83
|
+
.run(
|
|
84
|
+
record.projectId,
|
|
85
|
+
record.connectorId,
|
|
86
|
+
record.id,
|
|
87
|
+
record.slot,
|
|
88
|
+
record.initiatedByExecutionId,
|
|
89
|
+
record.stateHash,
|
|
90
|
+
json(record.codeVerifier),
|
|
91
|
+
record.redirectUri,
|
|
92
|
+
record.connectionRunId ?? null,
|
|
93
|
+
record.returnTo ?? null,
|
|
94
|
+
record.callbackBindingHash ?? null,
|
|
95
|
+
record.reauthorizationId ?? null,
|
|
96
|
+
record.reauthorizationRevision ?? null,
|
|
97
|
+
record.reauthorizationConnectionIds ? json(record.reauthorizationConnectionIds) : null,
|
|
98
|
+
iso(record.createdAt),
|
|
99
|
+
iso(record.expiresAt)
|
|
100
|
+
)
|
|
101
|
+
return true
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (isUniqueConstraintError(error)) return false
|
|
104
|
+
throw error
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async getAuthorizationAttempt(id: string): Promise<ConnectorAuthorizationAttemptRecord | null> {
|
|
109
|
+
const row = this.getById<SqliteAuthorizationAttemptRow>("connector_authorization_attempts", id)
|
|
110
|
+
return row ? authorizationAttemptFromRow(row) : null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async getAuthorizationAttemptByConnectionRunId(
|
|
114
|
+
connectionRunId: string
|
|
115
|
+
): Promise<ConnectorAuthorizationAttemptRecord | null> {
|
|
116
|
+
if (!this.scope.connectorId) return null
|
|
117
|
+
const row = this.db
|
|
118
|
+
.query(
|
|
119
|
+
`
|
|
120
|
+
SELECT * FROM connector_authorization_attempts
|
|
121
|
+
WHERE project_id = ? AND connector_id = ? AND connection_run_id = ?
|
|
122
|
+
`
|
|
123
|
+
)
|
|
124
|
+
.get(
|
|
125
|
+
this.scope.projectId,
|
|
126
|
+
this.scope.connectorId,
|
|
127
|
+
connectionRunId
|
|
128
|
+
) as SqliteAuthorizationAttemptRow | null
|
|
129
|
+
return row ? authorizationAttemptFromRow(row) : null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async deleteAuthorizationAttempt(id: string): Promise<boolean> {
|
|
133
|
+
return this.deleteById("connector_authorization_attempts", id)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async insertConnectionRun(record: ConnectorConnectionRunRecord): Promise<boolean> {
|
|
137
|
+
try {
|
|
138
|
+
this.insertRun(record)
|
|
139
|
+
return true
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (isUniqueConstraintError(error)) return false
|
|
142
|
+
throw error
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async getConnectionRun(id: string): Promise<ConnectorConnectionRunRecord | null> {
|
|
147
|
+
const row = this.getById<SqliteConnectionRunRow>("connector_connection_runs", id)
|
|
148
|
+
return row ? connectionRunFromRow(row) : null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async updateConnectionRun(record: ConnectorConnectionRunRecord): Promise<void> {
|
|
152
|
+
const values = runValues(record)
|
|
153
|
+
const result = this.db
|
|
154
|
+
.query(
|
|
155
|
+
`
|
|
156
|
+
UPDATE connector_connection_runs
|
|
157
|
+
SET kind = ?, slot = ?, initiated_by_execution_id = ?, status = ?,
|
|
158
|
+
waiting_for = ?, processing_id = ?,
|
|
159
|
+
callback_started_at = ?, expires_at = ?, authorization_id = ?,
|
|
160
|
+
cleanup_authorization_id = ?, connections = ?, error = ?, updated_at = ?, finished_at = ?
|
|
161
|
+
WHERE project_id = ? AND connector_id = ? AND id = ?
|
|
162
|
+
`
|
|
163
|
+
)
|
|
164
|
+
.run(...values, record.projectId, record.connectorId, record.id)
|
|
165
|
+
assertChanged(result.changes, "connector connection run", record.id)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async insertAuthorization(record: ConnectorAuthorizationRecord): Promise<boolean> {
|
|
169
|
+
try {
|
|
170
|
+
this.insertAuthorizationRow(record)
|
|
171
|
+
return true
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (isUniqueConstraintError(error)) return false
|
|
174
|
+
throw error
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async getAuthorization(id: string): Promise<ConnectorAuthorizationRecord | null> {
|
|
179
|
+
const row = this.getById<SqliteAuthorizationRow>("connector_authorizations", id)
|
|
180
|
+
return row ? authorizationFromRow(row) : null
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async updateAuthorization(record: ConnectorAuthorizationRecord): Promise<void> {
|
|
184
|
+
const result = this.db
|
|
185
|
+
.query(
|
|
186
|
+
`
|
|
187
|
+
UPDATE connector_authorizations
|
|
188
|
+
SET authorized_by = ?, credentials = ?, credential_expires_at = ?, scopes = ?, accounts = ?,
|
|
189
|
+
status = ?, selection_expires_at = ?, revision = ?, mutation_id = ?, mutation_kind = ?,
|
|
190
|
+
mutation_phase = ?, mutation_holder_id = ?, mutation_expires_at = ?,
|
|
191
|
+
mutation_deadline_at = ?, mutation_expected_connection_ids = ?, staged_credentials = ?,
|
|
192
|
+
staged_credential_expires_at = ?, staged_scopes = ?, updated_at = ?
|
|
193
|
+
WHERE project_id = ? AND connector_id = ? AND id = ?
|
|
194
|
+
`
|
|
195
|
+
)
|
|
196
|
+
.run(...authorizationValues(record), record.projectId, record.connectorId, record.id)
|
|
197
|
+
assertChanged(result.changes, "connector authorization", record.id)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async insertConnection(record: ConnectorConnectionRecord): Promise<boolean> {
|
|
201
|
+
try {
|
|
202
|
+
this.db
|
|
203
|
+
.query(
|
|
204
|
+
`
|
|
205
|
+
INSERT INTO connector_connections (
|
|
206
|
+
project_id, connector_id, id, authorization_id, slot, account,
|
|
207
|
+
account_id, status, disconnected_at, created_at, updated_at
|
|
208
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
209
|
+
`
|
|
210
|
+
)
|
|
211
|
+
.run(
|
|
212
|
+
record.projectId,
|
|
213
|
+
record.connectorId,
|
|
214
|
+
record.id,
|
|
215
|
+
record.authorizationId,
|
|
216
|
+
record.slot,
|
|
217
|
+
json(record.account),
|
|
218
|
+
record.account.id,
|
|
219
|
+
record.status,
|
|
220
|
+
optionalIso(record.disconnectedAt),
|
|
221
|
+
iso(record.createdAt),
|
|
222
|
+
iso(record.updatedAt)
|
|
223
|
+
)
|
|
224
|
+
return true
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (isUniqueConstraintError(error)) return false
|
|
227
|
+
throw error
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getConnectionById(id: string): Promise<ConnectorConnectionRecord | null> {
|
|
232
|
+
const row = this.getById<SqliteConnectionRow>("connector_connections", id)
|
|
233
|
+
return row ? connectionFromRow(row) : null
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async getConnectionBySelector(input: {
|
|
237
|
+
readonly owner: { readonly type: "project" }
|
|
238
|
+
readonly slot: string
|
|
239
|
+
}): Promise<ConnectorConnectionRecord | null> {
|
|
240
|
+
if (!this.scope.connectorId) return null
|
|
241
|
+
const row = this.db
|
|
242
|
+
.query(
|
|
243
|
+
`
|
|
244
|
+
SELECT * FROM connector_connections
|
|
245
|
+
WHERE project_id = ? AND connector_id = ? AND slot = ?
|
|
246
|
+
`
|
|
247
|
+
)
|
|
248
|
+
.get(this.scope.projectId, this.scope.connectorId, input.slot) as SqliteConnectionRow | null
|
|
249
|
+
return row ? connectionFromRow(row) : null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async listConnections(): Promise<readonly ConnectorConnectionRecord[]> {
|
|
253
|
+
if (!this.scope.connectorId) return []
|
|
254
|
+
const rows = this.db
|
|
255
|
+
.query(
|
|
256
|
+
`
|
|
257
|
+
SELECT * FROM connector_connections
|
|
258
|
+
WHERE project_id = ? AND connector_id = ?
|
|
259
|
+
ORDER BY id
|
|
260
|
+
`
|
|
261
|
+
)
|
|
262
|
+
.all(this.scope.projectId, this.scope.connectorId) as SqliteConnectionRow[]
|
|
263
|
+
return rows.map(connectionFromRow)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async listConnectionsByAuthorization(
|
|
267
|
+
authorizationId: string,
|
|
268
|
+
options: { readonly connectedOnly?: boolean } = {}
|
|
269
|
+
): Promise<readonly ConnectorConnectionRecord[]> {
|
|
270
|
+
if (!this.scope.connectorId) return []
|
|
271
|
+
const rows = options.connectedOnly
|
|
272
|
+
? (this.db
|
|
273
|
+
.query(
|
|
274
|
+
`
|
|
275
|
+
SELECT * FROM connector_connections
|
|
276
|
+
WHERE project_id = ? AND connector_id = ? AND authorization_id = ?
|
|
277
|
+
AND status = 'connected'
|
|
278
|
+
ORDER BY id
|
|
279
|
+
`
|
|
280
|
+
)
|
|
281
|
+
.all(
|
|
282
|
+
this.scope.projectId,
|
|
283
|
+
this.scope.connectorId,
|
|
284
|
+
authorizationId
|
|
285
|
+
) as SqliteConnectionRow[])
|
|
286
|
+
: (this.db
|
|
287
|
+
.query(
|
|
288
|
+
`
|
|
289
|
+
SELECT * FROM connector_connections
|
|
290
|
+
WHERE project_id = ? AND connector_id = ? AND authorization_id = ?
|
|
291
|
+
ORDER BY id
|
|
292
|
+
`
|
|
293
|
+
)
|
|
294
|
+
.all(
|
|
295
|
+
this.scope.projectId,
|
|
296
|
+
this.scope.connectorId,
|
|
297
|
+
authorizationId
|
|
298
|
+
) as SqliteConnectionRow[])
|
|
299
|
+
return rows.map(connectionFromRow)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async updateConnection(record: ConnectorConnectionRecord): Promise<void> {
|
|
303
|
+
const result = this.db
|
|
304
|
+
.query(
|
|
305
|
+
`
|
|
306
|
+
UPDATE connector_connections
|
|
307
|
+
SET authorization_id = ?, slot = ?, account = ?, account_id = ?,
|
|
308
|
+
status = ?, disconnected_at = ?, updated_at = ?
|
|
309
|
+
WHERE project_id = ? AND connector_id = ? AND id = ?
|
|
310
|
+
`
|
|
311
|
+
)
|
|
312
|
+
.run(
|
|
313
|
+
record.authorizationId,
|
|
314
|
+
record.slot,
|
|
315
|
+
json(record.account),
|
|
316
|
+
record.account.id,
|
|
317
|
+
record.status,
|
|
318
|
+
optionalIso(record.disconnectedAt),
|
|
319
|
+
iso(record.updatedAt),
|
|
320
|
+
record.projectId,
|
|
321
|
+
record.connectorId,
|
|
322
|
+
record.id
|
|
323
|
+
)
|
|
324
|
+
assertChanged(result.changes, "connector connection", record.id)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private insertRun(record: ConnectorConnectionRunRecord): void {
|
|
328
|
+
const values = runValues(record)
|
|
329
|
+
this.db
|
|
330
|
+
.query(
|
|
331
|
+
`
|
|
332
|
+
INSERT INTO connector_connection_runs (
|
|
333
|
+
project_id, connector_id, id, kind, slot, initiated_by_execution_id,
|
|
334
|
+
status, waiting_for, processing_id, callback_started_at,
|
|
335
|
+
expires_at, authorization_id, cleanup_authorization_id, connections, error,
|
|
336
|
+
created_at, updated_at, finished_at
|
|
337
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
338
|
+
`
|
|
339
|
+
)
|
|
340
|
+
.run(
|
|
341
|
+
record.projectId,
|
|
342
|
+
record.connectorId,
|
|
343
|
+
record.id,
|
|
344
|
+
...values.slice(0, -2),
|
|
345
|
+
iso(record.createdAt),
|
|
346
|
+
...values.slice(-2)
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private insertAuthorizationRow(record: ConnectorAuthorizationRecord): void {
|
|
351
|
+
const values = authorizationValues(record)
|
|
352
|
+
this.db
|
|
353
|
+
.query(
|
|
354
|
+
`
|
|
355
|
+
INSERT INTO connector_authorizations (
|
|
356
|
+
project_id, connector_id, id, authorized_by, credentials, credential_expires_at,
|
|
357
|
+
scopes, accounts, status, selection_expires_at, revision, mutation_id, mutation_kind,
|
|
358
|
+
mutation_phase, mutation_holder_id, mutation_expires_at, mutation_deadline_at,
|
|
359
|
+
mutation_expected_connection_ids, staged_credentials, staged_credential_expires_at,
|
|
360
|
+
staged_scopes, created_at, updated_at
|
|
361
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
362
|
+
`
|
|
363
|
+
)
|
|
364
|
+
.run(
|
|
365
|
+
record.projectId,
|
|
366
|
+
record.connectorId,
|
|
367
|
+
record.id,
|
|
368
|
+
...values.slice(0, -1),
|
|
369
|
+
iso(record.createdAt),
|
|
370
|
+
...values.slice(-1)
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private getById<TRow>(table: ConnectorTable, id: string): TRow | null {
|
|
375
|
+
if (this.scope.connectorId) {
|
|
376
|
+
return this.db
|
|
377
|
+
.query(`SELECT * FROM ${table} WHERE project_id = ? AND connector_id = ? AND id = ?`)
|
|
378
|
+
.get(this.scope.projectId, this.scope.connectorId, id) as TRow | null
|
|
379
|
+
}
|
|
380
|
+
return this.db
|
|
381
|
+
.query(`SELECT * FROM ${table} WHERE project_id = ? AND id = ?`)
|
|
382
|
+
.get(this.scope.projectId, id) as TRow | null
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private deleteById(table: ConnectorTable, id: string): boolean {
|
|
386
|
+
const result = this.scope.connectorId
|
|
387
|
+
? this.db
|
|
388
|
+
.query(`DELETE FROM ${table} WHERE project_id = ? AND connector_id = ? AND id = ?`)
|
|
389
|
+
.run(this.scope.projectId, this.scope.connectorId, id)
|
|
390
|
+
: this.db
|
|
391
|
+
.query(`DELETE FROM ${table} WHERE project_id = ? AND id = ?`)
|
|
392
|
+
.run(this.scope.projectId, id)
|
|
393
|
+
return result.changes === 1
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
type ConnectorTable =
|
|
398
|
+
| "connector_authorization_attempts"
|
|
399
|
+
| "connector_connection_runs"
|
|
400
|
+
| "connector_authorizations"
|
|
401
|
+
| "connector_connections"
|
|
402
|
+
|
|
403
|
+
function authorizationAttemptFromRow(
|
|
404
|
+
row: SqliteAuthorizationAttemptRow
|
|
405
|
+
): ConnectorAuthorizationAttemptRecord {
|
|
406
|
+
return {
|
|
407
|
+
id: row.id,
|
|
408
|
+
projectId: row.project_id,
|
|
409
|
+
connectorId: row.connector_id,
|
|
410
|
+
owner: { type: "project" },
|
|
411
|
+
slot: row.slot,
|
|
412
|
+
initiatedByExecutionId: row.initiated_by_execution_id,
|
|
413
|
+
stateHash: row.state_hash,
|
|
414
|
+
codeVerifier: parseJson(row.code_verifier),
|
|
415
|
+
redirectUri: row.redirect_uri,
|
|
416
|
+
...(row.connection_run_id === null ? {} : { connectionRunId: row.connection_run_id }),
|
|
417
|
+
...(row.return_to === null ? {} : { returnTo: row.return_to }),
|
|
418
|
+
...(row.callback_binding_hash === null
|
|
419
|
+
? {}
|
|
420
|
+
: { callbackBindingHash: row.callback_binding_hash }),
|
|
421
|
+
...(row.reauthorization_id === null ? {} : { reauthorizationId: row.reauthorization_id }),
|
|
422
|
+
...(row.reauthorization_revision === null
|
|
423
|
+
? {}
|
|
424
|
+
: { reauthorizationRevision: row.reauthorization_revision }),
|
|
425
|
+
...(row.reauthorization_connection_ids === null
|
|
426
|
+
? {}
|
|
427
|
+
: { reauthorizationConnectionIds: parseJson(row.reauthorization_connection_ids) }),
|
|
428
|
+
createdAt: new Date(row.created_at),
|
|
429
|
+
expiresAt: new Date(row.expires_at),
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function runValues(record: ConnectorConnectionRunRecord): readonly SQLQueryBindings[] {
|
|
434
|
+
const waitingFor = record.status === "waiting" ? record.waitingFor : null
|
|
435
|
+
const processingId = record.status === "running" ? record.processingId : null
|
|
436
|
+
const callbackStartedAt = record.status === "running" ? iso(record.callbackStartedAt) : null
|
|
437
|
+
const expiresAt =
|
|
438
|
+
record.status === "waiting" || record.status === "running" ? iso(record.expiresAt) : null
|
|
439
|
+
const authorizationId = "authorizationId" in record ? (record.authorizationId ?? null) : null
|
|
440
|
+
const cleanupAuthorizationId =
|
|
441
|
+
record.status === "succeeded" ? (record.cleanupAuthorizationId ?? null) : null
|
|
442
|
+
const connections = record.status === "succeeded" ? json(record.connections) : null
|
|
443
|
+
const error = record.status === "failed" ? json(record.error) : null
|
|
444
|
+
const finishedAt =
|
|
445
|
+
record.status === "succeeded" ||
|
|
446
|
+
record.status === "failed" ||
|
|
447
|
+
record.status === "cancelled" ||
|
|
448
|
+
record.status === "expired"
|
|
449
|
+
? iso(record.finishedAt)
|
|
450
|
+
: null
|
|
451
|
+
return [
|
|
452
|
+
record.kind,
|
|
453
|
+
record.slot,
|
|
454
|
+
record.initiatedByExecutionId,
|
|
455
|
+
record.status,
|
|
456
|
+
waitingFor,
|
|
457
|
+
processingId,
|
|
458
|
+
callbackStartedAt,
|
|
459
|
+
expiresAt,
|
|
460
|
+
authorizationId,
|
|
461
|
+
cleanupAuthorizationId,
|
|
462
|
+
connections,
|
|
463
|
+
error,
|
|
464
|
+
iso(record.updatedAt),
|
|
465
|
+
finishedAt,
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function connectionRunFromRow(row: SqliteConnectionRunRow): ConnectorConnectionRunRecord {
|
|
470
|
+
const base = {
|
|
471
|
+
id: row.id,
|
|
472
|
+
projectId: row.project_id,
|
|
473
|
+
connectorId: row.connector_id,
|
|
474
|
+
kind: row.kind,
|
|
475
|
+
owner: { type: "project" as const },
|
|
476
|
+
slot: row.slot,
|
|
477
|
+
initiatedByExecutionId: row.initiated_by_execution_id,
|
|
478
|
+
createdAt: new Date(row.created_at),
|
|
479
|
+
updatedAt: new Date(row.updated_at),
|
|
480
|
+
}
|
|
481
|
+
if (row.status === "waiting" && row.waiting_for === "provider_authorization") {
|
|
482
|
+
return {
|
|
483
|
+
...base,
|
|
484
|
+
status: "waiting",
|
|
485
|
+
waitingFor: "provider_authorization",
|
|
486
|
+
expiresAt: new Date(required(row.expires_at, "run expiry")),
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (row.status === "waiting" && row.waiting_for === "account_selection") {
|
|
490
|
+
return {
|
|
491
|
+
...base,
|
|
492
|
+
status: "waiting",
|
|
493
|
+
waitingFor: "account_selection",
|
|
494
|
+
authorizationId: required(row.authorization_id, "authorization id"),
|
|
495
|
+
expiresAt: new Date(required(row.expires_at, "run expiry")),
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (row.status === "running") {
|
|
499
|
+
return {
|
|
500
|
+
...base,
|
|
501
|
+
status: "running",
|
|
502
|
+
processingId: required(row.processing_id, "processing id"),
|
|
503
|
+
callbackStartedAt: new Date(required(row.callback_started_at, "callback start")),
|
|
504
|
+
expiresAt: new Date(required(row.expires_at, "run expiry")),
|
|
505
|
+
...(row.authorization_id === null ? {} : { authorizationId: row.authorization_id }),
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const finishedAt = new Date(required(row.finished_at, "run finish"))
|
|
509
|
+
if (row.status === "succeeded") {
|
|
510
|
+
return {
|
|
511
|
+
...base,
|
|
512
|
+
status: "succeeded",
|
|
513
|
+
authorizationId: required(row.authorization_id, "authorization id"),
|
|
514
|
+
...(row.cleanup_authorization_id === null
|
|
515
|
+
? {}
|
|
516
|
+
: { cleanupAuthorizationId: row.cleanup_authorization_id }),
|
|
517
|
+
connections: parseJson<readonly SerializedConnectorConnection[]>(
|
|
518
|
+
required(row.connections, "connections")
|
|
519
|
+
).map(connectionFromSerialized),
|
|
520
|
+
finishedAt,
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (row.status === "failed") {
|
|
524
|
+
return {
|
|
525
|
+
...base,
|
|
526
|
+
status: "failed",
|
|
527
|
+
error: parseJson(required(row.error, "run error")),
|
|
528
|
+
...(row.authorization_id === null ? {} : { authorizationId: row.authorization_id }),
|
|
529
|
+
finishedAt,
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (row.status === "cancelled") {
|
|
533
|
+
return {
|
|
534
|
+
...base,
|
|
535
|
+
status: "cancelled",
|
|
536
|
+
...(row.authorization_id === null ? {} : { authorizationId: row.authorization_id }),
|
|
537
|
+
finishedAt,
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (row.status === "expired") {
|
|
541
|
+
return {
|
|
542
|
+
...base,
|
|
543
|
+
status: "expired",
|
|
544
|
+
...(row.authorization_id === null ? {} : { authorizationId: row.authorization_id }),
|
|
545
|
+
finishedAt,
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
throw invalidStoredValue("connector connection run status", row.status)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function authorizationValues(record: ConnectorAuthorizationRecord): readonly SQLQueryBindings[] {
|
|
552
|
+
const mutation = record.credentialMutation
|
|
553
|
+
const staged = mutation?.stagedCredentials
|
|
554
|
+
return [
|
|
555
|
+
json(record.authorizedBy),
|
|
556
|
+
record.credentials ? json(record.credentials) : null,
|
|
557
|
+
optionalIso(record.credentialExpiresAt),
|
|
558
|
+
json(record.scopes),
|
|
559
|
+
json(record.accounts),
|
|
560
|
+
record.status,
|
|
561
|
+
optionalIso(record.selectionExpiresAt),
|
|
562
|
+
record.revision,
|
|
563
|
+
mutation?.id ?? null,
|
|
564
|
+
mutation?.kind ?? null,
|
|
565
|
+
mutation?.phase ?? null,
|
|
566
|
+
mutation?.holderId ?? null,
|
|
567
|
+
optionalIso(mutation?.expiresAt),
|
|
568
|
+
optionalIso(mutation?.deadlineAt),
|
|
569
|
+
mutation?.expectedConnectionIds ? json(mutation.expectedConnectionIds) : null,
|
|
570
|
+
staged ? json(staged.credentials) : null,
|
|
571
|
+
optionalIso(staged?.credentialExpiresAt),
|
|
572
|
+
staged ? json(staged.scopes) : null,
|
|
573
|
+
iso(record.updatedAt),
|
|
574
|
+
]
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function authorizationFromRow(row: SqliteAuthorizationRow): ConnectorAuthorizationRecord {
|
|
578
|
+
const mutation = mutationFromRow(row)
|
|
579
|
+
return {
|
|
580
|
+
id: row.id,
|
|
581
|
+
projectId: row.project_id,
|
|
582
|
+
connectorId: row.connector_id,
|
|
583
|
+
authorizedBy: parseJson(row.authorized_by),
|
|
584
|
+
...(row.credentials === null ? {} : { credentials: parseJson(row.credentials) }),
|
|
585
|
+
...(row.credential_expires_at === null
|
|
586
|
+
? {}
|
|
587
|
+
: { credentialExpiresAt: new Date(row.credential_expires_at) }),
|
|
588
|
+
scopes: parseJson(row.scopes),
|
|
589
|
+
accounts: parseJson(row.accounts),
|
|
590
|
+
status: row.status,
|
|
591
|
+
...(row.selection_expires_at === null
|
|
592
|
+
? {}
|
|
593
|
+
: { selectionExpiresAt: new Date(row.selection_expires_at) }),
|
|
594
|
+
revision: row.revision,
|
|
595
|
+
...(mutation === undefined ? {} : { credentialMutation: mutation }),
|
|
596
|
+
createdAt: new Date(row.created_at),
|
|
597
|
+
updatedAt: new Date(row.updated_at),
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function mutationFromRow(row: SqliteAuthorizationRow): ConnectorCredentialMutation | undefined {
|
|
602
|
+
if (row.mutation_id === null) return undefined
|
|
603
|
+
const kind = required(row.mutation_kind, "credential mutation kind")
|
|
604
|
+
const phase = required(row.mutation_phase, "credential mutation phase")
|
|
605
|
+
const stagedCredentials: ConnectorCredentialMutation["stagedCredentials"] =
|
|
606
|
+
row.staged_credentials === null
|
|
607
|
+
? undefined
|
|
608
|
+
: {
|
|
609
|
+
credentials: parseJson<
|
|
610
|
+
NonNullable<ConnectorCredentialMutation["stagedCredentials"]>["credentials"]
|
|
611
|
+
>(row.staged_credentials),
|
|
612
|
+
...(row.staged_credential_expires_at === null
|
|
613
|
+
? {}
|
|
614
|
+
: { credentialExpiresAt: new Date(row.staged_credential_expires_at) }),
|
|
615
|
+
scopes: parseJson<readonly string[]>(
|
|
616
|
+
required(row.staged_scopes, "staged credential scopes")
|
|
617
|
+
),
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
id: row.mutation_id,
|
|
621
|
+
kind,
|
|
622
|
+
phase,
|
|
623
|
+
holderId: required(row.mutation_holder_id, "credential mutation holder"),
|
|
624
|
+
expiresAt: new Date(required(row.mutation_expires_at, "credential mutation expiry")),
|
|
625
|
+
deadlineAt: new Date(required(row.mutation_deadline_at, "credential mutation deadline")),
|
|
626
|
+
...(row.mutation_expected_connection_ids === null
|
|
627
|
+
? {}
|
|
628
|
+
: {
|
|
629
|
+
expectedConnectionIds: parseJson<readonly string[]>(row.mutation_expected_connection_ids),
|
|
630
|
+
}),
|
|
631
|
+
...(stagedCredentials === undefined ? {} : { stagedCredentials }),
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function connectionFromRow(row: SqliteConnectionRow): ConnectorConnectionRecord {
|
|
636
|
+
return {
|
|
637
|
+
id: row.id,
|
|
638
|
+
projectId: row.project_id,
|
|
639
|
+
connectorId: row.connector_id,
|
|
640
|
+
authorizationId: row.authorization_id,
|
|
641
|
+
owner: { type: "project" },
|
|
642
|
+
slot: row.slot,
|
|
643
|
+
account: parseJson(row.account),
|
|
644
|
+
status: row.status,
|
|
645
|
+
...(row.disconnected_at === null ? {} : { disconnectedAt: new Date(row.disconnected_at) }),
|
|
646
|
+
createdAt: new Date(row.created_at),
|
|
647
|
+
updatedAt: new Date(row.updated_at),
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
type SerializedConnectorConnection = Omit<
|
|
652
|
+
ConnectorConnectionRecord,
|
|
653
|
+
"createdAt" | "updatedAt" | "disconnectedAt"
|
|
654
|
+
> & {
|
|
655
|
+
readonly createdAt: string
|
|
656
|
+
readonly updatedAt: string
|
|
657
|
+
readonly disconnectedAt?: string
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function connectionFromSerialized(
|
|
661
|
+
record: SerializedConnectorConnection
|
|
662
|
+
): ConnectorConnectionRecord {
|
|
663
|
+
const { createdAt, updatedAt, disconnectedAt, ...connection } = record
|
|
664
|
+
return {
|
|
665
|
+
...connection,
|
|
666
|
+
createdAt: new Date(createdAt),
|
|
667
|
+
updatedAt: new Date(updatedAt),
|
|
668
|
+
...(disconnectedAt === undefined ? {} : { disconnectedAt: new Date(disconnectedAt) }),
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function json(value: unknown): string {
|
|
673
|
+
return JSON.stringify(value)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function parseJson<T>(value: string): T {
|
|
677
|
+
return JSON.parse(value) as T
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function iso(value: Date): string {
|
|
681
|
+
return value.toISOString()
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function optionalIso(value: Date | undefined): string | null {
|
|
685
|
+
return value?.toISOString() ?? null
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function required<T>(value: T | null, label: string): T {
|
|
689
|
+
if (value !== null) return value
|
|
690
|
+
throw new Error(`[SixbSqlite] Stored ${label} is missing.`)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function invalidStoredValue(label: string, value: unknown): Error {
|
|
694
|
+
return new Error(`[SixbSqlite] Stored ${label} '${String(value)}' is invalid.`)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function assertChanged(changes: number, label: string, id: string): void {
|
|
698
|
+
if (changes === 1) return
|
|
699
|
+
throw new Error(`[SixbSqlite] Stored ${label} '${id}' is unavailable.`)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
interface SqliteAuthorizationAttemptRow {
|
|
703
|
+
readonly project_id: string
|
|
704
|
+
readonly connector_id: string
|
|
705
|
+
readonly id: string
|
|
706
|
+
readonly slot: string
|
|
707
|
+
readonly initiated_by_execution_id: string
|
|
708
|
+
readonly state_hash: string
|
|
709
|
+
readonly code_verifier: string
|
|
710
|
+
readonly redirect_uri: string
|
|
711
|
+
readonly connection_run_id: string | null
|
|
712
|
+
readonly return_to: string | null
|
|
713
|
+
readonly callback_binding_hash: string | null
|
|
714
|
+
readonly reauthorization_id: string | null
|
|
715
|
+
readonly reauthorization_revision: number | null
|
|
716
|
+
readonly reauthorization_connection_ids: string | null
|
|
717
|
+
readonly created_at: string
|
|
718
|
+
readonly expires_at: string
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
interface SqliteConnectionRunRow {
|
|
722
|
+
readonly project_id: string
|
|
723
|
+
readonly connector_id: string
|
|
724
|
+
readonly id: string
|
|
725
|
+
readonly kind: ConnectorConnectionRunRecord["kind"]
|
|
726
|
+
readonly slot: string
|
|
727
|
+
readonly initiated_by_execution_id: string
|
|
728
|
+
readonly status: ConnectorConnectionRunRecord["status"]
|
|
729
|
+
readonly waiting_for: "provider_authorization" | "account_selection" | null
|
|
730
|
+
readonly processing_id: string | null
|
|
731
|
+
readonly callback_started_at: string | null
|
|
732
|
+
readonly expires_at: string | null
|
|
733
|
+
readonly authorization_id: string | null
|
|
734
|
+
readonly cleanup_authorization_id: string | null
|
|
735
|
+
readonly connections: string | null
|
|
736
|
+
readonly error: string | null
|
|
737
|
+
readonly created_at: string
|
|
738
|
+
readonly updated_at: string
|
|
739
|
+
readonly finished_at: string | null
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
interface SqliteAuthorizationRow {
|
|
743
|
+
readonly project_id: string
|
|
744
|
+
readonly connector_id: string
|
|
745
|
+
readonly id: string
|
|
746
|
+
readonly authorized_by: string
|
|
747
|
+
readonly credentials: string | null
|
|
748
|
+
readonly credential_expires_at: string | null
|
|
749
|
+
readonly scopes: string
|
|
750
|
+
readonly accounts: string
|
|
751
|
+
readonly status: ConnectorAuthorizationRecord["status"]
|
|
752
|
+
readonly selection_expires_at: string | null
|
|
753
|
+
readonly revision: number
|
|
754
|
+
readonly mutation_id: string | null
|
|
755
|
+
readonly mutation_kind: ConnectorCredentialMutation["kind"] | null
|
|
756
|
+
readonly mutation_phase: ConnectorCredentialMutation["phase"] | null
|
|
757
|
+
readonly mutation_holder_id: string | null
|
|
758
|
+
readonly mutation_expires_at: string | null
|
|
759
|
+
readonly mutation_deadline_at: string | null
|
|
760
|
+
readonly mutation_expected_connection_ids: string | null
|
|
761
|
+
readonly staged_credentials: string | null
|
|
762
|
+
readonly staged_credential_expires_at: string | null
|
|
763
|
+
readonly staged_scopes: string | null
|
|
764
|
+
readonly created_at: string
|
|
765
|
+
readonly updated_at: string
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
interface SqliteConnectionRow {
|
|
769
|
+
readonly project_id: string
|
|
770
|
+
readonly connector_id: string
|
|
771
|
+
readonly id: string
|
|
772
|
+
readonly authorization_id: string
|
|
773
|
+
readonly slot: string
|
|
774
|
+
readonly account: string
|
|
775
|
+
readonly account_id: string
|
|
776
|
+
readonly status: ConnectorConnectionRecord["status"]
|
|
777
|
+
readonly disconnected_at: string | null
|
|
778
|
+
readonly created_at: string
|
|
779
|
+
readonly updated_at: string
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
interface ConnectorConnectionTestClock {
|
|
783
|
+
offsetMs: number
|
|
784
|
+
}
|