@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.
Files changed (54) hide show
  1. package/README.md +54 -2
  2. package/dist/adapters/bullmq.cjs +110 -0
  3. package/dist/adapters/bullmq.cjs.map +1 -0
  4. package/dist/adapters/bullmq.d.cts +11 -0
  5. package/dist/adapters/bullmq.d.ts +11 -0
  6. package/dist/adapters/bullmq.js +85 -0
  7. package/dist/adapters/bullmq.js.map +1 -0
  8. package/dist/adapters/postgres.cjs +396 -0
  9. package/dist/adapters/postgres.cjs.map +1 -0
  10. package/dist/adapters/postgres.d.cts +51 -0
  11. package/dist/adapters/postgres.d.ts +51 -0
  12. package/dist/adapters/postgres.js +368 -0
  13. package/dist/adapters/postgres.js.map +1 -0
  14. package/dist/adapters/prisma.cjs +414 -0
  15. package/dist/adapters/prisma.cjs.map +1 -0
  16. package/dist/adapters/prisma.d.cts +15 -0
  17. package/dist/adapters/prisma.d.ts +15 -0
  18. package/dist/adapters/prisma.js +386 -0
  19. package/dist/adapters/prisma.js.map +1 -0
  20. package/dist/adapters/redis.cjs +125 -0
  21. package/dist/adapters/redis.cjs.map +1 -0
  22. package/dist/adapters/redis.d.cts +20 -0
  23. package/dist/adapters/redis.d.ts +20 -0
  24. package/dist/adapters/redis.js +98 -0
  25. package/dist/adapters/redis.js.map +1 -0
  26. package/dist/errors-3hDEtzuZ.d.cts +11 -0
  27. package/dist/errors-3hDEtzuZ.d.ts +11 -0
  28. package/dist/index.cjs +1328 -0
  29. package/dist/index.cjs.map +1 -0
  30. package/dist/index.d.cts +128 -0
  31. package/dist/index.d.ts +128 -0
  32. package/dist/index.js +1297 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/providers/index.cjs +1287 -0
  35. package/dist/providers/index.cjs.map +1 -0
  36. package/dist/providers/index.d.cts +144 -0
  37. package/dist/providers/index.d.ts +144 -0
  38. package/dist/providers/index.js +1252 -0
  39. package/dist/providers/index.js.map +1 -0
  40. package/dist/testing.cjs +275 -0
  41. package/dist/testing.cjs.map +1 -0
  42. package/dist/testing.d.cts +20 -0
  43. package/dist/testing.d.ts +20 -0
  44. package/dist/testing.js +248 -0
  45. package/dist/testing.js.map +1 -0
  46. package/dist/types-Y4-DoJ1A.d.cts +260 -0
  47. package/dist/types-Y4-DoJ1A.d.ts +260 -0
  48. package/docs/migration.md +23 -0
  49. package/docs/security.md +26 -0
  50. package/docs/verification.md +22 -0
  51. package/examples/embedded-host.ts +62 -0
  52. package/migrations/0001_initial.sql +40 -0
  53. package/package.json +90 -4
  54. package/schema.prisma +80 -0
