@friggframework/api-module-frigg-scale-test 0.1.1-canary.50.12ea60a.0 → 0.2.0-next.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/jest.config.js ADDED
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ roots: ['<rootDir>/test'],
5
+ moduleFileExtensions: ['ts', 'js', 'json'],
6
+ moduleNameMapper: {
7
+ '^services/(.*)$': '<rootDir>/../../../services/$1'
8
+ }
9
+ };
package/package.json CHANGED
@@ -1,20 +1,14 @@
1
1
  {
2
2
  "name": "@friggframework/api-module-frigg-scale-test",
3
- "version": "0.1.1-canary.50.12ea60a.0",
3
+ "version": "0.2.0-next.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc -p tsconfig.json",
9
- "prepare": "npm run build",
10
9
  "test": "jest --runInBand",
11
10
  "lint": "eslint ."
12
11
  },
13
- "files": [
14
- "dist/**/*",
15
- "README.md",
16
- "LICENSE"
17
- ],
18
12
  "dependencies": {
19
13
  "@friggframework/core": "^1.0.0"
20
14
  },
@@ -28,5 +22,5 @@
28
22
  "publishConfig": {
29
23
  "access": "public"
30
24
  },
31
- "gitHead": "12ea60ac1e9b86b7c5e9368165aac64fa39130d3"
25
+ "gitHead": "65e73334219f8e50b565dbdf5dee0455ac568920"
32
26
  }
