@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
@@ -1,19 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setup = exports.executeStandardModels = exports.handleODataRequest = exports.getAffectedIds = exports.getAbstractSqlModel = exports.runURI = exports.api = exports.PinejsClient = exports.runRule = exports.getID = exports.deleteModel = exports.executeModels = exports.executeModel = exports.generateModels = exports.generateSqlModel = exports.generateAbstractSqlModel = exports.generateLfModel = exports.validateModel = exports.resolveNavigationResource = exports.resolveSynonym = exports.resolveOdataBind = exports.sbvrTypes = exports.db = exports.addSideEffectHook = exports.addPureHook = void 0;
4
- const Bluebird = require("bluebird");
3
+ exports.setup = exports.executeStandardModels = exports.handleHttpErrors = exports.onHandleHttpError = exports.handleODataRequest = exports.getAffectedIds = exports.getAbstractSqlModel = exports.runURI = exports.api = exports.PinejsClient = exports.runRule = exports.getID = exports.deleteModel = exports.executeModels = exports.executeModel = exports.generateModels = exports.generateSqlModel = exports.generateAbstractSqlModel = exports.generateLfModel = exports.validateModel = exports.isModelNew = exports.resolveNavigationResource = exports.resolveSynonym = exports.resolveOdataBind = exports.sbvrTypes = exports.db = exports.addSideEffectHook = exports.addPureHook = void 0;
5
4
  const _ = require("lodash");
6
5
  const cached_compile_1 = require("./cached-compile");
7
6
  const AbstractSQLCompiler = require("@balena/abstract-sql-compiler");
8
7
  const package_json_1 = require("@balena/abstract-sql-compiler/package.json");
9
8
  const LF2AbstractSQL = require("@balena/lf-to-abstract-sql");
10
9
  const odata_to_abstract_sql_1 = require("@balena/odata-to-abstract-sql");
11
- const sbvrTypes = require("@balena/sbvr-types");
12
- exports.sbvrTypes = sbvrTypes;
10
+ const sbvr_types_1 = require("@balena/sbvr-types");
11
+ exports.sbvrTypes = sbvr_types_1.default;
13
12
  const deepFreeze = require("deep-freeze");
14
13
  const pinejs_client_core_1 = require("pinejs-client-core");
15
14
  const extended_sbvr_parser_1 = require("../extended-sbvr-parser/extended-sbvr-parser");
16
- const migrator = require("../migrator/migrator");
15
+ const asyncMigrator = require("../migrator/async");
16
+ const syncMigrator = require("../migrator/sync");
17
17
  const odata_metadata_generator_1 = require("../odata-metadata/odata-metadata-generator");
18
18
  const devModel = require('./dev.sbvr');
19
19
  const permissions = require("./permissions");
@@ -33,11 +33,12 @@ var abstract_sql_2 = require("./abstract-sql");
33
33
  Object.defineProperty(exports, "resolveOdataBind", { enumerable: true, get: function () { return abstract_sql_2.resolveOdataBind; } });
34
34
  const odataResponse = require("./odata-response");
35
35
  const module_1 = require("../server-glue/module");
36
- const LF2AbstractSQLTranslator = LF2AbstractSQL.createTranslator(sbvrTypes);
36
+ const translations_1 = require("./translations");
37
+ const LF2AbstractSQLTranslator = LF2AbstractSQL.createTranslator(sbvr_types_1.default);
37
38
  const LF2AbstractSQLTranslatorVersion = `${package_json_2.version}+${package_json_3.version}`;
38
39
  const models = {};
