@fraym/projections 0.2.0 → 0.4.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/README.md CHANGED
@@ -20,7 +20,9 @@ You need to add the `Tenant-Id` header in order to use the graphQL Endpoint and
20
20
  Use the `projections` cli command to automatically apply your projection schemas to the projections service.
21
21
 
22
22
  Your type schemas have to match the glob you specify in the `PROJECTIONS_SCHEMA_GLOB` env variable (default: `./src/**/*.graphql`).
23
- You can specify the address (and port) of the crud service instance you use in the `PROJECTIONS_SERVER_ADDRESS` env variable (default: `127.0.0.1:9000`).
23
+ You can specify the address (and port) of the projections service instance you use in the `PROJECTIONS_SERVER_ADDRESS` env variable (default: `127.0.0.1:9000`).
24
+
25
+ Use the `PROJECTIONS_NAMESPACE` env variable to restrict all migrations to your namespace. This is useful if multiple apps share the projections service. Note: You cannot name your projection or namespace by a `Fraym` prefix. This is a reserved prefix for fraym apps.
24
26
 
25
27
  You need to add a file that contains all built-in directives to your type schemas. The latest version of this file can be found [here](default.graphql).
26
28
 
@@ -31,6 +33,7 @@ Use a `.env` file or env variables to configure cte clients and the command:
31
33
  ```env
32
34
  PROJECTIONS_SERVER_ADDRESS=127.0.0.1:9000
33
35
  PROJECTIONS_SCHEMA_GLOB=./src/projections/*.graphql
36
+ PROJECTIONS_NAMESPACE=
34
37
  ```
35
38
 
36
39
  ## Usage
@@ -94,7 +97,7 @@ You can specify a fourth parameter if you want to return a empty dataset instead
94
97
  const data = await deliveryClient.getData("tenantId", "YourProjection", "id", true);
95
98
  ```
96
99
 
97
- ### Get (paginated) data
100
+ ### Get (paginated / filtered) data
98
101
 
99
102
  The name of `YourProjection` has to equal your type name in your schema (also in casing).
100
103
 
@@ -112,6 +115,59 @@ const page = 1; // number of the page you want to select, first page starts at:
112
115
  const data = await deliveryClient.getDataList("tenantId", "YourProjection", limit, page);
113
116
  ```
114
117
 
118
+ With filter:
119
+
120
+ ```typescript
121
+ const data = await deliveryClient.getDataList("tenantId", "YourProjection", undefined, undefined, {
122
+ fields: {
123
+ fieldName: {
124
+ operation: "equals",
125
+ type: "Int",
126
+ value: 123,
127
+ },
128
+ },
129
+ });
130
+ ```
131
+
132
+ All `Filter`s are evaluated by:
133
+
134
+ - checking that all field filters match
135
+ - checking that all `and` filters match
136
+ - checking that one of the `or` filters match
137
+
138
+ Avaliable types:
139
+
140
+ - `String`
141
+ - `ID`
142
+ - `DateTime`
143
+ - `Int`
144
+ - `Float`
145
+ - `Boolean`
146
+
147
+ Avaliable operators for all types:
148
+
149
+ - `equals`
150
+ - `notEquals`
151
+
152
+ Avaliable options for the filter type `DateTime`:
153
+
154
+ - `inArray`
155
+ - `notInArray`
156
+ - `after`
157
+ - `before`
158
+
159
+ Avaliable options for the filter type `String` and `ID`:
160
+
161
+ - `inArray`
162
+ - `notInArray`
163
+
164
+ Avaliable options for the filter type `Int` and `Float`:
165
+
166
+ - `lessThan`
167
+ - `greaterThan`
168
+ - `lessThanOrEqual`
169
+ - `greaterThanOrEqual`
170
+
115
171
  ### Gracefully close the clients
116
172
 
117
173
  You won't lose any data if you don't. Use it for your peace of mind.
