@mycreat-labs/integrations-sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -2
- package/dist/adapters/bullmq.cjs +110 -0
- package/dist/adapters/bullmq.cjs.map +1 -0
- package/dist/adapters/bullmq.d.cts +11 -0
- package/dist/adapters/bullmq.d.ts +11 -0
- package/dist/adapters/bullmq.js +85 -0
- package/dist/adapters/bullmq.js.map +1 -0
- package/dist/adapters/postgres.cjs +396 -0
- package/dist/adapters/postgres.cjs.map +1 -0
- package/dist/adapters/postgres.d.cts +51 -0
- package/dist/adapters/postgres.d.ts +51 -0
- package/dist/adapters/postgres.js +368 -0
- package/dist/adapters/postgres.js.map +1 -0
- package/dist/adapters/prisma.cjs +414 -0
- package/dist/adapters/prisma.cjs.map +1 -0
- package/dist/adapters/prisma.d.cts +15 -0
- package/dist/adapters/prisma.d.ts +15 -0
- package/dist/adapters/prisma.js +386 -0
- package/dist/adapters/prisma.js.map +1 -0
- package/dist/adapters/redis.cjs +125 -0
- package/dist/adapters/redis.cjs.map +1 -0
- package/dist/adapters/redis.d.cts +20 -0
- package/dist/adapters/redis.d.ts +20 -0
- package/dist/adapters/redis.js +98 -0
- package/dist/adapters/redis.js.map +1 -0
- package/dist/errors-3hDEtzuZ.d.cts +11 -0
- package/dist/errors-3hDEtzuZ.d.ts +11 -0
- package/dist/index.cjs +1328 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +1297 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.cjs +1287 -0
- package/dist/providers/index.cjs.map +1 -0
- package/dist/providers/index.d.cts +144 -0
- package/dist/providers/index.d.ts +144 -0
- package/dist/providers/index.js +1252 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/testing.cjs +275 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +20 -0
- package/dist/testing.d.ts +20 -0
- package/dist/testing.js +248 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-Y4-DoJ1A.d.cts +260 -0
- package/dist/types-Y4-DoJ1A.d.ts +260 -0
- package/docs/migration.md +23 -0
- package/docs/security.md +26 -0
- package/docs/verification.md +22 -0
- package/examples/embedded-host.ts +62 -0
- package/migrations/0001_initial.sql +40 -0
- package/package.json +90 -4
- package/schema.prisma +80 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var IntegrationError = class extends Error {
|
|
3
|
+
constructor(code, disposition = "permanent_failure", retryAfterMs) {
|
|
4
|
+
super(code);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.disposition = disposition;
|
|
7
|
+
this.retryAfterMs = retryAfterMs;
|
|
8
|
+
this.name = "IntegrationError";
|
|
9
|
+
}
|
|
10
|
+
code;
|
|
11
|
+
disposition;
|
|
12
|
+
retryAfterMs;
|
|
13
|
+
sdkError = "@mycreat-labs/integrations-sdk/v1";
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/adapters/postgres.ts
|
|
17
|
+
var encoded = (value) => JSON.stringify(value);
|
|
18
|
+
var first = (rows) => rows[0]?.data ?? null;
|
|
19
|
+
var PostgresStore = class _PostgresStore {
|
|
20
|
+
constructor(executor, runner) {
|
|
21
|
+
this.executor = executor;
|
|
22
|
+
this.runner = runner;
|
|
23
|
+
}
|
|
24
|
+
executor;
|
|
25
|
+
runner;
|
|
26
|
+
async assertCompatible() {
|
|
27
|
+
const { rows } = await this.executor.query(
|
|
28
|
+
"SELECT version FROM integration_sdk_meta WHERE id = 1"
|
|
29
|
+
);
|
|
30
|
+
if (rows[0]?.version !== 1)
|
|
31
|
+
throw new IntegrationError("incompatible_storage_schema");
|
|
32
|
+
const missing = await this.executor.query(
|
|
33
|
+
"SELECT name FROM unnest(ARRAY['integration_sdk_owners','integration_sdk_flows','integration_sdk_grants','integration_sdk_connections','integration_sdk_operations','integration_sdk_events']) AS name WHERE to_regclass(name) IS NULL"
|
|
34
|
+
);
|
|
35
|
+
const unique = await this.executor.query(
|
|
36
|
+
"SELECT indisunique AND indisvalid AS valid FROM pg_index WHERE indexrelid=to_regclass('integration_sdk_connections_account_owner_key')"
|
|
37
|
+
);
|
|
38
|
+
if (missing.rows.length || unique.rows[0]?.valid !== true)
|
|
39
|
+
throw new IntegrationError("incompatible_storage_schema");
|
|
40
|
+
}
|
|
41
|
+
transaction(work) {
|
|
42
|
+
return this.runner(
|
|
43
|
+
(executor, native) => work(new PostgresTransaction(executor, native))
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
static bind(executor, nativeContext) {
|
|
47
|
+
return new _PostgresStore(
|
|
48
|
+
executor,
|
|
49
|
+
async (work) => work(executor, nativeContext)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var PostgresTransaction = class {
|
|
54
|
+
constructor(sql, nativeContext) {
|
|
55
|
+
this.sql = sql;
|
|
56
|
+
this.nativeContext = nativeContext;
|
|
57
|
+
}
|
|
58
|
+
sql;
|
|
59
|
+
nativeContext;
|
|
60
|
+
async lockScope(scope, allowErased = false) {
|
|
61
|
+
await this.sql.query(
|
|
62
|
+
"INSERT INTO integration_sdk_owners (namespace,owner_id,erased,epochs) VALUES ($1,$2,false,'{}'::jsonb) ON CONFLICT DO NOTHING",
|
|
63
|
+
[scope.namespace, scope.ownerId]
|
|
64
|
+
);
|
|
65
|
+
const { rows } = await this.sql.query(
|
|
66
|
+
"SELECT erased FROM integration_sdk_owners WHERE namespace=$1 AND owner_id=$2 FOR UPDATE",
|
|
67
|
+
[scope.namespace, scope.ownerId]
|
|
68
|
+
);
|
|
69
|
+
if (rows[0]?.erased && !allowErased)
|
|
70
|
+
throw new IntegrationError("owner_erased");
|
|
71
|
+
return rows[0]?.erased ?? false;
|
|
72
|
+
}
|
|
73
|
+
async authorizationEpoch(scope, key, advance = false) {
|
|
74
|
+
if (advance) {
|
|
75
|
+
const { rows: rows2 } = await this.sql.query(
|
|
76
|
+
"UPDATE integration_sdk_owners SET epochs=jsonb_set(epochs,ARRAY[$3::text],to_jsonb(COALESCE((epochs->>$3)::int,0)+1)) WHERE namespace=$1 AND owner_id=$2 RETURNING (epochs->>$3)::int AS epoch",
|
|
77
|
+
[scope.namespace, scope.ownerId, key]
|
|
78
|
+
);
|
|
79
|
+
if (!rows2[0]) throw new IntegrationError("owner_not_locked");
|
|
80
|
+
return rows2[0].epoch;
|
|
81
|
+
}
|
|
82
|
+
const { rows } = await this.sql.query(
|
|
83
|
+
"SELECT COALESCE((epochs->>$3)::int,0) AS epoch FROM integration_sdk_owners WHERE namespace=$1 AND owner_id=$2",
|
|
84
|
+
[scope.namespace, scope.ownerId, key]
|
|
85
|
+
);
|
|
86
|
+
return rows[0]?.epoch ?? 0;
|
|
87
|
+
}
|
|
88
|
+
async markOwnerErased(scope) {
|
|
89
|
+
await this.sql.query(
|
|
90
|
+
"UPDATE integration_sdk_owners SET erased=true WHERE namespace=$1 AND owner_id=$2",
|
|
91
|
+
[scope.namespace, scope.ownerId]
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
async assertAccountOwner(scope, provider, registrationId, accountId) {
|
|
95
|
+
await this.sql.query(
|
|
96
|
+
"SELECT 1 AS locked FROM pg_advisory_xact_lock(hashtextextended($1,0))",
|
|
97
|
+
[JSON.stringify([scope.namespace, provider, registrationId, accountId])]
|
|
98
|
+
);
|
|
99
|
+
const { rows } = await this.sql.query(
|
|
100
|
+
"SELECT id FROM integration_sdk_connections WHERE namespace=$1 AND provider=$2 AND registration_id=$3 AND account_id=$4 AND owner_id<>$5 LIMIT 1",
|
|
101
|
+
[scope.namespace, provider, registrationId, accountId, scope.ownerId]
|
|
102
|
+
);
|
|
103
|
+
if (rows.length) throw new IntegrationError("account_already_connected");
|
|
104
|
+
}
|
|
105
|
+
async collectGarbage(namespace, now, retentionMs) {
|
|
106
|
+
await this.sql.query(
|
|
107
|
+
"DELETE FROM integration_sdk_flows WHERE namespace=$1 AND expires_at<=$2",
|
|
108
|
+
[namespace, now]
|
|
109
|
+
);
|
|
110
|
+
await this.sql.query(
|
|
111
|
+
"UPDATE integration_sdk_operations SET status='failed',version=version+1,data=(data-'hostContext') || jsonb_build_object('status','failed','version',version+1,'input',jsonb_build_object('keyId','','value',''),'errorCode','payload_expired') WHERE namespace=$1 AND action<>'__sdk.revoke' AND status IN ('failed','blocked','outcome_unknown','cancelled') AND (data->>'updatedAt')::bigint<=$2 AND data->>'errorCode' IS DISTINCT FROM 'payload_expired'",
|
|
112
|
+
[namespace, now - retentionMs]
|
|
113
|
+
);
|
|
114
|
+
await this.sql.query(
|
|
115
|
+
"DELETE FROM integration_sdk_grants g WHERE namespace=$1 AND data->>'status'='revoked' AND NOT EXISTS (SELECT 1 FROM integration_sdk_connections c WHERE c.namespace=g.namespace AND c.data->>'grantId'=g.id)",
|
|
116
|
+
[namespace]
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
async insertFlow(flow) {
|
|
120
|
+
await this.sql.query(
|
|
121
|
+
"INSERT INTO integration_sdk_flows (namespace,id,owner_id,purpose,binding_hash,expires_at,data) VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb)",
|
|
122
|
+
[
|
|
123
|
+
flow.namespace,
|
|
124
|
+
flow.id,
|
|
125
|
+
flow.ownerId,
|
|
126
|
+
flow.purpose,
|
|
127
|
+
flow.bindingHash,
|
|
128
|
+
flow.expiresAt,
|
|
129
|
+
encoded(flow)
|
|
130
|
+
]
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
async consumeFlow(namespace, id, bindingHash, purpose, now) {
|
|
134
|
+
const { rows } = await this.sql.query(
|
|
135
|
+
"DELETE FROM integration_sdk_flows WHERE namespace=$1 AND id=$2 AND binding_hash=$3 AND purpose=$4 AND expires_at>$5 RETURNING data",
|
|
136
|
+
[namespace, id, bindingHash, purpose, now]
|
|
137
|
+
);
|
|
138
|
+
return first(rows);
|
|
139
|
+
}
|
|
140
|
+
async insertGrant(grant) {
|
|
141
|
+
await this.sql.query(
|
|
142
|
+
"INSERT INTO integration_sdk_grants (namespace,id,owner_id,version,data) VALUES ($1,$2,$3,$4,$5::jsonb)",
|
|
143
|
+
[grant.namespace, grant.id, grant.ownerId, grant.version, encoded(grant)]
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
async getGrant(scope, id, lock = false) {
|
|
147
|
+
return first(
|
|
148
|
+
(await this.sql.query(
|
|
149
|
+
`SELECT data FROM integration_sdk_grants WHERE namespace=$1 AND id=$2 AND owner_id=$3${lock ? " FOR UPDATE" : ""}`,
|
|
150
|
+
[scope.namespace, id, scope.ownerId]
|
|
151
|
+
)).rows
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
async saveGrant(grant, expectedVersion) {
|
|
155
|
+
return (await this.sql.query(
|
|
156
|
+
"UPDATE integration_sdk_grants SET version=$4,data=$5::jsonb WHERE namespace=$1 AND id=$2 AND owner_id=$3 AND version=$6 RETURNING id",
|
|
157
|
+
[
|
|
158
|
+
grant.namespace,
|
|
159
|
+
grant.id,
|
|
160
|
+
grant.ownerId,
|
|
161
|
+
grant.version,
|
|
162
|
+
encoded(grant),
|
|
163
|
+
expectedVersion
|
|
164
|
+
]
|
|
165
|
+
)).rows.length === 1;
|
|
166
|
+
}
|
|
167
|
+
async insertConnection(connection) {
|
|
168
|
+
await this.sql.query(
|
|
169
|
+
"INSERT INTO integration_sdk_connections (namespace,id,owner_id,provider,registration_id,account_id,generation,data) VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb)",
|
|
170
|
+
[
|
|
171
|
+
connection.namespace,
|
|
172
|
+
connection.id,
|
|
173
|
+
connection.ownerId,
|
|
174
|
+
connection.provider,
|
|
175
|
+
connection.registrationId,
|
|
176
|
+
connection.account.id,
|
|
177
|
+
connection.generation,
|
|
178
|
+
encoded(connection)
|
|
179
|
+
]
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
async getConnection(scope, id, lock = false) {
|
|
183
|
+
return first(
|
|
184
|
+
(await this.sql.query(
|
|
185
|
+
`SELECT data FROM integration_sdk_connections WHERE namespace=$1 AND id=$2 AND owner_id=$3${lock ? " FOR UPDATE" : ""}`,
|
|
186
|
+
[scope.namespace, id, scope.ownerId]
|
|
187
|
+
)).rows
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
async listConnections(scope) {
|
|
191
|
+
return (await this.sql.query(
|
|
192
|
+
"SELECT data FROM integration_sdk_connections WHERE namespace=$1 AND owner_id=$2 ORDER BY id",
|
|
193
|
+
[scope.namespace, scope.ownerId]
|
|
194
|
+
)).rows.map((row) => row.data);
|
|
195
|
+
}
|
|
196
|
+
async saveConnection(connection, expectedGeneration) {
|
|
197
|
+
return (await this.sql.query(
|
|
198
|
+
"UPDATE integration_sdk_connections SET generation=$4,data=$5::jsonb WHERE namespace=$1 AND id=$2 AND owner_id=$3 AND generation=$6 RETURNING id",
|
|
199
|
+
[
|
|
200
|
+
connection.namespace,
|
|
201
|
+
connection.id,
|
|
202
|
+
connection.ownerId,
|
|
203
|
+
connection.generation,
|
|
204
|
+
encoded(connection),
|
|
205
|
+
expectedGeneration
|
|
206
|
+
]
|
|
207
|
+
)).rows.length === 1;
|
|
208
|
+
}
|
|
209
|
+
async insertOperation(operation) {
|
|
210
|
+
await this.sql.query(
|
|
211
|
+
"INSERT INTO integration_sdk_operations (namespace,id,owner_id,connection_id,action,idempotency_key,version,status,available_at,lease_until,data) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,NULL,$10::jsonb) ON CONFLICT (namespace,owner_id,action,idempotency_key) DO NOTHING",
|
|
212
|
+
[
|
|
213
|
+
operation.namespace,
|
|
214
|
+
operation.id,
|
|
215
|
+
operation.ownerId,
|
|
216
|
+
operation.connectionId,
|
|
217
|
+
operation.action,
|
|
218
|
+
operation.idempotencyKey,
|
|
219
|
+
operation.version,
|
|
220
|
+
operation.status,
|
|
221
|
+
operation.availableAt,
|
|
222
|
+
encoded(operation)
|
|
223
|
+
]
|
|
224
|
+
);
|
|
225
|
+
const result = first(
|
|
226
|
+
(await this.sql.query(
|
|
227
|
+
"SELECT data FROM integration_sdk_operations WHERE namespace=$1 AND owner_id=$2 AND action=$3 AND idempotency_key=$4",
|
|
228
|
+
[
|
|
229
|
+
operation.namespace,
|
|
230
|
+
operation.ownerId,
|
|
231
|
+
operation.action,
|
|
232
|
+
operation.idempotencyKey
|
|
233
|
+
]
|
|
234
|
+
)).rows
|
|
235
|
+
);
|
|
236
|
+
if (!result) throw new IntegrationError("operation_insert_failed");
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
async getOperation(scope, id, lock = false) {
|
|
240
|
+
return first(
|
|
241
|
+
(await this.sql.query(
|
|
242
|
+
`SELECT data FROM integration_sdk_operations WHERE namespace=$1 AND id=$2 AND owner_id=$3${lock ? " FOR UPDATE" : ""}`,
|
|
243
|
+
[scope.namespace, id, scope.ownerId]
|
|
244
|
+
)).rows
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
async listOperations(scope, connectionId) {
|
|
248
|
+
return (await this.sql.query(
|
|
249
|
+
"SELECT data FROM integration_sdk_operations WHERE namespace=$1 AND owner_id=$2 AND ($3::text IS NULL OR connection_id=$3) ORDER BY available_at DESC,id DESC LIMIT 100",
|
|
250
|
+
[scope.namespace, scope.ownerId, connectionId ?? null]
|
|
251
|
+
)).rows.map((row) => row.data);
|
|
252
|
+
}
|
|
253
|
+
async hasPendingRevocation(scope, connectionId) {
|
|
254
|
+
return (await this.sql.query(
|
|
255
|
+
"SELECT id FROM integration_sdk_operations WHERE namespace=$1 AND owner_id=$2 AND connection_id=$3 AND action='__sdk.revoke' AND status NOT IN ('succeeded','cancelled') LIMIT 1",
|
|
256
|
+
[scope.namespace, scope.ownerId, connectionId]
|
|
257
|
+
)).rows.length > 0;
|
|
258
|
+
}
|
|
259
|
+
async saveOperation(operation, expectedVersion, leaseToken) {
|
|
260
|
+
return (await this.sql.query(
|
|
261
|
+
"UPDATE integration_sdk_operations SET version=$4,status=$5,available_at=$6,lease_until=$7,data=$8::jsonb WHERE namespace=$1 AND id=$2 AND owner_id=$3 AND version=$9 AND ($10::text IS NULL OR (data->>'leaseToken'=$10 AND lease_until>$11)) RETURNING id",
|
|
262
|
+
[
|
|
263
|
+
operation.namespace,
|
|
264
|
+
operation.id,
|
|
265
|
+
operation.ownerId,
|
|
266
|
+
operation.version,
|
|
267
|
+
operation.status,
|
|
268
|
+
operation.availableAt,
|
|
269
|
+
operation.leaseUntil ?? null,
|
|
270
|
+
encoded(operation),
|
|
271
|
+
expectedVersion,
|
|
272
|
+
leaseToken ?? null,
|
|
273
|
+
Date.now()
|
|
274
|
+
]
|
|
275
|
+
)).rows.length === 1;
|
|
276
|
+
}
|
|
277
|
+
async claimOperation(namespace, id, version, now, leaseToken, leaseUntil) {
|
|
278
|
+
const locked = first(
|
|
279
|
+
(await this.sql.query(
|
|
280
|
+
"SELECT data FROM integration_sdk_operations WHERE namespace=$1 AND id=$2 AND version=$3 AND status IN ('pending','outcome_unknown') AND available_at<=$4 FOR UPDATE SKIP LOCKED",
|
|
281
|
+
[namespace, id, version, now]
|
|
282
|
+
)).rows
|
|
283
|
+
);
|
|
284
|
+
if (!locked) return null;
|
|
285
|
+
const operation = {
|
|
286
|
+
...locked,
|
|
287
|
+
status: "running",
|
|
288
|
+
version: locked.version + 1,
|
|
289
|
+
attempts: locked.attempts + 1,
|
|
290
|
+
leaseToken,
|
|
291
|
+
leaseUntil,
|
|
292
|
+
updatedAt: now
|
|
293
|
+
};
|
|
294
|
+
return await this.saveOperation(operation, locked.version) ? operation : null;
|
|
295
|
+
}
|
|
296
|
+
async dueOperations(namespace, now, limit) {
|
|
297
|
+
return (await this.sql.query(
|
|
298
|
+
"SELECT id,version FROM integration_sdk_operations WHERE namespace=$1 AND status IN ('pending','outcome_unknown') AND available_at<=$2 ORDER BY available_at,id LIMIT $3",
|
|
299
|
+
[namespace, now, Math.min(1e3, Math.max(1, limit))]
|
|
300
|
+
)).rows;
|
|
301
|
+
}
|
|
302
|
+
async recoverOperations(namespace, now) {
|
|
303
|
+
const { rows } = await this.sql.query(
|
|
304
|
+
"UPDATE integration_sdk_operations SET version=version+1,status=CASE WHEN (data->>'dispatching')::boolean THEN 'outcome_unknown' ELSE 'pending' END,lease_until=NULL,available_at=$2,data=(data-'leaseToken'-'leaseUntil') || jsonb_build_object('version',version+1,'status',CASE WHEN (data->>'dispatching')::boolean THEN 'outcome_unknown' ELSE 'pending' END,'availableAt',$2::bigint,'updatedAt',$2::bigint,'errorCode','execution_interrupted') WHERE namespace=$1 AND status='running' AND lease_until<=$2 RETURNING id",
|
|
305
|
+
[namespace, now]
|
|
306
|
+
);
|
|
307
|
+
return rows.length;
|
|
308
|
+
}
|
|
309
|
+
async insertEvent(event) {
|
|
310
|
+
await this.sql.query(
|
|
311
|
+
"INSERT INTO integration_sdk_events (namespace,id,owner_id,operation_id,created_at,data) VALUES ($1,$2,$3,$4,$5,$6::jsonb) ON CONFLICT (namespace,id) DO NOTHING",
|
|
312
|
+
[
|
|
313
|
+
event.namespace,
|
|
314
|
+
event.id,
|
|
315
|
+
event.ownerId,
|
|
316
|
+
event.operationId,
|
|
317
|
+
event.createdAt,
|
|
318
|
+
encoded(event)
|
|
319
|
+
]
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
async listEvents(scope, after) {
|
|
323
|
+
return (await this.sql.query(
|
|
324
|
+
"SELECT data FROM integration_sdk_events WHERE namespace=$1 AND owner_id=$2 AND ($3::text IS NULL OR (created_at,id) > (SELECT created_at,id FROM integration_sdk_events WHERE namespace=$1 AND owner_id=$2 AND id=$3)) ORDER BY created_at,id LIMIT 100",
|
|
325
|
+
[scope.namespace, scope.ownerId, after ?? null]
|
|
326
|
+
)).rows.map((row) => row.data);
|
|
327
|
+
}
|
|
328
|
+
async eraseOwner(scope, preserveRevocations) {
|
|
329
|
+
const params = [scope.namespace, scope.ownerId];
|
|
330
|
+
await this.sql.query(
|
|
331
|
+
"DELETE FROM integration_sdk_flows WHERE namespace=$1 AND owner_id=$2",
|
|
332
|
+
params
|
|
333
|
+
);
|
|
334
|
+
await this.sql.query(
|
|
335
|
+
"DELETE FROM integration_sdk_events WHERE namespace=$1 AND owner_id=$2",
|
|
336
|
+
params
|
|
337
|
+
);
|
|
338
|
+
if (preserveRevocations) {
|
|
339
|
+
await this.sql.query(
|
|
340
|
+
"DELETE FROM integration_sdk_operations WHERE namespace=$1 AND owner_id=$2 AND (action<>'__sdk.revoke' OR status IN ('succeeded','cancelled'))",
|
|
341
|
+
params
|
|
342
|
+
);
|
|
343
|
+
await this.sql.query(
|
|
344
|
+
"DELETE FROM integration_sdk_connections c WHERE namespace=$1 AND owner_id=$2 AND NOT EXISTS (SELECT 1 FROM integration_sdk_operations o WHERE o.namespace=c.namespace AND o.connection_id=c.id)",
|
|
345
|
+
params
|
|
346
|
+
);
|
|
347
|
+
await this.sql.query(
|
|
348
|
+
"DELETE FROM integration_sdk_grants g WHERE namespace=$1 AND owner_id=$2 AND NOT EXISTS (SELECT 1 FROM integration_sdk_connections c WHERE c.namespace=g.namespace AND c.data->>'grantId'=g.id)",
|
|
349
|
+
params
|
|
350
|
+
);
|
|
351
|
+
} else {
|
|
352
|
+
for (const table of [
|
|
353
|
+
"integration_sdk_operations",
|
|
354
|
+
"integration_sdk_connections",
|
|
355
|
+
"integration_sdk_grants"
|
|
356
|
+
])
|
|
357
|
+
await this.sql.query(
|
|
358
|
+
`DELETE FROM ${table} WHERE namespace=$1 AND owner_id=$2`,
|
|
359
|
+
params
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
export {
|
|
365
|
+
PostgresStore,
|
|
366
|
+
PostgresTransaction
|
|
367
|
+
};
|
|
368
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors.ts","../../src/adapters/postgres.ts"],"sourcesContent":["export type FailureDisposition =\n \"retryable\" | \"requires_reauth\" | \"outcome_unknown\" | \"permanent_failure\";\nexport class IntegrationError extends Error {\n readonly sdkError = \"@mycreat-labs/integrations-sdk/v1\";\n constructor(\n readonly code: string,\n readonly disposition: FailureDisposition = \"permanent_failure\",\n readonly retryAfterMs?: number,\n ) {\n super(code);\n this.name = \"IntegrationError\";\n }\n}\nexport function integrationError(error: unknown): IntegrationError {\n if (error instanceof IntegrationError) return error;\n // Bundled subpath entries and independently installed copies have separate class identities.\n if (\n error instanceof Error &&\n \"sdkError\" in error &&\n error.sdkError === \"@mycreat-labs/integrations-sdk/v1\" &&\n \"code\" in error &&\n typeof error.code === \"string\" &&\n /^[a-zA-Z0-9_]{1,100}$/.test(error.code) &&\n \"disposition\" in error &&\n [\n \"retryable\",\n \"requires_reauth\",\n \"outcome_unknown\",\n \"permanent_failure\",\n ].includes(String(error.disposition))\n ) {\n const delay =\n \"retryAfterMs\" in error &&\n typeof error.retryAfterMs === \"number\" &&\n Number.isFinite(error.retryAfterMs) &&\n error.retryAfterMs >= 0\n ? error.retryAfterMs\n : undefined;\n return new IntegrationError(\n error.code,\n error.disposition as FailureDisposition,\n delay,\n );\n }\n return new IntegrationError(\"provider_unavailable\", \"outcome_unknown\");\n}\n","import { IntegrationError } from \"../errors\";\nimport type {\n Connection,\n Flow,\n Grant,\n IntegrationEvent,\n IntegrationStore,\n IntegrationTransaction,\n Operation,\n Scope,\n} from \"../types\";\n\nexport interface SqlExecutor {\n query<T = Record<string, unknown>>(\n text: string,\n values?: readonly unknown[],\n ): Promise<{ rows: T[] }>;\n}\nexport type SqlTransactionRunner = <T>(\n work: (executor: SqlExecutor, nativeContext?: unknown) => Promise<T>,\n) => Promise<T>;\nconst encoded = (value: unknown) => JSON.stringify(value);\nconst first = <T>(rows: Array<{ data: T }>) => rows[0]?.data ?? null;\n\nexport class PostgresStore implements IntegrationStore {\n constructor(\n private readonly executor: SqlExecutor,\n private readonly runner: SqlTransactionRunner,\n ) {}\n async assertCompatible() {\n const { rows } = await this.executor.query<{ version: number }>(\n \"SELECT version FROM integration_sdk_meta WHERE id = 1\",\n );\n if (rows[0]?.version !== 1)\n throw new IntegrationError(\"incompatible_storage_schema\");\n const missing = await this.executor.query<{ name: string }>(\n \"SELECT name FROM unnest(ARRAY['integration_sdk_owners','integration_sdk_flows','integration_sdk_grants','integration_sdk_connections','integration_sdk_operations','integration_sdk_events']) AS name WHERE to_regclass(name) IS NULL\",\n );\n const unique = await this.executor.query<{ valid: boolean }>(\n \"SELECT indisunique AND indisvalid AS valid FROM pg_index WHERE indexrelid=to_regclass('integration_sdk_connections_account_owner_key')\",\n );\n if (missing.rows.length || unique.rows[0]?.valid !== true)\n throw new IntegrationError(\"incompatible_storage_schema\");\n }\n transaction<T>(work: (tx: IntegrationTransaction) => Promise<T>): Promise<T> {\n return this.runner((executor, native) =>\n work(new PostgresTransaction(executor, native)),\n );\n }\n static bind(executor: SqlExecutor, nativeContext?: unknown) {\n return new PostgresStore(executor, async (work) =>\n work(executor, nativeContext),\n );\n }\n}\n\nexport class PostgresTransaction implements IntegrationTransaction {\n constructor(\n private readonly sql: SqlExecutor,\n readonly nativeContext?: unknown,\n ) {}\n async lockScope(scope: Scope, allowErased = false) {\n await this.sql.query(\n \"INSERT INTO integration_sdk_owners (namespace,owner_id,erased,epochs) VALUES ($1,$2,false,'{}'::jsonb) ON CONFLICT DO NOTHING\",\n [scope.namespace, scope.ownerId],\n );\n const { rows } = await this.sql.query<{ erased: boolean }>(\n \"SELECT erased FROM integration_sdk_owners WHERE namespace=$1 AND owner_id=$2 FOR UPDATE\",\n [scope.namespace, scope.ownerId],\n );\n if (rows[0]?.erased && !allowErased)\n throw new IntegrationError(\"owner_erased\");\n return rows[0]?.erased ?? false;\n }\n async authorizationEpoch(scope: Scope, key: string, advance = false) {\n if (advance) {\n const { rows } = await this.sql.query<{ epoch: number }>(\n \"UPDATE integration_sdk_owners SET epochs=jsonb_set(epochs,ARRAY[$3::text],to_jsonb(COALESCE((epochs->>$3)::int,0)+1)) WHERE namespace=$1 AND owner_id=$2 RETURNING (epochs->>$3)::int AS epoch\",\n [scope.namespace, scope.ownerId, key],\n );\n if (!rows[0]) throw new IntegrationError(\"owner_not_locked\");\n return rows[0].epoch;\n }\n const { rows } = await this.sql.query<{ epoch: number }>(\n \"SELECT COALESCE((epochs->>$3)::int,0) AS epoch FROM integration_sdk_owners WHERE namespace=$1 AND owner_id=$2\",\n [scope.namespace, scope.ownerId, key],\n );\n return rows[0]?.epoch ?? 0;\n }\n async markOwnerErased(scope: Scope) {\n await this.sql.query(\n \"UPDATE integration_sdk_owners SET erased=true WHERE namespace=$1 AND owner_id=$2\",\n [scope.namespace, scope.ownerId],\n );\n }\n async assertAccountOwner(\n scope: Scope,\n provider: string,\n registrationId: string,\n accountId: string,\n ) {\n await this.sql.query(\n \"SELECT 1 AS locked FROM pg_advisory_xact_lock(hashtextextended($1,0))\",\n [JSON.stringify([scope.namespace, provider, registrationId, accountId])],\n );\n const { rows } = await this.sql.query(\n \"SELECT id FROM integration_sdk_connections WHERE namespace=$1 AND provider=$2 AND registration_id=$3 AND account_id=$4 AND owner_id<>$5 LIMIT 1\",\n [scope.namespace, provider, registrationId, accountId, scope.ownerId],\n );\n if (rows.length) throw new IntegrationError(\"account_already_connected\");\n }\n async collectGarbage(namespace: string, now: number, retentionMs: number) {\n await this.sql.query(\n \"DELETE FROM integration_sdk_flows WHERE namespace=$1 AND expires_at<=$2\",\n [namespace, now],\n );\n await this.sql.query(\n \"UPDATE integration_sdk_operations SET status='failed',version=version+1,data=(data-'hostContext') || jsonb_build_object('status','failed','version',version+1,'input',jsonb_build_object('keyId','','value',''),'errorCode','payload_expired') WHERE namespace=$1 AND action<>'__sdk.revoke' AND status IN ('failed','blocked','outcome_unknown','cancelled') AND (data->>'updatedAt')::bigint<=$2 AND data->>'errorCode' IS DISTINCT FROM 'payload_expired'\",\n [namespace, now - retentionMs],\n );\n await this.sql.query(\n \"DELETE FROM integration_sdk_grants g WHERE namespace=$1 AND data->>'status'='revoked' AND NOT EXISTS (SELECT 1 FROM integration_sdk_connections c WHERE c.namespace=g.namespace AND c.data->>'grantId'=g.id)\",\n [namespace],\n );\n }\n async insertFlow(flow: Flow) {\n await this.sql.query(\n \"INSERT INTO integration_sdk_flows (namespace,id,owner_id,purpose,binding_hash,expires_at,data) VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb)\",\n [\n flow.namespace,\n flow.id,\n flow.ownerId,\n flow.purpose,\n flow.bindingHash,\n flow.expiresAt,\n encoded(flow),\n ],\n );\n }\n async consumeFlow(\n namespace: string,\n id: string,\n bindingHash: string,\n purpose: Flow[\"purpose\"],\n now: number,\n ) {\n const { rows } = await this.sql.query<{ data: Flow }>(\n \"DELETE FROM integration_sdk_flows WHERE namespace=$1 AND id=$2 AND binding_hash=$3 AND purpose=$4 AND expires_at>$5 RETURNING data\",\n [namespace, id, bindingHash, purpose, now],\n );\n return first(rows);\n }\n async insertGrant(grant: Grant) {\n await this.sql.query(\n \"INSERT INTO integration_sdk_grants (namespace,id,owner_id,version,data) VALUES ($1,$2,$3,$4,$5::jsonb)\",\n [grant.namespace, grant.id, grant.ownerId, grant.version, encoded(grant)],\n );\n }\n async getGrant(scope: Scope, id: string, lock = false) {\n return first(\n (\n await this.sql.query<{ data: Grant }>(\n `SELECT data FROM integration_sdk_grants WHERE namespace=$1 AND id=$2 AND owner_id=$3${lock ? \" FOR UPDATE\" : \"\"}`,\n [scope.namespace, id, scope.ownerId],\n )\n ).rows,\n );\n }\n async saveGrant(grant: Grant, expectedVersion: number) {\n return (\n (\n await this.sql.query(\n \"UPDATE integration_sdk_grants SET version=$4,data=$5::jsonb WHERE namespace=$1 AND id=$2 AND owner_id=$3 AND version=$6 RETURNING id\",\n [\n grant.namespace,\n grant.id,\n grant.ownerId,\n grant.version,\n encoded(grant),\n expectedVersion,\n ],\n )\n ).rows.length === 1\n );\n }\n async insertConnection(connection: Connection) {\n await this.sql.query(\n \"INSERT INTO integration_sdk_connections (namespace,id,owner_id,provider,registration_id,account_id,generation,data) VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb)\",\n [\n connection.namespace,\n connection.id,\n connection.ownerId,\n connection.provider,\n connection.registrationId,\n connection.account.id,\n connection.generation,\n encoded(connection),\n ],\n );\n }\n async getConnection(scope: Scope, id: string, lock = false) {\n return first(\n (\n await this.sql.query<{ data: Connection }>(\n `SELECT data FROM integration_sdk_connections WHERE namespace=$1 AND id=$2 AND owner_id=$3${lock ? \" FOR UPDATE\" : \"\"}`,\n [scope.namespace, id, scope.ownerId],\n )\n ).rows,\n );\n }\n async listConnections(scope: Scope) {\n return (\n await this.sql.query<{ data: Connection }>(\n \"SELECT data FROM integration_sdk_connections WHERE namespace=$1 AND owner_id=$2 ORDER BY id\",\n [scope.namespace, scope.ownerId],\n )\n ).rows.map((row) => row.data);\n }\n async saveConnection(connection: Connection, expectedGeneration: number) {\n return (\n (\n await this.sql.query(\n \"UPDATE integration_sdk_connections SET generation=$4,data=$5::jsonb WHERE namespace=$1 AND id=$2 AND owner_id=$3 AND generation=$6 RETURNING id\",\n [\n connection.namespace,\n connection.id,\n connection.ownerId,\n connection.generation,\n encoded(connection),\n expectedGeneration,\n ],\n )\n ).rows.length === 1\n );\n }\n async insertOperation(operation: Operation) {\n await this.sql.query(\n \"INSERT INTO integration_sdk_operations (namespace,id,owner_id,connection_id,action,idempotency_key,version,status,available_at,lease_until,data) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,NULL,$10::jsonb) ON CONFLICT (namespace,owner_id,action,idempotency_key) DO NOTHING\",\n [\n operation.namespace,\n operation.id,\n operation.ownerId,\n operation.connectionId,\n operation.action,\n operation.idempotencyKey,\n operation.version,\n operation.status,\n operation.availableAt,\n encoded(operation),\n ],\n );\n const result = first(\n (\n await this.sql.query<{ data: Operation }>(\n \"SELECT data FROM integration_sdk_operations WHERE namespace=$1 AND owner_id=$2 AND action=$3 AND idempotency_key=$4\",\n [\n operation.namespace,\n operation.ownerId,\n operation.action,\n operation.idempotencyKey,\n ],\n )\n ).rows,\n );\n if (!result) throw new IntegrationError(\"operation_insert_failed\");\n return result;\n }\n async getOperation(scope: Scope, id: string, lock = false) {\n return first(\n (\n await this.sql.query<{ data: Operation }>(\n `SELECT data FROM integration_sdk_operations WHERE namespace=$1 AND id=$2 AND owner_id=$3${lock ? \" FOR UPDATE\" : \"\"}`,\n [scope.namespace, id, scope.ownerId],\n )\n ).rows,\n );\n }\n async listOperations(scope: Scope, connectionId?: string) {\n return (\n await this.sql.query<{ data: Operation }>(\n \"SELECT data FROM integration_sdk_operations WHERE namespace=$1 AND owner_id=$2 AND ($3::text IS NULL OR connection_id=$3) ORDER BY available_at DESC,id DESC LIMIT 100\",\n [scope.namespace, scope.ownerId, connectionId ?? null],\n )\n ).rows.map((row) => row.data);\n }\n async hasPendingRevocation(scope: Scope, connectionId: string) {\n return (\n (\n await this.sql.query(\n \"SELECT id FROM integration_sdk_operations WHERE namespace=$1 AND owner_id=$2 AND connection_id=$3 AND action='__sdk.revoke' AND status NOT IN ('succeeded','cancelled') LIMIT 1\",\n [scope.namespace, scope.ownerId, connectionId],\n )\n ).rows.length > 0\n );\n }\n async saveOperation(\n operation: Operation,\n expectedVersion: number,\n leaseToken?: string,\n ) {\n return (\n (\n await this.sql.query(\n \"UPDATE integration_sdk_operations SET version=$4,status=$5,available_at=$6,lease_until=$7,data=$8::jsonb WHERE namespace=$1 AND id=$2 AND owner_id=$3 AND version=$9 AND ($10::text IS NULL OR (data->>'leaseToken'=$10 AND lease_until>$11)) RETURNING id\",\n [\n operation.namespace,\n operation.id,\n operation.ownerId,\n operation.version,\n operation.status,\n operation.availableAt,\n operation.leaseUntil ?? null,\n encoded(operation),\n expectedVersion,\n leaseToken ?? null,\n Date.now(),\n ],\n )\n ).rows.length === 1\n );\n }\n async claimOperation(\n namespace: string,\n id: string,\n version: number,\n now: number,\n leaseToken: string,\n leaseUntil: number,\n ) {\n const locked = first(\n (\n await this.sql.query<{ data: Operation }>(\n \"SELECT data FROM integration_sdk_operations WHERE namespace=$1 AND id=$2 AND version=$3 AND status IN ('pending','outcome_unknown') AND available_at<=$4 FOR UPDATE SKIP LOCKED\",\n [namespace, id, version, now],\n )\n ).rows,\n );\n if (!locked) return null;\n const operation: Operation = {\n ...locked,\n status: \"running\",\n version: locked.version + 1,\n attempts: locked.attempts + 1,\n leaseToken,\n leaseUntil,\n updatedAt: now,\n };\n return (await this.saveOperation(operation, locked.version))\n ? operation\n : null;\n }\n async dueOperations(namespace: string, now: number, limit: number) {\n return (\n await this.sql.query<{ id: string; version: number }>(\n \"SELECT id,version FROM integration_sdk_operations WHERE namespace=$1 AND status IN ('pending','outcome_unknown') AND available_at<=$2 ORDER BY available_at,id LIMIT $3\",\n [namespace, now, Math.min(1000, Math.max(1, limit))],\n )\n ).rows;\n }\n async recoverOperations(namespace: string, now: number) {\n const { rows } = await this.sql.query(\n \"UPDATE integration_sdk_operations SET version=version+1,status=CASE WHEN (data->>'dispatching')::boolean THEN 'outcome_unknown' ELSE 'pending' END,lease_until=NULL,available_at=$2,data=(data-'leaseToken'-'leaseUntil') || jsonb_build_object('version',version+1,'status',CASE WHEN (data->>'dispatching')::boolean THEN 'outcome_unknown' ELSE 'pending' END,'availableAt',$2::bigint,'updatedAt',$2::bigint,'errorCode','execution_interrupted') WHERE namespace=$1 AND status='running' AND lease_until<=$2 RETURNING id\",\n [namespace, now],\n );\n return rows.length;\n }\n async insertEvent(event: IntegrationEvent) {\n await this.sql.query(\n \"INSERT INTO integration_sdk_events (namespace,id,owner_id,operation_id,created_at,data) VALUES ($1,$2,$3,$4,$5,$6::jsonb) ON CONFLICT (namespace,id) DO NOTHING\",\n [\n event.namespace,\n event.id,\n event.ownerId,\n event.operationId,\n event.createdAt,\n encoded(event),\n ],\n );\n }\n async listEvents(scope: Scope, after?: string) {\n return (\n await this.sql.query<{ data: IntegrationEvent }>(\n \"SELECT data FROM integration_sdk_events WHERE namespace=$1 AND owner_id=$2 AND ($3::text IS NULL OR (created_at,id) > (SELECT created_at,id FROM integration_sdk_events WHERE namespace=$1 AND owner_id=$2 AND id=$3)) ORDER BY created_at,id LIMIT 100\",\n [scope.namespace, scope.ownerId, after ?? null],\n )\n ).rows.map((row) => row.data);\n }\n async eraseOwner(scope: Scope, preserveRevocations: boolean) {\n const params = [scope.namespace, scope.ownerId];\n await this.sql.query(\n \"DELETE FROM integration_sdk_flows WHERE namespace=$1 AND owner_id=$2\",\n params,\n );\n await this.sql.query(\n \"DELETE FROM integration_sdk_events WHERE namespace=$1 AND owner_id=$2\",\n params,\n );\n if (preserveRevocations) {\n await this.sql.query(\n \"DELETE FROM integration_sdk_operations WHERE namespace=$1 AND owner_id=$2 AND (action<>'__sdk.revoke' OR status IN ('succeeded','cancelled'))\",\n params,\n );\n await this.sql.query(\n \"DELETE FROM integration_sdk_connections c WHERE namespace=$1 AND owner_id=$2 AND NOT EXISTS (SELECT 1 FROM integration_sdk_operations o WHERE o.namespace=c.namespace AND o.connection_id=c.id)\",\n params,\n );\n await this.sql.query(\n \"DELETE FROM integration_sdk_grants g WHERE namespace=$1 AND owner_id=$2 AND NOT EXISTS (SELECT 1 FROM integration_sdk_connections c WHERE c.namespace=g.namespace AND c.data->>'grantId'=g.id)\",\n params,\n );\n } else {\n for (const table of [\n \"integration_sdk_operations\",\n \"integration_sdk_connections\",\n \"integration_sdk_grants\",\n ])\n await this.sql.query(\n `DELETE FROM ${table} WHERE namespace=$1 AND owner_id=$2`,\n params,\n );\n }\n }\n}\n"],"mappings":"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","names":["rows"]}
|