@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 +58 -2
- package/dist/cmd/projections.js +43 -22
- package/dist/delivery/client.d.ts +2 -2
- package/dist/delivery/client.js +2 -2
- package/dist/delivery/getData.js +1 -0
- package/dist/delivery/getDataList.d.ts +11 -1
- package/dist/delivery/getDataList.js +25 -1
- package/dist/index.d.ts +1 -0
- package/package.json +2 -2
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
|
|
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.
|
package/dist/cmd/projections.js
CHANGED
|
@@ -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({
|
|
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>;
|
package/dist/delivery/client.js
CHANGED
|
@@ -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();
|
package/dist/delivery/getData.js
CHANGED
|
@@ -4,4 +4,14 @@ export interface GetProjectionDataList {
|
|
|
4
4
|
page: number;
|
|
5
5
|
data: Record<string, any>[];
|
|
6
6
|
}
|
|
7
|
-
export
|
|
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
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fraym/projections",
|
|
3
|
-
"version": "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.
|
|
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",
|