@@ -14,47 +14,60 @@ const client_1 = require("../management/client");
14
14
  const run = async () => {
15
15
  (0, dotenv_1.config)();
16
16
  const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
17
- .config({ schemaGlob: "./src/**/*.graphql", serverAddress: "127.0.0.1:9000" })
17
+ .config({
18
+ schemaGlob: "./src/**/*.graphql",
19
+ serverAddress: "127.0.0.1:9000",
20
+ namespace: "",
21
+ })
18
22
  .pkgConf("projections").argv;
19
23
  let schemaGlob = argv.schemaGlob;
20
24
  let serverAddress = argv.serverAddress;
25
+ let namespace = argv.namespace;
21
26
  if (process.env.PROJECTIONS_SCHEMA_GLOB) {
22
27
  schemaGlob = process.env.PROJECTIONS_SCHEMA_GLOB;
23
28
  }
24
29
  if (process.env.PROJECTIONS_SERVER_ADDRESS) {
25
30
  serverAddress = process.env.PROJECTIONS_SERVER_ADDRESS;
26
31
  }
32
+ if (process.env.PROJECTIONS_NAMESPACE) {
33
+ namespace = process.env.PROJECTIONS_NAMESPACE;
34
+ }
35
+ if (namespace === "Fraym") {
36
+ throw new Error("Cannot use Fraym as namespace as it is reserved for fraym apps");
37
+ }
27
38
  const schema = await (0, load_1.loadSchema)(`${schemaGlob}`, {
28
39
  loaders: [new graphql_file_loader_1.GraphQLFileLoader()],
29
40
  });
30
- const definitions = getTypeDefinition(schema);
31
- await migrateSchemas(definitions, serverAddress);
41
+ const definitions = getTypeDefinition(schema, namespace);
42
+ await migrateSchemas(definitions, serverAddress, namespace);
32
43
  };
33
44
  run();
