@devwithbobby/loops 0.1.14 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/component/helpers.d.ts +9 -0
- package/dist/component/helpers.d.ts.map +1 -1
- package/dist/component/helpers.js +69 -0
- package/dist/component/http.d.ts.map +1 -1
- package/dist/component/http.js +2 -62
- package/dist/component/tables/contacts.d.ts.map +1 -1
- package/dist/component/tables/emailOperations.d.ts.map +1 -1
- package/package.json +1 -2
- package/src/component/helpers.ts +86 -0
- package/src/component/http.ts +11 -76
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import { type HeadersInitParam } from "../types";
|
|
1
2
|
export declare const LOOPS_API_BASE_URL = "https://app.loops.so/api/v1";
|
|
2
3
|
export declare const sanitizeLoopsError: (status: number, _errorText: string) => Error;
|
|
3
4
|
export type LoopsRequestInit = Omit<RequestInit, "body"> & {
|
|
4
5
|
json?: unknown;
|
|
5
6
|
};
|
|
6
7
|
export declare const loopsFetch: (apiKey: string, path: string, init?: LoopsRequestInit) => Promise<Response>;
|
|
8
|
+
export declare const buildCorsHeaders: (extra?: HeadersInitParam) => Headers;
|
|
9
|
+
export declare const jsonResponse: (data: unknown, init?: ResponseInit) => Response;
|
|
10
|
+
export declare const emptyResponse: (init?: ResponseInit) => Response;
|
|
11
|
+
export declare const readJsonBody: <T>(request: Request) => Promise<T>;
|
|
12
|
+
export declare const booleanFromQuery: (value: string | null) => boolean | undefined;
|
|
13
|
+
export declare const numberFromQuery: (value: string | null, fallback: number) => number;
|
|
14
|
+
export declare const requireLoopsApiKey: () => string;
|
|
15
|
+
export declare const respondError: (error: unknown) => Response;
|
|
7
16
|
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/component/helpers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAEhE,eAAO,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/component/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAWjD,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAEhE,eAAO,MAAM,kBAAkB,WACtB,MAAM,cACF,MAAM,KAChB,KAcF,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,UAAU,WACd,MAAM,QACR,MAAM,SACN,gBAAgB,sBAetB,CAAC;AAEF,eAAO,MAAM,gBAAgB,WAAY,gBAAgB,YAQxD,CAAC;AAEF,eAAO,MAAM,YAAY,SAAU,OAAO,SAAS,YAAY,aAM9D,CAAC;AAEF,eAAO,MAAM,aAAa,UAAW,YAAY,aAKhD,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,CAAC,WAAW,OAAO,KAAG,OAAO,CAAC,CAAC,CAMjE,CAAC;AAEF,eAAO,MAAM,gBAAgB,UAAW,MAAM,GAAG,IAAI,wBAWpD,CAAC;AAEF,eAAO,MAAM,eAAe,UAAW,MAAM,GAAG,IAAI,YAAY,MAAM,WAMrE,CAAC;AAEF,eAAO,MAAM,kBAAkB,cAQ9B,CAAC;AAEF,eAAO,MAAM,YAAY,UAAW,OAAO,aAS1C,CAAC"}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {} from "../types";
|
|
2
|
+
const allowedOrigin = process.env.CONVEX_URL ??
|
|
3
|
+
process.env.NEXT_PUBLIC_CONVEX_URL ??
|
|
4
|
+
process.env.CONVEX_SITE_URL ??
|
|
5
|
+
process.env.NEXT_PUBLIC_CONVEX_SITE_URL ??
|
|
6
|
+
process.env.LOOPS_HTTP_ALLOWED_ORIGIN ??
|
|
7
|
+
process.env.CLIENT_ORIGIN ??
|
|
8
|
+
"*";
|
|
1
9
|
export const LOOPS_API_BASE_URL = "https://app.loops.so/api/v1";
|
|
2
10
|
export const sanitizeLoopsError = (status, _errorText) => {
|
|
3
11
|
if (status === 401 || status === 403) {
|
|
@@ -28,3 +36,64 @@ export const loopsFetch = async (apiKey, path, init = {}) => {
|
|
|
28
36
|
body: json !== undefined ? JSON.stringify(json) : rest.body,
|
|
29
37
|
});
|
|
30
38
|
};
|
|
39
|
+
export const buildCorsHeaders = (extra) => {
|
|
40
|
+
const headers = new Headers(extra ?? {});
|
|
41
|
+
headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
42
|
+
headers.set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
|
|
43
|
+
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
44
|
+
headers.set("Access-Control-Max-Age", "86400");
|
|
45
|
+
headers.set("Vary", "Origin");
|
|
46
|
+
return headers;
|
|
47
|
+
};
|
|
48
|
+
export const jsonResponse = (data, init) => {
|
|
49
|
+
const headers = buildCorsHeaders(init?.headers ?? undefined);
|
|
50
|
+
headers.set("Content-Type", "application/json");
|
|
51
|
+
return new Response(JSON.stringify(data), { ...init, headers });
|
|
52
|
+
};
|
|
53
|
+
export const emptyResponse = (init) => {
|
|
54
|
+
const headers = buildCorsHeaders(init?.headers ?? undefined);
|
|
55
|
+
return new Response(null, { ...init, headers });
|
|
56
|
+
};
|
|
57
|
+
export const readJsonBody = async (request) => {
|
|
58
|
+
try {
|
|
59
|
+
return (await request.json());
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new Error("Invalid JSON body");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
export const booleanFromQuery = (value) => {
|
|
66
|
+
if (value === null) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
if (value === "true") {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
if (value === "false") {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
};
|
|
77
|
+
export const numberFromQuery = (value, fallback) => {
|
|
78
|
+
if (!value) {
|
|
79
|
+
return fallback;
|
|
80
|
+
}
|
|
81
|
+
const parsed = Number.parseInt(value, 10);
|
|
82
|
+
return Number.isNaN(parsed) ? fallback : parsed;
|
|
83
|
+
};
|
|
84
|
+
export const requireLoopsApiKey = () => {
|
|
85
|
+
const apiKey = process.env.LOOPS_API_KEY;
|
|
86
|
+
if (!apiKey) {
|
|
87
|
+
throw new Error("LOOPS_API_KEY environment variable must be set to use the HTTP API.");
|
|
88
|
+
}
|
|
89
|
+
return apiKey;
|
|
90
|
+
};
|
|
91
|
+
export const respondError = (error) => {
|
|
92
|
+
console.error("[loops:http]", error);
|
|
93
|
+
const message = error instanceof Error ? error.message : "Unexpected error";
|
|
94
|
+
const status = error instanceof Error &&
|
|
95
|
+
error.message.includes("LOOPS_API_KEY environment variable")
|
|
96
|
+
? 500
|
|
97
|
+
: 400;
|
|
98
|
+
return jsonResponse({ error: message }, { status });
|
|
99
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/component/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/component/http.ts"],"names":[],"mappings":"AAsBA,QAAA,MAAM,IAAI,oCAAe,CAAC;AAwN1B,eAAe,IAAI,CAAC"}
|
package/dist/component/http.js
CHANGED
|
@@ -1,71 +1,11 @@
|
|
|
1
1
|
import { httpRouter } from "convex/server";
|
|
2
2
|
import { internalLib, } from "../types";
|
|
3
|
+
import { buildCorsHeaders, jsonResponse, emptyResponse, readJsonBody, respondError, booleanFromQuery, numberFromQuery, requireLoopsApiKey, } from "./helpers";
|
|
3
4
|
import { httpAction } from "./_generated/server";
|
|
4
5
|
const http = httpRouter();
|
|
5
6
|
const allowedOrigin = process.env.LOOPS_HTTP_ALLOWED_ORIGIN ?? process.env.CLIENT_ORIGIN ?? "*";
|
|
6
|
-
const buildCorsHeaders = (extra) => {
|
|
7
|
-
const headers = new Headers(extra ?? {});
|
|
8
|
-
headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
9
|
-
headers.set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
|
|
10
|
-
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
11
|
-
headers.set("Access-Control-Max-Age", "86400");
|
|
12
|
-
headers.set("Vary", "Origin");
|
|
13
|
-
return headers;
|
|
14
|
-
};
|
|
15
|
-
const jsonResponse = (data, init) => {
|
|
16
|
-
const headers = buildCorsHeaders(init?.headers ?? undefined);
|
|
17
|
-
headers.set("Content-Type", "application/json");
|
|
18
|
-
return new Response(JSON.stringify(data), { ...init, headers });
|
|
19
|
-
};
|
|
20
|
-
const emptyResponse = (init) => {
|
|
21
|
-
const headers = buildCorsHeaders(init?.headers ?? undefined);
|
|
22
|
-
return new Response(null, { ...init, headers });
|
|
23
|
-
};
|
|
24
|
-
const readJsonBody = async (request) => {
|
|
25
|
-
try {
|
|
26
|
-
return (await request.json());
|
|
27
|
-
}
|
|
28
|
-
catch (_error) {
|
|
29
|
-
throw new Error("Invalid JSON body");
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
const booleanFromQuery = (value) => {
|
|
33
|
-
if (value === null) {
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
if (value === "true") {
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
if (value === "false") {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
return undefined;
|
|
43
|
-
};
|
|
44
|
-
const numberFromQuery = (value, fallback) => {
|
|
45
|
-
if (!value) {
|
|
46
|
-
return fallback;
|
|
47
|
-
}
|
|
48
|
-
const parsed = Number.parseInt(value, 10);
|
|
49
|
-
return Number.isNaN(parsed) ? fallback : parsed;
|
|
50
|
-
};
|
|
51
|
-
const requireLoopsApiKey = () => {
|
|
52
|
-
const apiKey = process.env.LOOPS_API_KEY;
|
|
53
|
-
if (!apiKey) {
|
|
54
|
-
throw new Error("LOOPS_API_KEY environment variable must be set to use the HTTP API.");
|
|
55
|
-
}
|
|
56
|
-
return apiKey;
|
|
57
|
-
};
|
|
58
|
-
const respondError = (error) => {
|
|
59
|
-
console.error("[loops:http]", error);
|
|
60
|
-
const message = error instanceof Error ? error.message : "Unexpected error";
|
|
61
|
-
const status = error instanceof Error &&
|
|
62
|
-
error.message.includes("LOOPS_API_KEY environment variable")
|
|
63
|
-
? 500
|
|
64
|
-
: 400;
|
|
65
|
-
return jsonResponse({ error: message }, { status });
|
|
66
|
-
};
|
|
67
7
|
http.route({
|
|
68
|
-
pathPrefix: "/loops",
|
|
8
|
+
pathPrefix: "/loops/",
|
|
69
9
|
method: "OPTIONS",
|
|
70
10
|
handler: httpAction(async (_ctx, request) => {
|
|
71
11
|
const headers = buildCorsHeaders();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contacts.d.ts","sourceRoot":"","sources":["../../../src/component/tables/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,QAAQ
|
|
1
|
+
{"version":3,"file":"contacts.d.ts","sourceRoot":"","sources":["../../../src/component/tables/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAYu99B,EAAG,SAAS;;;;;;;;uBAAgW,EAAG,SAAS;;CADl2+B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emailOperations.d.ts","sourceRoot":"","sources":["../../../src/component/tables/emailOperations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"emailOperations.d.ts","sourceRoot":"","sources":["../../../src/component/tables/emailOperations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAay29B,EAAG,SAAS;;;;;;;;uBAAgW,EAAG,SAAS;;CAD3v+B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devwithbobby/loops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Convex component for integrating with Loops.so email marketing platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -93,7 +93,6 @@
|
|
|
93
93
|
"convex": "^1.28.0",
|
|
94
94
|
"convex-helpers": "^0.1.104",
|
|
95
95
|
"convex-test": "^0.0.38",
|
|
96
|
-
"lefthook": "^2.0.0",
|
|
97
96
|
"npm-run-all": "^4.1.5",
|
|
98
97
|
"pkg-pr-new": "^0.0.60",
|
|
99
98
|
"react": "^19.2.0",
|
package/src/component/helpers.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import { type HeadersInitParam } from "../types";
|
|
2
|
+
|
|
3
|
+
const allowedOrigin =
|
|
4
|
+
process.env.CONVEX_URL ??
|
|
5
|
+
process.env.NEXT_PUBLIC_CONVEX_URL ??
|
|
6
|
+
process.env.CONVEX_SITE_URL ??
|
|
7
|
+
process.env.NEXT_PUBLIC_CONVEX_SITE_URL ??
|
|
8
|
+
process.env.LOOPS_HTTP_ALLOWED_ORIGIN ??
|
|
9
|
+
process.env.CLIENT_ORIGIN ??
|
|
10
|
+
"*";
|
|
11
|
+
|
|
1
12
|
export const LOOPS_API_BASE_URL = "https://app.loops.so/api/v1";
|
|
2
13
|
|
|
3
14
|
export const sanitizeLoopsError = (
|
|
@@ -42,3 +53,78 @@ export const loopsFetch = async (
|
|
|
42
53
|
body: json !== undefined ? JSON.stringify(json) : rest.body,
|
|
43
54
|
});
|
|
44
55
|
};
|
|
56
|
+
|
|
57
|
+
export const buildCorsHeaders = (extra?: HeadersInitParam) => {
|
|
58
|
+
const headers = new Headers(extra ?? {});
|
|
59
|
+
headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
60
|
+
headers.set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
|
|
61
|
+
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
62
|
+
headers.set("Access-Control-Max-Age", "86400");
|
|
63
|
+
headers.set("Vary", "Origin");
|
|
64
|
+
return headers;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const jsonResponse = (data: unknown, init?: ResponseInit) => {
|
|
68
|
+
const headers = buildCorsHeaders(
|
|
69
|
+
(init?.headers as HeadersInitParam | undefined) ?? undefined,
|
|
70
|
+
);
|
|
71
|
+
headers.set("Content-Type", "application/json");
|
|
72
|
+
return new Response(JSON.stringify(data), { ...init, headers });
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const emptyResponse = (init?: ResponseInit) => {
|
|
76
|
+
const headers = buildCorsHeaders(
|
|
77
|
+
(init?.headers as HeadersInitParam | undefined) ?? undefined,
|
|
78
|
+
);
|
|
79
|
+
return new Response(null, { ...init, headers });
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const readJsonBody = async <T>(request: Request): Promise<T> => {
|
|
83
|
+
try {
|
|
84
|
+
return (await request.json()) as T;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error("Invalid JSON body");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const booleanFromQuery = (value: string | null) => {
|
|
91
|
+
if (value === null) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
if (value === "true") {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (value === "false") {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const numberFromQuery = (value: string | null, fallback: number) => {
|
|
104
|
+
if (!value) {
|
|
105
|
+
return fallback;
|
|
106
|
+
}
|
|
107
|
+
const parsed = Number.parseInt(value, 10);
|
|
108
|
+
return Number.isNaN(parsed) ? fallback : parsed;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const requireLoopsApiKey = () => {
|
|
112
|
+
const apiKey = process.env.LOOPS_API_KEY;
|
|
113
|
+
if (!apiKey) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
"LOOPS_API_KEY environment variable must be set to use the HTTP API.",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return apiKey;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const respondError = (error: unknown) => {
|
|
122
|
+
console.error("[loops:http]", error);
|
|
123
|
+
const message = error instanceof Error ? error.message : "Unexpected error";
|
|
124
|
+
const status =
|
|
125
|
+
error instanceof Error &&
|
|
126
|
+
error.message.includes("LOOPS_API_KEY environment variable")
|
|
127
|
+
? 500
|
|
128
|
+
: 400;
|
|
129
|
+
return jsonResponse({ error: message }, { status });
|
|
130
|
+
};
|
package/src/component/http.ts
CHANGED
|
@@ -3,12 +3,21 @@ import {
|
|
|
3
3
|
type ContactPayload,
|
|
4
4
|
type DeleteContactPayload,
|
|
5
5
|
type EventPayload,
|
|
6
|
-
type HeadersInitParam,
|
|
7
6
|
internalLib,
|
|
8
7
|
type TransactionalPayload,
|
|
9
8
|
type TriggerPayload,
|
|
10
9
|
type UpdateContactPayload,
|
|
11
10
|
} from "../types";
|
|
11
|
+
import {
|
|
12
|
+
buildCorsHeaders,
|
|
13
|
+
jsonResponse,
|
|
14
|
+
emptyResponse,
|
|
15
|
+
readJsonBody,
|
|
16
|
+
respondError,
|
|
17
|
+
booleanFromQuery,
|
|
18
|
+
numberFromQuery,
|
|
19
|
+
requireLoopsApiKey,
|
|
20
|
+
} from "./helpers";
|
|
12
21
|
import { httpAction } from "./_generated/server";
|
|
13
22
|
|
|
14
23
|
const http = httpRouter();
|
|
@@ -16,83 +25,9 @@ const http = httpRouter();
|
|
|
16
25
|
const allowedOrigin =
|
|
17
26
|
process.env.LOOPS_HTTP_ALLOWED_ORIGIN ?? process.env.CLIENT_ORIGIN ?? "*";
|
|
18
27
|
|
|
19
|
-
const buildCorsHeaders = (extra?: HeadersInitParam) => {
|
|
20
|
-
const headers = new Headers(extra ?? {});
|
|
21
|
-
headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
22
|
-
headers.set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
|
|
23
|
-
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
24
|
-
headers.set("Access-Control-Max-Age", "86400");
|
|
25
|
-
headers.set("Vary", "Origin");
|
|
26
|
-
return headers;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const jsonResponse = (data: unknown, init?: ResponseInit) => {
|
|
30
|
-
const headers = buildCorsHeaders(
|
|
31
|
-
(init?.headers as HeadersInitParam | undefined) ?? undefined,
|
|
32
|
-
);
|
|
33
|
-
headers.set("Content-Type", "application/json");
|
|
34
|
-
return new Response(JSON.stringify(data), { ...init, headers });
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const emptyResponse = (init?: ResponseInit) => {
|
|
38
|
-
const headers = buildCorsHeaders(
|
|
39
|
-
(init?.headers as HeadersInitParam | undefined) ?? undefined,
|
|
40
|
-
);
|
|
41
|
-
return new Response(null, { ...init, headers });
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const readJsonBody = async <T>(request: Request): Promise<T> => {
|
|
45
|
-
try {
|
|
46
|
-
return (await request.json()) as T;
|
|
47
|
-
} catch (_error) {
|
|
48
|
-
throw new Error("Invalid JSON body");
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const booleanFromQuery = (value: string | null) => {
|
|
53
|
-
if (value === null) {
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
if (value === "true") {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
if (value === "false") {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
return undefined;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const numberFromQuery = (value: string | null, fallback: number) => {
|
|
66
|
-
if (!value) {
|
|
67
|
-
return fallback;
|
|
68
|
-
}
|
|
69
|
-
const parsed = Number.parseInt(value, 10);
|
|
70
|
-
return Number.isNaN(parsed) ? fallback : parsed;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const requireLoopsApiKey = () => {
|
|
74
|
-
const apiKey = process.env.LOOPS_API_KEY;
|
|
75
|
-
if (!apiKey) {
|
|
76
|
-
throw new Error(
|
|
77
|
-
"LOOPS_API_KEY environment variable must be set to use the HTTP API.",
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
return apiKey;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const respondError = (error: unknown) => {
|
|
84
|
-
console.error("[loops:http]", error);
|
|
85
|
-
const message = error instanceof Error ? error.message : "Unexpected error";
|
|
86
|
-
const status =
|
|
87
|
-
error instanceof Error &&
|
|
88
|
-
error.message.includes("LOOPS_API_KEY environment variable")
|
|
89
|
-
? 500
|
|
90
|
-
: 400;
|
|
91
|
-
return jsonResponse({ error: message }, { status });
|
|
92
|
-
};
|
|
93
28
|
|
|
94
29
|
http.route({
|
|
95
|
-
pathPrefix: "/loops",
|
|
30
|
+
pathPrefix: "/loops/",
|
|
96
31
|
method: "OPTIONS",
|
|
97
32
|
handler: httpAction(async (_ctx, request) => {
|
|
98
33
|
const headers = buildCorsHeaders();
|