@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.
Files changed (145) hide show
  1. package/.dockerignore +4 -0
  2. package/.github/workflows/flowzone.yml +21 -0
  3. package/.husky/pre-commit +4 -0
  4. package/.pinejs-cache.json +1 -0
  5. package/.resinci.yml +1 -0
  6. package/.versionbot/CHANGELOG.yml +9678 -2002
  7. package/CHANGELOG.md +2976 -2
  8. package/Dockerfile +14 -0
  9. package/Gruntfile.ts +3 -6
  10. package/README.md +10 -1
  11. package/VERSION +1 -0
  12. package/build/browser.ts +1 -1
  13. package/build/config.ts +0 -1
  14. package/docker-compose.npm-test.yml +11 -0
  15. package/docs/AdvancedUsage.md +77 -63
  16. package/docs/GettingStarted.md +90 -41
  17. package/docs/Migrations.md +102 -1
  18. package/docs/ProjectConfig.md +12 -21
  19. package/docs/Testing.md +7 -0
  20. package/out/bin/abstract-sql-compiler.js +17 -17
  21. package/out/bin/abstract-sql-compiler.js.map +1 -1
  22. package/out/bin/odata-compiler.js +23 -20
  23. package/out/bin/odata-compiler.js.map +1 -1
  24. package/out/bin/sbvr-compiler.js +22 -22
  25. package/out/bin/sbvr-compiler.js.map +1 -1
  26. package/out/bin/utils.d.ts +2 -2
  27. package/out/bin/utils.js +3 -3
  28. package/out/bin/utils.js.map +1 -1
  29. package/out/config-loader/config-loader.d.ts +9 -8
  30. package/out/config-loader/config-loader.js +135 -78
  31. package/out/config-loader/config-loader.js.map +1 -1
  32. package/out/config-loader/env.d.ts +41 -16
  33. package/out/config-loader/env.js +46 -2
  34. package/out/config-loader/env.js.map +1 -1
  35. package/out/data-server/sbvr-server.d.ts +2 -19
  36. package/out/data-server/sbvr-server.js +44 -38
  37. package/out/data-server/sbvr-server.js.map +1 -1
  38. package/out/database-layer/db.d.ts +32 -14
  39. package/out/database-layer/db.js +120 -41
  40. package/out/database-layer/db.js.map +1 -1
  41. package/out/express-emulator/express.js +10 -11
  42. package/out/express-emulator/express.js.map +1 -1
  43. package/out/http-transactions/transactions.d.ts +2 -18
  44. package/out/http-transactions/transactions.js +29 -21
  45. package/out/http-transactions/transactions.js.map +1 -1
  46. package/out/migrator/async.d.ts +7 -0
  47. package/out/migrator/async.js +168 -0
  48. package/out/migrator/async.js.map +1 -0
  49. package/out/migrator/migrations.sbvr +43 -0
  50. package/out/migrator/sync.d.ts +9 -0
  51. package/out/migrator/sync.js +106 -0
  52. package/out/migrator/sync.js.map +1 -0
  53. package/out/migrator/utils.d.ts +78 -0
  54. package/out/migrator/utils.js +283 -0
  55. package/out/migrator/utils.js.map +1 -0
  56. package/out/odata-metadata/odata-metadata-generator.js +10 -13
  57. package/out/odata-metadata/odata-metadata-generator.js.map +1 -1
  58. package/out/passport-pinejs/passport-pinejs.d.ts +1 -1
  59. package/out/passport-pinejs/passport-pinejs.js +8 -7
  60. package/out/passport-pinejs/passport-pinejs.js.map +1 -1
  61. package/out/pinejs-session-store/pinejs-session-store.d.ts +1 -1
  62. package/out/pinejs-session-store/pinejs-session-store.js +20 -6
  63. package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
  64. package/out/sbvr-api/abstract-sql.d.ts +3 -2
  65. package/out/sbvr-api/abstract-sql.js +9 -9
  66. package/out/sbvr-api/abstract-sql.js.map +1 -1
  67. package/out/sbvr-api/cached-compile.js +1 -1
  68. package/out/sbvr-api/cached-compile.js.map +1 -1
  69. package/out/sbvr-api/common-types.d.ts +6 -5
  70. package/out/sbvr-api/control-flow.d.ts +8 -1
  71. package/out/sbvr-api/control-flow.js +36 -9
  72. package/out/sbvr-api/control-flow.js.map +1 -1
  73. package/out/sbvr-api/errors.d.ts +47 -40
  74. package/out/sbvr-api/errors.js +78 -77
  75. package/out/sbvr-api/errors.js.map +1 -1
  76. package/out/sbvr-api/express-extension.d.ts +4 -0
  77. package/out/sbvr-api/hooks.d.ts +16 -15
  78. package/out/sbvr-api/hooks.js +74 -48
  79. package/out/sbvr-api/hooks.js.map +1 -1
  80. package/out/sbvr-api/odata-response.d.ts +2 -2
  81. package/out/sbvr-api/odata-response.js +28 -30
  82. package/out/sbvr-api/odata-response.js.map +1 -1
  83. package/out/sbvr-api/permissions.d.ts +17 -16
  84. package/out/sbvr-api/permissions.js +369 -304
  85. package/out/sbvr-api/permissions.js.map +1 -1
  86. package/out/sbvr-api/sbvr-utils.d.ts +33 -15
  87. package/out/sbvr-api/sbvr-utils.js +397 -235
  88. package/out/sbvr-api/sbvr-utils.js.map +1 -1
  89. package/out/sbvr-api/translations.d.ts +6 -0
  90. package/out/sbvr-api/translations.js +150 -0
  91. package/out/sbvr-api/translations.js.map +1 -0
  92. package/out/sbvr-api/uri-parser.d.ts +23 -17
  93. package/out/sbvr-api/uri-parser.js +33 -27
  94. package/out/sbvr-api/uri-parser.js.map +1 -1
  95. package/out/sbvr-api/user.sbvr +2 -0
  96. package/out/server-glue/module.d.ts +6 -6
  97. package/out/server-glue/module.js +4 -2
  98. package/out/server-glue/module.js.map +1 -1
  99. package/out/server-glue/server.js +5 -5
  100. package/out/server-glue/server.js.map +1 -1
  101. package/package.json +89 -73
  102. package/pinejs.png +0 -0
  103. package/repo.yml +9 -9
  104. package/src/bin/abstract-sql-compiler.ts +5 -7
  105. package/src/bin/odata-compiler.ts +11 -13
  106. package/src/bin/sbvr-compiler.ts +11 -17
  107. package/src/bin/utils.ts +3 -5
  108. package/src/config-loader/config-loader.ts +167 -53
  109. package/src/config-loader/env.ts +106 -6
  110. package/src/data-server/sbvr-server.js +44 -38
  111. package/src/database-layer/db.ts +205 -64
  112. package/src/express-emulator/express.js +10 -11
  113. package/src/http-transactions/transactions.js +29 -21
  114. package/src/migrator/async.ts +323 -0
  115. package/src/migrator/migrations.sbvr +43 -0
  116. package/src/migrator/sync.ts +152 -0
  117. package/src/migrator/utils.ts +458 -0
  118. package/src/odata-metadata/odata-metadata-generator.ts +12 -15
  119. package/src/passport-pinejs/passport-pinejs.ts +9 -7
  120. package/src/pinejs-session-store/pinejs-session-store.ts +15 -1
  121. package/src/sbvr-api/abstract-sql.ts +17 -14
  122. package/src/sbvr-api/common-types.ts +2 -1
  123. package/src/sbvr-api/control-flow.ts +45 -11
  124. package/src/sbvr-api/errors.ts +82 -77
  125. package/src/sbvr-api/express-extension.ts +6 -1
  126. package/src/sbvr-api/hooks.ts +123 -50
  127. package/src/sbvr-api/odata-response.ts +23 -28
  128. package/src/sbvr-api/permissions.ts +548 -415
  129. package/src/sbvr-api/sbvr-utils.ts +581 -259
  130. package/src/sbvr-api/translations.ts +248 -0
  131. package/src/sbvr-api/uri-parser.ts +63 -49
  132. package/src/sbvr-api/user.sbvr +2 -0
  133. package/src/server-glue/module.ts +16 -10
  134. package/src/server-glue/server.ts +5 -5
  135. package/tsconfig.dev.json +1 -0
  136. package/tsconfig.json +1 -2
  137. package/typings/lf-to-abstract-sql.d.ts +6 -9
  138. package/typings/memoizee.d.ts +1 -1
  139. package/.github/CODEOWNERS +0 -1
  140. package/circle.yml +0 -37
  141. package/docs/todo.txt +0 -22
  142. package/out/migrator/migrator.d.ts +0 -20
  143. package/out/migrator/migrator.js +0 -188
  144. package/out/migrator/migrator.js.map +0 -1
  145. 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 ODataRequest {
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
- values: AnyObject;
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?: AbstractSQLCompiler.Engines;
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 = memoize(parseOdata, {
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()).forEach(([key, value]) => {
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(parsedParams.tree)) {
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 = memoizeWeak(
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 = memoizedGetOData2AbstractSQL(
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
- max: env.cache.odataToAbstractSql.max,
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<ODataRequest>;
266
+ ): Promise<ParsedODataRequest>;
259
267
  export async function parseOData(
260
268
  b: UnparsedRequest & { _isChangeSet: true },
261
- ): Promise<ODataRequest[]>;
269
+ ): Promise<ParsedODataRequest[]>;
262
270
  export async function parseOData(
263
271
  b: UnparsedRequest,
264
- ): Promise<ODataRequest | ODataRequest[]>;
272
+ ): Promise<ParsedODataRequest | ParsedODataRequest[]>;
265
273
  export async function parseOData(
266
274
  b: UnparsedRequest,
267
- ): Promise<ODataRequest | ODataRequest[]> {
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<ODataRequest['id'], ODataRequest>();
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
- odataBinds: odata.binds,
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<ODataRequest['id'], ODataRequest>,
320
+ csReferences: Map<ParsedODataRequest['id'], ParsedODataRequest>,
309
321
  b: UnparsedRequest,
310
322
  ): void => {
311
- const contentId: ODataRequest['id'] = mustExtractHeader(b, 'content-id');
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: ODataRequest = {
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 = '/' + urlParts.slice(2).join('/');
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'];
@@ -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/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 { migrator as Migrator };
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 = envDatabaseOptions,
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, err.stack);
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, err.stack);
99
- res.sendStatus(500);
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.sendStatus(401);
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.sendStatus(200);
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, err.stack);
126
+ console.error('Error initialising server', err);
127
127
  process.exit(1);
128
128
  });
package/tsconfig.dev.json CHANGED
@@ -6,6 +6,7 @@
6
6
  "include": [
7
7
  "build/**/*",
8
8
  "src/**/*",
9
+ "test/**/*",
9
10
  "typings/**/*.d.ts"
10
11
  ]
11
12
  }
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": "es2018",
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 LF2AbstractSQL.LF2AbstractSQLPrep;
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
  }
@@ -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;
@@ -1 +0,0 @@
1
- * @balena-io/pinejs