package/src/api.ts ADDED
@@ -0,0 +1,138 @@
1
+ export type ListParams = {
2
+ accountId: string;
3
+ limit?: number;
4
+ cursor?: string;
5
+ updatedSince?: string;
6
+ };
7
+
8
+ export type ListActivitiesParams = ListParams & {
9
+ type?: "phone_call" | "email" | "sms";
10
+ contactId?: string;
11
+ };
12
+
13
+ export class FriggScaleTestAPI {
14
+ constructor(readonly opts: { baseUrl?: string; apiKey?: string } = {}) {}
15
+
16
+ private get base(): string {
17
+ return (
18
+ this.opts.baseUrl ||
19
+ process.env.FRIGG_SCALE_TEST_BASE_URL ||
20
+ "http://localhost:4000"
21
+ );
22
+ }
23
+
24
+ private headers(): Record<string, string> {
25
+ const apiKey = this.opts.apiKey || process.env.FRIGG_SCALE_TEST_API_KEY;
26
+ return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
27
+ }
28
+
29
+ async health() {
30
+ const r = await fetch(new URL("/health", this.base));
31
+ if (!r.ok) throw new Error(`health ${r.status}`);
32
+ return r.json();
33
+ }
34
+
35
+ async getConfig(accountId: string) {
36
+ const r = await fetch(new URL(`/config/${encodeURIComponent(accountId)}`, this.base), {
37
+ headers: this.headers()
38
+ });
39
+ if (!r.ok) throw new Error(`getConfig ${r.status}`);
40
+ return r.json();
41
+ }
42
+
43
+ async putConfig(accountId: string, cfg: any) {
44
+ const headers: Record<string, string> = {
45
+ "content-type": "application/json",
46
+ ...this.headers()
47
+ };
48
+ const r = await fetch(new URL(`/config/${encodeURIComponent(accountId)}`, this.base), {
49
+ method: "PUT",
50
+ headers,
51
+ body: JSON.stringify(cfg)
52
+ });
53
+ if (!r.ok) throw new Error(`putConfig ${r.status}`);
54
+ return r.json();
55
+ }
56
+
57
+ async listContacts(params: ListParams) {
58
+ const url = new URL(`/contacts`, this.base);
59
+ url.searchParams.set("accountId", params.accountId);
60
+ if (params.limit) url.searchParams.set("limit", String(params.limit));
61
+ if (params.cursor) url.searchParams.set("cursor", params.cursor);
62
+ if (params.updatedSince) url.searchParams.set("updatedSince", params.updatedSince);
63
+ const r = await fetch(url, { headers: this.headers() });
64
+ if (!r.ok) throw new Error(`listContacts ${r.status}`);
65
+ return r.json();
66
+ }
67
+
68
+ async listActivities(params: ListActivitiesParams) {
69
+ const url = new URL(`/activities`, this.base);
70
+ url.searchParams.set("accountId", params.accountId);
71
+ if (params.limit) url.searchParams.set("limit", String(params.limit));
72
+ if (params.cursor) url.searchParams.set("cursor", params.cursor);
73
+ if (params.updatedSince) url.searchParams.set("updatedSince", params.updatedSince);
74
+ if (params.type) url.searchParams.set("type", params.type);
75
+ if (params.contactId) url.searchParams.set("contactId", params.contactId);
76
+ const r = await fetch(url, { headers: this.headers() });
77
+ if (!r.ok) throw new Error(`listActivities ${r.status}`);
78
+ return r.json();
79
+ }
80
+
81
+ async createActivity(body: any) {
82
+ const headers: Record<string, string> = {
83
+ "content-type": "application/json",
84
+ ...this.headers()
85
+ };
86
+ const r = await fetch(new URL(`/activities`, this.base), {
87
+ method: "POST",
88
+ headers,
89
+ body: JSON.stringify(body)
90
+ });
91
+ if (!r.ok) throw new Error(`createActivity ${r.status}`);
92
+ return r.json();
93
+ }
94
+
95
+ async requestContactsExport(body: {
96
+ accountId: string;
97
+ format?: "ndjson" | "csv";
98
+ fields?: string[];
99
+ }) {
100
+ const headers: Record<string, string> = {
101
+ "content-type": "application/json",
102
+ ...this.headers()
103
+ };
104
+ const r = await fetch(new URL(`/bulk/exports/contacts`, this.base), {
105
+ method: "POST",
106
+ headers,
107
+ body: JSON.stringify(body)
108
+ });
109
+ if (r.status !== 202 && r.status !== 200) throw new Error(`requestContactsExport ${r.status}`);
110
+ return r.json();
111
+ }
112
+
113
+ async requestActivitiesExport(body: {
114
+ accountId: string;
115
+ format?: "ndjson" | "csv";
116
+ fields?: string[];
117
+ }) {
118
+ const headers: Record<string, string> = {
119
+ "content-type": "application/json",
120
+ ...this.headers()
121
+ };
122
+ const r = await fetch(new URL(`/bulk/exports/activities`, this.base), {
123
+ method: "POST",
124
+ headers,
125
+ body: JSON.stringify(body)
126
+ });
127
+ if (r.status !== 202 && r.status !== 200) throw new Error(`requestActivitiesExport ${r.status}`);
128
+ return r.json();
129
+ }
130
+
131
+ async getExportJob(jobId: string) {
132
+ const r = await fetch(new URL(`/bulk/exports/${encodeURIComponent(jobId)}`, this.base), {
133
+ headers: this.headers()
134
+ });
135
+ if (!r.ok) throw new Error(`getExportJob ${r.status}`);
136
+ return r.json();
137
+ }
138
+ }
@@ -0,0 +1,4 @@
1
+ declare module "services/frigg-scale-test-lambda/src/handler" {
2
+ import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
3
+ export function handler(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2>;
4
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { FriggModuleAuthDefinition } from "@friggframework/core";
2
+ import { FriggScaleTestAPI } from "./api";
3
+
4
+ export const authDef: FriggModuleAuthDefinition = {
5
+ API: FriggScaleTestAPI,
6
+ getName: () => "Frigg Scale Test (Mock CRM)",
7
+ moduleName: "frigg-scale-test",
8
+ requiredAuthMethods: {
9
+ apiPropertiesToPersist: { credential: ["apiKey"], entity: [] },
10
+ getToken: async () => undefined,
11
+ getEntityDetails: async (_api: FriggScaleTestAPI, params?: any) => ({
12
+ identifiers: { externalId: `frigg-scale-test:${params?.accountId || "default"}` },
13
+ details: { name: "Frigg Scale Test Mock CRM" }
14
+ }),
15
+ getCredentialDetails: async () => ({ identifiers: { externalId: "frigg-scale-test-cred" }, details: {} }),
16
+ testAuthRequest: async (api: FriggScaleTestAPI) => {
17
+ await api.health();
18
+ }
19
+ },
20
+ env: {
21
+ FRIGG_SCALE_TEST_BASE_URL: process.env.FRIGG_SCALE_TEST_BASE_URL,
22
+ FRIGG_SCALE_TEST_API_KEY: process.env.FRIGG_SCALE_TEST_API_KEY
23
+ }
24
+ };
25
+
26
+ export { FriggScaleTestAPI };
27
+ export default authDef;
package/src/types.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ declare module "@friggframework/core" {
2
+ export interface FriggModuleAuthDefinition {
3
+ API: new (...args: any[]) => any;
4
+ getName: () => string;
5
+ moduleName: string;
6
+ requiredAuthMethods: Record<string, any>;
7
+ env?: Record<string, any>;
8
+ }
9
+ }
@@ -0,0 +1,117 @@
1
+ import { createServer, IncomingMessage, ServerResponse } from "http";
2
+ import { AddressInfo } from "net";
3
+ import { handler } from "services/frigg-scale-test-lambda/src/handler";
4
+ import { FriggScaleTestAPI } from "../src/api";
5
+ import { APIGatewayProxyEventV2 } from "aws-lambda";
6
+
7
+ let server: ReturnType<typeof createServer>;
8
+ let baseUrl: string;
9
+
10
+ function buildEvent(req: IncomingMessage, body: string): APIGatewayProxyEventV2 {
11
+ const url = new URL(req.url || "/", "http://localhost");
12
+ const headers: Record<string, string> = {};
13
+ for (const [key, value] of Object.entries(req.headers)) {
14
+ if (typeof value === "string") headers[key] = value;
15
+ if (Array.isArray(value)) headers[key] = value.join(",");
16
+ }
17
+ const query: Record<string, string> = {};
18
+ url.searchParams.forEach((value, key) => {
19
+ query[key] = value;
20
+ });
21
+ return {
22
+ version: "2.0",
23
+ routeKey: "$default",
24
+ rawPath: url.pathname,
25
+ rawQueryString: url.search.slice(1),
26
+ headers,
27
+ queryStringParameters: Object.keys(query).length ? query : null,
28
+ requestContext: {
29
+ accountId: "local",
30
+ apiId: "local",
31
+ domainName: "localhost",
32
+ domainPrefix: "",
33
+ requestId: "local",
34
+ routeKey: "$default",
35
+ stage: "$default",
36
+ time: new Date().toISOString(),
37
+ timeEpoch: Date.now(),
38
+ http: {
39
+ method: req.method || "GET",
40
+ path: url.pathname,
41
+ protocol: "HTTP/1.1",
42
+ sourceIp: "127.0.0.1",
43
+ userAgent: "jest"
44
+ }
45
+ },
46
+ isBase64Encoded: false,
47
+ body: body || undefined,
48
+ pathParameters: null,
49
+ stageVariables: null,
50
+ cookies: [],
51
+ multiValueQueryStringParameters: null
52
+ } as unknown as APIGatewayProxyEventV2;
53
+ }
54
+
55
+ function ensureStructured(result: Awaited<ReturnType<typeof handler>>) {
56
+ if (typeof result === "string") {
57
+ return { statusCode: 200, headers: {}, body: result } as const;
58
+ }
59
+ return result;
60
+ }
61
+
62
+ beforeAll(async () => {
63
+ server = createServer((req: IncomingMessage, res: ServerResponse) => {
64
+ const chunks: Buffer[] = [];
65
+ req.on("data", (chunk: Buffer) => chunks.push(chunk));
66
+ req.on("end", async () => {
67
+ const body = Buffer.concat(chunks).toString();
68
+ try {
69
+ const event = buildEvent(req, body);
70
+ const result = ensureStructured(await handler(event));
71
+ res.statusCode = result.statusCode || 200;
72
+ for (const [key, value] of Object.entries(result.headers || {})) {
73
+ if (value !== undefined) {
74
+ res.setHeader(key, value as string);
75
+ }
76
+ }
77
+ res.end(result.body || "");
78
+ } catch (err: any) {
79
+ res.statusCode = 500;
80
+ res.end(JSON.stringify({ error: err?.message || "error" }));
81
+ }
82
+ });
83
+ });
84
+
85
+ await new Promise<void>((resolve) => {
86
+ server.listen(0, () => resolve());
87
+ });
88
+ const address = server.address() as AddressInfo;
89
+ baseUrl = `http://127.0.0.1:${address.port}`;
90
+ process.env.FRIGG_SCALE_TEST_BASE_URL = baseUrl;
91
+ });
92
+
93
+ afterAll(async () => {
94
+ await new Promise<void>((resolve) => server.close(() => resolve()));
95
+ });
96
+
97
+ const api = new FriggScaleTestAPI();
98
+
99
+ describe("Frigg Scale Test Mock CRM", () => {
100
+ it("health", async () => {
101
+ const health = await api.health();
102
+ expect(health.ok).toBeTruthy();
103
+ });
104
+
105
+ it("contacts pagination", async () => {
106
+ const page = await api.listContacts({ accountId: "demo", limit: 10 });
107
+ expect(Array.isArray(page.items)).toBe(true);
108
+ expect(page.items.length).toBeLessThanOrEqual(10);
109
+ });
110
+
111
+ it("activities list and create", async () => {
112
+ const list = await api.listActivities({ accountId: "demo", limit: 5, type: "email" });
113
+ expect(Array.isArray(list.items)).toBe(true);
114
+ const created = await api.createActivity({ accountId: "demo", type: "sms", contactId: "contact-1", subject: "Ping" });
115
+ expect(created.id).toBeTruthy();
116
+ });
117
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "dist",
4
+ "rootDir": "src",
5
+ "declaration": true,
6
+ "module": "commonjs",
7
+ "target": "ES2020",
8
+ "esModuleInterop": true,
9
+ "strict": true,
10
+ "baseUrl": ".",
11
+ "paths": {
12
+ "services/*": ["../../../services/*"]
13
+ }
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["dist", "test"]
17
+ }
package/dist/api.d.ts DELETED
@@ -1,39 +0,0 @@
1
- export type ListParams = {
2
- accountId: string;
3
- limit?: number;
4
- cursor?: string;
5
- updatedSince?: string;
6
- };
7
- export type ListActivitiesParams = ListParams & {
8
- type?: "phone_call" | "email" | "sms";
9
- contactId?: string;
10
- };
11
- export default class FriggScaleTestAPI {
12
- readonly opts: {
13
- baseUrl?: string;
14
- apiKey?: string;
15
- };
16
- constructor(opts?: {
17
- baseUrl?: string;
18
- apiKey?: string;
19
- });
20
- private get base();
21
- private headers;
22
- health(): Promise<any>;
23
- getConfig(accountId: string): Promise<any>;
24
- putConfig(accountId: string, cfg: any): Promise<any>;
25
- listContacts(params: ListParams): Promise<any>;
26
- listActivities(params: ListActivitiesParams): Promise<any>;
27
- createActivity(body: any): Promise<any>;
28
- requestContactsExport(body: {
29
- accountId: string;
30
- format?: "ndjson" | "csv";
31
- fields?: string[];
32
- }): Promise<any>;
33
- requestActivitiesExport(body: {
34
- accountId: string;
35
- format?: "ndjson" | "csv";
36
- fields?: string[];
37
- }): Promise<any>;
38
- getExportJob(jobId: string): Promise<any>;
39
- }
package/dist/api.js DELETED
@@ -1,127 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- class FriggScaleTestAPI {
4
- constructor(opts = {}) {
5
- this.opts = opts;
6
- }
7
- get base() {
8
- return (this.opts.baseUrl ||
9
- process.env.FRIGG_SCALE_TEST_BASE_URL ||
10
- "http://localhost:4000");
11
- }
12
- headers() {
13
- const apiKey = this.opts.apiKey || process.env.FRIGG_SCALE_TEST_API_KEY;
14
- return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
15
- }
16
- async health() {
17
- const r = await fetch(new URL("/health", this.base));
18
- if (!r.ok)
19
- throw new Error(`health ${r.status}`);
20
- return r.json();
21
- }
22
- async getConfig(accountId) {
23
- const r = await fetch(new URL(`/config/${encodeURIComponent(accountId)}`, this.base), {
24
- headers: this.headers(),
25
- });
26
- if (!r.ok)
27
- throw new Error(`getConfig ${r.status}`);
28
- return r.json();
29
- }
30
- async putConfig(accountId, cfg) {
31
- const headers = {
32
- "content-type": "application/json",
33
- ...this.headers(),
34
- };
35
- const r = await fetch(new URL(`/config/${encodeURIComponent(accountId)}`, this.base), {
36
- method: "PUT",
37
- headers,
38
- body: JSON.stringify(cfg),
39
- });
40
- if (!r.ok)
41
- throw new Error(`putConfig ${r.status}`);
42
- return r.json();
43
- }
44
- async listContacts(params) {
45
- const url = new URL(`/contacts`, this.base);
46
- url.searchParams.set("accountId", params.accountId);
47
- if (params.limit)
48
- url.searchParams.set("limit", String(params.limit));
49
- if (params.cursor)
50
- url.searchParams.set("cursor", params.cursor);
51
- if (params.updatedSince)
52
- url.searchParams.set("updatedSince", params.updatedSince);
53
- const r = await fetch(url, { headers: this.headers() });
54
- if (!r.ok)
55
- throw new Error(`listContacts ${r.status}`);
56
- return r.json();
57
- }
58
- async listActivities(params) {
59
- const url = new URL(`/activities`, this.base);
60
- url.searchParams.set("accountId", params.accountId);
61
- if (params.limit)
62
- url.searchParams.set("limit", String(params.limit));
63
- if (params.cursor)
64
- url.searchParams.set("cursor", params.cursor);
65
- if (params.updatedSince)
66
- url.searchParams.set("updatedSince", params.updatedSince);
67
- if (params.type)
68
- url.searchParams.set("type", params.type);
69
- if (params.contactId)
70
- url.searchParams.set("contactId", params.contactId);
71
- const r = await fetch(url, { headers: this.headers() });
72
- if (!r.ok)
73
- throw new Error(`listActivities ${r.status}`);
74
- return r.json();
75
- }
76
- async createActivity(body) {
77
- const headers = {
78
- "content-type": "application/json",
79
- ...this.headers(),
80
- };
81
- const r = await fetch(new URL(`/activities`, this.base), {
82
- method: "POST",
83
- headers,
84
- body: JSON.stringify(body),
85
- });
86
- if (!r.ok)
87
- throw new Error(`createActivity ${r.status}`);
88
- return r.json();
89
- }
90
- async requestContactsExport(body) {
91
- const headers = {
92
- "content-type": "application/json",
93
- ...this.headers(),
94
- };
95
- const r = await fetch(new URL(`/bulk/exports/contacts`, this.base), {
96
- method: "POST",
97
- headers,
98
- body: JSON.stringify(body),
99
- });
100
- if (r.status !== 202 && r.status !== 200)
101
- throw new Error(`requestContactsExport ${r.status}`);
102
- return r.json();
103
- }
104
- async requestActivitiesExport(body) {
105
- const headers = {
106
- "content-type": "application/json",
107
- ...this.headers(),
108
- };
109
- const r = await fetch(new URL(`/bulk/exports/activities`, this.base), {
110
- method: "POST",
111
- headers,
112
- body: JSON.stringify(body),
113
- });
114
- if (r.status !== 202 && r.status !== 200)
115
- throw new Error(`requestActivitiesExport ${r.status}`);
116
- return r.json();
117
- }
118
- async getExportJob(jobId) {
119
- const r = await fetch(new URL(`/bulk/exports/${encodeURIComponent(jobId)}`, this.base), {
120
- headers: this.headers(),
121
- });
122
- if (!r.ok)
123
- throw new Error(`getExportJob ${r.status}`);
124
- return r.json();
125
- }
126
- }
127
- exports.default = FriggScaleTestAPI;
@@ -1,3 +0,0 @@
1
- import { FriggModuleAuthDefinition } from "@friggframework/core";
2
- declare const definition: FriggModuleAuthDefinition;
3
- export default definition;
package/dist/defintion.js DELETED
@@ -1,49 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const api_1 = __importDefault(require("./api"));
7
- const definition = {
8
- API: api_1.default,
9
- getName: () => "Frgg Scale Test API",
10
- moduleName: "scale-test",
11
- requiredAuthMethods: {
12
- getToken: async (api, params) => {
13
- // For scale test API, use API key authentication
14
- const apiKey = params.data?.apiKey || params.data?.access_token;
15
- return { access_token: apiKey };
16
- },
17
- getEntityDetails: async (api, callbackParams, tokenResponse, userId) => {
18
- const healthCheck = await api.health();
19
- return {
20
- identifiers: {
21
- externalId: "scale-test-account",
22
- user: userId,
23
- },
24
- details: {
25
- name: "Scale Test Account",
26
- status: healthCheck.ok ? "healthy" : "error",
27
- },
28
- };
29
- },
30
- apiPropertiesToPersist: {
31
- credential: ["access_token"],
32
- entity: [],
33
- },
34
- getCredentialDetails: async (api, userId) => {
35
- return {
36
- identifiers: {
37
- externalId: "scale-test-account",
38
- user: userId,
39
- },
40
- details: {},
41
- };
42
- },
43
- testAuthRequest: async (api) => api.health(),
44
- },
45
- env: {
46
- apiKey: process.env.SCALE_TEST_API_KEY,
47
- },
48
- };
49
- exports.default = definition;
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import FriggScaleTestAPI from "./api";
2
- import Definition from "./defintion";
3
- export { FriggScaleTestAPI, Definition };
package/dist/index.js DELETED
@@ -1,10 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Definition = exports.FriggScaleTestAPI = void 0;
7
- const api_1 = __importDefault(require("./api"));
8
- exports.FriggScaleTestAPI = api_1.default;
9
- const defintion_1 = __importDefault(require("./defintion"));
10
- exports.Definition = defintion_1.default;