@mycreat-labs/integrations-sdk 0.0.0-stage → 0.1.1
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/CHANGELOG.md +14 -0
- 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/releases/0.1.0.md +46 -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 +91 -4
- package/schema.prisma +80 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// src/adapters/redis.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
var IntegrationError = class extends Error {
|
|
6
|
+
constructor(code, disposition = "permanent_failure", retryAfterMs) {
|
|
7
|
+
super(code);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.disposition = disposition;
|
|
10
|
+
this.retryAfterMs = retryAfterMs;
|
|
11
|
+
this.name = "IntegrationError";
|
|
12
|
+
}
|
|
13
|
+
code;
|
|
14
|
+
disposition;
|
|
15
|
+
retryAfterMs;
|
|
16
|
+
sdkError = "@mycreat-labs/integrations-sdk/v1";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/adapters/redis.ts
|
|
20
|
+
function createRedisOneTimeStore(redis, prefix) {
|
|
21
|
+
if (!/^[a-zA-Z0-9:_-]{1,128}$/.test(prefix))
|
|
22
|
+
throw new IntegrationError("invalid_redis_prefix");
|
|
23
|
+
return {
|
|
24
|
+
async put(id, value, ttlMs) {
|
|
25
|
+
if (!Number.isInteger(ttlMs) || ttlMs < 1 || ttlMs > 6e5)
|
|
26
|
+
throw new IntegrationError("invalid_expiry");
|
|
27
|
+
const inserted = await redis.set(
|
|
28
|
+
`${prefix}:once:${id}`,
|
|
29
|
+
value,
|
|
30
|
+
"PX",
|
|
31
|
+
ttlMs,
|
|
32
|
+
"NX"
|
|
33
|
+
);
|
|
34
|
+
if (inserted !== "OK") throw new IntegrationError("one_time_conflict");
|
|
35
|
+
},
|
|
36
|
+
async consume(id) {
|
|
37
|
+
return await redis.eval(
|
|
38
|
+
"local v=redis.call('GET',KEYS[1]); if v then redis.call('DEL',KEYS[1]); end; return v",
|
|
39
|
+
1,
|
|
40
|
+
`${prefix}:once:${id}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function createRedisLease(redis, prefix) {
|
|
46
|
+
if (!/^[a-zA-Z0-9:_-]{1,128}$/.test(prefix))
|
|
47
|
+
throw new IntegrationError("invalid_redis_prefix");
|
|
48
|
+
return {
|
|
49
|
+
async acquire(key, ttlMs) {
|
|
50
|
+
if (!Number.isInteger(ttlMs) || ttlMs < 1)
|
|
51
|
+
throw new IntegrationError("invalid_expiry");
|
|
52
|
+
const token = randomUUID();
|
|
53
|
+
return await redis.set(
|
|
54
|
+
`${prefix}:lease:${key}`,
|
|
55
|
+
token,
|
|
56
|
+
"PX",
|
|
57
|
+
ttlMs,
|
|
58
|
+
"NX"
|
|
59
|
+
) === "OK" ? token : null;
|
|
60
|
+
},
|
|
61
|
+
async release(key, token) {
|
|
62
|
+
return Number(
|
|
63
|
+
await redis.eval(
|
|
64
|
+
"if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('DEL',KEYS[1]); end; return 0",
|
|
65
|
+
1,
|
|
66
|
+
`${prefix}:lease:${key}`,
|
|
67
|
+
token
|
|
68
|
+
)
|
|
69
|
+
) === 1;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function createRedisRateLimiter(redis, options) {
|
|
74
|
+
if (!/^[a-zA-Z0-9:_-]{1,128}$/.test(options.prefix) || !Number.isInteger(options.limit) || options.limit < 1 || !Number.isInteger(options.windowMs) || options.windowMs < 1)
|
|
75
|
+
throw new IntegrationError("invalid_rate_limit");
|
|
76
|
+
return {
|
|
77
|
+
async take(key) {
|
|
78
|
+
const result = await redis.eval(
|
|
79
|
+
"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])}",
|
|
80
|
+
1,
|
|
81
|
+
`${options.prefix}:rate:${key}`,
|
|
82
|
+
options.windowMs
|
|
83
|
+
);
|
|
84
|
+
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)
|
|
85
|
+
throw new IntegrationError("rate_limit_unavailable", "retryable", 1e3);
|
|
86
|
+
return {
|
|
87
|
+
allowed: Number(result[0]) <= options.limit,
|
|
88
|
+
retryAfterMs: Math.max(1, Number(result[1]))
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
createRedisLease,
|
|
95
|
+
createRedisOneTimeStore,
|
|
96
|
+
createRedisRateLimiter
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=redis.js.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,SAAS,kBAAkB;;;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,QAAQ,WAAW;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,11 @@
|
|
|
1
|
+
type FailureDisposition = "retryable" | "requires_reauth" | "outcome_unknown" | "permanent_failure";
|
|
2
|
+
declare class IntegrationError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly disposition: FailureDisposition;
|
|
5
|
+
readonly retryAfterMs?: number | undefined;
|
|
6
|
+
readonly sdkError = "@mycreat-labs/integrations-sdk/v1";
|
|
7
|
+
constructor(code: string, disposition?: FailureDisposition, retryAfterMs?: number | undefined);
|
|
8
|
+
}
|
|
9
|
+
declare function integrationError(error: unknown): IntegrationError;
|
|
10
|
+
|
|
11
|
+
export { type FailureDisposition as F, IntegrationError as I, integrationError as i };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type FailureDisposition = "retryable" | "requires_reauth" | "outcome_unknown" | "permanent_failure";
|
|
2
|
+
declare class IntegrationError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly disposition: FailureDisposition;
|
|
5
|
+
readonly retryAfterMs?: number | undefined;
|
|
6
|
+
readonly sdkError = "@mycreat-labs/integrations-sdk/v1";
|
|
7
|
+
constructor(code: string, disposition?: FailureDisposition, retryAfterMs?: number | undefined);
|
|
8
|
+
}
|
|
9
|
+
declare function integrationError(error: unknown): IntegrationError;
|
|
10
|
+
|
|
11
|
+
export { type FailureDisposition as F, IntegrationError as I, integrationError as i };
|