34
- const getTypeDefinition = (schema) => {
45
+ const getTypeDefinition = (schema, namespace) => {
35
46
  const definitions = {};
36
47
  schema.toConfig().types.forEach(t => {
37
48
  if (!(t instanceof graphql_1.GraphQLObjectType) && !(t instanceof graphql_1.GraphQLEnumType)) {
38
49
  return;
39
50
  }
40
- const name = t.toString();
51
+ const name = `${namespace}${t.toString()}`;
52
+ ensureValidName(name);
41
53
  if (definitions[name]) {
42
54
  throw new Error(`duplicate definition for type "${name}" detected, try renaming one of them as they have to be uniquely named`);
43
55
  }
44
56
  if (t instanceof graphql_1.GraphQLObjectType) {
45
- definitions[name] = getTypeDefinitionFromGraphQLObjectType(t);
57
+ definitions[name] = getTypeDefinitionFromGraphQLObjectType(t, namespace);
46
58
  return;
47
59
  }
48
60
  if (t instanceof graphql_1.GraphQLEnumType) {
49
- definitions[name] = getTypeDefinitionFromGraphQLEnumType(t);
61
+ definitions[name] = getTypeDefinitionFromGraphQLEnumType(t, namespace);
50
62
  return;
51
63
  }
52
64
  });
53
65
  return definitions;
54
66
  };
55
- const getTypeDefinitionFromGraphQLEnumType = (t) => {
67
+ const getTypeDefinitionFromGraphQLEnumType = (t, namespace) => {
56
68
  var _a, _b;
57
- const name = t.toString();
69
+ const name = `${namespace}${t.toString()}`;
70
+ ensureValidName(name);
58
71
  let enumValuesString = "";
59
72
  (_b = (_a = t.astNode) === null || _a === void 0 ? void 0 : _a.values) === null || _b === void 0 ? void 0 : _b.forEach(value => {
60
73
  enumValuesString += `\n\t${value.name.value}`;
@@ -66,14 +79,15 @@ const getTypeDefinitionFromGraphQLEnumType = (t) => {
66
79
  schema,
67
80
  };
68
81
  };
69
- const getTypeDefinitionFromGraphQLObjectType = (t) => {
82
+ const getTypeDefinitionFromGraphQLObjectType = (t, namespace) => {
70
83
  var _a, _b, _c, _d, _e, _f;
71
84
  let isProjection = false;
72
85
  if (((_a = t.astNode) === null || _a === void 0 ? void 0 : _a.directives) && ((_b = t.astNode) === null || _b === void 0 ? void 0 : _b.directives.length) > 0) {
73
86
  const directiveNames = t.astNode.directives.map(directive => directive.name.value);
74
87
  isProjection = directiveNames.includes("upsertOn");
75
88
  }
76
- const name = t.toString();
89
+ const name = `${namespace}${t.toString()}`;
90
+ ensureValidName(name);
77
91
  let objectDirectivesString = "";
78
92
  let objectFieldsString = "";
79
93
  let nestedTypes = [];
@@ -81,7 +95,7 @@ const getTypeDefinitionFromGraphQLObjectType = (t) => {
81
95
  objectDirectivesString += getDirectiveString(d);
82
96
  });
83
97
  (_f = (_e = t.astNode) === null || _e === void 0 ? void 0 : _e.fields) === null || _f === void 0 ? void 0 : _f.forEach(f => {
84
- const { str, nestedTypes: newNestedTypes } = getFieldStringAndNestedTypes(f);
98
+ const { str, nestedTypes: newNestedTypes } = getFieldStringAndNestedTypes(f, namespace);
85
99
  objectFieldsString += str;
86
100
  newNestedTypes.forEach(nested => {
87
101
  if (nestedTypes.indexOf(nested) === -1) {
@@ -96,13 +110,13 @@ const getTypeDefinitionFromGraphQLObjectType = (t) => {
96
110
  schema,
97
111
  };
98
112
  };
99
- const getFieldStringAndNestedTypes = (f) => {
113
+ const getFieldStringAndNestedTypes = (f, namespace) => {
100
114
  var _a;
101
115
  let directivesString = "";
102
116
  (_a = f.directives) === null || _a === void 0 ? void 0 : _a.forEach(d => {
103
117
  directivesString += getDirectiveString(d);
104
118
  });
105
- const { nestedType, str: typeString } = getTypeData(f.type);
119
+ const { nestedType, str: typeString } = getTypeData(f.type, namespace);
106
120
  const nestedTypes = [];
107
121
  if (nestedType) {
108
122
  nestedTypes.push(nestedType);
@@ -112,31 +126,33 @@ const getFieldStringAndNestedTypes = (f) => {
112
126
  nestedTypes,
113
127
  };
114
128
  };
115
- const getTypeData = (t) => {
129
+ const getTypeData = (t, namespace) => {
116
130
  switch (t.kind) {
117
131
  case graphql_1.Kind.NAMED_TYPE:
118
132
  const name = t.name.value;
133
+ ensureValidName(`${namespace}${name}`);
119
134
  return name === "String" ||
120
135
  name === "Float" ||
121
136
  name === "ID" ||
122
137
  name === "Boolean" ||
123
138
  name === "Int" ||
124
- name === "DateTime"
139
+ name === "DateTime" ||
140
+ name === "EventEnvelope"
125
141
  ? {
126
142
  str: name,
127
143
  }
128
144
  : {
129
- str: name,
130
- nestedType: name,
145
+ str: `${namespace}${name}`,
146
+ nestedType: `${namespace}${name}`,
131
147
  };
132
148
  case graphql_1.Kind.LIST_TYPE:
133
- const { nestedType: listNestedType, str: listStr } = getTypeData(t.type);
149
+ const { nestedType: listNestedType, str: listStr } = getTypeData(t.type, namespace);
134
150
  return {
135
151
  str: `[${listStr}]`,
136
152
  nestedType: listNestedType,
137
153
  };
138
154
  case graphql_1.Kind.NON_NULL_TYPE:
139
- const { nestedType: nonNullNestedType, str: nonNullStr } = getTypeData(t.type);
155
+ const { nestedType: nonNullNestedType, str: nonNullStr } = getTypeData(t.type, namespace);
140
156
  return {
141
157
  str: `${nonNullStr}!`,
142
158
  nestedType: nonNullNestedType,
@@ -188,9 +204,9 @@ const getValueString = (v) => {
188
204
  throw new Error(`values of kind ${v.kind} are currently not supported`);
189
205
  }
190
206
  };
191
- const migrateSchemas = async (definitions, serverAddress) => {
207
+ const migrateSchemas = async (definitions, serverAddress, namespace) => {
192
208
  const managementClient = await (0, client_1.newManagementClient)({ serverAddress });
193
- let existingProjections = (await managementClient.getAll()).filter(projectionName => !projectionName.startsWith("Crud"));
209
+ let existingProjections = (await managementClient.getAll()).filter(projectionName => !projectionName.startsWith("Crud") && projectionName.startsWith(namespace));
194
210
  let createSchema = "";
195
211
  let updateSchema = "";
196
212
  const projectionsToCreate = [];
@@ -246,3 +262,8 @@ const migrateSchemas = async (definitions, serverAddress) => {
246
262
  console.log(`Removed ${projectionsToRemove.length} projections`);
247
263
  }
248
264
  };
265
+ const ensureValidName = (name) => {
266
+ if (name.startsWith("Fraym")) {
267
+ throw new Error(`Cannot use Fraym as projection name prefix as it is reserved for fraym apps, got ${name}`);
268
+ }
269
+ };
@@ -1,9 +1,9 @@
1
1
  import { ClientConfig } from "../config/config";
2
2
  import { GetProjectionData } from "./getData";
3
- import { GetProjectionDataList } from "./getDataList";
3
+ import { Filter, GetProjectionDataList } from "./getDataList";
4
4
  export interface DeliveryClient {
5
5
  getData: (tenantId: string, type: string, id: string, returnEmptyDataIfNotFound?: boolean) => Promise<GetProjectionData | null>;
6
- getDataList: (tenantId: string, type: string, limit?: number, page?: number) => Promise<GetProjectionDataList | null>;
6
+ getDataList: (tenantId: string, type: string, limit?: number, page?: number, filter?: Filter) => Promise<GetProjectionDataList | null>;
7
7
  close: () => Promise<void>;
8
8
  }
9
9
  export declare const newDeliveryClient: (config?: ClientConfig) => Promise<DeliveryClient>;
@@ -16,8 +16,8 @@ const newDeliveryClient = async (config) => {
16
16
  const getData = async (tenantId, projection, id, returnEmptyDataIfNotFound = false) => {
17
17
  return await (0, getData_1.getProjectionData)(tenantId, projection, id, returnEmptyDataIfNotFound, serviceClient);
18
18
  };
19
- const getDataList = async (tenantId, projection, limit = 0, page = 1) => {
20
- return await (0, getDataList_1.getProjectionDataList)(tenantId, projection, limit, page, serviceClient);
19
+ const getDataList = async (tenantId, projection, limit = 0, page = 1, filter = { fields: {}, and: [], or: [] }) => {
20
+ return await (0, getDataList_1.getProjectionDataList)(tenantId, projection, limit, page, filter, serviceClient);
21
21
  };
22
22
  const close = async () => {
23
23
  serviceClient.close();
@@ -10,6 +10,7 @@ const getProjectionData = async (tenantId, projection, dataId, returnEmptyDataIf
10
10
  limit: 0,
11
11
  page: 0,
12
12
  returnEmptyDataIfNotFound,
13
+ filter: { fields: {}, and: [], or: [] },
13
14
  }, (error, response) => {
14
15
  if (error) {
15
16
  reject(error.message);
@@ -4,4 +4,14 @@ export interface GetProjectionDataList {
4
4
  page: number;
5
5
  data: Record<string, any>[];
6
6
  }
7
- export declare const getProjectionDataList: (tenantId: string, projection: string, limit: number, page: number, serviceClient: DeliveryServiceClient) => Promise<GetProjectionDataList | null>;
7
+ export interface Filter {
8
+ fields: Record<string, FieldFilter>;
9
+ and: Filter[];
10
+ or: Filter[];
11
+ }
12
+ export interface FieldFilter {
13
+ type: string;
14
+ operation: string;
15
+ value: any;
16
+ }
17
+ export declare const getProjectionDataList: (tenantId: string, projection: string, limit: number, page: number, filter: Filter, serviceClient: DeliveryServiceClient) => Promise<GetProjectionDataList | null>;
@@ -1,7 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getProjectionDataList = void 0;
4
- const getProjectionDataList = async (tenantId, projection, limit, page, serviceClient) => {
4
+ const getProtobufDataFilter = (filter) => {
5
+ const fields = {};
6
+ for (const fieldName in filter.fields) {
7
+ const field = filter.fields[fieldName];
8
+ let value = "";
9
+ if (field.type === "String" && typeof field.value == "string") {
10
+ value = field.value;
11
+ }
12
+ else {
13
+ value = JSON.stringify(field.value);
14
+ }
15
+ fields[fieldName] = {
16
+ operation: field.operation,
17
+ type: field.type,
18
+ value,
19
+ };
20
+ }
21
+ return {
22
+ fields: fields,
23
+ and: filter.and.map(and => getProtobufDataFilter(and)),
24
+ or: filter.or.map(or => getProtobufDataFilter(or)),
25
+ };
26
+ };
27
+ const getProjectionDataList = async (tenantId, projection, limit, page, filter, serviceClient) => {
5
28
  return new Promise((resolve, reject) => {
6
29
  serviceClient.getData({
7
30
  tenantId,
@@ -10,6 +33,7 @@ const getProjectionDataList = async (tenantId, projection, limit, page, serviceC
10
33
  limit,
11
34
  page,
12
35
  returnEmptyDataIfNotFound: false,
36
+ filter: getProtobufDataFilter(filter),
13
37
  }, (error, response) => {
14
38
  if (error) {
15
39
  reject(error.message);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./management/client";
2
2
  export * from "./delivery/client";
3
+ export { Filter, FieldFilter } from "./delivery/getDataList";
3
4
  export { ClientConfig } from "./config/config";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraym/projections",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "license": "UNLICENSED",
5
5
  "homepage": "https://github.com/fraym/projections-nodejs",
6
6
  "repository": {
@@ -27,7 +27,7 @@
27
27
  "projections": "dist/cmd/projections.js"
28
28
  },
29
29
  "dependencies": {
30
- "@fraym/projections-proto": "^1.0.0-alpha.9",
30
+ "@fraym/projections-proto": "^1.0.0-alpha.11",
31
31
  "@graphql-tools/graphql-file-loader": "^7.5.11",
32
32
  "@graphql-tools/load": "^7.8.6",
33
33
  "@grpc/grpc-js": "^1.7.2",