Skip to content

Commit 946cfc1

Browse files
authored
Merge pull request #55 from glenstack/feature/data/filters
Add filters and fix Fauna setup | Closes GS-36 GS-46
2 parents 62a490f + 3c8a05e commit 946cfc1

File tree

13 files changed

+416
-106
lines changed

13 files changed

+416
-106
lines changed

.github/workflows/test.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: Test
22
on: [push]
3-
3+
env:
4+
DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }}
45
jobs:
56
test:
67
runs-on: ubuntu-latest
@@ -15,6 +16,8 @@ jobs:
1516
uses: actions/setup-node@v2
1617
with:
1718
node-version: "${{ steps.nvm.outputs.NVMRC }}"
19+
- name: Install Doppler
20+
run: (curl -Ls https://cli.doppler.com/install.sh || wget -qO- https://cli.doppler.com/install.sh) | sudo sh
1821
- name: Install
1922
run: npm ci
2023
- name: Test & publish code coverage

packages/api/package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
"prettier": "prettier --config ../../.prettierrc --ignore-path .gitignore '**/*.(js|ts|json|md)'",
1010
"format": "npm run prettier -- --write",
1111
"check-format": "npm run prettier -- --list-different",
12-
"precommit": "npm run check-types && lint-staged && npm run build && npm run coverage",
12+
"precommit": "doppler run --command='npm run check-types && lint-staged && npm run build && npm run coverage'",
1313
"validate": "concurrently 'npm:check-types' 'npm:check-format' 'npm:lint' 'npm:build' 'npm:coverage'",
14-
"test": "jest",
15-
"coverage": "npm run test -- --coverage",
16-
"test:watch": "npm run test -- -o --watch",
17-
"start": "wrangler dev",
18-
"start:data": "ts-node src/data/test_data.ts",
19-
"start:https": "wrangler dev --local-protocol https"
14+
"test": "doppler run -- jest",
15+
"coverage": "doppler run -- npm run test -- --coverage",
16+
"test:watch": "doppler run -- npm run test -- -o --watch",
17+
"start": "doppler run -- wrangler dev",
18+
"start:demo": "DEMO_PROJECT_ID='302914045660365323' doppler run -- ts-node src/data/demo_server.ts",
19+
"start:https": "doppler run -- wrangler dev --local-protocol https"
2020
},
2121
"devDependencies": {
2222
"@cloudflare/workers-types": "2.2.2",

packages/api/src/data/__tests__/index.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ import { definitions } from "../definitions";
88

99
jest.setTimeout(30000);
1010
test("Integration Test", async () => {
11-
const test_db_name = "altest_" + Date.now();
11+
const test_db_name = "test_" + Date.now();
12+
13+
if (!process.env.FAUNA_INTEGRATION_TEST_KEY) {
14+
throw new Error("Missing env 'FAUNA_INTEGRATION_TEST_KEY'");
15+
}
1216

1317
const client = new Client({
14-
secret: "fnAEKpUbg1ACBTYHxtqayKNrCnnmgHLyWoSSlyvX",
18+
secret: process.env.FAUNA_INTEGRATION_TEST_KEY,
1519
});
1620

1721
const tables: Record<string, { name: string; apiName: string; id: string }> =
@@ -108,7 +112,7 @@ test("Integration Test", async () => {
108112
* First query should return empty array
109113
*/
110114

111-
let queryName = definitions(tables["Book"]).queries.findMany.name();
115+
let queryName = definitions.queries(tables["Book"]).findMany.name();
112116
let result = await server.executeOperation({
113117
query: `query { ${queryName} {
114118
id
@@ -128,7 +132,7 @@ test("Integration Test", async () => {
128132
* Create single book and expect that book in the response.
129133
*/
130134

131-
queryName = definitions(tables["Book"]).queries.createOne.name();
135+
queryName = definitions.queries(tables["Book"]).createOne.name();
132136
result = await server.executeOperation({
133137
query: `mutation ($title: String!) { ${queryName} (input: {title: $title}) {
134138
id
@@ -154,7 +158,7 @@ test("Integration Test", async () => {
154158
* Create single author of that book and expect that author with the right book in the response.
155159
*/
156160

157-
queryName = definitions(tables["Author"]).queries.createOne.name();
161+
queryName = definitions.queries(tables["Author"]).createOne.name();
158162
result = await server.executeOperation({
159163
query: `mutation ($name: String!, $connect: [ID!]!) { ${queryName} (input: {name: $name, books: {connect: $connect}}) {
160164
id

packages/api/src/data/definitions.ts

+117-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
/* eslint-disable @typescript-eslint/ban-ts-comment */
22
/* eslint-disable @typescript-eslint/no-explicit-any */
3-
import { Expr, query as q } from "faunadb";
4-
import { Table, FaunaSchema, RelationshipField } from "./types";
3+
import { Expr, ExprArg, query as q } from "faunadb";
4+
5+
import {
6+
Table,
7+
FaunaSchema,
8+
RelationshipField,
9+
ScalarType,
10+
ScalarField,
11+
} from "./types";
512

613
const generateRelationQueries = (field: RelationshipField) => {
714
const existingRelation = (id: string) =>
@@ -83,22 +90,97 @@ const generateRelationQueries = (field: RelationshipField) => {
8390
};
8491
};
8592

86-
export const definitions = (
87-
table: Pick<Table, "apiName" | "id">
88-
): {
89-
queries: {
93+
export const definitions: {
94+
operators: Record<
95+
string,
96+
{
97+
allowedTypes: Array<ScalarType>;
98+
argType: (type: ScalarType) => ScalarField["type"];
99+
expr: (fieldId: string, value: ExprArg) => Expr;
100+
}
101+
>;
102+
queries: (table: Pick<Table, "apiName" | "id">) => {
90103
[p: string]: {
91104
name: () => string;
92-
// @ts-ignore
93-
query: (args: any, faunaSchema?: FaunaSchema) => Expr;
105+
query: (args: any, faunaSchema: FaunaSchema) => Expr;
94106
};
95107
};
96-
} => ({
97-
queries: {
108+
} = {
109+
operators: {
110+
equals: {
111+
allowedTypes: ["String", "Number", "Boolean", "JSON", "EmailAddress"],
112+
argType: (type: ScalarType) => type,
113+
expr: (fieldId: string, value: ExprArg) =>
114+
//@ts-ignore
115+
q.Equals(q.Select(["data", fieldId], q.Get(q.Var("ref")), null), value),
116+
},
117+
notEquals: {
118+
allowedTypes: ["String", "Number", "Boolean", "JSON", "EmailAddress"],
119+
argType: (type: ScalarType) => type,
120+
expr: (fieldId: string, value: ExprArg) =>
121+
q.Not(
122+
q.Equals(
123+
//@ts-ignore
124+
q.Select(["data", fieldId], q.Get(q.Var("ref")), null),
125+
value
126+
)
127+
),
128+
},
129+
lt: {
130+
allowedTypes: ["String", "Number", "Boolean", "JSON", "EmailAddress"],
131+
argType: (type: ScalarType) => type,
132+
expr: (fieldId: string, value: ExprArg) =>
133+
//@ts-ignore
134+
q.LT(q.Select(["data", fieldId], q.Get(q.Var("ref")), null), value),
135+
},
136+
gt: {
137+
allowedTypes: ["String", "Number", "Boolean", "JSON", "EmailAddress"],
138+
argType: (type: ScalarType) => type,
139+
expr: (fieldId: string, value: ExprArg) =>
140+
//@ts-ignore
141+
q.GT(q.Select(["data", fieldId], q.Get(q.Var("ref")), null), value),
142+
},
143+
startsWith: {
144+
allowedTypes: ["String", "EmailAddress"],
145+
argType: (type: ScalarType) => type,
146+
expr: (fieldId: string, value: ExprArg) =>
147+
//@ts-ignore
148+
q.StartsWith(
149+
q.Select(["data", fieldId], q.Get(q.Var("ref")), ""),
150+
value
151+
),
152+
},
153+
endsWith: {
154+
allowedTypes: ["String", "EmailAddress"],
155+
argType: (type: ScalarType) => type,
156+
expr: (fieldId: string, value: ExprArg) =>
157+
//@ts-ignore
158+
q.EndsWith(q.Select(["data", fieldId], q.Get(q.Var("ref")), ""), value),
159+
},
160+
contains: {
161+
allowedTypes: ["String", "EmailAddress"],
162+
argType: (type: ScalarType) => type,
163+
expr: (fieldId: string, value: ExprArg) =>
164+
//@ts-ignore
165+
q.ContainsStr(
166+
q.Select(["data", fieldId], q.Get(q.Var("ref")), ""),
167+
value
168+
),
169+
},
170+
},
171+
queries: (table: Pick<Table, "apiName" | "id">) => ({
98172
findMany: {
99173
name: (): string => "query" + table.apiName,
100174
// @ts-ignore
101-
query: (args): Expr => {
175+
query: (
176+
args: {
177+
first: number;
178+
before: string;
179+
after: string;
180+
where: Record<string, Record<string, ExprArg>>;
181+
},
182+
faunaSchema: FaunaSchema
183+
): Expr => {
102184
const options: { size: number; after?: Expr; before?: Expr } = {
103185
size: args.first,
104186
};
@@ -108,19 +190,31 @@ export const definitions = (
108190
if (args.before) {
109191
options.before = q.Ref(q.Collection(table.id), args.before);
110192
}
111-
let filter: boolean | Expr = true;
112-
if (args.where?.title_eq) {
113-
filter = q.Equals(
114-
args.where.title_eq,
115-
q.Select(["data", "294845251673129473"], q.Get(q.Var("ref")))
193+
const filters: Array<boolean | Expr> = [];
194+
if (args.where) {
195+
for (const [fieldName, operators] of Object.entries(args.where)) {
196+
for (const [operator, value] of Object.entries(operators)) {
197+
filters.push(
198+
definitions.operators[operator].expr(
199+
faunaSchema[table.apiName].fields[fieldName].id,
200+
value
201+
)
202+
);
203+
}
204+
}
205+
}
206+
if (filters.length > 0) {
207+
return q.Map(
208+
q.Filter(
209+
q.Paginate(q.Documents(q.Collection(table.id)), { ...options }),
210+
q.Lambda("ref", q.And(...filters))
211+
),
212+
q.Lambda("ref", q.Get(q.Var("ref")))
116213
);
117214
}
118-
119215
return q.Map(
120-
q.Filter(
121-
q.Paginate(q.Documents(q.Collection(table.id)), { ...options }),
122-
q.Lambda("ref", filter)
123-
),
216+
q.Paginate(q.Documents(q.Collection(table.id)), { ...options }),
217+
124218
q.Lambda("ref", q.Get(q.Var("ref")))
125219
);
126220
},
@@ -228,5 +322,5 @@ export const definitions = (
228322
);
229323
},
230324
},
231-
},
232-
});
325+
}),
326+
};

packages/api/src/data/demo_server.ts

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { ApolloServer } from "apollo-server";
2+
3+
import { Client } from "faunadb";
4+
import { getProjectSchema } from "./index";
5+
import { GraphQLSchema } from "graphql";
6+
// import scaffold from "./fauna/scaffold";
7+
import repositories from "./fauna/repositories";
8+
9+
(async () => {
10+
if (process.env.NODE_ENV === "production") {
11+
throw new Error("Demo server should not be run in production");
12+
}
13+
if (!process.env.FAUNA_ADMIN_KEY) {
14+
throw new Error("Missing env 'FAUNA_ADMIN_KEY'");
15+
}
16+
17+
const client = new Client({
18+
secret: process.env.FAUNA_ADMIN_KEY,
19+
});
20+
21+
const tables: Record<string, { name: string; apiName: string; id: string }> =
22+
{
23+
Book: { id: "", name: "Book", apiName: "Book" },
24+
Author: { id: "", name: "Author", apiName: "Author" },
25+
};
26+
let projectId = process.env.DEMO_PROJECT_ID;
27+
if (!projectId) {
28+
const { organization, project, table, scalarField, relationshipField } =
29+
repositories(client);
30+
// await scaffold(client);
31+
32+
const organizationId = await organization.create({
33+
name: "LibraryOrg",
34+
apiName: "LibraryOrg",
35+
});
36+
37+
projectId = await project.create({
38+
name: "WrongName",
39+
apiName: "WrongApiName",
40+
organizationId,
41+
});
42+
await project.update(projectId, {
43+
name: "LibraryProj",
44+
apiName: "LibraryProj",
45+
organizationId,
46+
});
47+
48+
for (const [key, value] of Object.entries(tables)) {
49+
tables[key].id = await table.create({
50+
...value,
51+
projectId,
52+
});
53+
}
54+
55+
await scalarField.create({
56+
name: "title",
57+
apiName: "title",
58+
tableId: tables["Book"].id,
59+
type: "String",
60+
});
61+
await scalarField.create({
62+
name: "pubYear",
63+
apiName: "pubYear",
64+
tableId: tables["Book"].id,
65+
type: "Number",
66+
});
67+
await scalarField.create({
68+
name: "jsonDescription",
69+
apiName: "jsonDescription",
70+
tableId: tables["Book"].id,
71+
type: "JSON",
72+
});
73+
await scalarField.create({
74+
name: "name",
75+
apiName: "name",
76+
tableId: tables["Author"].id,
77+
type: "String",
78+
});
79+
await scalarField.create({
80+
name: "emails",
81+
apiName: "emails",
82+
tableId: tables["Author"].id,
83+
type: ["EmailAddress"],
84+
});
85+
86+
// const [authorsFieldId, booksFieldId] =
87+
await relationshipField.createBidirectional({
88+
name: "authors",
89+
apiName: "authors",
90+
backName: "books",
91+
backApiName: "books",
92+
to: tables["Author"].id,
93+
tableId: tables["Book"].id,
94+
});
95+
}
96+
97+
getProjectSchema(client, projectId).then(async (schema: GraphQLSchema) => {
98+
const server = new ApolloServer({
99+
schema,
100+
cors: {
101+
origin: "*",
102+
},
103+
});
104+
server.listen().then(({ url }) => {
105+
console.log(`🚀 Server ready at ${url}`);
106+
});
107+
});
108+
})();

packages/api/src/data/fauna/client.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import "cross-fetch/polyfill";
22
import { Client } from "faunadb";
33

4-
export const getClient = (secret: string): Client =>
5-
new Client({
4+
export const getClient = (secret: string | null | undefined): Client => {
5+
if (!secret) {
6+
throw new Error("Client requires a secret.");
7+
}
8+
return new Client({
69
secret,
710
fetch: (requestInfo: RequestInfo, requestInit?: RequestInit) => {
811
const signal = requestInit?.signal;
@@ -25,5 +28,5 @@ export const getClient = (secret: string): Client =>
2528
]);
2629
},
2730
});
28-
29-
export const client = getClient("fnAEI3aezIACAcwwrsWsIDvyHVRNnSFOSg1Yjz8T");
31+
};
32+
export const client = getClient(process.env.FAUNA_ADMIN_KEY);

0 commit comments

Comments
 (0)