39
40
  const memoizedResolvedSynonym = memoizeWeak((abstractSqlModel, resourceName) => {
40
- const sqlName = odata_to_abstract_sql_1.odataNameToSqlName(resourceName);
41
+ const sqlName = (0, odata_to_abstract_sql_1.odataNameToSqlName)(resourceName);
41
42
  return _(sqlName)
42
43
  .split('-')
43
44
  .map((namePart) => {
@@ -50,16 +51,15 @@ const memoizedResolvedSynonym = memoizeWeak((abstractSqlModel, resourceName) =>
50
51
  .join('-');
51
52
  }, { primitive: true });
52
53
  const resolveSynonym = (request) => {
53
- const abstractSqlModel = exports.getAbstractSqlModel(request);
54
+ const abstractSqlModel = (0, exports.getAbstractSqlModel)(request);
54
55
  return memoizedResolvedSynonym(abstractSqlModel, request.resourceName);
55
56
  };
56
57
  exports.resolveSynonym = resolveSynonym;
57
58
  const memoizedResolveNavigationResource = memoizeWeak((abstractSqlModel, resourceName, navigationName) => {
58
- const navigation = _(odata_to_abstract_sql_1.odataNameToSqlName(navigationName))
59
+ const navigation = (0, odata_to_abstract_sql_1.odataNameToSqlName)(navigationName)
59
60
  .split('-')
60
- .flatMap((namePart) => memoizedResolvedSynonym(abstractSqlModel, namePart).split('-'))
61
- .concat('$')
62
- .value();
61
+ .flatMap((namePart) => memoizedResolvedSynonym(abstractSqlModel, namePart).split('-'));
62
+ navigation.push('$');
63
63
  const resolvedResourceName = memoizedResolvedSynonym(abstractSqlModel, resourceName);
64
64
  const mapping = _.get(abstractSqlModel.relationships[resolvedResourceName], navigation);
65
65
  if (mapping == null) {
@@ -68,10 +68,10 @@ const memoizedResolveNavigationResource = memoizeWeak((abstractSqlModel, resourc
68
68
  if (mapping.length < 2) {
69
69
  throw new Error(`'${resourceName}' to '${navigationName}' is a field not a navigation`);
70
70
  }
71
- return odata_to_abstract_sql_1.sqlNameToODataName(abstractSqlModel.tables[mapping[1][0]].name);
71
+ return (0, odata_to_abstract_sql_1.sqlNameToODataName)(abstractSqlModel.tables[mapping[1][0]].name);
72
72
  }, { primitive: true });
73
73
  const resolveNavigationResource = (request, navigationName) => {
74
- const abstractSqlModel = exports.getAbstractSqlModel(request);
74
+ const abstractSqlModel = (0, exports.getAbstractSqlModel)(request);
75
75
  return memoizedResolveNavigationResource(abstractSqlModel, request.resourceName, navigationName);
76
76
  };
77
77
  exports.resolveNavigationResource = resolveNavigationResource;
@@ -81,11 +81,12 @@ const prettifyConstraintError = (err, request) => {
81
81
  if (err instanceof exports.db.UniqueConstraintError) {
82
82
  switch (exports.db.engine) {
83
83
  case 'mysql':
84
- matches = /ER_DUP_ENTRY: Duplicate entry '.*?[^\\]' for key '(.*?[^\\])'/.exec(err.message);
84
+ matches =
85
+ /ER_DUP_ENTRY: Duplicate entry '.*?[^\\]' for key '(.*?[^\\])'/.exec(err.message);
85
86
  break;
86
87
  case 'postgres':
87
- const resourceName = exports.resolveSynonym(request);
88
- const abstractSqlModel = exports.getAbstractSqlModel(request);
88
+ const resourceName = (0, exports.resolveSynonym)(request);
89
+ const abstractSqlModel = getFinalAbstractSqlModel(request);
89
90
  matches = new RegExp('"' + abstractSqlModel.tables[resourceName].name + '_(.*?)_key"').exec(err.message);
90
91
  break;
91
92
  }
@@ -103,11 +104,12 @@ const prettifyConstraintError = (err, request) => {
103
104
  if (err instanceof exports.db.ForeignKeyConstraintError) {
104
105
  switch (exports.db.engine) {
105
106
  case 'mysql':
106
- matches = /ER_ROW_IS_REFERENCED_: Cannot delete or update a parent row: a foreign key constraint fails \(".*?"\.(".*?").*/.exec(err.message);
107
+ matches =
108
+ /ER_ROW_IS_REFERENCED_: Cannot delete or update a parent row: a foreign key constraint fails \(".*?"\.(".*?").*/.exec(err.message);
107
109
  break;
108
110
  case 'postgres':
109
- const resourceName = exports.resolveSynonym(request);
110
- const abstractSqlModel = exports.getAbstractSqlModel(request);
111
+ const resourceName = (0, exports.resolveSynonym)(request);
112
+ const abstractSqlModel = getFinalAbstractSqlModel(request);
111
113
  const tableName = abstractSqlModel.tables[resourceName].name;
112
114
  matches = new RegExp('"' +
113
115
  tableName +
@@ -124,11 +126,11 @@ const prettifyConstraintError = (err, request) => {
124
126
  if (matches == null) {
125
127
  throw new exports.db.ForeignKeyConstraintError('Foreign key constraint violated');
126
128
  }
127
- throw new exports.db.ForeignKeyConstraintError('Data is referenced by ' + odata_to_abstract_sql_1.sqlNameToODataName(matches[1]) + '.');
129
+ throw new exports.db.ForeignKeyConstraintError('Data is referenced by ' + (0, odata_to_abstract_sql_1.sqlNameToODataName)(matches[1]) + '.');
128
130
  }
129
131
  if (err instanceof exports.db.CheckConstraintError) {
130
- const resourceName = exports.resolveSynonym(request);
131
- const abstractSqlModel = exports.getAbstractSqlModel(request);
132
+ const resourceName = (0, exports.resolveSynonym)(request);
133
+ const abstractSqlModel = getFinalAbstractSqlModel(request);
132
134
  const table = abstractSqlModel.tables[resourceName];
133
135
  if (table.checks) {
134
136
  switch (exports.db.engine) {
@@ -142,7 +144,7 @@ const prettifyConstraintError = (err, request) => {
142
144
  if (matches != null) {
143
145
  const checkName = matches[1];
144
146
  const check = table.checks.find((c) => c.name === checkName);
145
- if ((check === null || check === void 0 ? void 0 : check.description) != null) {
147
+ if (check?.description != null) {
146
148
  throw new errors_1.BadRequestError(check.description);
147
149
  }
148
150
  }
@@ -152,14 +154,54 @@ const prettifyConstraintError = (err, request) => {
152
154
  throw err;
153
155
  }
154
156
  };
157
+ let cachedIsModelNew;
158
+ const isModelNew = async (tx, modelName) => {
159
+ const result = await tx.tableList("name = 'model'");
160
+ if (result.rows.length === 0) {
161
+ return true;
162
+ }
163
+ if (cachedIsModelNew == null) {
164
+ const { rows } = await tx.executeSql(`SELECT "is of-vocabulary" FROM "model";`);
165
+ cachedIsModelNew = new Set(rows.map((row) => row['is of-vocabulary']));
166
+ }
167
+ return !cachedIsModelNew.has(modelName);
168
+ };
169
+ exports.isModelNew = isModelNew;
170
+ const bindsForAffectedIds = (bindings, request) => {
171
+ if (request?.affectedIds == null) {
172
+ return {};
173
+ }
174
+ const tableName = (0, exports.getAbstractSqlModel)(request).tables[(0, exports.resolveSynonym)(request)].name;
175
+ const isDelete = request.method === 'DELETE';
176
+ const odataBinds = {};
177
+ for (const bind of bindings) {
178
+ if (bind.length !== 2 ||
179
+ bind[0] !== 'Bind' ||
180
+ typeof bind[1] !== 'string') {
181
+ continue;
182
+ }
183
+ const bindName = bind[1];
184
+ if (!isDelete && bindName === tableName) {
185
+ odataBinds[bindName] = ['Text', `{${request.affectedIds}}`];
186
+ }
187
+ else {
188
+ odataBinds[bindName] = ['Text', '{}'];
189
+ }
190
+ }
191
+ return odataBinds;
192
+ };
155
193
  const validateModel = async (tx, modelName, request) => {
156
- await Promise.all(models[modelName].sql.rules.map(async (rule) => {
157
- if (!abstract_sql_1.isRuleAffected(rule, request)) {
194
+ const { sql } = models[modelName];
195
+ if (!sql) {
196
+ throw new Error(`Tried to validate a virtual model: '${modelName}'`);
197
+ }
198
+ await Promise.all(sql.rules.map(async (rule) => {
199
+ if (!(0, abstract_sql_1.isRuleAffected)(rule, request)) {
158
200
  return;
159
201
  }
160
- const values = await abstract_sql_1.getAndCheckBindValues({
202
+ const values = await (0, abstract_sql_1.getAndCheckBindValues)({
161
203
  vocabulary: modelName,
162
- odataBinds: [],
204
+ odataBinds: bindsForAffectedIds(rule.bindings, request),
163
205
  values: {},
164
206
  engine: exports.db.engine,
165
207
  }, rule.bindings);
@@ -171,26 +213,26 @@ const validateModel = async (tx, modelName, request) => {
171
213
  }));
172
214
  };
173
215
  exports.validateModel = validateModel;
174
- const generateLfModel = (seModel) => cached_compile_1.cachedCompile('lfModel', extended_sbvr_parser_1.ExtendedSBVRParser.version, seModel, () => extended_sbvr_parser_1.ExtendedSBVRParser.matchAll(seModel, 'Process'));
216
+ const generateLfModel = (seModel) => (0, cached_compile_1.cachedCompile)('lfModel', extended_sbvr_parser_1.ExtendedSBVRParser.version, seModel, () => extended_sbvr_parser_1.ExtendedSBVRParser.matchAll(seModel, 'Process'));
175
217
  exports.generateLfModel = generateLfModel;
176
- const generateAbstractSqlModel = (lfModel) => cached_compile_1.cachedCompile('abstractSqlModel', LF2AbstractSQLTranslatorVersion, lfModel, () => LF2AbstractSQLTranslator(lfModel, 'Process'));
218
+ const generateAbstractSqlModel = (lfModel) => (0, cached_compile_1.cachedCompile)('abstractSqlModel', LF2AbstractSQLTranslatorVersion, lfModel, () => LF2AbstractSQLTranslator(lfModel, 'Process'));
177
219
  exports.generateAbstractSqlModel = generateAbstractSqlModel;
178
- const generateSqlModel = (abstractSql, targetDatabaseEngine) => cached_compile_1.cachedCompile('sqlModel', package_json_1.version + '+' + targetDatabaseEngine, abstractSql, () => AbstractSQLCompiler[targetDatabaseEngine].compileSchema(abstractSql));
220
+ const generateSqlModel = (abstractSql, targetDatabaseEngine) => (0, cached_compile_1.cachedCompile)('sqlModel', package_json_1.version + '+' + targetDatabaseEngine, abstractSql, () => AbstractSQLCompiler[targetDatabaseEngine].compileSchema(abstractSql));
179
221
  exports.generateSqlModel = generateSqlModel;
180
- const generateModels = (model, targetDatabaseEngine) => {
181
- const { apiRoot: vocab, modelText: se } = model;
222
+ function generateModels(model, targetDatabaseEngine) {
223
+ const { apiRoot: vocab, modelText: se, translateTo, translations } = model;
182
224
  let { abstractSql: maybeAbstractSql } = model;
183
225
  let lf;
184
226
  if (se) {
185
227
  try {
186
- lf = exports.generateLfModel(se);
228
+ lf = (0, exports.generateLfModel)(se);
187
229
  }
188
230
  catch (e) {
189
231
  console.error(`Error parsing model '${vocab}':`, e);
190
232
  throw new Error(`Error parsing model '${vocab}': ` + e);
191
233
  }
192
234
  try {
193
- maybeAbstractSql = exports.generateAbstractSqlModel(lf);
235
+ maybeAbstractSql = (0, exports.generateAbstractSqlModel)(lf);
194
236
  }
195
237
  catch (e) {
196
238
  console.error(`Error translating model '${vocab}':`, e);
@@ -198,50 +240,80 @@ const generateModels = (model, targetDatabaseEngine) => {
198
240
  }
199
241
  }
200
242
  const abstractSql = maybeAbstractSql;
201
- const odataMetadata = cached_compile_1.cachedCompile('metadata', odata_metadata_generator_1.generateODataMetadata.version, { vocab, abstractSqlModel: abstractSql }, () => odata_metadata_generator_1.generateODataMetadata(vocab, abstractSql));
243
+ const odataMetadata = (0, cached_compile_1.cachedCompile)('metadata', odata_metadata_generator_1.generateODataMetadata.version, { vocab, abstractSqlModel: abstractSql }, () => (0, odata_metadata_generator_1.generateODataMetadata)(vocab, abstractSql));
202
244
  let sql;
203
- try {
204
- sql = exports.generateSqlModel(abstractSql, targetDatabaseEngine);
245
+ let resourceRenames;
246
+ if (translateTo != null) {
247
+ resourceRenames = (0, translations_1.translateAbstractSqlModel)(abstractSql, models[translateTo].abstractSql, model.apiRoot, translateTo, translations);
205
248
  }
206
- catch (e) {
207
- console.error(`Error compiling model '${vocab}':`, e);
208
- throw new Error(`Error compiling model '${vocab}': ` + e);
249
+ else {
250
+ for (const [key, table] of Object.entries(abstractSql.tables)) {
251
+ abstractSql.tables[`${key}$${model.apiRoot}`] = { ...table };
252
+ }
253
+ try {
254
+ sql = (0, exports.generateSqlModel)(abstractSql, targetDatabaseEngine);
255
+ }
256
+ catch (e) {
257
+ console.error(`Error compiling model '${vocab}':`, e);
258
+ throw new Error(`Error compiling model '${vocab}': ` + e);
259
+ }
209
260
  }
210
- return { vocab, se, lf, abstractSql, sql, odataMetadata };
211
- };
261
+ return {
262
+ vocab,
263
+ translateTo,
264
+ resourceRenames,
265
+ se,
266
+ lf,
267
+ abstractSql,
268
+ sql,
269
+ odataMetadata,
270
+ };
271
+ }
212
272
  exports.generateModels = generateModels;
213
- const executeModel = (tx, model) => exports.executeModels(tx, [model]);
273
+ const executeModel = (tx, model) => (0, exports.executeModels)(tx, [model]);
214
274
  exports.executeModel = executeModel;
215
275
  const executeModels = async (tx, execModels) => {
216
276
  try {
217
277
  const compiledModels = await Promise.all(execModels.map(async (model) => {
218
- var _a, _b, _c, _d;
219
278
  const { apiRoot } = model;
220
- await migrator.run(tx, model);
221
- const compiledModel = exports.generateModels(model, exports.db.engine);
222
- for (const createStatement of compiledModel.sql.createSchema) {
223
- const promise = tx.executeSql(createStatement);
224
- if (exports.db.engine === 'websql') {
225
- promise.catch((err) => {
226
- console.warn("Ignoring errors in the create table statements for websql as it doesn't support CREATE IF NOT EXISTS", err);
227
- });
279
+ await syncMigrator.run(tx, model);
280
+ const compiledModel = generateModels(model, exports.db.engine);
281
+ if (compiledModel.sql) {
282
+ for (const createStatement of compiledModel.sql.createSchema) {
283
+ const promise = tx.executeSql(createStatement);
284
+ if (exports.db.engine === 'websql') {
285
+ promise.catch((err) => {
286
+ console.warn("Ignoring errors in the create table statements for websql as it doesn't support CREATE IF NOT EXISTS", err);
287
+ });
288
+ }
289
+ await promise;
228
290
  }
229
- await promise;
230
291
  }
231
- await migrator.postRun(tx, model);
292
+ await syncMigrator.postRun(tx, model);
232
293
  odataResponse.prepareModel(compiledModel.abstractSql);
233
294
  deepFreeze(compiledModel.abstractSql);
234
- models[apiRoot] = compiledModel;
235
- await exports.validateModel(tx, apiRoot);
295
+ const versions = [apiRoot];
296
+ if (compiledModel.translateTo != null) {
297
+ versions.push(...models[compiledModel.translateTo].versions);
298
+ }
299
+ models[apiRoot] = {
300
+ ...compiledModel,
301
+ versions,
302
+ };
303
+ if (compiledModel.sql) {
304
+ await (0, exports.validateModel)(tx, apiRoot);
305
+ }
236
306
  exports.api[apiRoot] = new PinejsClient('/' + apiRoot + '/');
237
307
  exports.api[apiRoot].logger = { ...console };
238
308
  if (model.logging != null) {
239
- const defaultSetting = (_b = (_a = model.logging) === null || _a === void 0 ? void 0 : _a.default) !== null && _b !== void 0 ? _b : true;
309
+ const defaultSetting = model.logging?.default ?? true;
310
+ const { logger } = exports.api[apiRoot];
240
311
  for (const k of Object.keys(model.logging)) {
241
312
  const key = k;
242
- if (typeof exports.api[apiRoot].logger[key] === 'function' &&
243
- !((_d = (_c = model.logging) === null || _c === void 0 ? void 0 : _c[key]) !== null && _d !== void 0 ? _d : defaultSetting)) {
244
- exports.api[apiRoot].logger[key] = _.noop;
313
+ if (key !== 'Console' &&
314
+ typeof logger[key] === 'function' &&
315
+ !(model.logging?.[key] ?? defaultSetting)) {
316
+ logger[key] = _.noop;
245
317
  }
246
318
  }
247
319
  }
@@ -249,7 +321,6 @@ const executeModels = async (tx, execModels) => {
249
321
  }));
250
322
  await Promise.all(compiledModels.map(async (model) => {
251
323
  const updateModel = async (modelType) => {
252
- var _a;
253
324
  if (model[modelType] == null) {
254
325
  return await exports.api.dev.delete({
255
326
  resource: 'model',
@@ -283,10 +354,12 @@ const executeModels = async (tx, execModels) => {
283
354
  let uri = '/dev/model';
284
355
  const body = {
285
356
  is_of__vocabulary: model.vocab,
286
- model_value: model[modelType],
357
+ model_value: typeof model[modelType] === 'string'
358
+ ? { value: model[modelType] }
359
+ : model[modelType],
287
360
  model_type: modelType,
288
361
  };
289
- const id = (_a = result === null || result === void 0 ? void 0 : result[0]) === null || _a === void 0 ? void 0 : _a.id;
362
+ const id = result?.[0]?.id;
290
363
  if (id != null) {
291
364
  uri += '(' + id + ')';
292
365
  method = 'PATCH';
@@ -295,10 +368,13 @@ const executeModels = async (tx, execModels) => {
295
368
  else {
296
369
  uri += '?returnResource=false';
297
370
  }
298
- return await exports.runURI(method, uri, body, tx, permissions.root);
371
+ return await (0, exports.runURI)(method, uri, body, tx, permissions.root);
299
372
  };
300
373
  await Promise.all(['se', 'lf', 'abstractSql', 'sql', 'odataMetadata'].map(updateModel));
301
374
  }));
375
+ await Promise.all(execModels.map(async (model) => {
376
+ await asyncMigrator.run(tx, model);
377
+ }));
302
378
  }
303
379
  catch (err) {
304
380
  await Promise.all(execModels.map(async ({ apiRoot }) => {
@@ -313,23 +389,26 @@ const cleanupModel = (vocab) => {
313
389
  delete exports.api[vocab];
314
390
  };
315
391
  const deleteModel = async (vocabulary) => {
316
- await exports.db.transaction(async (tx) => {
317
- const dropStatements = models[vocabulary].sql.dropSchema.map((dropStatement) => tx.executeSql(dropStatement));
318
- await Promise.all(dropStatements.concat([
319
- exports.api.dev.delete({
320
- resource: 'model',
321
- passthrough: {
322
- tx,
323
- req: permissions.root,
324
- },
325
- options: {
326
- $filter: {
327
- is_of__vocabulary: vocabulary,
392
+ const { sql } = models[vocabulary];
393
+ if (sql) {
394
+ await exports.db.transaction(async (tx) => {
395
+ const dropStatements = sql.dropSchema.map((dropStatement) => tx.executeSql(dropStatement));
396
+ await Promise.all(dropStatements.concat([
397
+ exports.api.dev.delete({
398
+ resource: 'model',
399
+ passthrough: {
400
+ tx,
401
+ req: permissions.root,
328
402
  },
329
- },
330
- }),
331
- ]));
332
- });
403
+ options: {
404
+ $filter: {
405
+ is_of__vocabulary: vocabulary,
406
+ },
407
+ },
408
+ }),
409
+ ]));
410
+ });
411
+ }
333
412
  await cleanupModel(vocabulary);
334
413
  };
335
414
  exports.deleteModel = deleteModel;
@@ -364,7 +443,7 @@ exports.runRule = (() => {
364
443
  },
365
444
  });
366
445
  const translator = LF2AbstractSQL.LF2AbstractSQL.createInstance();
367
- translator.addTypes(sbvrTypes);
446
+ translator.addTypes(sbvr_types_1.default);
368
447
  return async (vocab, rule) => {
369
448
  const seModel = models[vocab].se;
370
449
  const { logger } = exports.api[vocab];
@@ -440,7 +519,7 @@ exports.runRule = (() => {
440
519
  if (Array.isArray(compiledRule)) {
441
520
  throw new Error('Unexpected query generated');
442
521
  }
443
- const values = await abstract_sql_1.getAndCheckBindValues({
522
+ const values = await (0, abstract_sql_1.getAndCheckBindValues)({
444
523
  vocabulary: vocab,
445
524
  odataBinds: [],
446
525
  values: {},
@@ -448,7 +527,7 @@ exports.runRule = (() => {
448
527
  }, compiledRule.bindings);
449
528
  const result = await exports.db.executeSql(compiledRule.query, values);
450
529
  const table = models[vocab].abstractSql.tables[resourceName];
451
- const odataIdField = odata_to_abstract_sql_1.sqlNameToODataName(table.idField);
530
+ const odataIdField = (0, odata_to_abstract_sql_1.sqlNameToODataName)(table.idField);
452
531
  let ids = result.rows.map((row) => row[table.idField]);
453
532
  ids = _.uniq(ids);
454
533
  ids = ids.map((id) => odataIdField + ' eq ' + id);
@@ -459,10 +538,10 @@ exports.runRule = (() => {
459
538
  else {
460
539
  filter = '0 eq 1';
461
540
  }
462
- const odataResult = (await exports.runURI('GET', '/' +
541
+ const odataResult = (await (0, exports.runURI)('GET', '/' +
463
542
  vocab +
464
543
  '/' +
465
- odata_to_abstract_sql_1.sqlNameToODataName(table.resourceName) +
544
+ (0, odata_to_abstract_sql_1.sqlNameToODataName)(table.resourceName) +
466
545
  '?$filter=' +
467
546
  filter, undefined, undefined, permissions.rootRead));
468
547
  odataResult.__formulationType = formulationType;
@@ -472,7 +551,7 @@ exports.runRule = (() => {
472
551
  })();
473
552
  class PinejsClient extends pinejs_client_core_1.PinejsClientCore {
474
553
  async _request({ method, url, body, tx, req, custom, }) {
475
- return (await exports.runURI(method, url, body, tx, req, custom));
554
+ return (await (0, exports.runURI)(method, url, body, tx, req, custom));
476
555
  }
477
556
  }
478
557
  exports.PinejsClient = PinejsClient;
@@ -484,7 +563,7 @@ const runURI = async (method, uri, body = {}, tx, req, custom) => {
484
563
  }
485
564
  let user;
486
565
  let apiKey;
487
- if (req != null && _.isObject(req)) {
566
+ if (req != null && typeof req === 'object') {
488
567
  user = req.user;
489
568
  apiKey = req.apiKey;
490
569
  }
@@ -520,23 +599,26 @@ const runURI = async (method, uri, body = {}, tx, req, custom) => {
520
599
  if (_.isError(response)) {
521
600
  throw response;
522
601
  }
523
- const { body: responseBody, status } = response;
524
- if (status != null && status >= 400) {
525
- const ErrorClass = errors_1.statusCodeToError[status];
602
+ const { body: responseBody, statusCode, headers } = response;
603
+ if (statusCode != null && statusCode >= 400) {
604
+ const ErrorClass = errors_1.statusCodeToError[statusCode];
526
605
  if (ErrorClass != null) {
527
- throw new ErrorClass(undefined, responseBody);
606
+ throw new ErrorClass(undefined, responseBody, headers);
528
607
  }
529
- throw new errors_1.HttpError(status, undefined, responseBody);
608
+ throw new errors_1.HttpError(statusCode, undefined, responseBody, headers);
530
609
  }
531
610
  return responseBody;
532
611
  };
533
612
  exports.runURI = runURI;
534
613
  const getAbstractSqlModel = (request) => {
535
- var _a;
536
- return ((_a = request.abstractSqlModel) !== null && _a !== void 0 ? _a : (request.abstractSqlModel = models[request.vocabulary].abstractSql));
614
+ return (request.abstractSqlModel ??= models[request.vocabulary].abstractSql);
537
615
  };
538
616
  exports.getAbstractSqlModel = getAbstractSqlModel;
539
- const getIdField = (request) => exports.getAbstractSqlModel(request).tables[exports.resolveSynonym(request)].idField;
617
+ const getFinalAbstractSqlModel = (request) => {
618
+ const finalModel = _.last(request.translateVersions);
619
+ return (request.finalAbstractSqlModel ??= models[finalModel].abstractSql);
620
+ };
621
+ const getIdField = (request) => getFinalAbstractSqlModel(request).tables[(0, exports.resolveSynonym)(request)].idField;
540
622
  const getAffectedIds = async (args) => {
541
623
  const { request } = args;
542
624
  if (request.affectedIds) {
@@ -552,38 +634,38 @@ const getAffectedIds = async (args) => {
552
634
  };
553
635
  exports.getAffectedIds = getAffectedIds;
554
636
  const $getAffectedIds = async ({ req, request, tx, }) => {
555
- var _a;
556
- var _b;
557
637
  if (!['PATCH', 'DELETE'].includes(request.method)) {
558
638
  throw new Error('Can only call `getAffectedIds` with PATCH/DELETE requests');
559
639
  }
560
- request = await uriParser.parseOData({
640
+ const parsedRequest = await uriParser.parseOData({
561
641
  method: request.method,
562
642
  url: `/${request.vocabulary}${request.url}`,
563
643
  });
564
- request.engine = exports.db.engine;
565
- const abstractSqlModel = exports.getAbstractSqlModel(request);
566
- const resourceName = exports.resolveSynonym(request);
644
+ parsedRequest.engine = request.engine;
645
+ parsedRequest.translateVersions = request.translateVersions;
646
+ let affectedRequest = parsedRequest;
647
+ const abstractSqlModel = (0, exports.getAbstractSqlModel)(affectedRequest);
648
+ const resourceName = (0, exports.resolveSynonym)(affectedRequest);
567
649
  const resourceTable = abstractSqlModel.tables[resourceName];
568
650
  if (resourceTable == null) {
569
- throw new Error('Unknown resource: ' + request.resourceName);
651
+ throw new Error('Unknown resource: ' + affectedRequest.resourceName);
570
652
  }
571
653
  const { idField } = resourceTable;
572
- (_a = (_b = request.odataQuery).options) !== null && _a !== void 0 ? _a : (_b.options = {});
573
- request.odataQuery.options.$select = {
654
+ affectedRequest.odataQuery.options ??= {};
655
+ affectedRequest.odataQuery.options.$select = {
574
656
  properties: [{ name: idField }],
575
657
  };
576
- delete request.odataQuery.options.$expand;
577
- await permissions.addPermissions(req, request);
578
- request.method = 'GET';
579
- request = uriParser.translateUri(request);
580
- request = abstract_sql_1.compileRequest(request);
658
+ delete affectedRequest.odataQuery.options.$expand;
659
+ await permissions.addPermissions(req, affectedRequest);
660
+ affectedRequest.method = 'GET';
661
+ affectedRequest = uriParser.translateUri(affectedRequest);
662
+ affectedRequest = (0, abstract_sql_1.compileRequest)(affectedRequest);
581
663
  let result;
582
664
  if (tx != null) {
583
- result = await runQuery(tx, request);
665
+ result = await runQuery(tx, affectedRequest);
584
666
  }
585
667
  else {
586
- result = await runTransaction(req, (newTx) => runQuery(newTx, request));
668
+ result = await runTransaction(req, affectedRequest, (newTx) => runQuery(newTx, affectedRequest));
587
669
  }
588
670
  return result.rows.map((row) => row[idField]);
589
671
  };
@@ -591,10 +673,14 @@ const runODataRequest = (req, vocabulary) => {
591
673
  if (module_1.env.DEBUG) {
592
674
  exports.api[vocabulary].logger.log('Parsing', req.method, req.url);
593
675
  }
594
- const reqHooks = hooks_1.getHooks({
595
- method: req.method,
596
- vocabulary,
597
- });
676
+ const { versions } = models[vocabulary];
677
+ const reqHooks = versions.map((version) => [
678
+ version,
679
+ (0, hooks_1.getHooks)({
680
+ method: req.method,
681
+ vocabulary: version,
682
+ }, version === versions[0]),
683
+ ]);
598
684
  const transactions = [];
599
685
  const tryCancelRequest = () => {
600
686
  transactions.forEach(async (tx) => {
@@ -604,22 +690,22 @@ const runODataRequest = (req, vocabulary) => {
604
690
  try {
605
691
  await tx.rollback();
606
692
  }
607
- catch (_a) {
693
+ catch {
608
694
  }
609
695
  });
610
696
  transactions.length = 0;
611
- hooks_1.rollbackRequestHooks(reqHooks);
697
+ (0, hooks_1.rollbackRequestHooks)(reqHooks);
612
698
  };
613
699
  req.on('close', tryCancelRequest);
614
700
  if (req.tx != null) {
615
701
  transactions.push(req.tx);
616
702
  req.tx.on('rollback', tryCancelRequest);
617
703
  }
618
- const mapSeries = controlFlow.getMappingFn(req.headers);
704
+ const mappingFn = controlFlow.getMappingFn(req.headers);
619
705
  return {
620
706
  tryCancelRequest,
621
707
  promise: (async () => {
622
- await hooks_1.runHooks('PREPARSE', reqHooks, { req, tx: req.tx });
708
+ await (0, hooks_1.runHooks)('PREPARSE', reqHooks, { req, tx: req.tx });
623
709
  let requests;
624
710
  if (req.batch != null && req.batch.length > 0) {
625
711
  requests = req.batch;
@@ -628,43 +714,64 @@ const runODataRequest = (req, vocabulary) => {
628
714
  const { method, url, body } = req;
629
715
  requests = [{ method, url, data: body }];
630
716
  }
631
- const prepareRequest = async ($request) => {
632
- $request.engine = exports.db.engine;
633
- $request.hooks = hooks_1.getHooks($request);
717
+ const prepareRequest = async (parsedRequest) => {
718
+ parsedRequest.engine = exports.db.engine;
719
+ parsedRequest.translateVersions = [...versions];
720
+ const $request = parsedRequest;
634
721
  try {
635
- await hooks_1.runHooks('POSTPARSE', $request.hooks, {
636
- req,
637
- request: $request,
638
- tx: req.tx,
639
- });
722
+ $request.hooks = [];
723
+ for (const version of versions) {
724
+ const hooks = [
725
+ version,
726
+ (0, hooks_1.getHooks)({
727
+ resourceName: $request.resourceName,
728
+ vocabulary: version,
729
+ method: $request.method,
730
+ }, version === versions[0]),
731
+ ];
732
+ $request.hooks.push(hooks);
733
+ await (0, hooks_1.runHooks)('POSTPARSE', [hooks], {
734
+ req,
735
+ request: $request,
736
+ tx: req.tx,
737
+ });
738
+ const { resourceRenames } = models[version];
739
+ if (resourceRenames) {
740
+ const resourceName = (0, exports.resolveSynonym)($request);
741
+ if (resourceRenames[resourceName]) {
742
+ $request.resourceName = (0, odata_to_abstract_sql_1.sqlNameToODataName)(resourceRenames[resourceName]);
743
+ }
744
+ }
745
+ }
640
746
  const translatedRequest = await uriParser.translateUri($request);
641
- return await abstract_sql_1.compileRequest(translatedRequest);
747
+ return await (0, abstract_sql_1.compileRequest)(translatedRequest);
642
748
  }
643
749
  catch (err) {
644
- hooks_1.rollbackRequestHooks(reqHooks);
645
- hooks_1.rollbackRequestHooks($request.hooks);
750
+ (0, hooks_1.rollbackRequestHooks)(reqHooks);
751
+ (0, hooks_1.rollbackRequestHooks)($request.hooks);
646
752
  throw err;
647
753
  }
648
754
  };
649
- const results = await mapSeries(requests, async (requestPart) => {
650
- let request = await uriParser.parseOData(requestPart);
651
- if (Array.isArray(request)) {
652
- request = await Bluebird.mapSeries(request, prepareRequest);
755
+ const results = await mappingFn(requests, async (requestPart) => {
756
+ const parsedRequest = await uriParser.parseOData(requestPart);
757
+ let request;
758
+ if (Array.isArray(parsedRequest)) {
759
+ request = await controlFlow.mapSeries(parsedRequest, prepareRequest);
653
760
  }
654
761
  else {
655
- request = await prepareRequest(request);
762
+ request = await prepareRequest(parsedRequest);
656
763
  }
657
- return await runTransaction(req, async (tx) => {
764
+ return await runTransaction(req, request, async (tx) => {
658
765
  transactions.push(tx);
659
766
  tx.on('rollback', () => {
660
- hooks_1.rollbackRequestHooks(reqHooks);
767
+ (0, hooks_1.rollbackRequestHooks)(reqHooks);
661
768
  if (Array.isArray(request)) {
662
- request.forEach(({ hooks }) => {
663
- hooks_1.rollbackRequestHooks(hooks);
664
- });
769
+ for (const { hooks } of request) {
770
+ (0, hooks_1.rollbackRequestHooks)(hooks);
771
+ }
665
772
  }
666
773
  else {
667
- hooks_1.rollbackRequestHooks(request.hooks);
774
+ (0, hooks_1.rollbackRequestHooks)(request.hooks);
668
775
  }
669
776
  });
670
777
  if (Array.isArray(request)) {
@@ -687,7 +794,7 @@ const runODataRequest = (req, vocabulary) => {
687
794
  else {
688
795
  if (!Array.isArray(result) &&
689
796
  result.body == null &&
690
- result.status == null) {
797
+ result.statusCode == null) {
691
798
  console.error('No status or body set', req.url, responses);
692
799
  return new errors_1.InternalRequestError();
693
800
  }
@@ -710,36 +817,15 @@ const handleODataRequest = async (req, res, next) => {
710
817
  res.set('Cache-Control', 'no-cache');
711
818
  if (req.batch == null || req.batch.length === 0) {
712
819
  let [response] = responses;
713
- if (_.isError(response)) {
714
- response = {
715
- status: response.status,
716
- body: response.getResponseBody(),
717
- };
718
- }
719
- const { body, headers, status } = response;
720
- if (status) {
721
- res.status(status);
722
- }
723
- _.forEach(headers, (headerValue, headerName) => {
724
- res.set(headerName, headerValue);
725
- });
726
- if (!body) {
727
- res.sendStatus(status);
728
- }
729
- else {
730
- if (status != null) {
731
- res.status(status);
732
- }
733
- res.json(body);
820
+ if (response instanceof errors_1.HttpError) {
821
+ response = httpErrorToResponse(response);
734
822
  }
823
+ handleResponse(res, response);
735
824
  }
736
825
  else {
737
826
  res.status(200).sendMulti(responses.map((response) => {
738
- if (_.isError(response)) {
739
- return {
740
- status: response.status,
741
- body: response.getResponseBody(),
742
- };
827
+ if (response instanceof errors_1.HttpError) {
828
+ response = httpErrorToResponse(response);
743
829
  }
744
830
  else {
745
831
  return response;
@@ -748,21 +834,49 @@ const handleODataRequest = async (req, res, next) => {
748
834
  }
749
835
  }
750
836
  catch (e) {
751
- if (e instanceof errors_1.HttpError) {
752
- const body = e.getResponseBody();
753
- if (body) {
754
- res.status(e.status).send(body);
755
- }
756
- else {
757
- res.sendStatus(e.status);
758
- }
837
+ if ((0, exports.handleHttpErrors)(req, res, e)) {
759
838
  return;
760
839
  }
761
840
  console.error('An error occurred while constructing the response', e);
762
- res.sendStatus(500);
841
+ res.status(500).end();
763
842
  }
764
843
  };
765
844
  exports.handleODataRequest = handleODataRequest;
845
+ const handleErrorFns = [];
846
+ const onHandleHttpError = (fn) => {
847
+ handleErrorFns.push(fn);
848
+ };
849
+ exports.onHandleHttpError = onHandleHttpError;
850
+ const handleHttpErrors = (req, res, err) => {
851
+ if (err instanceof errors_1.HttpError) {
852
+ for (const handleErrorFn of handleErrorFns) {
853
+ handleErrorFn(req, err);
854
+ }
855
+ const response = httpErrorToResponse(err);
856
+ handleResponse(res, response);
857
+ return true;
858
+ }
859
+ return false;
860
+ };
861
+ exports.handleHttpErrors = handleHttpErrors;
862
+ const handleResponse = (res, response) => {
863
+ const { body, headers, statusCode } = response;
864
+ res.set(headers);
865
+ res.status(statusCode);
866
+ if (!body) {
867
+ res.end();
868
+ }
869
+ else {
870
+ res.json(body);
871
+ }
872
+ };
873
+ const httpErrorToResponse = (err) => {
874
+ return {
875
+ statusCode: err.status,
876
+ body: err.getResponseBody(),
877
+ headers: err.headers,
878
+ };
879
+ };
766
880
  const convertToHttpError = (err) => {
767
881
  if (err instanceof errors_1.HttpError) {
768
882
  return err;
@@ -794,7 +908,7 @@ const runRequest = async (req, tx, request) => {
794
908
  let result;
795
909
  try {
796
910
  try {
797
- await hooks_1.runHooks('PRERUN', request.hooks, { req, request, tx });
911
+ await (0, hooks_1.runHooks)('PRERUN', request.hooks, { req, request, tx });
798
912
  switch (request.method) {
799
913
  case 'GET':
800
914
  result = await runGet(req, request, tx);
@@ -831,10 +945,10 @@ const runRequest = async (req, tx, request) => {
831
945
  }
832
946
  throw err;
833
947
  }
834
- await hooks_1.runHooks('POSTRUN', request.hooks, { req, request, result, tx });
948
+ await (0, hooks_1.runHooks)('POSTRUN', request.hooks, { req, request, result, tx });
835
949
  }
836
950
  catch (err) {
837
- await hooks_1.runHooks('POSTRUN-ERROR', request.hooks, {
951
+ await (0, hooks_1.runHooks)('POSTRUN-ERROR', request.hooks, {
838
952
  req,
839
953
  request,
840
954
  tx,
@@ -845,30 +959,29 @@ const runRequest = async (req, tx, request) => {
845
959
  return await prepareResponse(req, request, result, tx);
846
960
  };
847
961
  const runChangeSet = (req, tx) => async (changeSetResults, request) => {
848
- var _a;
849
962
  request = updateBinds(changeSetResults, request);
850
963
  const result = await runRequest(req, tx, request);
851
964
  if (request.id == null) {
852
965
  throw new Error('No request id');
853
966
  }
854
- (_a = result.headers) !== null && _a !== void 0 ? _a : (result.headers = {});
855
- result.headers['Content-Id'] = request.id;
967
+ result.headers ??= {};
968
+ result.headers['content-id'] = request.id;
856
969
  changeSetResults.set(request.id, result);
857
970
  };
858
971
  const updateBinds = (changeSetResults, request) => {
859
972
  if (request._defer) {
860
- request.odataBinds = request.odataBinds.map(([tag, id]) => {
973
+ for (let i = 0; i < request.odataBinds.length; i++) {
974
+ const [tag, id] = request.odataBinds[i];
861
975
  if (tag === 'ContentReference') {
862
976
  const ref = changeSetResults.get(id);
863
- if ((ref === null || ref === void 0 ? void 0 : ref.body) == null ||
977
+ if (ref?.body == null ||
864
978
  typeof ref.body === 'string' ||
865
979
  ref.body.id === undefined) {
866
980
  throw new errors_1.BadRequestError('Reference to a non existing resource in Changeset');
867
981
  }
868
- return uriParser.parseId(ref.body.id);
982
+ request.odataBinds[i] = uriParser.parseId(ref.body.id);
869
983
  }
870
- return [tag, id];
871
- });
984
+ }
872
985
  }
873
986
  return request;
874
987
  };
@@ -890,15 +1003,33 @@ const prepareResponse = async (req, request, result, tx) => {
890
1003
  throw new errors_1.MethodNotAllowedError();
891
1004
  }
892
1005
  };
893
- const runTransaction = async (req, callback) => {
1006
+ const checkReadOnlyRequests = (request) => {
1007
+ if (request.method !== 'GET') {
1008
+ return false;
1009
+ }
1010
+ const { hooks } = request;
1011
+ if (hooks == null) {
1012
+ return true;
1013
+ }
1014
+ return hooks.every(([, versionedHooks]) => Object.values(versionedHooks).every((hookTypeHooks) => {
1015
+ return (hookTypeHooks == null || hookTypeHooks.every((hook) => hook.readOnlyTx));
1016
+ }));
1017
+ };
1018
+ const runTransaction = async (req, request, callback) => {
894
1019
  if (req.tx != null) {
895
1020
  return await callback(req.tx);
896
1021
  }
897
- else {
898
- return await exports.db.transaction(callback);
1022
+ if (Array.isArray(request)) {
1023
+ if (request.every(checkReadOnlyRequests)) {
1024
+ return await exports.db.readTransaction(callback);
1025
+ }
1026
+ }
1027
+ else if (checkReadOnlyRequests(request)) {
1028
+ return await exports.db.readTransaction(callback);
899
1029
  }
1030
+ return await exports.db.transaction(callback);
900
1031
  };
901
- const runQuery = async (tx, request, queryIndex, returningIdField) => {
1032
+ const runQuery = async (tx, request, queryIndex, addReturning = false) => {
902
1033
  const { vocabulary } = request;
903
1034
  let { sqlQuery } = request;
904
1035
  if (sqlQuery == null) {
@@ -914,13 +1045,14 @@ const runQuery = async (tx, request, queryIndex, returningIdField) => {
914
1045
  sqlQuery = sqlQuery[queryIndex];
915
1046
  }
916
1047
  const { query, bindings } = sqlQuery;
917
- const values = await abstract_sql_1.getAndCheckBindValues(request, bindings);
1048
+ const values = await (0, abstract_sql_1.getAndCheckBindValues)(request, bindings);
918
1049
  if (module_1.env.DEBUG) {
919
1050
  exports.api[vocabulary].logger.log(query, values);
920
1051
  }
1052
+ const returningIdField = addReturning && request.affectedIds == null ? getIdField(request) : false;
921
1053
  const sqlResult = await tx.executeSql(query, values, returningIdField);
922
1054
  if (returningIdField) {
923
- request.affectedIds = sqlResult.rows.map((row) => row[returningIdField]);
1055
+ request.affectedIds ??= sqlResult.rows.map((row) => row[returningIdField]);
924
1056
  }
925
1057
  return sqlResult;
926
1058
  };
@@ -930,117 +1062,123 @@ const runGet = async (_req, request, tx) => {
930
1062
  }
931
1063
  };
932
1064
  const respondGet = async (req, request, result, tx) => {
933
- var _a;
934
1065
  const vocab = request.vocabulary;
935
1066
  if (request.sqlQuery != null) {
936
- const format = (_a = request.odataQuery.options) === null || _a === void 0 ? void 0 : _a.$format;
1067
+ const format = request.odataQuery.options?.$format;
937
1068
  const metadata = format != null && typeof format === 'object'
938
1069
  ? format.metadata
939
1070
  : undefined;
940
- const d = await odataResponse.process(vocab, exports.getAbstractSqlModel(request), request.resourceName, result.rows, { includeMetadata: metadata === 'full' });
941
- await hooks_1.runHooks('PRERESPOND', request.hooks, {
1071
+ const d = await odataResponse.process(vocab, (0, exports.getAbstractSqlModel)(request), request.originalResourceName, result.rows, { includeMetadata: metadata === 'full' });
1072
+ const response = {
1073
+ statusCode: 200,
1074
+ body: { d },
1075
+ headers: { 'content-type': 'application/json' },
1076
+ };
1077
+ await (0, hooks_1.runHooks)('PRERESPOND', request.hooks, {
942
1078
  req,
943
1079
  request,
944
1080
  result,
945
- data: d,
1081
+ response,
946
1082
  tx,
947
1083
  });
948
- return { body: { d }, headers: { contentType: 'application/json' } };
1084
+ return response;
949
1085
  }
950
1086
  else {
951
1087
  if (request.resourceName === '$metadata') {
952
1088
  return {
1089
+ statusCode: 200,
953
1090
  body: models[vocab].odataMetadata,
954
- headers: { contentType: 'xml' },
1091
+ headers: { 'content-type': 'xml' },
955
1092
  };
956
1093
  }
957
1094
  else {
958
1095
  return {
959
- status: 404,
1096
+ statusCode: 404,
960
1097
  };
961
1098
  }
962
1099
  }
963
1100
  };
964
1101
  const runPost = async (_req, request, tx) => {
965
- const { rowsAffected, insertId } = await runQuery(tx, request, undefined, getIdField(request));
1102
+ const { rowsAffected, insertId } = await runQuery(tx, request, undefined, true);
966
1103
  if (rowsAffected === 0) {
967
1104
  throw new errors_1.PermissionError();
968
1105
  }
969
- await exports.validateModel(tx, request.vocabulary, request);
1106
+ await (0, exports.validateModel)(tx, _.last(request.translateVersions), request);
970
1107
  return insertId;
971
1108
  };
972
1109
  const respondPost = async (req, request, id, tx) => {
973
- var _a, _b;
974
1110
  const vocab = request.vocabulary;
975
- const location = odataResponse.resourceURI(vocab, request.resourceName, id);
1111
+ const location = odataResponse.resourceURI(vocab, request.originalResourceName, id);
976
1112
  if (module_1.env.DEBUG) {
977
1113
  exports.api[vocab].logger.log('Insert ID: ', request.resourceName, id);
978
1114
  }
979
1115
  let result = { d: [{ id }] };
980
1116
  if (location != null &&
981
- !['0', 'false'].includes((_b = (_a = request === null || request === void 0 ? void 0 : request.odataQuery) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.returnResource)) {
1117
+ !['0', 'false'].includes(request?.odataQuery?.options?.returnResource)) {
982
1118
  try {
983
- result = (await exports.runURI('GET', location, undefined, tx, req));
1119
+ result = (await (0, exports.runURI)('GET', location, undefined, tx, req));
984
1120
  }
985
- catch (_c) {
1121
+ catch {
986
1122
  }
987
1123
  }
988
- await hooks_1.runHooks('PRERESPOND', request.hooks, {
1124
+ const response = {
1125
+ statusCode: 201,
1126
+ body: result.d[0],
1127
+ headers: {
1128
+ 'content-type': 'application/json',
1129
+ location,
1130
+ },
1131
+ };
1132
+ await (0, hooks_1.runHooks)('PRERESPOND', request.hooks, {
989
1133
  req,
990
1134
  request,
991
1135
  result,
1136
+ response,
992
1137
  tx,
993
1138
  });
994
- return {
995
- status: 201,
996
- body: result.d[0],
997
- headers: {
998
- contentType: 'application/json',
999
- Location: location,
1000
- },
1001
- };
1139
+ return response;
1002
1140
  };
1003
1141
  const runPut = async (_req, request, tx) => {
1004
- const idField = getIdField(request);
1005
1142
  let rowsAffected;
1006
1143
  if (Array.isArray(request.sqlQuery)) {
1007
- ({ rowsAffected } = await runQuery(tx, request, 1, idField));
1144
+ ({ rowsAffected } = await runQuery(tx, request, 1, true));
1008
1145
  if (rowsAffected === 0) {
1009
- ({ rowsAffected } = await runQuery(tx, request, 0, idField));
1146
+ ({ rowsAffected } = await runQuery(tx, request, 0, true));
1010
1147
  }
1011
1148
  }
1012
1149
  else {
1013
- ({ rowsAffected } = await runQuery(tx, request, undefined, idField));
1150
+ ({ rowsAffected } = await runQuery(tx, request, undefined, true));
1014
1151
  }
1015
1152
  if (rowsAffected > 0) {
1016
- await exports.validateModel(tx, request.vocabulary, request);
1153
+ await (0, exports.validateModel)(tx, _.last(request.translateVersions), request);
1017
1154
  }
1018
1155
  return undefined;
1019
1156
  };
1020
1157
  const respondPut = async (req, request, result, tx) => {
1021
- await hooks_1.runHooks('PRERESPOND', request.hooks, {
1158
+ const response = {
1159
+ statusCode: 200,
1160
+ };
1161
+ await (0, hooks_1.runHooks)('PRERESPOND', request.hooks, {
1022
1162
  req,
1023
1163
  request,
1024
1164
  result,
1165
+ response,
1025
1166
  tx,
1026
1167
  });
1027
- return {
1028
- status: 200,
1029
- headers: {},
1030
- };
1168
+ return response;
1031
1169
  };
1032
1170
  const respondDelete = respondPut;
1033
1171
  const respondOptions = respondPut;
1034
1172
  const runDelete = async (_req, request, tx) => {
1035
- const { rowsAffected } = await runQuery(tx, request, undefined, getIdField(request));
1173
+ const { rowsAffected } = await runQuery(tx, request, undefined, true);
1036
1174
  if (rowsAffected > 0) {
1037
- await exports.validateModel(tx, request.vocabulary, request);
1175
+ await (0, exports.validateModel)(tx, _.last(request.translateVersions), request);
1038
1176
  }
1039
1177
  return undefined;
1040
1178
  };
1041
1179
  const executeStandardModels = async (tx) => {
1042
1180
  try {
1043
- await exports.executeModel(tx, {
1181
+ await (0, exports.executeModel)(tx, {
1044
1182
  apiRoot: 'dev',
1045
1183
  modelText: devModel,
1046
1184
  logging: {
@@ -1051,9 +1189,33 @@ const executeStandardModels = async (tx) => {
1051
1189
  ALTER TABLE "model"
1052
1190
  ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
1053
1191
  `,
1192
+ '15.0.0-data-types': async ($tx, sbvrUtils) => {
1193
+ switch (sbvrUtils.db.engine) {
1194
+ case 'mysql':
1195
+ await $tx.executeSql(`\
1196
+ ALTER TABLE "model"
1197
+ MODIFY "model value" JSON NOT NULL;
1198
+
1199
+ UPDATE "model"
1200
+ SET "model value" = CAST('{"value":' || CAST("model value" AS CHAR) || '}' AS JSON)
1201
+ WHERE "model type" IN ('se', 'odataMetadata')
1202
+ AND CAST("model value" AS CHAR) LIKE '"%';`);
1203
+ break;
1204
+ case 'postgres':
1205
+ await $tx.executeSql(`\
1206
+ ALTER TABLE "model"
1207
+ ALTER COLUMN "model value" SET DATA TYPE JSONB USING "model value"::JSONB;
1208
+
1209
+ UPDATE "model"
1210
+ SET "model value" = CAST('{"value":' || CAST("model value" AS TEXT) || '}' AS JSON)
1211
+ WHERE "model type" IN ('se', 'odataMetadata')
1212
+ AND CAST("model value" AS TEXT) LIKE '"%';`);
1213
+ break;
1214
+ }
1215
+ },
1054
1216
  },
1055
1217
  });
1056
- await exports.executeModels(tx, permissions.config.models);
1218
+ await (0, exports.executeModels)(tx, permissions.config.models);
1057
1219
  console.info('Successfully executed standard models.');
1058
1220
  }
1059
1221
  catch (err) {
@@ -1066,7 +1228,7 @@ const setup = async (_app, $db) => {
1066
1228
  exports.db = exports.db = $db;
1067
1229
  try {
1068
1230
  await exports.db.transaction(async (tx) => {
1069
- await exports.executeStandardModels(tx);
1231
+ await (0, exports.executeStandardModels)(tx);
1070
1232
  await permissions.setup();
1071
1233
  });
1072
1234
  }
@@ -1077,7 +1239,7 @@ const setup = async (_app, $db) => {
1077
1239
  try {
1078
1240
  await exports.db.executeSql('CREATE UNIQUE INDEX "uniq_model_model_type_vocab" ON "model" ("is of-vocabulary", "model type");');
1079
1241
  }
1080
- catch (_a) {
1242
+ catch {
1081
1243
  }
1082
1244
  };
1083
1245
  exports.setup = setup;