@@ -0,0 +1,386 @@
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(executor2, runner) {
21
+ this.executor = executor2;
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
+ (executor2, native) => work(new PostgresTransaction(executor2, native))
44
+ );
45
+ }
46
+ static bind(executor2, nativeContext) {
47
+ return new _PostgresStore(
48
+ executor2,
49
+ async (work) => work(executor2, 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
+
365
+ // src/adapters/prisma.ts
366
+ function executor(client) {
367
+ return {
368
+ async query(text, values = []) {
369
+ return { rows: await client.$queryRawUnsafe(text, ...values) };
370
+ }
371
+ };
372
+ }
373
+ function createPrismaStore(client) {
374
+ return new PostgresStore(
375
+ executor(client),
376
+ (work) => client.$transaction((tx) => work(executor(tx), tx))
377
+ );
378
+ }
379
+ function bindPrismaTransaction(transaction) {
380
+ return PostgresStore.bind(executor(transaction), transaction);
381
+ }
382
+ export {
383
+ bindPrismaTransaction,
384
+ createPrismaStore
385
+ };
386
+ //# sourceMappingURL=prisma.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/adapters/postgres.ts","../../src/adapters/prisma.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","import { PostgresStore, type SqlExecutor } from \"./postgres\";\n\nexport interface PrismaTransactionLike {\n $queryRawUnsafe<T = unknown>(query: string, ...values: unknown[]): Promise<T>;\n}\nexport interface PrismaClientLike extends PrismaTransactionLike {\n $transaction<T>(work: (tx: PrismaTransactionLike) => Promise<T>): Promise<T>;\n}\nfunction executor(client: PrismaTransactionLike): SqlExecutor {\n return {\n async query<T>(text: string, values: readonly unknown[] = []) {\n return { rows: await client.$queryRawUnsafe<T[]>(text, ...values) };\n },\n };\n}\n/** Uses the host's supplied client. No client, pool, environment or schema is created. */\nexport function createPrismaStore(client: PrismaClientLike) {\n return new PostgresStore(executor(client), (work) =>\n client.$transaction((tx) => work(executor(tx), tx)),\n );\n}\n/** The caller must already be inside its native Prisma transaction. */\nexport function bindPrismaTransaction(transaction: PrismaTransactionLike) {\n return PostgresStore.bind(executor(transaction), transaction);\n}\n"],"mappings":"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","names":["executor","rows"]}
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/adapters/redis.ts
21
+ var redis_exports = {};
22
+ __export(redis_exports, {
23
+ createRedisLease: () => createRedisLease,
24
+ createRedisOneTimeStore: () => createRedisOneTimeStore,
25
+ createRedisRateLimiter: () => createRedisRateLimiter
26
+ });
27
+ module.exports = __toCommonJS(redis_exports);
28
+ var import_node_crypto = require("crypto");
29
+
30
+ // src/errors.ts
31
+ var IntegrationError = class extends Error {
32
+ constructor(code, disposition = "permanent_failure", retryAfterMs) {
33
+ super(code);
34
+ this.code = code;
35
+ this.disposition = disposition;
36
+ this.retryAfterMs = retryAfterMs;
37
+ this.name = "IntegrationError";
38
+ }
39
+ code;
40
+ disposition;
41
+ retryAfterMs;
42
+ sdkError = "@mycreat-labs/integrations-sdk/v1";
43
+ };
44
+
45
+ // src/adapters/redis.ts
46
+ function createRedisOneTimeStore(redis, prefix) {
47
+ if (!/^[a-zA-Z0-9:_-]{1,128}$/.test(prefix))
48
+ throw new IntegrationError("invalid_redis_prefix");
49
+ return {
50
+ async put(id, value, ttlMs) {
51
+ if (!Number.isInteger(ttlMs) || ttlMs < 1 || ttlMs > 6e5)
52
+ throw new IntegrationError("invalid_expiry");
53
+ const inserted = await redis.set(
54
+ `${prefix}:once:${id}`,
55
+ value,
56
+ "PX",
57
+ ttlMs,
58
+ "NX"
59
+ );
60
+ if (inserted !== "OK") throw new IntegrationError("one_time_conflict");
61
+ },
62
+ async consume(id) {
63
+ return await redis.eval(
64
+ "local v=redis.call('GET',KEYS[1]); if v then redis.call('DEL',KEYS[1]); end; return v",
65
+ 1,
66
+ `${prefix}:once:${id}`
67
+ );
68
+ }
69
+ };
70
+ }
71
+ function createRedisLease(redis, prefix) {
72
+ if (!/^[a-zA-Z0-9:_-]{1,128}$/.test(prefix))
73
+ throw new IntegrationError("invalid_redis_prefix");
74
+ return {
75
+ async acquire(key, ttlMs) {
76
+ if (!Number.isInteger(ttlMs) || ttlMs < 1)
77
+ throw new IntegrationError("invalid_expiry");
78
+ const token = (0, import_node_crypto.randomUUID)();
79
+ return await redis.set(
80
+ `${prefix}:lease:${key}`,
81
+ token,
82
+ "PX",
83
+ ttlMs,
84
+ "NX"
85
+ ) === "OK" ? token : null;
86
+ },
87
+ async release(key, token) {
88
+ return Number(
89
+ await redis.eval(
90
+ "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]); end; return 0",
91
+ 1,
92
+ `${prefix}:lease:${key}`,
93
+ token
94
+ )
95
+ ) === 1;
96
+ }
97
+ };
98
+ }
99
+ function createRedisRateLimiter(redis, options) {
100
+ if (!/^[a-zA-Z0-9:_-]{1,128}$/.test(options.prefix) || !Number.isInteger(options.limit) || options.limit < 1 || !Number.isInteger(options.windowMs) || options.windowMs < 1)
101
+ throw new IntegrationError("invalid_rate_limit");
102
+ return {
103
+ async take(key) {
104
+ const result = await redis.eval(
105
+ "local n=redis.call('INCR',KEYS[1]); if n==1 then redis.call('PEXPIRE',KEYS[1],ARGV[1]); end; return {n,redis.call('PTTL',KEYS[1])}",
106
+ 1,
107
+ `${options.prefix}:rate:${key}`,
108
+ options.windowMs
109
+ );
110
+ if (!Array.isArray(result) || result.length !== 2 || !Number.isInteger(Number(result[0])) || Number(result[0]) < 1 || !Number.isFinite(Number(result[1])) || Number(result[1]) < 0)
111
+ throw new IntegrationError("rate_limit_unavailable", "retryable", 1e3);
112
+ return {
113
+ allowed: Number(result[0]) <= options.limit,
114
+ retryAfterMs: Math.max(1, Number(result[1]))
115
+ };
116
+ }
117
+ };
118
+ }
119
+ // Annotate the CommonJS export names for ESM import in node:
120
+ 0 && (module.exports = {
121
+ createRedisLease,
122
+ createRedisOneTimeStore,
123
+ createRedisRateLimiter
124
+ });
125
+ //# sourceMappingURL=redis.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/redis.ts","../../src/errors.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type Redis from \"ioredis\";\nimport { IntegrationError } from \"../errors\";\nimport type { ApiRateLimiter } from \"../types\";\n\n/** Atomic one-shot values for product-native handoffs; namespace every product/environment. */\nexport function createRedisOneTimeStore(\n redis: Pick<Redis, \"set\" | \"eval\">,\n prefix: string,\n) {\n if (!/^[a-zA-Z0-9:_-]{1,128}$/.test(prefix))\n throw new IntegrationError(\"invalid_redis_prefix\");\n return {\n async put(id: string, value: string, ttlMs: number) {\n if (!Number.isInteger(ttlMs) || ttlMs < 1 || ttlMs > 600_000)\n throw new IntegrationError(\"invalid_expiry\");\n const inserted = await redis.set(\n `${prefix}:once:${id}`,\n value,\n \"PX\",\n ttlMs,\n \"NX\",\n );\n if (inserted !== \"OK\") throw new IntegrationError(\"one_time_conflict\");\n },\n async consume(id: string) {\n return (await redis.eval(\n \"local v=redis.call('GET',KEYS[1]); if v then redis.call('DEL',KEYS[1]); end; return v\",\n 1,\n `${prefix}:once:${id}`,\n )) as string | null;\n },\n };\n}\nexport function createRedisLease(\n redis: Pick<Redis, \"set\" | \"eval\">,\n prefix: string,\n) {\n if (!/^[a-zA-Z0-9:_-]{1,128}$/.test(prefix))\n throw new IntegrationError(\"invalid_redis_prefix\");\n return {\n async acquire(key: string, ttlMs: number) {\n if (!Number.isInteger(ttlMs) || ttlMs < 1)\n throw new IntegrationError(\"invalid_expiry\");\n const token = randomUUID();\n return (await redis.set(\n `${prefix}:lease:${key}`,\n token,\n \"PX\",\n ttlMs,\n \"NX\",\n )) === \"OK\"\n ? token\n : null;\n },\n async release(key: string, token: string) {\n return (\n Number(\n await redis.eval(\n \"if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]); end; return 0\",\n 1,\n `${prefix}:lease:${key}`,\n token,\n ),\n ) === 1\n );\n },\n };\n}\n/** Atomic fixed-window admission shared by the product's API and Worker processes. */\nexport function createRedisRateLimiter(\n redis: Pick<Redis, \"eval\">,\n options: { prefix: string; limit: number; windowMs: number },\n): ApiRateLimiter {\n if (\n !/^[a-zA-Z0-9:_-]{1,128}$/.test(options.prefix) ||\n !Number.isInteger(options.limit) ||\n options.limit < 1 ||\n !Number.isInteger(options.windowMs) ||\n options.windowMs < 1\n )\n throw new IntegrationError(\"invalid_rate_limit\");\n return {\n async take(key) {\n const result = (await redis.eval(\n \"local n=redis.call('INCR',KEYS[1]); if n==1 then redis.call('PEXPIRE',KEYS[1],ARGV[1]); end; return {n,redis.call('PTTL',KEYS[1])}\",\n 1,\n `${options.prefix}:rate:${key}`,\n options.windowMs,\n )) as unknown;\n if (\n !Array.isArray(result) ||\n result.length !== 2 ||\n !Number.isInteger(Number(result[0])) ||\n Number(result[0]) < 1 ||\n !Number.isFinite(Number(result[1])) ||\n Number(result[1]) < 0\n )\n throw new IntegrationError(\"rate_limit_unavailable\", \"retryable\", 1000);\n return {\n allowed: Number(result[0]) <= options.limit,\n retryAfterMs: Math.max(1, Number(result[1])),\n };\n },\n };\n}\n","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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA2B;;;ACEpB,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAE1C,YACW,MACA,cAAkC,qBAClC,cACT;AACA,UAAM,IAAI;AAJD;AACA;AACA;AAGT,SAAK,OAAO;AAAA,EACd;AAAA,EANW;AAAA,EACA;AAAA,EACA;AAAA,EAJF,WAAW;AAStB;;;ADNO,SAAS,wBACd,OACA,QACA;AACA,MAAI,CAAC,0BAA0B,KAAK,MAAM;AACxC,UAAM,IAAI,iBAAiB,sBAAsB;AACnD,SAAO;AAAA,IACL,MAAM,IAAI,IAAY,OAAe,OAAe;AAClD,UAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ;AACnD,cAAM,IAAI,iBAAiB,gBAAgB;AAC7C,YAAM,WAAW,MAAM,MAAM;AAAA,QAC3B,GAAG,MAAM,SAAS,EAAE;AAAA,QACpB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,UAAI,aAAa,KAAM,OAAM,IAAI,iBAAiB,mBAAmB;AAAA,IACvE;AAAA,IACA,MAAM,QAAQ,IAAY;AACxB,aAAQ,MAAM,MAAM;AAAA,QAClB;AAAA,QACA;AAAA,QACA,GAAG,MAAM,SAAS,EAAE;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AACF;AACO,SAAS,iBACd,OACA,QACA;AACA,MAAI,CAAC,0BAA0B,KAAK,MAAM;AACxC,UAAM,IAAI,iBAAiB,sBAAsB;AACnD,SAAO;AAAA,IACL,MAAM,QAAQ,KAAa,OAAe;AACxC,UAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ;AACtC,cAAM,IAAI,iBAAiB,gBAAgB;AAC7C,YAAM,YAAQ,+BAAW;AACzB,aAAQ,MAAM,MAAM;AAAA,QAClB,GAAG,MAAM,UAAU,GAAG;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,MAAO,OACH,QACA;AAAA,IACN;AAAA,IACA,MAAM,QAAQ,KAAa,OAAe;AACxC,aACE;AAAA,QACE,MAAM,MAAM;AAAA,UACV;AAAA,UACA;AAAA,UACA,GAAG,MAAM,UAAU,GAAG;AAAA,UACtB;AAAA,QACF;AAAA,MACF,MAAM;AAAA,IAEV;AAAA,EACF;AACF;AAEO,SAAS,uBACd,OACA,SACgB;AAChB,MACE,CAAC,0BAA0B,KAAK,QAAQ,MAAM,KAC9C,CAAC,OAAO,UAAU,QAAQ,KAAK,KAC/B,QAAQ,QAAQ,KAChB,CAAC,OAAO,UAAU,QAAQ,QAAQ,KAClC,QAAQ,WAAW;AAEnB,UAAM,IAAI,iBAAiB,oBAAoB;AACjD,SAAO;AAAA,IACL,MAAM,KAAK,KAAK;AACd,YAAM,SAAU,MAAM,MAAM;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,GAAG,QAAQ,MAAM,SAAS,GAAG;AAAA,QAC7B,QAAQ;AAAA,MACV;AACA,UACE,CAAC,MAAM,QAAQ,MAAM,KACrB,OAAO,WAAW,KAClB,CAAC,OAAO,UAAU,OAAO,OAAO,CAAC,CAAC,CAAC,KACnC,OAAO,OAAO,CAAC,CAAC,IAAI,KACpB,CAAC,OAAO,SAAS,OAAO,OAAO,CAAC,CAAC,CAAC,KAClC,OAAO,OAAO,CAAC,CAAC,IAAI;AAEpB,cAAM,IAAI,iBAAiB,0BAA0B,aAAa,GAAI;AACxE,aAAO;AAAA,QACL,SAAS,OAAO,OAAO,CAAC,CAAC,KAAK,QAAQ;AAAA,QACtC,cAAc,KAAK,IAAI,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,20 @@
1
+ import Redis from 'ioredis';
2
+ import { A as ApiRateLimiter } from '../types-Y4-DoJ1A.cjs';
3
+
4
+ /** Atomic one-shot values for product-native handoffs; namespace every product/environment. */
5
+ declare function createRedisOneTimeStore(redis: Pick<Redis, "set" | "eval">, prefix: string): {
6
+ put(id: string, value: string, ttlMs: number): Promise<void>;
7
+ consume(id: string): Promise<string | null>;
8
+ };
9
+ declare function createRedisLease(redis: Pick<Redis, "set" | "eval">, prefix: string): {
10
+ acquire(key: string, ttlMs: number): Promise<`${string}-${string}-${string}-${string}-${string}` | null>;
11
+ release(key: string, token: string): Promise<boolean>;
12
+ };
13
+ /** Atomic fixed-window admission shared by the product's API and Worker processes. */
14
+ declare function createRedisRateLimiter(redis: Pick<Redis, "eval">, options: {
15
+ prefix: string;
16
+ limit: number;
17
+ windowMs: number;
18
+ }): ApiRateLimiter;
19
+
20
+ export { createRedisLease, createRedisOneTimeStore, createRedisRateLimiter };
@@ -0,0 +1,20 @@
1
+ import Redis from 'ioredis';
2
+ import { A as ApiRateLimiter } from '../types-Y4-DoJ1A.js';
3
+
4
+ /** Atomic one-shot values for product-native handoffs; namespace every product/environment. */
5
+ declare function createRedisOneTimeStore(redis: Pick<Redis, "set" | "eval">, prefix: string): {
6
+ put(id: string, value: string, ttlMs: number): Promise<void>;
7
+ consume(id: string): Promise<string | null>;
8
+ };
9
+ declare function createRedisLease(redis: Pick<Redis, "set" | "eval">, prefix: string): {
10
+ acquire(key: string, ttlMs: number): Promise<`${string}-${string}-${string}-${string}-${string}` | null>;
11
+ release(key: string, token: string): Promise<boolean>;
12
+ };
13
+ /** Atomic fixed-window admission shared by the product's API and Worker processes. */
14
+ declare function createRedisRateLimiter(redis: Pick<Redis, "eval">, options: {
15
+ prefix: string;
16
+ limit: number;
17
+ windowMs: number;
18
+ }): ApiRateLimiter;
19
+
20
+ export { createRedisLease, createRedisOneTimeStore, createRedisRateLimiter };