@balena/pinejs 15.0.0-true-boolean-7896b116c446d891d7a0d5e4085c02a13bc9c725 → 15.0.1-build-migrations-clarify-marking-sbvr-optional-d6d0ded8eccc6eadb2492f4697918cf0afd00215-1
Sign up to get free protection for your applications and to get access to all the features.
- package/.dockerignore +4 -0
- package/.github/workflows/flowzone.yml +21 -0
- package/.husky/pre-commit +4 -0
- package/.pinejs-cache.json +1 -0
- package/.resinci.yml +1 -0
- package/.versionbot/CHANGELOG.yml +9678 -2002
- package/CHANGELOG.md +2976 -2
- package/Dockerfile +14 -0
- package/Gruntfile.ts +3 -6
- package/README.md +10 -1
- package/VERSION +1 -0
- package/build/browser.ts +1 -1
- package/build/config.ts +0 -1
- package/docker-compose.npm-test.yml +11 -0
- package/docs/AdvancedUsage.md +77 -63
- package/docs/GettingStarted.md +90 -41
- package/docs/Migrations.md +102 -1
- package/docs/ProjectConfig.md +12 -21
- package/docs/Testing.md +7 -0
- package/out/bin/abstract-sql-compiler.js +17 -17
- package/out/bin/abstract-sql-compiler.js.map +1 -1
- package/out/bin/odata-compiler.js +23 -20
- package/out/bin/odata-compiler.js.map +1 -1
- package/out/bin/sbvr-compiler.js +22 -22
- package/out/bin/sbvr-compiler.js.map +1 -1
- package/out/bin/utils.d.ts +2 -2
- package/out/bin/utils.js +3 -3
- package/out/bin/utils.js.map +1 -1
- package/out/config-loader/config-loader.d.ts +9 -8
- package/out/config-loader/config-loader.js +135 -78
- package/out/config-loader/config-loader.js.map +1 -1
- package/out/config-loader/env.d.ts +41 -16
- package/out/config-loader/env.js +46 -2
- package/out/config-loader/env.js.map +1 -1
- package/out/data-server/sbvr-server.d.ts +2 -19
- package/out/data-server/sbvr-server.js +44 -38
- package/out/data-server/sbvr-server.js.map +1 -1
- package/out/database-layer/db.d.ts +32 -14
- package/out/database-layer/db.js +120 -41
- package/out/database-layer/db.js.map +1 -1
- package/out/express-emulator/express.js +10 -11
- package/out/express-emulator/express.js.map +1 -1
- package/out/http-transactions/transactions.d.ts +2 -18
- package/out/http-transactions/transactions.js +29 -21
- package/out/http-transactions/transactions.js.map +1 -1
- package/out/migrator/async.d.ts +7 -0
- package/out/migrator/async.js +168 -0
- package/out/migrator/async.js.map +1 -0
- package/out/migrator/migrations.sbvr +43 -0
- package/out/migrator/sync.d.ts +9 -0
- package/out/migrator/sync.js +106 -0
- package/out/migrator/sync.js.map +1 -0
- package/out/migrator/utils.d.ts +78 -0
- package/out/migrator/utils.js +283 -0
- package/out/migrator/utils.js.map +1 -0
- package/out/odata-metadata/odata-metadata-generator.js +10 -13
- package/out/odata-metadata/odata-metadata-generator.js.map +1 -1
- package/out/passport-pinejs/passport-pinejs.d.ts +1 -1
- package/out/passport-pinejs/passport-pinejs.js +8 -7
- package/out/passport-pinejs/passport-pinejs.js.map +1 -1
- package/out/pinejs-session-store/pinejs-session-store.d.ts +1 -1
- package/out/pinejs-session-store/pinejs-session-store.js +20 -6
- package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
- package/out/sbvr-api/abstract-sql.d.ts +3 -2
- package/out/sbvr-api/abstract-sql.js +9 -9
- package/out/sbvr-api/abstract-sql.js.map +1 -1
- package/out/sbvr-api/cached-compile.js +1 -1
- package/out/sbvr-api/cached-compile.js.map +1 -1
- package/out/sbvr-api/common-types.d.ts +6 -5
- package/out/sbvr-api/control-flow.d.ts +8 -1
- package/out/sbvr-api/control-flow.js +36 -9
- package/out/sbvr-api/control-flow.js.map +1 -1
- package/out/sbvr-api/errors.d.ts +47 -40
- package/out/sbvr-api/errors.js +78 -77
- package/out/sbvr-api/errors.js.map +1 -1
- package/out/sbvr-api/express-extension.d.ts +4 -0
- package/out/sbvr-api/hooks.d.ts +16 -15
- package/out/sbvr-api/hooks.js +74 -48
- package/out/sbvr-api/hooks.js.map +1 -1
- package/out/sbvr-api/odata-response.d.ts +2 -2
- package/out/sbvr-api/odata-response.js +28 -30
- package/out/sbvr-api/odata-response.js.map +1 -1
- package/out/sbvr-api/permissions.d.ts +17 -16
- package/out/sbvr-api/permissions.js +369 -304
- package/out/sbvr-api/permissions.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +33 -15
- package/out/sbvr-api/sbvr-utils.js +397 -235
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/sbvr-api/translations.d.ts +6 -0
- package/out/sbvr-api/translations.js +150 -0
- package/out/sbvr-api/translations.js.map +1 -0
- package/out/sbvr-api/uri-parser.d.ts +23 -17
- package/out/sbvr-api/uri-parser.js +33 -27
- package/out/sbvr-api/uri-parser.js.map +1 -1
- package/out/sbvr-api/user.sbvr +2 -0
- package/out/server-glue/module.d.ts +6 -6
- package/out/server-glue/module.js +4 -2
- package/out/server-glue/module.js.map +1 -1
- package/out/server-glue/server.js +5 -5
- package/out/server-glue/server.js.map +1 -1
- package/package.json +89 -73
- package/pinejs.png +0 -0
- package/repo.yml +9 -9
- package/src/bin/abstract-sql-compiler.ts +5 -7
- package/src/bin/odata-compiler.ts +11 -13
- package/src/bin/sbvr-compiler.ts +11 -17
- package/src/bin/utils.ts +3 -5
- package/src/config-loader/config-loader.ts +167 -53
- package/src/config-loader/env.ts +106 -6
- package/src/data-server/sbvr-server.js +44 -38
- package/src/database-layer/db.ts +205 -64
- package/src/express-emulator/express.js +10 -11
- package/src/http-transactions/transactions.js +29 -21
- package/src/migrator/async.ts +323 -0
- package/src/migrator/migrations.sbvr +43 -0
- package/src/migrator/sync.ts +152 -0
- package/src/migrator/utils.ts +458 -0
- package/src/odata-metadata/odata-metadata-generator.ts +12 -15
- package/src/passport-pinejs/passport-pinejs.ts +9 -7
- package/src/pinejs-session-store/pinejs-session-store.ts +15 -1
- package/src/sbvr-api/abstract-sql.ts +17 -14
- package/src/sbvr-api/common-types.ts +2 -1
- package/src/sbvr-api/control-flow.ts +45 -11
- package/src/sbvr-api/errors.ts +82 -77
- package/src/sbvr-api/express-extension.ts +6 -1
- package/src/sbvr-api/hooks.ts +123 -50
- package/src/sbvr-api/odata-response.ts +23 -28
- package/src/sbvr-api/permissions.ts +548 -415
- package/src/sbvr-api/sbvr-utils.ts +581 -259
- package/src/sbvr-api/translations.ts +248 -0
- package/src/sbvr-api/uri-parser.ts +63 -49
- package/src/sbvr-api/user.sbvr +2 -0
- package/src/server-glue/module.ts +16 -10
- package/src/server-glue/server.ts +5 -5
- package/tsconfig.dev.json +1 -0
- package/tsconfig.json +1 -2
- package/typings/lf-to-abstract-sql.d.ts +6 -9
- package/typings/memoizee.d.ts +1 -1
- package/.github/CODEOWNERS +0 -1
- package/circle.yml +0 -37
- package/docs/todo.txt +0 -22
- package/out/migrator/migrator.d.ts +0 -20
- package/out/migrator/migrator.js +0 -188
- package/out/migrator/migrator.js.map +0 -1
- package/src/migrator/migrator.ts +0 -286
@@ -0,0 +1,248 @@
|
|
1
|
+
import * as _ from 'lodash';
|
2
|
+
import {
|
3
|
+
AbstractSqlModel,
|
4
|
+
Relationship,
|
5
|
+
ReferencedFieldNode,
|
6
|
+
SelectNode,
|
7
|
+
AliasNode,
|
8
|
+
Definition,
|
9
|
+
RelationshipInternalNode,
|
10
|
+
RelationshipLeafNode,
|
11
|
+
SelectQueryNode,
|
12
|
+
NumberTypeNodes,
|
13
|
+
BooleanTypeNodes,
|
14
|
+
UnknownTypeNodes,
|
15
|
+
NullNode,
|
16
|
+
} from '@balena/abstract-sql-compiler';
|
17
|
+
import { Dictionary } from './common-types';
|
18
|
+
|
19
|
+
export type AliasValidNodeType =
|
20
|
+
| ReferencedFieldNode
|
21
|
+
| SelectQueryNode
|
22
|
+
| NumberTypeNodes
|
23
|
+
| BooleanTypeNodes
|
24
|
+
| UnknownTypeNodes
|
25
|
+
| NullNode;
|
26
|
+
const aliasFields = (
|
27
|
+
fromAbstractSqlModel: AbstractSqlModel,
|
28
|
+
toAbstractSqlModel: AbstractSqlModel,
|
29
|
+
fromResourceName: string,
|
30
|
+
toResource: string,
|
31
|
+
aliases: Dictionary<string | AliasValidNodeType>,
|
32
|
+
): SelectNode[1] => {
|
33
|
+
const fromFieldNames = fromAbstractSqlModel.tables[
|
34
|
+
fromResourceName
|
35
|
+
].fields.map(({ fieldName }) => fieldName);
|
36
|
+
const nonexistentFields = _.difference(Object.keys(aliases), fromFieldNames);
|
37
|
+
if (nonexistentFields.length > 0) {
|
38
|
+
throw new Error(
|
39
|
+
`Tried to alias non-existent fields: '${nonexistentFields.join(', ')}'`,
|
40
|
+
);
|
41
|
+
}
|
42
|
+
const toFieldNames = toAbstractSqlModel.tables[toResource].fields.map(
|
43
|
+
({ fieldName }) => fieldName,
|
44
|
+
);
|
45
|
+
const checkToFieldExists = (fromFieldName: string, toFieldName: string) => {
|
46
|
+
if (!toFieldNames.includes(toFieldName)) {
|
47
|
+
throw new Error(
|
48
|
+
`Tried to alias '${fromFieldName}' to the non-existent target field: '${toFieldName}'`,
|
49
|
+
);
|
50
|
+
}
|
51
|
+
};
|
52
|
+
return fromFieldNames.map(
|
53
|
+
(fieldName): AliasNode<AliasValidNodeType> | ReferencedFieldNode => {
|
54
|
+
const alias = aliases[fieldName];
|
55
|
+
if (alias) {
|
56
|
+
if (typeof alias === 'string') {
|
57
|
+
checkToFieldExists(fieldName, alias);
|
58
|
+
return [
|
59
|
+
'Alias',
|
60
|
+
['ReferencedField', fromResourceName, alias],
|
61
|
+
fieldName,
|
62
|
+
];
|
63
|
+
}
|
64
|
+
return ['Alias', alias, fieldName];
|
65
|
+
}
|
66
|
+
checkToFieldExists(fieldName, fieldName);
|
67
|
+
return ['ReferencedField', fromResourceName, fieldName];
|
68
|
+
},
|
69
|
+
);
|
70
|
+
};
|
71
|
+
|
72
|
+
const aliasResource = (
|
73
|
+
fromAbstractSqlModel: AbstractSqlModel,
|
74
|
+
toAbstractSqlModel: AbstractSqlModel,
|
75
|
+
fromResourceName: string,
|
76
|
+
toResource: string,
|
77
|
+
aliases: Dictionary<string | AliasValidNodeType>,
|
78
|
+
): Definition => {
|
79
|
+
if (!toAbstractSqlModel.tables[toResource]) {
|
80
|
+
throw new Error(`Tried to alias to a non-existent resource: ${toResource}`);
|
81
|
+
}
|
82
|
+
return {
|
83
|
+
abstractSql: [
|
84
|
+
'SelectQuery',
|
85
|
+
[
|
86
|
+
'Select',
|
87
|
+
aliasFields(
|
88
|
+
fromAbstractSqlModel,
|
89
|
+
toAbstractSqlModel,
|
90
|
+
fromResourceName,
|
91
|
+
toResource,
|
92
|
+
aliases,
|
93
|
+
),
|
94
|
+
],
|
95
|
+
['From', ['Alias', ['Resource', toResource], fromResourceName]],
|
96
|
+
],
|
97
|
+
};
|
98
|
+
};
|
99
|
+
|
100
|
+
const namespaceRelationships = (
|
101
|
+
relationships: Relationship,
|
102
|
+
alias: string,
|
103
|
+
): void => {
|
104
|
+
for (const [key, relationship] of Object.entries(
|
105
|
+
relationships as RelationshipInternalNode,
|
106
|
+
)) {
|
107
|
+
if (key === '$') {
|
108
|
+
return;
|
109
|
+
}
|
110
|
+
|
111
|
+
let mapping = (relationship as RelationshipLeafNode).$;
|
112
|
+
if (mapping != null && mapping.length === 2) {
|
113
|
+
if (!key.includes('$')) {
|
114
|
+
mapping = _.cloneDeep(mapping);
|
115
|
+
mapping[1]![0] = `${mapping[1]![0]}$${alias}`;
|
116
|
+
(relationships as RelationshipInternalNode)[`${key}$${alias}`] = {
|
117
|
+
$: mapping,
|
118
|
+
};
|
119
|
+
delete (relationships as RelationshipInternalNode)[key];
|
120
|
+
}
|
121
|
+
}
|
122
|
+
namespaceRelationships(relationship, alias);
|
123
|
+
}
|
124
|
+
};
|
125
|
+
|
126
|
+
export const translateAbstractSqlModel = (
|
127
|
+
fromAbstractSqlModel: AbstractSqlModel,
|
128
|
+
toAbstractSqlModel: AbstractSqlModel,
|
129
|
+
fromVersion: string,
|
130
|
+
toVersion: string,
|
131
|
+
translationDefinitions: Dictionary<
|
132
|
+
| (Definition & { $toResource?: string })
|
133
|
+
| Dictionary<string | AliasValidNodeType>
|
134
|
+
> = {},
|
135
|
+
): Dictionary<string> => {
|
136
|
+
const isDefinition = (
|
137
|
+
d: (typeof translationDefinitions)[string],
|
138
|
+
): d is Definition => 'abstractSql' in d;
|
139
|
+
|
140
|
+
const resourceRenames: Dictionary<string> = {};
|
141
|
+
|
142
|
+
fromAbstractSqlModel.rules = toAbstractSqlModel.rules;
|
143
|
+
|
144
|
+
const fromResourceKeys = Object.keys(fromAbstractSqlModel.tables);
|
145
|
+
const nonexistentTables = _.difference(
|
146
|
+
Object.keys(translationDefinitions),
|
147
|
+
fromResourceKeys,
|
148
|
+
);
|
149
|
+
if (nonexistentTables.length > 0) {
|
150
|
+
throw new Error(
|
151
|
+
`Tried to define non-existent resources: '${nonexistentTables.join(
|
152
|
+
', ',
|
153
|
+
)}'`,
|
154
|
+
);
|
155
|
+
}
|
156
|
+
for (const [synonym, canonicalForm] of Object.entries(
|
157
|
+
toAbstractSqlModel.synonyms,
|
158
|
+
)) {
|
159
|
+
// Don't double alias
|
160
|
+
if (synonym.includes('$')) {
|
161
|
+
fromAbstractSqlModel.synonyms[synonym] = canonicalForm;
|
162
|
+
} else {
|
163
|
+
fromAbstractSqlModel.synonyms[
|
164
|
+
`${synonym}$${toVersion}`
|
165
|
+
] = `${canonicalForm}$${toVersion}`;
|
166
|
+
}
|
167
|
+
}
|
168
|
+
const relationships = _.cloneDeep(toAbstractSqlModel.relationships);
|
169
|
+
namespaceRelationships(relationships, toVersion);
|
170
|
+
for (let [key, relationship] of Object.entries(relationships)) {
|
171
|
+
// Don't double alias
|
172
|
+
if (!key.includes('$')) {
|
173
|
+
key = `${key}$${toVersion}`;
|
174
|
+
}
|
175
|
+
fromAbstractSqlModel.relationships[key] = relationship;
|
176
|
+
}
|
177
|
+
|
178
|
+
// TODO: We also need to keep the original relationship refs to non $version resources
|
179
|
+
|
180
|
+
// Also alias for ourselves to allow explicit referencing
|
181
|
+
const aliasedFromRelationships = _.cloneDeep(
|
182
|
+
fromAbstractSqlModel.relationships,
|
183
|
+
);
|
184
|
+
namespaceRelationships(aliasedFromRelationships, fromVersion);
|
185
|
+
for (let [key, relationship] of Object.entries(aliasedFromRelationships)) {
|
186
|
+
// Don't double alias
|
187
|
+
if (!key.includes('$')) {
|
188
|
+
key = `${key}$${fromVersion}`;
|
189
|
+
fromAbstractSqlModel.relationships[key] = relationship;
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
for (let [key, table] of Object.entries(toAbstractSqlModel.tables)) {
|
194
|
+
// Don't double alias
|
195
|
+
if (!key.includes('$')) {
|
196
|
+
key = `${key}$${toVersion}`;
|
197
|
+
}
|
198
|
+
fromAbstractSqlModel.tables[key] = _.cloneDeep(table);
|
199
|
+
}
|
200
|
+
|
201
|
+
for (const key of fromResourceKeys) {
|
202
|
+
const translationDefinition = translationDefinitions[key];
|
203
|
+
const table = fromAbstractSqlModel.tables[key];
|
204
|
+
if (translationDefinition) {
|
205
|
+
const { $toResource, ...definition } = translationDefinition;
|
206
|
+
const hasToResource = typeof $toResource === 'string';
|
207
|
+
if (hasToResource) {
|
208
|
+
resourceRenames[key] = `${$toResource}`;
|
209
|
+
}
|
210
|
+
const toResource = hasToResource ? $toResource : `${key}$${toVersion}`;
|
211
|
+
// TODO: Should this use the toAbstractSqlModel?
|
212
|
+
const toTable = fromAbstractSqlModel.tables[toResource];
|
213
|
+
if (!toTable) {
|
214
|
+
if (hasToResource) {
|
215
|
+
throw new Error(`Unknown $toResource: '${toResource}'`);
|
216
|
+
} else {
|
217
|
+
throw new Error(`Missing $toResource: '${toResource}'`);
|
218
|
+
}
|
219
|
+
}
|
220
|
+
table.modifyFields = _.cloneDeep(toTable.modifyFields ?? toTable.fields);
|
221
|
+
table.modifyName = toTable.modifyName ?? toTable.name;
|
222
|
+
if (isDefinition(definition)) {
|
223
|
+
table.definition = definition;
|
224
|
+
} else {
|
225
|
+
table.definition = aliasResource(
|
226
|
+
fromAbstractSqlModel,
|
227
|
+
toAbstractSqlModel,
|
228
|
+
key,
|
229
|
+
toResource,
|
230
|
+
definition,
|
231
|
+
);
|
232
|
+
}
|
233
|
+
} else {
|
234
|
+
const toTable = fromAbstractSqlModel.tables[`${key}$${toVersion}`];
|
235
|
+
if (!toTable) {
|
236
|
+
throw new Error(`Missing translation for: '${key}'`);
|
237
|
+
}
|
238
|
+
table.modifyFields = _.cloneDeep(toTable.modifyFields ?? toTable.fields);
|
239
|
+
table.definition = {
|
240
|
+
abstractSql: ['Resource', `${key}$${toVersion}`],
|
241
|
+
};
|
242
|
+
}
|
243
|
+
// Also alias the current version so it can be explicitly referenced
|
244
|
+
fromAbstractSqlModel.tables[`${key}$${fromVersion}`] = table;
|
245
|
+
}
|
246
|
+
|
247
|
+
return resourceRenames;
|
248
|
+
};
|
@@ -13,7 +13,6 @@ import * as ODataParser from '@balena/odata-parser';
|
|
13
13
|
export const SyntaxError = ODataParser.SyntaxError;
|
14
14
|
import { OData2AbstractSQL } from '@balena/odata-to-abstract-sql';
|
15
15
|
import * as _ from 'lodash';
|
16
|
-
import * as memoize from 'memoizee';
|
17
16
|
import memoizeWeak = require('memoizee/weak');
|
18
17
|
|
19
18
|
export { BadRequestError, ParsingError, TranslationError } from './errors';
|
@@ -38,28 +37,33 @@ export interface UnparsedRequest {
|
|
38
37
|
_isChangeSet?: boolean;
|
39
38
|
}
|
40
39
|
|
41
|
-
export interface
|
40
|
+
export interface ParsedODataRequest {
|
42
41
|
method: SupportedMethod;
|
43
42
|
url: string;
|
43
|
+
vocabulary: string;
|
44
|
+
resourceName: string;
|
45
|
+
originalResourceName: string;
|
46
|
+
values: AnyObject;
|
44
47
|
odataQuery: ODataQuery;
|
45
48
|
odataBinds: OdataBinds;
|
46
|
-
|
49
|
+
custom: AnyObject;
|
50
|
+
id?: number | undefined;
|
51
|
+
_defer?: boolean;
|
52
|
+
}
|
53
|
+
export interface ODataRequest extends ParsedODataRequest {
|
54
|
+
translateVersions: string[];
|
47
55
|
abstractSqlModel?: AbstractSQLCompiler.AbstractSqlModel;
|
56
|
+
finalAbstractSqlModel?: AbstractSQLCompiler.AbstractSqlModel;
|
48
57
|
abstractSqlQuery?: AbstractSQLCompiler.AbstractSqlQuery;
|
49
58
|
sqlQuery?: AbstractSQLCompiler.SqlResult | AbstractSQLCompiler.SqlResult[];
|
50
|
-
resourceName: string;
|
51
|
-
vocabulary: string;
|
52
|
-
_defer?: boolean;
|
53
|
-
id?: number;
|
54
|
-
custom: AnyObject;
|
55
59
|
tx?: Tx;
|
56
60
|
modifiedFields?: ReturnType<
|
57
61
|
AbstractSQLCompiler.EngineInstance['getModifiedFields']
|
58
62
|
>;
|
59
63
|
affectedIds?: number[];
|
60
64
|
pendingAffectedIds?: Promise<number[]>;
|
61
|
-
hooks?: InstantiatedHooks
|
62
|
-
engine
|
65
|
+
hooks?: Array<[string, InstantiatedHooks]>;
|
66
|
+
engine: AbstractSQLCompiler.Engines;
|
63
67
|
}
|
64
68
|
|
65
69
|
// Converts a value to its string representation and tries to parse is as an
|
@@ -87,9 +91,8 @@ export const memoizedParseOdata = (() => {
|
|
87
91
|
return odata;
|
88
92
|
};
|
89
93
|
|
90
|
-
const _memoizedParseOdata =
|
94
|
+
const _memoizedParseOdata = env.createCache('parseOData', parseOdata, {
|
91
95
|
primitive: true,
|
92
|
-
max: env.cache.parseOData.max,
|
93
96
|
});
|
94
97
|
return (url: string) => {
|
95
98
|
const queryParamsIndex = url.indexOf('?');
|
@@ -98,12 +101,12 @@ export const memoizedParseOdata = (() => {
|
|
98
101
|
// Try to cache based on parameter aliases if there might be some
|
99
102
|
const parameterAliases = new URLSearchParams();
|
100
103
|
const queryParams = new URLSearchParams(url.slice(queryParamsIndex));
|
101
|
-
Array.from(queryParams.entries())
|
104
|
+
for (const [key, value] of Array.from(queryParams.entries())) {
|
102
105
|
if (key.startsWith('@')) {
|
103
106
|
parameterAliases.append(key, value);
|
104
107
|
queryParams.delete(key);
|
105
108
|
}
|
106
|
-
}
|
109
|
+
}
|
107
110
|
const parameterAliasesString = parameterAliases.toString();
|
108
111
|
if (parameterAliasesString !== '') {
|
109
112
|
const parsed = _.cloneDeep(
|
@@ -121,7 +124,9 @@ export const memoizedParseOdata = (() => {
|
|
121
124
|
},
|
122
125
|
);
|
123
126
|
parsed.tree.options ??= {};
|
124
|
-
for (const key of Object.keys(
|
127
|
+
for (const key of Object.keys(
|
128
|
+
parsedParams.tree,
|
129
|
+
) as ODataParser.BindKey[]) {
|
125
130
|
parsed.tree.options[key] = parsedParams.tree[key];
|
126
131
|
parsed.binds[key] = parsedParams.binds[key];
|
127
132
|
}
|
@@ -144,12 +149,16 @@ export const memoizedParseOdata = (() => {
|
|
144
149
|
|
145
150
|
export const memoizedGetOData2AbstractSQL = memoizeWeak(
|
146
151
|
(abstractSqlModel: AbstractSQLCompiler.AbstractSqlModel) => {
|
147
|
-
return new OData2AbstractSQL(abstractSqlModel
|
152
|
+
return new OData2AbstractSQL(abstractSqlModel, undefined, {
|
153
|
+
// Use minimized aliases when not in debug mode for smaller queries
|
154
|
+
minimizeAliases: !env.DEBUG,
|
155
|
+
});
|
148
156
|
},
|
149
157
|
);
|
150
158
|
|
151
159
|
const memoizedOdata2AbstractSQL = (() => {
|
152
|
-
const $memoizedOdata2AbstractSQL =
|
160
|
+
const $memoizedOdata2AbstractSQL = env.createCache(
|
161
|
+
'odataToAbstractSql',
|
153
162
|
(
|
154
163
|
abstractSqlModel: AbstractSQLCompiler.AbstractSqlModel,
|
155
164
|
odataQuery: ODataQuery,
|
@@ -158,9 +167,8 @@ const memoizedOdata2AbstractSQL = (() => {
|
|
158
167
|
existingBindVarsLength: number,
|
159
168
|
) => {
|
160
169
|
try {
|
161
|
-
const odata2AbstractSQL =
|
162
|
-
abstractSqlModel
|
163
|
-
);
|
170
|
+
const odata2AbstractSQL =
|
171
|
+
memoizedGetOData2AbstractSQL(abstractSqlModel);
|
164
172
|
const abstractSql = odata2AbstractSQL.match(
|
165
173
|
odataQuery,
|
166
174
|
method,
|
@@ -200,9 +208,20 @@ const memoizedOdata2AbstractSQL = (() => {
|
|
200
208
|
existingBindVarsLength
|
201
209
|
);
|
202
210
|
},
|
203
|
-
|
211
|
+
weak: true,
|
204
212
|
},
|
205
213
|
);
|
214
|
+
const cachedProps = [
|
215
|
+
'$select',
|
216
|
+
'$filter',
|
217
|
+
'$expand',
|
218
|
+
'$orderby',
|
219
|
+
'$top',
|
220
|
+
'$skip',
|
221
|
+
'$count',
|
222
|
+
'$inlinecount',
|
223
|
+
'$format',
|
224
|
+
].map(_.toPath);
|
206
225
|
|
207
226
|
return (
|
208
227
|
request: Pick<
|
@@ -224,18 +243,7 @@ const memoizedOdata2AbstractSQL = (() => {
|
|
224
243
|
if (odataQuery.options) {
|
225
244
|
odataQuery = {
|
226
245
|
...odataQuery,
|
227
|
-
options: _.pick(
|
228
|
-
odataQuery.options,
|
229
|
-
'$select',
|
230
|
-
'$filter',
|
231
|
-
'$expand',
|
232
|
-
'$orderby',
|
233
|
-
'$top',
|
234
|
-
'$skip',
|
235
|
-
'$count',
|
236
|
-
'$inlinecount',
|
237
|
-
'$format',
|
238
|
-
) as ODataOptions,
|
246
|
+
options: _.pick(odataQuery.options, cachedProps) as ODataOptions,
|
239
247
|
};
|
240
248
|
}
|
241
249
|
const { tree, extraBodyVars, extraBindVars } = $memoizedOdata2AbstractSQL(
|
@@ -255,23 +263,26 @@ export const metadataEndpoints = ['$metadata', '$serviceroot'];
|
|
255
263
|
|
256
264
|
export async function parseOData(
|
257
265
|
b: UnparsedRequest & { _isChangeSet?: false },
|
258
|
-
): Promise<
|
266
|
+
): Promise<ParsedODataRequest>;
|
259
267
|
export async function parseOData(
|
260
268
|
b: UnparsedRequest & { _isChangeSet: true },
|
261
|
-
): Promise<
|
269
|
+
): Promise<ParsedODataRequest[]>;
|
262
270
|
export async function parseOData(
|
263
271
|
b: UnparsedRequest,
|
264
|
-
): Promise<
|
272
|
+
): Promise<ParsedODataRequest | ParsedODataRequest[]>;
|
265
273
|
export async function parseOData(
|
266
274
|
b: UnparsedRequest,
|
267
|
-
): Promise<
|
275
|
+
): Promise<ParsedODataRequest | ParsedODataRequest[]> {
|
268
276
|
try {
|
269
277
|
if (b._isChangeSet && b.changeSet != null) {
|
270
278
|
// We sort the CS set once, we must assure that requests which reference
|
271
279
|
// other requests in the changeset are placed last. Once they are sorted
|
272
280
|
// Map will guarantee retrival of results in insertion order
|
273
281
|
const sortedCS = _.sortBy(b.changeSet, (el) => el.url[0] !== '/');
|
274
|
-
const csReferences = new Map<
|
282
|
+
const csReferences = new Map<
|
283
|
+
ParsedODataRequest['id'],
|
284
|
+
ParsedODataRequest
|
285
|
+
>();
|
275
286
|
for (const cs of sortedCS) {
|
276
287
|
parseODataChangeset(csReferences, cs);
|
277
288
|
}
|
@@ -285,14 +296,15 @@ export async function parseOData(
|
|
285
296
|
url,
|
286
297
|
vocabulary: apiRoot,
|
287
298
|
resourceName: odata.tree.resource,
|
288
|
-
|
289
|
-
odataQuery: odata.tree,
|
299
|
+
originalResourceName: odata.tree.resource,
|
290
300
|
values: b.data ?? {},
|
301
|
+
odataQuery: odata.tree,
|
302
|
+
odataBinds: odata.binds,
|
291
303
|
custom: {},
|
292
304
|
_defer: false,
|
293
305
|
};
|
294
306
|
}
|
295
|
-
} catch (err) {
|
307
|
+
} catch (err: any) {
|
296
308
|
if (err instanceof ODataParser.SyntaxError) {
|
297
309
|
throw new BadRequestError(`Malformed url: '${b.url}'`);
|
298
310
|
}
|
@@ -305,10 +317,13 @@ export async function parseOData(
|
|
305
317
|
}
|
306
318
|
|
307
319
|
const parseODataChangeset = (
|
308
|
-
csReferences: Map<
|
320
|
+
csReferences: Map<ParsedODataRequest['id'], ParsedODataRequest>,
|
309
321
|
b: UnparsedRequest,
|
310
322
|
): void => {
|
311
|
-
const contentId:
|
323
|
+
const contentId: ParsedODataRequest['id'] = mustExtractHeader(
|
324
|
+
b,
|
325
|
+
'content-id',
|
326
|
+
);
|
312
327
|
|
313
328
|
if (csReferences.has(contentId)) {
|
314
329
|
throw new BadRequestError('Content-Id must be unique inside a changeset');
|
@@ -339,11 +354,12 @@ const parseODataChangeset = (
|
|
339
354
|
defer = true;
|
340
355
|
}
|
341
356
|
|
342
|
-
const parseResult:
|
357
|
+
const parseResult: ParsedODataRequest = {
|
343
358
|
method: b.method as SupportedMethod,
|
344
359
|
url,
|
345
360
|
vocabulary: apiRoot,
|
346
361
|
resourceName: odata.tree.resource,
|
362
|
+
originalResourceName: odata.tree.resource,
|
347
363
|
odataBinds: odata.binds,
|
348
364
|
odataQuery: odata.tree,
|
349
365
|
values: b.data ?? {},
|
@@ -355,13 +371,11 @@ const parseODataChangeset = (
|
|
355
371
|
};
|
356
372
|
|
357
373
|
const splitApiRoot = (url: string) => {
|
358
|
-
const urlParts = url.split('/');
|
359
|
-
const apiRoot = urlParts[1];
|
374
|
+
const [, apiRoot, ...urlParts] = url.split('/');
|
360
375
|
if (apiRoot == null) {
|
361
376
|
throw new ParsingError(`No such api root: ${apiRoot}`);
|
362
377
|
}
|
363
|
-
url
|
364
|
-
return { url, apiRoot };
|
378
|
+
return { url: '/' + urlParts.join('/'), apiRoot };
|
365
379
|
};
|
366
380
|
|
367
381
|
const mustExtractHeader = (
|
@@ -385,7 +399,7 @@ export const translateUri = <
|
|
385
399
|
| 'vocabulary'
|
386
400
|
| 'resourceName'
|
387
401
|
| 'abstractSqlModel'
|
388
|
-
|
402
|
+
>,
|
389
403
|
>(
|
390
404
|
request: T & {
|
391
405
|
abstractSqlQuery?: ODataRequest['abstractSqlQuery'];
|
package/src/sbvr-api/user.sbvr
CHANGED
@@ -51,6 +51,8 @@ Term: api key
|
|
51
51
|
Fact type: api key has key
|
52
52
|
Necessity: each api key has exactly one key
|
53
53
|
Necessity: each key is of exactly one api key
|
54
|
+
Fact type: api key has expiry date
|
55
|
+
Necessity: each api key has at most one expiry date.
|
54
56
|
Fact type: api key has role
|
55
57
|
Note: An 'api key' will inherit all the 'permissions' that the 'role' has.
|
56
58
|
Fact type: api key has permission
|
@@ -4,9 +4,11 @@ import './sbvr-loader';
|
|
4
4
|
|
5
5
|
import * as dbModule from '../database-layer/db';
|
6
6
|
import * as configLoader from '../config-loader/config-loader';
|
7
|
-
import * as migrator from '../migrator/
|
7
|
+
import * as migrator from '../migrator/sync';
|
8
|
+
import * as migratorUtils from '../migrator/utils';
|
8
9
|
|
9
10
|
import * as sbvrUtils from '../sbvr-api/sbvr-utils';
|
11
|
+
import { PINEJS_ADVISORY_LOCK } from '../config-loader/env';
|
10
12
|
|
11
13
|
export * as dbModule from '../database-layer/db';
|
12
14
|
export { PinejsSessionStore } from '../pinejs-session-store/pinejs-session-store';
|
@@ -17,12 +19,9 @@ export * as env from '../config-loader/env';
|
|
17
19
|
export * as types from '../sbvr-api/common-types';
|
18
20
|
export * as hooks from '../sbvr-api/hooks';
|
19
21
|
export type { configLoader as ConfigLoader };
|
20
|
-
export type {
|
22
|
+
export type { migratorUtils as Migrator };
|
21
23
|
|
22
|
-
let envDatabaseOptions:
|
23
|
-
engine: string;
|
24
|
-
params: string;
|
25
|
-
};
|
24
|
+
let envDatabaseOptions: dbModule.DatabaseOptions<string>;
|
26
25
|
if (dbModule.engines.websql != null) {
|
27
26
|
envDatabaseOptions = {
|
28
27
|
engine: 'websql',
|
@@ -45,13 +44,20 @@ if (dbModule.engines.websql != null) {
|
|
45
44
|
};
|
46
45
|
}
|
47
46
|
|
48
|
-
export const init = async (
|
47
|
+
export const init = async <T extends string>(
|
49
48
|
app: Express.Application,
|
50
49
|
config?: string | configLoader.Config,
|
51
|
-
databaseOptions
|
50
|
+
databaseOptions:
|
51
|
+
| dbModule.DatabaseOptions<T>
|
52
|
+
| typeof envDatabaseOptions = envDatabaseOptions,
|
52
53
|
): Promise<ReturnType<typeof configLoader.setup>> => {
|
53
54
|
try {
|
54
55
|
const db = dbModule.connect(databaseOptions);
|
56
|
+
// register a pinejs unique lock namespace
|
57
|
+
dbModule.registerTransactionLockNamespace(
|
58
|
+
PINEJS_ADVISORY_LOCK.namespaceKey,
|
59
|
+
PINEJS_ADVISORY_LOCK.namespaceId,
|
60
|
+
);
|
55
61
|
await sbvrUtils.setup(app, db);
|
56
62
|
const cfgLoader = await configLoader.setup(app);
|
57
63
|
await cfgLoader.loadConfig(migrator.config);
|
@@ -73,8 +79,8 @@ export const init = async (
|
|
73
79
|
await Promise.all(promises);
|
74
80
|
|
75
81
|
return cfgLoader;
|
76
|
-
} catch (err) {
|
77
|
-
console.error('Error initialising server', err
|
82
|
+
} catch (err: any) {
|
83
|
+
console.error('Error initialising server', err);
|
78
84
|
process.exit(1);
|
79
85
|
}
|
80
86
|
};
|
@@ -95,17 +95,17 @@ export const initialised = Pinejs.init(app)
|
|
95
95
|
'/login',
|
96
96
|
passportPinejs.login((err, user, req, res) => {
|
97
97
|
if (err) {
|
98
|
-
console.error('Error logging in', err
|
99
|
-
res.
|
98
|
+
console.error('Error logging in', err);
|
99
|
+
res.status(500).end();
|
100
100
|
} else if (user === false) {
|
101
101
|
if (req.xhr === true) {
|
102
|
-
res.
|
102
|
+
res.status(401).end();
|
103
103
|
} else {
|
104
104
|
res.redirect('/login.html');
|
105
105
|
}
|
106
106
|
} else {
|
107
107
|
if (req.xhr === true) {
|
108
|
-
res.
|
108
|
+
res.status(200).end();
|
109
109
|
} else {
|
110
110
|
res.redirect('/');
|
111
111
|
}
|
@@ -123,6 +123,6 @@ export const initialised = Pinejs.init(app)
|
|
123
123
|
});
|
124
124
|
})
|
125
125
|
.catch((err) => {
|
126
|
-
console.error('Error initialising server', err
|
126
|
+
console.error('Error initialising server', err);
|
127
127
|
process.exit(1);
|
128
128
|
});
|
package/tsconfig.dev.json
CHANGED
package/tsconfig.json
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
"module": "commonjs",
|
4
4
|
"strict": true,
|
5
5
|
"strictFunctionTypes": false,
|
6
|
-
"strictPropertyInitialization": false,
|
7
6
|
"noImplicitThis": false,
|
8
7
|
"noUnusedParameters": true,
|
9
8
|
"noUnusedLocals": true,
|
@@ -12,7 +11,7 @@
|
|
12
11
|
"removeComments": true,
|
13
12
|
"rootDir": "src",
|
14
13
|
"sourceMap": true,
|
15
|
-
"target": "
|
14
|
+
"target": "es2021",
|
16
15
|
"declaration": true,
|
17
16
|
"skipLibCheck": true,
|
18
17
|
"resolveJsonModule": true,
|
@@ -1,22 +1,19 @@
|
|
1
1
|
declare module '@balena/lf-to-abstract-sql' {
|
2
|
+
import type sbvrTypes from '@balena/sbvr-types';
|
3
|
+
import type { LFModel } from '@balena/sbvr-parser';
|
4
|
+
import type { AbstractSqlModel } from '@balena/abstract-sql-compiler';
|
2
5
|
export const LF2AbstractSQL: {
|
3
6
|
createInstance: () => {
|
4
|
-
match: (
|
5
|
-
lfModel: LFModel,
|
6
|
-
rule: 'Process',
|
7
|
-
) => AbstractSQLCompiler.AbstractSqlModel;
|
7
|
+
match: (lfModel: LFModel, rule: 'Process') => AbstractSqlModel;
|
8
8
|
addTypes: (types: typeof sbvrTypes) => void;
|
9
9
|
reset: () => void;
|
10
10
|
};
|
11
11
|
};
|
12
12
|
export const LF2AbstractSQLPrep: {
|
13
13
|
match: (lfModel: LFModel, rule: 'Process') => LFModel;
|
14
|
-
_extend({}): typeof
|
14
|
+
_extend({}): typeof LF2AbstractSQLPrep;
|
15
15
|
};
|
16
16
|
export const createTranslator: (
|
17
17
|
types: typeof sbvrTypes,
|
18
|
-
) => (
|
19
|
-
lfModel: LFModel,
|
20
|
-
rule: 'Process',
|
21
|
-
) => AbstractSQLCompiler.AbstractSqlModel;
|
18
|
+
) => (lfModel: LFModel, rule: 'Process') => AbstractSqlModel;
|
22
19
|
}
|
package/typings/memoizee.d.ts
CHANGED
@@ -5,7 +5,7 @@ declare module 'memoizee/weak' {
|
|
5
5
|
type RestArgs<T> = T extends (arg1: any, ...args: infer U) => any ? U : any[];
|
6
6
|
|
7
7
|
// tslint:disable-next-line ban-types
|
8
|
-
interface MemoizeWeakOptions<F extends Function> {
|
8
|
+
export interface MemoizeWeakOptions<F extends Function> {
|
9
9
|
length?: number | false;
|
10
10
|
maxAge?: number;
|
11
11
|
max?: number;
|
package/.github/CODEOWNERS
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
* @balena-io/pinejs
|