@ruiapp/rapid-core 0.8.21 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/CHANGELOG.md +11 -11
  2. package/dist/core/request.d.ts +1 -0
  3. package/dist/core/server.d.ts +2 -1
  4. package/dist/index.js +740 -740
  5. package/dist/server.d.ts +2 -1
  6. package/package.json +1 -1
  7. package/rollup.config.js +16 -16
  8. package/src/bootstrapApplicationConfig.ts +782 -782
  9. package/src/core/actionHandler.ts +23 -23
  10. package/src/core/eventManager.ts +20 -20
  11. package/src/core/facility.ts +7 -7
  12. package/src/core/http/formDataParser.ts +89 -89
  13. package/src/core/http-types.ts +4 -4
  14. package/src/core/pluginManager.ts +184 -184
  15. package/src/core/providers/runtimeProvider.ts +5 -5
  16. package/src/core/request.ts +96 -95
  17. package/src/core/response.ts +79 -79
  18. package/src/core/routeContext.ts +127 -127
  19. package/src/core/routesBuilder.ts +90 -90
  20. package/src/core/server.ts +152 -151
  21. package/src/dataAccess/columnTypeMapper.ts +22 -22
  22. package/src/dataAccess/dataAccessTypes.ts +165 -165
  23. package/src/dataAccess/dataAccessor.ts +135 -135
  24. package/src/dataAccess/entityManager.ts +1932 -1932
  25. package/src/dataAccess/entityMapper.ts +111 -111
  26. package/src/dataAccess/entityValidator.ts +33 -33
  27. package/src/dataAccess/propertyMapper.ts +28 -28
  28. package/src/deno-std/assert/assert.ts +9 -9
  29. package/src/deno-std/assert/assertion_error.ts +7 -7
  30. package/src/deno-std/datetime/to_imf.ts +32 -32
  31. package/src/deno-std/encoding/base64.ts +141 -141
  32. package/src/deno-std/http/cookie.ts +372 -372
  33. package/src/facilities/cache/CacheFacilityTypes.ts +29 -29
  34. package/src/facilities/cache/CacheFactory.ts +31 -31
  35. package/src/facilities/cache/MemoryCache.ts +58 -58
  36. package/src/facilities/cache/MemoryCacheProvider.ts +15 -15
  37. package/src/facilities/log/LogFacility.ts +35 -35
  38. package/src/helpers/entityHelpers.ts +76 -76
  39. package/src/helpers/filterHelper.ts +148 -148
  40. package/src/helpers/inputHelper.ts +11 -11
  41. package/src/helpers/licenseHelper.ts +29 -29
  42. package/src/helpers/metaHelper.ts +111 -111
  43. package/src/helpers/runCollectionEntityActionHandler.ts +58 -58
  44. package/src/index.ts +76 -76
  45. package/src/plugins/auth/AuthPlugin.ts +93 -93
  46. package/src/plugins/auth/actionHandlers/changePassword.ts +61 -61
  47. package/src/plugins/auth/actionHandlers/createSession.ts +67 -67
  48. package/src/plugins/auth/actionHandlers/deleteSession.ts +18 -18
  49. package/src/plugins/auth/actionHandlers/getMyProfile.ts +35 -35
  50. package/src/plugins/auth/actionHandlers/index.ts +8 -8
  51. package/src/plugins/auth/actionHandlers/resetPassword.ts +45 -45
  52. package/src/plugins/auth/models/AccessToken.ts +56 -56
  53. package/src/plugins/auth/models/index.ts +3 -3
  54. package/src/plugins/auth/routes/changePassword.ts +15 -15
  55. package/src/plugins/auth/routes/getMyProfile.ts +15 -15
  56. package/src/plugins/auth/routes/index.ts +7 -7
  57. package/src/plugins/auth/routes/resetPassword.ts +15 -15
  58. package/src/plugins/auth/routes/signin.ts +15 -15
  59. package/src/plugins/auth/routes/signout.ts +15 -15
  60. package/src/plugins/auth/services/AuthService.ts +39 -39
  61. package/src/plugins/cronJob/CronJobPlugin.ts +104 -104
  62. package/src/plugins/cronJob/CronJobPluginTypes.ts +44 -44
  63. package/src/plugins/cronJob/actionHandlers/index.ts +4 -4
  64. package/src/plugins/cronJob/actionHandlers/runCronJob.ts +32 -32
  65. package/src/plugins/cronJob/entityWatchers/cronJobEntityWatchers.ts +24 -24
  66. package/src/plugins/cronJob/entityWatchers/index.ts +4 -4
  67. package/src/plugins/cronJob/models/CronJob.ts +129 -129
  68. package/src/plugins/cronJob/models/index.ts +3 -3
  69. package/src/plugins/cronJob/routes/index.ts +3 -3
  70. package/src/plugins/cronJob/routes/runCronJob.ts +15 -15
  71. package/src/plugins/cronJob/services/CronJobService.ts +252 -252
  72. package/src/plugins/dataManage/DataManagePlugin.ts +163 -163
  73. package/src/plugins/dataManage/actionHandlers/addEntityRelations.ts +15 -15
  74. package/src/plugins/dataManage/actionHandlers/countCollectionEntities.ts +17 -17
  75. package/src/plugins/dataManage/actionHandlers/createCollectionEntitiesBatch.ts +81 -81
  76. package/src/plugins/dataManage/actionHandlers/createCollectionEntity.ts +20 -20
  77. package/src/plugins/dataManage/actionHandlers/deleteCollectionEntities.ts +45 -45
  78. package/src/plugins/dataManage/actionHandlers/deleteCollectionEntityById.ts +20 -20
  79. package/src/plugins/dataManage/actionHandlers/findCollectionEntities.ts +27 -27
  80. package/src/plugins/dataManage/actionHandlers/findCollectionEntityById.ts +30 -30
  81. package/src/plugins/dataManage/actionHandlers/queryDatabase.ts +22 -22
  82. package/src/plugins/dataManage/actionHandlers/removeEntityRelations.ts +15 -15
  83. package/src/plugins/dataManage/actionHandlers/updateCollectionEntityById.ts +38 -38
  84. package/src/plugins/entityAccessControl/EntityAccessControlPlugin.ts +146 -146
  85. package/src/plugins/fileManage/FileManagePlugin.ts +52 -52
  86. package/src/plugins/fileManage/actionHandlers/downloadDocument.ts +65 -65
  87. package/src/plugins/fileManage/actionHandlers/downloadFile.ts +44 -44
  88. package/src/plugins/fileManage/actionHandlers/uploadFile.ts +33 -33
  89. package/src/plugins/fileManage/routes/downloadDocument.ts +15 -15
  90. package/src/plugins/fileManage/routes/downloadFile.ts +15 -15
  91. package/src/plugins/fileManage/routes/index.ts +5 -5
  92. package/src/plugins/fileManage/routes/uploadFile.ts +15 -15
  93. package/src/plugins/license/LicensePlugin.ts +79 -79
  94. package/src/plugins/license/LicensePluginTypes.ts +95 -95
  95. package/src/plugins/license/LicenseService.ts +118 -118
  96. package/src/plugins/license/actionHandlers/getLicense.ts +18 -18
  97. package/src/plugins/license/actionHandlers/index.ts +4 -4
  98. package/src/plugins/license/helpers/certHelper.ts +21 -21
  99. package/src/plugins/license/helpers/cryptoHelper.ts +47 -47
  100. package/src/plugins/license/models/index.ts +1 -1
  101. package/src/plugins/license/routes/getLicense.ts +15 -15
  102. package/src/plugins/license/routes/index.ts +3 -3
  103. package/src/plugins/mail/MailPlugin.ts +74 -74
  104. package/src/plugins/mail/MailPluginTypes.ts +27 -27
  105. package/src/plugins/mail/MailService.ts +38 -38
  106. package/src/plugins/mail/actionHandlers/index.ts +3 -3
  107. package/src/plugins/mail/models/index.ts +1 -1
  108. package/src/plugins/mail/routes/index.ts +1 -1
  109. package/src/plugins/metaManage/MetaManagePlugin.ts +198 -198
  110. package/src/plugins/metaManage/actionHandlers/getMetaModelDetail.ts +10 -10
  111. package/src/plugins/metaManage/actionHandlers/listMetaModels.ts +9 -9
  112. package/src/plugins/metaManage/actionHandlers/listMetaRoutes.ts +9 -9
  113. package/src/plugins/metaManage/services/MetaService.ts +376 -376
  114. package/src/plugins/notification/NotificationPlugin.ts +68 -68
  115. package/src/plugins/notification/NotificationPluginTypes.ts +13 -13
  116. package/src/plugins/notification/NotificationService.ts +25 -25
  117. package/src/plugins/notification/actionHandlers/index.ts +3 -3
  118. package/src/plugins/notification/models/Notification.ts +60 -60
  119. package/src/plugins/notification/models/index.ts +3 -3
  120. package/src/plugins/notification/routes/index.ts +1 -1
  121. package/src/plugins/routeManage/RouteManagePlugin.ts +62 -62
  122. package/src/plugins/routeManage/actionHandlers/httpProxy.ts +13 -13
  123. package/src/plugins/sequence/SequencePlugin.ts +146 -146
  124. package/src/plugins/sequence/SequencePluginTypes.ts +69 -69
  125. package/src/plugins/sequence/SequenceService.ts +92 -92
  126. package/src/plugins/sequence/actionHandlers/generateSn.ts +32 -32
  127. package/src/plugins/sequence/actionHandlers/index.ts +4 -4
  128. package/src/plugins/sequence/models/SequenceAutoIncrementRecord.ts +49 -49
  129. package/src/plugins/sequence/models/SequenceRule.ts +42 -42
  130. package/src/plugins/sequence/models/index.ts +4 -4
  131. package/src/plugins/sequence/routes/generateSn.ts +15 -15
  132. package/src/plugins/sequence/routes/index.ts +3 -3
  133. package/src/plugins/sequence/segment-utility.ts +11 -11
  134. package/src/plugins/sequence/segments/autoIncrement.ts +90 -90
  135. package/src/plugins/sequence/segments/dayOfMonth.ts +19 -19
  136. package/src/plugins/sequence/segments/index.ts +9 -9
  137. package/src/plugins/sequence/segments/literal.ts +16 -16
  138. package/src/plugins/sequence/segments/month.ts +19 -19
  139. package/src/plugins/sequence/segments/parameter.ts +20 -20
  140. package/src/plugins/sequence/segments/year.ts +19 -19
  141. package/src/plugins/serverOperation/ServerOperationPlugin.ts +91 -91
  142. package/src/plugins/serverOperation/ServerOperationPluginTypes.ts +15 -15
  143. package/src/plugins/serverOperation/actionHandlers/index.ts +4 -4
  144. package/src/plugins/serverOperation/actionHandlers/runServerOperation.ts +15 -15
  145. package/src/plugins/setting/SettingPlugin.ts +68 -68
  146. package/src/plugins/setting/SettingPluginTypes.ts +37 -37
  147. package/src/plugins/setting/SettingService.ts +213 -213
  148. package/src/plugins/setting/actionHandlers/getSystemSettingValues.ts +30 -30
  149. package/src/plugins/setting/actionHandlers/getUserSettingValues.ts +38 -38
  150. package/src/plugins/setting/actionHandlers/index.ts +6 -6
  151. package/src/plugins/setting/actionHandlers/setSystemSettingValues.ts +30 -30
  152. package/src/plugins/setting/models/SystemSettingGroupSetting.ts +57 -57
  153. package/src/plugins/setting/models/SystemSettingItem.ts +48 -48
  154. package/src/plugins/setting/models/SystemSettingItemSetting.ts +73 -73
  155. package/src/plugins/setting/models/UserSettingGroupSetting.ts +57 -57
  156. package/src/plugins/setting/models/UserSettingItem.ts +55 -55
  157. package/src/plugins/setting/models/UserSettingItemSetting.ts +73 -73
  158. package/src/plugins/setting/models/index.ts +8 -8
  159. package/src/plugins/setting/routes/getSystemSettingValues.ts +15 -15
  160. package/src/plugins/setting/routes/getUserSettingValues.ts +15 -15
  161. package/src/plugins/setting/routes/index.ts +5 -5
  162. package/src/plugins/setting/routes/setSystemSettingValues.ts +15 -15
  163. package/src/plugins/stateMachine/StateMachinePlugin.ts +196 -196
  164. package/src/plugins/stateMachine/StateMachinePluginTypes.ts +48 -48
  165. package/src/plugins/stateMachine/actionHandlers/index.ts +4 -4
  166. package/src/plugins/stateMachine/actionHandlers/sendStateMachineEvent.ts +54 -54
  167. package/src/plugins/stateMachine/models/StateMachine.ts +42 -42
  168. package/src/plugins/stateMachine/models/index.ts +3 -3
  169. package/src/plugins/stateMachine/routes/index.ts +3 -3
  170. package/src/plugins/stateMachine/routes/sendStateMachineEvent.ts +15 -15
  171. package/src/plugins/stateMachine/stateMachineHelper.ts +36 -36
  172. package/src/plugins/webhooks/WebhooksPlugin.ts +148 -148
  173. package/src/plugins/webhooks/pluginConfig.ts +75 -75
  174. package/src/polyfill.ts +5 -5
  175. package/src/proxy/mod.ts +38 -38
  176. package/src/proxy/types.ts +21 -21
  177. package/src/queryBuilder/index.ts +1 -1
  178. package/src/queryBuilder/queryBuilder.ts +755 -755
  179. package/src/server.ts +523 -524
  180. package/src/types/cron-job-types.ts +66 -66
  181. package/src/types.ts +832 -832
  182. package/src/utilities/accessControlUtility.ts +33 -33
  183. package/src/utilities/entityUtility.ts +18 -18
  184. package/src/utilities/errorUtility.ts +15 -15
  185. package/src/utilities/fsUtility.ts +61 -61
  186. package/src/utilities/httpUtility.ts +19 -19
  187. package/src/utilities/jwtUtility.ts +26 -26
  188. package/src/utilities/pathUtility.ts +14 -14
  189. package/src/utilities/timeUtility.ts +17 -17
  190. package/src/utilities/typeUtility.ts +15 -15
  191. package/tsconfig.json +19 -19
package/dist/index.js CHANGED
@@ -1203,485 +1203,6 @@ class RouteContext {
1203
1203
  }
1204
1204
  }
1205
1205
 
1206
- const parseFormDataBody = async (request, options = { all: false }) => {
1207
- const contentType = request.headers.get("Content-Type");
1208
- if (isFormDataContent(contentType)) {
1209
- return parseFormData(request, options);
1210
- }
1211
- return {};
1212
- };
1213
- function isFormDataContent(contentType) {
1214
- if (contentType === null) {
1215
- return false;
1216
- }
1217
- return contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded");
1218
- }
1219
- async function parseFormData(request, options) {
1220
- const formData = await request.formData();
1221
- if (formData) {
1222
- return convertFormDataToBodyData(formData, options);
1223
- }
1224
- return {};
1225
- }
1226
- function convertFormDataToBodyData(formData, options) {
1227
- const form = {};
1228
- formData.forEach((value, key) => {
1229
- const shouldParseAllValues = options.all || key.endsWith("[]");
1230
- if (!shouldParseAllValues) {
1231
- form[key] = value;
1232
- }
1233
- else {
1234
- handleParsingAllValues(form, key, value);
1235
- }
1236
- });
1237
- return form;
1238
- }
1239
- const handleParsingAllValues = (form, key, value) => {
1240
- if (form[key] && isArrayField(form[key])) {
1241
- appendToExistingArray(form[key], value);
1242
- }
1243
- else if (form[key]) {
1244
- convertToNewArray(form, key, value);
1245
- }
1246
- else {
1247
- form[key] = value;
1248
- }
1249
- };
1250
- function isArrayField(field) {
1251
- return Array.isArray(field);
1252
- }
1253
- const appendToExistingArray = (arr, value) => {
1254
- arr.push(value);
1255
- };
1256
- const convertToNewArray = (form, key, value) => {
1257
- form[key] = [form[key], value];
1258
- };
1259
-
1260
- // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
1261
- class AssertionError extends Error {
1262
- name = "AssertionError";
1263
- constructor(message) {
1264
- super(message);
1265
- }
1266
- }
1267
-
1268
- // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
1269
- /** Make an assertion, error will be thrown if `expr` does not have truthy value. */
1270
- function assert(expr, msg = "") {
1271
- if (!expr) {
1272
- throw new AssertionError(msg);
1273
- }
1274
- }
1275
-
1276
- // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
1277
- // This module is browser compatible.
1278
- /**
1279
- * Formats the given date to IMF date time format. (Reference:
1280
- * https://tools.ietf.org/html/rfc7231#section-7.1.1.1).
1281
- * IMF is the time format to use when generating times in HTTP
1282
- * headers. The time being formatted must be in UTC for Format to
1283
- * generate the correct format.
1284
- *
1285
- * @example
1286
- * ```ts
1287
- * import { toIMF } from "https://deno.land/std@$STD_VERSION/datetime/to_imf.ts";
1288
- *
1289
- * toIMF(new Date(0)); // => returns "Thu, 01 Jan 1970 00:00:00 GMT"
1290
- * ```
1291
- * @param date Date to parse
1292
- * @return IMF date formatted string
1293
- */
1294
- function toIMF(date) {
1295
- function dtPad(v, lPad = 2) {
1296
- return v.padStart(lPad, "0");
1297
- }
1298
- const d = dtPad(date.getUTCDate().toString());
1299
- const h = dtPad(date.getUTCHours().toString());
1300
- const min = dtPad(date.getUTCMinutes().toString());
1301
- const s = dtPad(date.getUTCSeconds().toString());
1302
- const y = date.getUTCFullYear();
1303
- const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1304
- const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1305
- return `${days[date.getUTCDay()]}, ${d} ${months[date.getUTCMonth()]} ${y} ${h}:${min}:${s} GMT`;
1306
- }
1307
-
1308
- // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
1309
- const FIELD_CONTENT_REGEXP = /^(?=[\x20-\x7E]*$)[^()@<>,;:\\"\[\]?={}\s]+$/;
1310
- function toString(cookie) {
1311
- if (!cookie.name) {
1312
- return "";
1313
- }
1314
- const out = [];
1315
- validateName(cookie.name);
1316
- validateValue(cookie.name, cookie.value);
1317
- out.push(`${cookie.name}=${cookie.value}`);
1318
- // Fallback for invalid Set-Cookie
1319
- // ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
1320
- if (cookie.name.startsWith("__Secure")) {
1321
- cookie.secure = true;
1322
- }
1323
- if (cookie.name.startsWith("__Host")) {
1324
- cookie.path = "/";
1325
- cookie.secure = true;
1326
- delete cookie.domain;
1327
- }
1328
- if (cookie.secure) {
1329
- out.push("Secure");
1330
- }
1331
- if (cookie.httpOnly) {
1332
- out.push("HttpOnly");
1333
- }
1334
- if (typeof cookie.maxAge === "number" && Number.isInteger(cookie.maxAge)) {
1335
- assert(cookie.maxAge >= 0, "Max-Age must be an integer superior or equal to 0");
1336
- out.push(`Max-Age=${cookie.maxAge}`);
1337
- }
1338
- if (cookie.domain) {
1339
- validateDomain(cookie.domain);
1340
- out.push(`Domain=${cookie.domain}`);
1341
- }
1342
- if (cookie.sameSite) {
1343
- out.push(`SameSite=${cookie.sameSite}`);
1344
- }
1345
- if (cookie.path) {
1346
- validatePath(cookie.path);
1347
- out.push(`Path=${cookie.path}`);
1348
- }
1349
- if (cookie.expires) {
1350
- const { expires } = cookie;
1351
- const dateString = toIMF(typeof expires === "number" ? new Date(expires) : expires);
1352
- out.push(`Expires=${dateString}`);
1353
- }
1354
- if (cookie.unparsed) {
1355
- out.push(cookie.unparsed.join("; "));
1356
- }
1357
- return out.join("; ");
1358
- }
1359
- /**
1360
- * Validate Cookie Name.
1361
- * @param name Cookie name.
1362
- */
1363
- function validateName(name) {
1364
- if (name && !FIELD_CONTENT_REGEXP.test(name)) {
1365
- throw new TypeError(`Invalid cookie name: "${name}".`);
1366
- }
1367
- }
1368
- /**
1369
- * Validate Path Value.
1370
- * See {@link https://tools.ietf.org/html/rfc6265#section-4.1.2.4}.
1371
- * @param path Path value.
1372
- */
1373
- function validatePath(path) {
1374
- if (path == null) {
1375
- return;
1376
- }
1377
- for (let i = 0; i < path.length; i++) {
1378
- const c = path.charAt(i);
1379
- if (c < String.fromCharCode(0x20) || c > String.fromCharCode(0x7e) || c == ";") {
1380
- throw new Error(path + ": Invalid cookie path char '" + c + "'");
1381
- }
1382
- }
1383
- }
1384
- /**
1385
- * Validate Cookie Value.
1386
- * See {@link https://tools.ietf.org/html/rfc6265#section-4.1}.
1387
- * @param value Cookie value.
1388
- */
1389
- function validateValue(name, value) {
1390
- if (value == null || name == null)
1391
- return;
1392
- for (let i = 0; i < value.length; i++) {
1393
- const c = value.charAt(i);
1394
- if (c < String.fromCharCode(0x21) ||
1395
- c == String.fromCharCode(0x22) ||
1396
- c == String.fromCharCode(0x2c) ||
1397
- c == String.fromCharCode(0x3b) ||
1398
- c == String.fromCharCode(0x5c) ||
1399
- c == String.fromCharCode(0x7f)) {
1400
- throw new Error("RFC2616 cookie '" + name + "' cannot contain character '" + c + "'");
1401
- }
1402
- if (c > String.fromCharCode(0x80)) {
1403
- throw new Error("RFC2616 cookie '" + name + "' can only have US-ASCII chars as value" + c.charCodeAt(0).toString(16));
1404
- }
1405
- }
1406
- }
1407
- /**
1408
- * Validate Cookie Domain.
1409
- * See {@link https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3}.
1410
- * @param domain Cookie domain.
1411
- */
1412
- function validateDomain(domain) {
1413
- if (domain == null) {
1414
- return;
1415
- }
1416
- const char1 = domain.charAt(0);
1417
- const charN = domain.charAt(domain.length - 1);
1418
- if (char1 == "-" || charN == "." || charN == "-") {
1419
- throw new Error("Invalid first/last char in cookie domain: " + domain);
1420
- }
1421
- }
1422
- /**
1423
- * Parse cookies of a header
1424
- *
1425
- * @example
1426
- * ```ts
1427
- * import { getCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
1428
- *
1429
- * const headers = new Headers();
1430
- * headers.set("Cookie", "full=of; tasty=chocolate");
1431
- *
1432
- * const cookies = getCookies(headers);
1433
- * console.log(cookies); // { full: "of", tasty: "chocolate" }
1434
- * ```
1435
- *
1436
- * @param headers The headers instance to get cookies from
1437
- * @return Object with cookie names as keys
1438
- */
1439
- function getCookies(headers) {
1440
- const cookie = headers.get("Cookie");
1441
- if (cookie != null) {
1442
- const out = {};
1443
- const c = cookie.split(";");
1444
- for (const kv of c) {
1445
- const [cookieKey, ...cookieVal] = kv.split("=");
1446
- assert(cookieKey != null);
1447
- const key = cookieKey.trim();
1448
- out[key] = cookieVal.join("=");
1449
- }
1450
- return out;
1451
- }
1452
- return {};
1453
- }
1454
- /**
1455
- * Set the cookie header properly in the headers
1456
- *
1457
- * @example
1458
- * ```ts
1459
- * import {
1460
- * Cookie,
1461
- * setCookie,
1462
- * } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
1463
- *
1464
- * const headers = new Headers();
1465
- * const cookie: Cookie = { name: "Space", value: "Cat" };
1466
- * setCookie(headers, cookie);
1467
- *
1468
- * const cookieHeader = headers.get("set-cookie");
1469
- * console.log(cookieHeader); // Space=Cat
1470
- * ```
1471
- *
1472
- * @param headers The headers instance to set the cookie to
1473
- * @param cookie Cookie to set
1474
- */
1475
- function setCookie(headers, cookie) {
1476
- // Parsing cookie headers to make consistent set-cookie header
1477
- // ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
1478
- const v = toString(cookie);
1479
- if (v) {
1480
- headers.append("Set-Cookie", v);
1481
- }
1482
- }
1483
- /**
1484
- * Set the cookie header with empty value in the headers to delete it
1485
- *
1486
- * > Note: Deleting a `Cookie` will set its expiration date before now. Forcing
1487
- * > the browser to delete it.
1488
- *
1489
- * @example
1490
- * ```ts
1491
- * import { deleteCookie } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
1492
- *
1493
- * const headers = new Headers();
1494
- * deleteCookie(headers, "deno");
1495
- *
1496
- * const cookieHeader = headers.get("set-cookie");
1497
- * console.log(cookieHeader); // deno=; Expires=Thus, 01 Jan 1970 00:00:00 GMT
1498
- * ```
1499
- *
1500
- * @param headers The headers instance to delete the cookie from
1501
- * @param name Name of cookie
1502
- * @param attributes Additional cookie attributes
1503
- */
1504
- function deleteCookie(headers, name, attributes) {
1505
- setCookie(headers, {
1506
- name: name,
1507
- value: "",
1508
- expires: new Date(0),
1509
- ...attributes,
1510
- });
1511
- }
1512
- function parseSetCookie(value) {
1513
- const attrs = value.split(";").map((attr) => {
1514
- const [key, ...values] = attr.trim().split("=");
1515
- return [key, values.join("=")];
1516
- });
1517
- const cookie = {
1518
- name: attrs[0][0],
1519
- value: attrs[0][1],
1520
- };
1521
- for (const [key, value] of attrs.slice(1)) {
1522
- switch (key.toLocaleLowerCase()) {
1523
- case "expires":
1524
- cookie.expires = new Date(value);
1525
- break;
1526
- case "max-age":
1527
- cookie.maxAge = Number(value);
1528
- if (cookie.maxAge < 0) {
1529
- console.warn("Max-Age must be an integer superior or equal to 0. Cookie ignored.");
1530
- return null;
1531
- }
1532
- break;
1533
- case "domain":
1534
- cookie.domain = value;
1535
- break;
1536
- case "path":
1537
- cookie.path = value;
1538
- break;
1539
- case "secure":
1540
- cookie.secure = true;
1541
- break;
1542
- case "httponly":
1543
- cookie.httpOnly = true;
1544
- break;
1545
- case "samesite":
1546
- cookie.sameSite = value;
1547
- break;
1548
- default:
1549
- if (!Array.isArray(cookie.unparsed)) {
1550
- cookie.unparsed = [];
1551
- }
1552
- cookie.unparsed.push([key, value].join("="));
1553
- }
1554
- }
1555
- if (cookie.name.startsWith("__Secure-")) {
1556
- /** This requirement is mentioned in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie but not the RFC. */
1557
- if (!cookie.secure) {
1558
- console.warn("Cookies with names starting with `__Secure-` must be set with the secure flag. Cookie ignored.");
1559
- return null;
1560
- }
1561
- }
1562
- if (cookie.name.startsWith("__Host-")) {
1563
- if (!cookie.secure) {
1564
- console.warn("Cookies with names starting with `__Host-` must be set with the secure flag. Cookie ignored.");
1565
- return null;
1566
- }
1567
- if (cookie.domain !== undefined) {
1568
- console.warn("Cookies with names starting with `__Host-` must not have a domain specified. Cookie ignored.");
1569
- return null;
1570
- }
1571
- if (cookie.path !== "/") {
1572
- console.warn("Cookies with names starting with `__Host-` must have path be `/`. Cookie has been ignored.");
1573
- return null;
1574
- }
1575
- }
1576
- return cookie;
1577
- }
1578
- /**
1579
- * Parse set-cookies of a header
1580
- *
1581
- * @example
1582
- * ```ts
1583
- * import { getSetCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
1584
- *
1585
- * const headers = new Headers([
1586
- * ["Set-Cookie", "lulu=meow; Secure; Max-Age=3600"],
1587
- * ["Set-Cookie", "booya=kasha; HttpOnly; Path=/"],
1588
- * ]);
1589
- *
1590
- * const cookies = getSetCookies(headers);
1591
- * console.log(cookies); // [{ name: "lulu", value: "meow", secure: true, maxAge: 3600 }, { name: "booya", value: "kahsa", httpOnly: true, path: "/ }]
1592
- * ```
1593
- *
1594
- * @param headers The headers instance to get set-cookies from
1595
- * @return List of cookies
1596
- */
1597
- function getSetCookies(headers) {
1598
- // TODO(lino-levan): remove this ts-ignore when Typescript 5.2 lands in Deno
1599
- // @ts-ignore Typescript's TS Dom types will be out of date until 5.2
1600
- return headers
1601
- .getSetCookie()
1602
- /** Parse each `set-cookie` header separately */
1603
- .map(parseSetCookie)
1604
- /** Skip empty cookies */
1605
- .filter(Boolean);
1606
- }
1607
-
1608
- const GlobalRequest = global.Request;
1609
- class RapidRequest {
1610
- #logger;
1611
- #raw;
1612
- #bodyParsed;
1613
- #body;
1614
- #headers;
1615
- #parsedCookies;
1616
- method;
1617
- url;
1618
- constructor(server, req) {
1619
- this.#logger = server.getLogger();
1620
- this.#raw = req;
1621
- this.method = req.method;
1622
- this.url = new URL(req.url);
1623
- this.#headers = req.headers;
1624
- }
1625
- async parseBody() {
1626
- if (this.#bodyParsed) {
1627
- this.#logger.warn("Request body has been parsed. 'parseBody()' method should not be called more than once.");
1628
- return;
1629
- }
1630
- const requestMethod = this.method;
1631
- if (requestMethod !== "POST" && requestMethod !== "PUT" && requestMethod !== "PATCH") {
1632
- this.#body = null;
1633
- this.#bodyParsed = true;
1634
- return;
1635
- }
1636
- const contentLength = parseInt(this.#headers.get("Content-Length") || "0", 10);
1637
- if (!contentLength) {
1638
- this.#body = null;
1639
- this.#bodyParsed = true;
1640
- return;
1641
- }
1642
- const req = this.#raw;
1643
- const contentType = this.#headers.get("Content-Type") || "application/json";
1644
- if (contentType.includes("json")) {
1645
- this.#body = {
1646
- type: "json",
1647
- value: await req.json(),
1648
- };
1649
- }
1650
- else if (contentType.startsWith("application/x-www-form-urlencoded")) {
1651
- const bodyText = await req.text();
1652
- this.#body = {
1653
- type: "form",
1654
- value: qs__default["default"].parse(bodyText),
1655
- };
1656
- }
1657
- else if (contentType.startsWith("multipart/form-data")) {
1658
- this.#body = {
1659
- type: "form-data",
1660
- value: await parseFormDataBody(req),
1661
- };
1662
- }
1663
- this.#bodyParsed = true;
1664
- }
1665
- get rawRequest() {
1666
- return this.#raw;
1667
- }
1668
- get headers() {
1669
- return this.#headers;
1670
- }
1671
- get cookies() {
1672
- if (!this.#parsedCookies) {
1673
- this.#parsedCookies = getCookies(this.#headers);
1674
- }
1675
- return this.#parsedCookies;
1676
- }
1677
- get body() {
1678
- if (!this.#bodyParsed) {
1679
- throw new Error("Request body not parsed, you should call 'parseBody()' method before getting the body.");
1680
- }
1681
- return this.#body;
1682
- }
1683
- }
1684
-
1685
1206
  var bootstrapApplicationConfig = {
1686
1207
  code: "default",
1687
1208
  name: "default",
@@ -3294,8 +2815,8 @@ async function findManyRelationLinksViaLinkTable(options) {
3294
2815
  const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
3295
2816
  schema: relationProperty.linkSchema,
3296
2817
  tableName: relationProperty.linkTableName,
3297
- })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])
3298
- ORDER BY id
2818
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])
2819
+ ORDER BY id
3299
2820
  `;
3300
2821
  const params = [mainEntityIds];
3301
2822
  const relationLinks = await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
@@ -3926,7 +3447,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
3926
3447
  await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
3927
3448
  schema: property.linkSchema,
3928
3449
  tableName: property.linkTableName,
3929
- })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1
3450
+ })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1
3930
3451
  AND ${server.queryBuilder.quoteObject(property.targetIdColumnName)} <> ALL($2::int[])`, [id, targetIdsToKeep], routeContext?.getDbTransactionClient());
3931
3452
  }
3932
3453
  else {
@@ -3955,8 +3476,8 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
3955
3476
  await server.queryDatabaseObject(`UPDATE ${server.queryBuilder.quoteTable({
3956
3477
  schema: relationModel.schema,
3957
3478
  tableName: relationModel.tableName,
3958
- })}
3959
- SET ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = null
3479
+ })}
3480
+ SET ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = null
3960
3481
  WHERE id = ANY($1::int[])`, [targetIdsToRemove], routeContext?.getDbTransactionClient());
3961
3482
  }
3962
3483
  else {
@@ -4241,7 +3762,7 @@ async function deleteEntityById(server, dataAccessor, options, plugin) {
4241
3762
  await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
4242
3763
  schema: relationProperty.linkSchema,
4243
3764
  tableName: relationProperty.linkTableName,
4244
- })}
3765
+ })}
4245
3766
  WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
4246
3767
  }
4247
3768
  else {
@@ -4251,8 +3772,8 @@ async function deleteEntityById(server, dataAccessor, options, plugin) {
4251
3772
  await server.queryDatabaseObject(`UPDATE ${server.queryBuilder.quoteTable({
4252
3773
  schema: relationModel.schema,
4253
3774
  tableName: relationModel.tableName,
4254
- })}
4255
- SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = null
3775
+ })}
3776
+ SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = null
4256
3777
  WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
4257
3778
  }
4258
3779
  }
@@ -4349,11 +3870,11 @@ class EntityManager {
4349
3870
  const command = `INSERT INTO ${queryBuilder.quoteTable({
4350
3871
  schema: relationProperty.linkSchema,
4351
3872
  tableName: relationProperty.linkTableName,
4352
- })} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)})
4353
- SELECT $1, $2 WHERE NOT EXISTS (
4354
- SELECT ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}
4355
- FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
4356
- WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2
3873
+ })} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)})
3874
+ SELECT $1, $2 WHERE NOT EXISTS (
3875
+ SELECT ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}
3876
+ FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
3877
+ WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2
4357
3878
  )`;
4358
3879
  const params = [id, relation.id];
4359
3880
  await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
@@ -4393,7 +3914,7 @@ class EntityManager {
4393
3914
  const { queryBuilder } = server;
4394
3915
  if (relationProperty.linkTableName) {
4395
3916
  for (const relation of relations) {
4396
- const command = `DELETE FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
3917
+ const command = `DELETE FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
4397
3918
  WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2;`;
4398
3919
  const params = [id, relation.id];
4399
3920
  await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
@@ -4540,295 +4061,774 @@ class RapidServer {
4540
4061
  }
4541
4062
  }
4542
4063
  }
4543
- if (routes) {
4544
- for (const route of routes) {
4545
- const originalRoute = lodash.find(this.#applicationConfig.routes, (item) => item.code == route.code);
4546
- if (originalRoute) {
4547
- originalRoute.name = route.name;
4548
- originalRoute.actions = route.actions;
4549
- }
4550
- else {
4551
- this.#applicationConfig.routes.push(route);
4552
- }
4064
+ if (routes) {
4065
+ for (const route of routes) {
4066
+ const originalRoute = lodash.find(this.#applicationConfig.routes, (item) => item.code == route.code);
4067
+ if (originalRoute) {
4068
+ originalRoute.name = route.name;
4069
+ originalRoute.actions = route.actions;
4070
+ }
4071
+ else {
4072
+ this.#applicationConfig.routes.push(route);
4073
+ }
4074
+ }
4075
+ }
4076
+ }
4077
+ appendModelProperties(modelSingularCode, properties) {
4078
+ const originalModel = lodash.find(this.#applicationConfig.models, (item) => item.singularCode == modelSingularCode);
4079
+ if (!originalModel) {
4080
+ throw new Error(`Cannot append model properties. Model '${modelSingularCode}' was not found.`);
4081
+ }
4082
+ const originalProperties = originalModel.properties;
4083
+ for (const property of properties) {
4084
+ const originalProperty = lodash.find(originalProperties, (item) => item.code == property.code);
4085
+ if (originalProperty) {
4086
+ originalProperty.name = property.name;
4087
+ }
4088
+ else {
4089
+ originalProperties.push(property);
4090
+ }
4091
+ }
4092
+ }
4093
+ registerActionHandler(plugin, options) {
4094
+ const handler = lodash.bind(options.handler, null, plugin);
4095
+ this.#actionHandlersMapByCode.set(options.code, handler);
4096
+ }
4097
+ getActionHandlerByCode(code) {
4098
+ return this.#actionHandlersMapByCode.get(code);
4099
+ }
4100
+ registerMiddleware(middleware) {
4101
+ this.#middlewares.push(middleware);
4102
+ }
4103
+ getDataAccessor(options) {
4104
+ const { namespace, singularCode } = options;
4105
+ let dataAccessor = this.#cachedDataAccessors.get(singularCode);
4106
+ if (dataAccessor) {
4107
+ return dataAccessor;
4108
+ }
4109
+ const model = this.getModel(options);
4110
+ if (!model) {
4111
+ throw new Error(`Data model ${namespace}.${singularCode} not found.`);
4112
+ }
4113
+ dataAccessor = new DataAccessor(this, this.#databaseAccessor, {
4114
+ model,
4115
+ queryBuilder: this.queryBuilder,
4116
+ });
4117
+ this.#cachedDataAccessors.set(singularCode, dataAccessor);
4118
+ return dataAccessor;
4119
+ }
4120
+ getModel(options) {
4121
+ if (options.namespace) {
4122
+ return this.#applicationConfig?.models.find((e) => e.namespace === options.namespace && e.singularCode === options.singularCode);
4123
+ }
4124
+ return this.#applicationConfig?.models.find((e) => e.singularCode === options.singularCode);
4125
+ }
4126
+ getEntityManager(singularCode) {
4127
+ let entityManager = this.#cachedEntityManager.get(singularCode);
4128
+ if (entityManager) {
4129
+ return entityManager;
4130
+ }
4131
+ const dataAccessor = this.getDataAccessor({ singularCode });
4132
+ entityManager = new EntityManager(this, dataAccessor);
4133
+ this.#cachedEntityManager.set(singularCode, entityManager);
4134
+ return entityManager;
4135
+ }
4136
+ registerEventHandler(eventName, listener) {
4137
+ this.#eventManager.on(eventName, listener);
4138
+ }
4139
+ registerEntityWatcher(entityWatcher) {
4140
+ this.#entityWatchers.push(entityWatcher);
4141
+ }
4142
+ async emitEvent(event) {
4143
+ const { eventName, payload, sender, routeContext: routerContext } = event;
4144
+ this.#logger.debug(`Emitting '${eventName}' event.`, { eventName });
4145
+ this.#logger.verbose(`Event payload: `, { payload });
4146
+ await this.#eventManager.emit(eventName, sender, payload, routerContext);
4147
+ // TODO: should move this logic into metaManager
4148
+ // if (
4149
+ // (eventName === "entity.create" || eventName === "entity.update" ||
4150
+ // eventName === "entity.delete") &&
4151
+ // payload.namespace === "meta"
4152
+ // ) {
4153
+ // await this.configureApplication();
4154
+ // }
4155
+ }
4156
+ registerService(name, service) {
4157
+ this.#services.set(name, service);
4158
+ }
4159
+ getService(name) {
4160
+ return this.#services.get(name);
4161
+ }
4162
+ registerCronJob(job) {
4163
+ const jobDuplicate = lodash.find(this.#cronJobs, (item) => item.code === job.code);
4164
+ if (jobDuplicate) {
4165
+ this.#logger.warn(`Duplicated cron job with code "${job.code}"`);
4166
+ }
4167
+ this.#cronJobs.push(job);
4168
+ }
4169
+ listCronJobs() {
4170
+ return [...this.#cronJobs, ...this.#appCronJobs];
4171
+ }
4172
+ async start() {
4173
+ this.#logger.info("Starting rapid server...");
4174
+ const pluginManager = this.#pluginManager;
4175
+ await pluginManager.loadPlugins(this.#plugins);
4176
+ await pluginManager.initPlugins();
4177
+ await pluginManager.registerMiddlewares();
4178
+ await pluginManager.registerActionHandlers();
4179
+ await pluginManager.registerEventHandlers();
4180
+ await pluginManager.registerMessageHandlers();
4181
+ await pluginManager.registerTaskProcessors();
4182
+ this.#entityWatchers = this.#entityWatchers.concat(this.#appEntityWatchers);
4183
+ for (const entityWatcher of this.#entityWatchers) {
4184
+ if (entityWatcher.eventName === "entity.beforeCreate") {
4185
+ this.#entityBeforeCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4186
+ }
4187
+ else if (entityWatcher.eventName === "entity.create") {
4188
+ this.#entityCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4189
+ }
4190
+ else if (entityWatcher.eventName === "entity.beforeUpdate") {
4191
+ this.#entityBeforeUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4192
+ }
4193
+ else if (entityWatcher.eventName === "entity.update") {
4194
+ this.#entityUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4195
+ }
4196
+ else if (entityWatcher.eventName === "entity.beforeDelete") {
4197
+ this.#entityBeforeDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4198
+ }
4199
+ else if (entityWatcher.eventName === "entity.delete") {
4200
+ this.#entityDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4201
+ }
4202
+ else if (entityWatcher.eventName === "entity.addRelations") {
4203
+ this.#entityAddRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4204
+ }
4205
+ else if (entityWatcher.eventName === "entity.removeRelations") {
4206
+ this.#entityRemoveRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4207
+ }
4208
+ else if (entityWatcher.eventName === "entity.beforeResponse") {
4209
+ this.#entityBeforeResponseEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4210
+ }
4211
+ }
4212
+ await this.configureApplication();
4213
+ if (!this.#disableCronJobs) {
4214
+ await pluginManager.registerCronJobs();
4215
+ }
4216
+ this.#logger.info(`Rapid server ready.`);
4217
+ await pluginManager.onApplicationReady(this.#applicationConfig);
4218
+ }
4219
+ async configureApplication() {
4220
+ this.#applicationConfig = lodash.cloneDeep(this.#bootstrapApplicationConfig);
4221
+ const pluginManager = this.#pluginManager;
4222
+ await pluginManager.onLoadingApplication(this.#applicationConfig);
4223
+ await pluginManager.configureModels(this.#applicationConfig);
4224
+ await pluginManager.configureModelProperties(this.#applicationConfig);
4225
+ await pluginManager.configureServices(this.#applicationConfig);
4226
+ await pluginManager.configureRoutes(this.#applicationConfig);
4227
+ // TODO: check application configuration.
4228
+ await pluginManager.onApplicationLoaded(this.#applicationConfig);
4229
+ this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
4230
+ }
4231
+ registerFacilityFactory(factory) {
4232
+ this.#facilityFactories.set(factory.name, factory);
4233
+ }
4234
+ async getFacility(name, options, nullIfUnknownFacility) {
4235
+ const factory = this.#facilityFactories.get(name);
4236
+ if (!factory) {
4237
+ if (nullIfUnknownFacility) {
4238
+ return null;
4239
+ }
4240
+ else {
4241
+ throw new Error(`Failed to get facility. Unknown facility name: ${name}`);
4242
+ }
4243
+ }
4244
+ return await factory.createFacility(this, options);
4245
+ }
4246
+ async queryDatabaseObject(sql, params, client, dropErrorLog) {
4247
+ try {
4248
+ return await this.#databaseAccessor.queryDatabaseObject(sql, params, client);
4249
+ }
4250
+ catch (err) {
4251
+ if (!dropErrorLog) {
4252
+ this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
4253
+ }
4254
+ throw err;
4255
+ }
4256
+ }
4257
+ async tryQueryDatabaseObject(sql, params, client, dropErrorLog) {
4258
+ try {
4259
+ return await this.queryDatabaseObject(sql, params, client);
4260
+ }
4261
+ catch (err) {
4262
+ if (!dropErrorLog) {
4263
+ this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
4553
4264
  }
4554
4265
  }
4266
+ return [];
4555
4267
  }
4556
- appendModelProperties(modelSingularCode, properties) {
4557
- const originalModel = lodash.find(this.#applicationConfig.models, (item) => item.singularCode == modelSingularCode);
4558
- if (!originalModel) {
4559
- throw new Error(`Cannot append model properties. Model '${modelSingularCode}' was not found.`);
4268
+ get middlewares() {
4269
+ return this.#middlewares;
4270
+ }
4271
+ async handleRequest(rapidRequest, next) {
4272
+ await rapidRequest.parseBody();
4273
+ const routeContext = new RouteContext(this, rapidRequest);
4274
+ const { response } = routeContext;
4275
+ try {
4276
+ await this.#pluginManager.onPrepareRouteContext(routeContext);
4277
+ await this.#buildedRoutes(routeContext, next);
4560
4278
  }
4561
- const originalProperties = originalModel.properties;
4562
- for (const property of properties) {
4563
- const originalProperty = lodash.find(originalProperties, (item) => item.code == property.code);
4564
- if (originalProperty) {
4565
- originalProperty.name = property.name;
4279
+ catch (ex) {
4280
+ let error;
4281
+ if (lodash.isString(ex)) {
4282
+ error = {
4283
+ message: ex,
4284
+ };
4566
4285
  }
4567
4286
  else {
4568
- originalProperties.push(property);
4287
+ error = { name: ex.name, message: ex.message, stack: ex.stack };
4569
4288
  }
4289
+ this.#logger.error("handle request error.", { error });
4290
+ response.json({ error }, 500);
4291
+ }
4292
+ if (!response.status && !response.body) {
4293
+ response.json({
4294
+ error: {
4295
+ message: "No route handler was found to handle this request.",
4296
+ },
4297
+ }, 404);
4570
4298
  }
4299
+ return response.getResponse();
4571
4300
  }
4572
- registerActionHandler(plugin, options) {
4573
- const handler = lodash.bind(options.handler, null, plugin);
4574
- this.#actionHandlersMapByCode.set(options.code, handler);
4301
+ async beforeRunRouteActions(handlerContext) {
4302
+ await this.#pluginManager.beforeRunRouteActions(handlerContext);
4575
4303
  }
4576
- getActionHandlerByCode(code) {
4577
- return this.#actionHandlersMapByCode.get(code);
4304
+ async beforeCreateEntity(model, options) {
4305
+ await this.#pluginManager.beforeCreateEntity(model, options);
4578
4306
  }
4579
- registerMiddleware(middleware) {
4580
- this.#middlewares.push(middleware);
4307
+ async beforeUpdateEntity(model, options, currentEntity) {
4308
+ await this.#pluginManager.beforeUpdateEntity(model, options, currentEntity);
4581
4309
  }
4582
- getDataAccessor(options) {
4583
- const { namespace, singularCode } = options;
4584
- let dataAccessor = this.#cachedDataAccessors.get(singularCode);
4585
- if (dataAccessor) {
4586
- return dataAccessor;
4310
+ async #handleEntityEvent(eventName, sender, payload, routerContext) {
4311
+ const { modelSingularCode, baseModelSingularCode } = payload;
4312
+ if (!routerContext) {
4313
+ routerContext = new RouteContext(this);
4587
4314
  }
4588
- const model = this.getModel(options);
4589
- if (!model) {
4590
- throw new Error(`Data model ${namespace}.${singularCode} not found.`);
4315
+ const entityWatchHandlerContext = {
4316
+ server: this,
4317
+ payload,
4318
+ routerContext,
4319
+ };
4320
+ let emitter;
4321
+ if (eventName === "entity.beforeCreate") {
4322
+ emitter = this.#entityBeforeCreateEventEmitters;
4591
4323
  }
4592
- dataAccessor = new DataAccessor(this, this.#databaseAccessor, {
4593
- model,
4594
- queryBuilder: this.queryBuilder,
4595
- });
4596
- this.#cachedDataAccessors.set(singularCode, dataAccessor);
4597
- return dataAccessor;
4324
+ else if (eventName === "entity.create") {
4325
+ emitter = this.#entityCreateEventEmitters;
4326
+ }
4327
+ else if (eventName === "entity.beforeUpdate") {
4328
+ emitter = this.#entityBeforeUpdateEventEmitters;
4329
+ }
4330
+ else if (eventName === "entity.update") {
4331
+ emitter = this.#entityUpdateEventEmitters;
4332
+ }
4333
+ else if (eventName === "entity.beforeDelete") {
4334
+ emitter = this.#entityBeforeDeleteEventEmitters;
4335
+ }
4336
+ else if (eventName === "entity.delete") {
4337
+ emitter = this.#entityDeleteEventEmitters;
4338
+ }
4339
+ else if (eventName === "entity.addRelations") {
4340
+ emitter = this.#entityAddRelationsEventEmitters;
4341
+ }
4342
+ else if (eventName === "entity.removeRelations") {
4343
+ emitter = this.#entityRemoveRelationsEventEmitters;
4344
+ }
4345
+ else if (eventName === "entity.beforeResponse") {
4346
+ emitter = this.#entityBeforeResponseEventEmitters;
4347
+ }
4348
+ await emitter.emit(modelSingularCode, entityWatchHandlerContext);
4349
+ if (baseModelSingularCode) {
4350
+ await emitter.emit(baseModelSingularCode, entityWatchHandlerContext);
4351
+ }
4352
+ }
4353
+ }
4354
+
4355
+ const parseFormDataBody = async (request, options = { all: false }) => {
4356
+ const contentType = request.headers.get("Content-Type");
4357
+ if (isFormDataContent(contentType)) {
4358
+ return parseFormData(request, options);
4359
+ }
4360
+ return {};
4361
+ };
4362
+ function isFormDataContent(contentType) {
4363
+ if (contentType === null) {
4364
+ return false;
4365
+ }
4366
+ return contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded");
4367
+ }
4368
+ async function parseFormData(request, options) {
4369
+ const formData = await request.formData();
4370
+ if (formData) {
4371
+ return convertFormDataToBodyData(formData, options);
4372
+ }
4373
+ return {};
4374
+ }
4375
+ function convertFormDataToBodyData(formData, options) {
4376
+ const form = {};
4377
+ formData.forEach((value, key) => {
4378
+ const shouldParseAllValues = options.all || key.endsWith("[]");
4379
+ if (!shouldParseAllValues) {
4380
+ form[key] = value;
4381
+ }
4382
+ else {
4383
+ handleParsingAllValues(form, key, value);
4384
+ }
4385
+ });
4386
+ return form;
4387
+ }
4388
+ const handleParsingAllValues = (form, key, value) => {
4389
+ if (form[key] && isArrayField(form[key])) {
4390
+ appendToExistingArray(form[key], value);
4391
+ }
4392
+ else if (form[key]) {
4393
+ convertToNewArray(form, key, value);
4394
+ }
4395
+ else {
4396
+ form[key] = value;
4397
+ }
4398
+ };
4399
+ function isArrayField(field) {
4400
+ return Array.isArray(field);
4401
+ }
4402
+ const appendToExistingArray = (arr, value) => {
4403
+ arr.push(value);
4404
+ };
4405
+ const convertToNewArray = (form, key, value) => {
4406
+ form[key] = [form[key], value];
4407
+ };
4408
+
4409
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
4410
+ class AssertionError extends Error {
4411
+ name = "AssertionError";
4412
+ constructor(message) {
4413
+ super(message);
4414
+ }
4415
+ }
4416
+
4417
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
4418
+ /** Make an assertion, error will be thrown if `expr` does not have truthy value. */
4419
+ function assert(expr, msg = "") {
4420
+ if (!expr) {
4421
+ throw new AssertionError(msg);
4422
+ }
4423
+ }
4424
+
4425
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
4426
+ // This module is browser compatible.
4427
+ /**
4428
+ * Formats the given date to IMF date time format. (Reference:
4429
+ * https://tools.ietf.org/html/rfc7231#section-7.1.1.1).
4430
+ * IMF is the time format to use when generating times in HTTP
4431
+ * headers. The time being formatted must be in UTC for Format to
4432
+ * generate the correct format.
4433
+ *
4434
+ * @example
4435
+ * ```ts
4436
+ * import { toIMF } from "https://deno.land/std@$STD_VERSION/datetime/to_imf.ts";
4437
+ *
4438
+ * toIMF(new Date(0)); // => returns "Thu, 01 Jan 1970 00:00:00 GMT"
4439
+ * ```
4440
+ * @param date Date to parse
4441
+ * @return IMF date formatted string
4442
+ */
4443
+ function toIMF(date) {
4444
+ function dtPad(v, lPad = 2) {
4445
+ return v.padStart(lPad, "0");
4446
+ }
4447
+ const d = dtPad(date.getUTCDate().toString());
4448
+ const h = dtPad(date.getUTCHours().toString());
4449
+ const min = dtPad(date.getUTCMinutes().toString());
4450
+ const s = dtPad(date.getUTCSeconds().toString());
4451
+ const y = date.getUTCFullYear();
4452
+ const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
4453
+ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
4454
+ return `${days[date.getUTCDay()]}, ${d} ${months[date.getUTCMonth()]} ${y} ${h}:${min}:${s} GMT`;
4455
+ }
4456
+
4457
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
4458
+ const FIELD_CONTENT_REGEXP = /^(?=[\x20-\x7E]*$)[^()@<>,;:\\"\[\]?={}\s]+$/;
4459
+ function toString(cookie) {
4460
+ if (!cookie.name) {
4461
+ return "";
4462
+ }
4463
+ const out = [];
4464
+ validateName(cookie.name);
4465
+ validateValue(cookie.name, cookie.value);
4466
+ out.push(`${cookie.name}=${cookie.value}`);
4467
+ // Fallback for invalid Set-Cookie
4468
+ // ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
4469
+ if (cookie.name.startsWith("__Secure")) {
4470
+ cookie.secure = true;
4471
+ }
4472
+ if (cookie.name.startsWith("__Host")) {
4473
+ cookie.path = "/";
4474
+ cookie.secure = true;
4475
+ delete cookie.domain;
4476
+ }
4477
+ if (cookie.secure) {
4478
+ out.push("Secure");
4479
+ }
4480
+ if (cookie.httpOnly) {
4481
+ out.push("HttpOnly");
4482
+ }
4483
+ if (typeof cookie.maxAge === "number" && Number.isInteger(cookie.maxAge)) {
4484
+ assert(cookie.maxAge >= 0, "Max-Age must be an integer superior or equal to 0");
4485
+ out.push(`Max-Age=${cookie.maxAge}`);
4598
4486
  }
4599
- getModel(options) {
4600
- if (options.namespace) {
4601
- return this.#applicationConfig?.models.find((e) => e.namespace === options.namespace && e.singularCode === options.singularCode);
4602
- }
4603
- return this.#applicationConfig?.models.find((e) => e.singularCode === options.singularCode);
4487
+ if (cookie.domain) {
4488
+ validateDomain(cookie.domain);
4489
+ out.push(`Domain=${cookie.domain}`);
4604
4490
  }
4605
- getEntityManager(singularCode) {
4606
- let entityManager = this.#cachedEntityManager.get(singularCode);
4607
- if (entityManager) {
4608
- return entityManager;
4609
- }
4610
- const dataAccessor = this.getDataAccessor({ singularCode });
4611
- entityManager = new EntityManager(this, dataAccessor);
4612
- this.#cachedEntityManager.set(singularCode, entityManager);
4613
- return entityManager;
4491
+ if (cookie.sameSite) {
4492
+ out.push(`SameSite=${cookie.sameSite}`);
4614
4493
  }
4615
- registerEventHandler(eventName, listener) {
4616
- this.#eventManager.on(eventName, listener);
4494
+ if (cookie.path) {
4495
+ validatePath(cookie.path);
4496
+ out.push(`Path=${cookie.path}`);
4617
4497
  }
4618
- registerEntityWatcher(entityWatcher) {
4619
- this.#entityWatchers.push(entityWatcher);
4498
+ if (cookie.expires) {
4499
+ const { expires } = cookie;
4500
+ const dateString = toIMF(typeof expires === "number" ? new Date(expires) : expires);
4501
+ out.push(`Expires=${dateString}`);
4620
4502
  }
4621
- async emitEvent(event) {
4622
- const { eventName, payload, sender, routeContext: routerContext } = event;
4623
- this.#logger.debug(`Emitting '${eventName}' event.`, { eventName });
4624
- this.#logger.verbose(`Event payload: `, { payload });
4625
- await this.#eventManager.emit(eventName, sender, payload, routerContext);
4626
- // TODO: should move this logic into metaManager
4627
- // if (
4628
- // (eventName === "entity.create" || eventName === "entity.update" ||
4629
- // eventName === "entity.delete") &&
4630
- // payload.namespace === "meta"
4631
- // ) {
4632
- // await this.configureApplication();
4633
- // }
4503
+ if (cookie.unparsed) {
4504
+ out.push(cookie.unparsed.join("; "));
4634
4505
  }
4635
- registerService(name, service) {
4636
- this.#services.set(name, service);
4506
+ return out.join("; ");
4507
+ }
4508
+ /**
4509
+ * Validate Cookie Name.
4510
+ * @param name Cookie name.
4511
+ */
4512
+ function validateName(name) {
4513
+ if (name && !FIELD_CONTENT_REGEXP.test(name)) {
4514
+ throw new TypeError(`Invalid cookie name: "${name}".`);
4637
4515
  }
4638
- getService(name) {
4639
- return this.#services.get(name);
4516
+ }
4517
+ /**
4518
+ * Validate Path Value.
4519
+ * See {@link https://tools.ietf.org/html/rfc6265#section-4.1.2.4}.
4520
+ * @param path Path value.
4521
+ */
4522
+ function validatePath(path) {
4523
+ if (path == null) {
4524
+ return;
4640
4525
  }
4641
- registerCronJob(job) {
4642
- const jobDuplicate = lodash.find(this.#cronJobs, (item) => item.code === job.code);
4643
- if (jobDuplicate) {
4644
- this.#logger.warn(`Duplicated cron job with code "${job.code}"`);
4526
+ for (let i = 0; i < path.length; i++) {
4527
+ const c = path.charAt(i);
4528
+ if (c < String.fromCharCode(0x20) || c > String.fromCharCode(0x7e) || c == ";") {
4529
+ throw new Error(path + ": Invalid cookie path char '" + c + "'");
4645
4530
  }
4646
- this.#cronJobs.push(job);
4647
- }
4648
- listCronJobs() {
4649
- return [...this.#cronJobs, ...this.#appCronJobs];
4650
4531
  }
4651
- async start() {
4652
- this.#logger.info("Starting rapid server...");
4653
- const pluginManager = this.#pluginManager;
4654
- await pluginManager.loadPlugins(this.#plugins);
4655
- await pluginManager.initPlugins();
4656
- await pluginManager.registerMiddlewares();
4657
- await pluginManager.registerActionHandlers();
4658
- await pluginManager.registerEventHandlers();
4659
- await pluginManager.registerMessageHandlers();
4660
- await pluginManager.registerTaskProcessors();
4661
- this.#entityWatchers = this.#entityWatchers.concat(this.#appEntityWatchers);
4662
- for (const entityWatcher of this.#entityWatchers) {
4663
- if (entityWatcher.eventName === "entity.beforeCreate") {
4664
- this.#entityBeforeCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4665
- }
4666
- else if (entityWatcher.eventName === "entity.create") {
4667
- this.#entityCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4668
- }
4669
- else if (entityWatcher.eventName === "entity.beforeUpdate") {
4670
- this.#entityBeforeUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4671
- }
4672
- else if (entityWatcher.eventName === "entity.update") {
4673
- this.#entityUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4674
- }
4675
- else if (entityWatcher.eventName === "entity.beforeDelete") {
4676
- this.#entityBeforeDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4677
- }
4678
- else if (entityWatcher.eventName === "entity.delete") {
4679
- this.#entityDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4680
- }
4681
- else if (entityWatcher.eventName === "entity.addRelations") {
4682
- this.#entityAddRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4683
- }
4684
- else if (entityWatcher.eventName === "entity.removeRelations") {
4685
- this.#entityRemoveRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4686
- }
4687
- else if (entityWatcher.eventName === "entity.beforeResponse") {
4688
- this.#entityBeforeResponseEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4689
- }
4532
+ }
4533
+ /**
4534
+ * Validate Cookie Value.
4535
+ * See {@link https://tools.ietf.org/html/rfc6265#section-4.1}.
4536
+ * @param value Cookie value.
4537
+ */
4538
+ function validateValue(name, value) {
4539
+ if (value == null || name == null)
4540
+ return;
4541
+ for (let i = 0; i < value.length; i++) {
4542
+ const c = value.charAt(i);
4543
+ if (c < String.fromCharCode(0x21) ||
4544
+ c == String.fromCharCode(0x22) ||
4545
+ c == String.fromCharCode(0x2c) ||
4546
+ c == String.fromCharCode(0x3b) ||
4547
+ c == String.fromCharCode(0x5c) ||
4548
+ c == String.fromCharCode(0x7f)) {
4549
+ throw new Error("RFC2616 cookie '" + name + "' cannot contain character '" + c + "'");
4690
4550
  }
4691
- await this.configureApplication();
4692
- if (!this.#disableCronJobs) {
4693
- await pluginManager.registerCronJobs();
4551
+ if (c > String.fromCharCode(0x80)) {
4552
+ throw new Error("RFC2616 cookie '" + name + "' can only have US-ASCII chars as value" + c.charCodeAt(0).toString(16));
4694
4553
  }
4695
- this.#logger.info(`Rapid server ready.`);
4696
- await pluginManager.onApplicationReady(this.#applicationConfig);
4697
4554
  }
4698
- async configureApplication() {
4699
- this.#applicationConfig = lodash.cloneDeep(this.#bootstrapApplicationConfig);
4700
- const pluginManager = this.#pluginManager;
4701
- await pluginManager.onLoadingApplication(this.#applicationConfig);
4702
- await pluginManager.configureModels(this.#applicationConfig);
4703
- await pluginManager.configureModelProperties(this.#applicationConfig);
4704
- await pluginManager.configureServices(this.#applicationConfig);
4705
- await pluginManager.configureRoutes(this.#applicationConfig);
4706
- // TODO: check application configuration.
4707
- await pluginManager.onApplicationLoaded(this.#applicationConfig);
4708
- this.#buildedRoutes = await buildRoutes(this, this.#applicationConfig);
4555
+ }
4556
+ /**
4557
+ * Validate Cookie Domain.
4558
+ * See {@link https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3}.
4559
+ * @param domain Cookie domain.
4560
+ */
4561
+ function validateDomain(domain) {
4562
+ if (domain == null) {
4563
+ return;
4709
4564
  }
4710
- registerFacilityFactory(factory) {
4711
- this.#facilityFactories.set(factory.name, factory);
4565
+ const char1 = domain.charAt(0);
4566
+ const charN = domain.charAt(domain.length - 1);
4567
+ if (char1 == "-" || charN == "." || charN == "-") {
4568
+ throw new Error("Invalid first/last char in cookie domain: " + domain);
4712
4569
  }
4713
- async getFacility(name, options, nullIfUnknownFacility) {
4714
- const factory = this.#facilityFactories.get(name);
4715
- if (!factory) {
4716
- if (nullIfUnknownFacility) {
4717
- return null;
4718
- }
4719
- else {
4720
- throw new Error(`Failed to get facility. Unknown facility name: ${name}`);
4721
- }
4570
+ }
4571
+ /**
4572
+ * Parse cookies of a header
4573
+ *
4574
+ * @example
4575
+ * ```ts
4576
+ * import { getCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
4577
+ *
4578
+ * const headers = new Headers();
4579
+ * headers.set("Cookie", "full=of; tasty=chocolate");
4580
+ *
4581
+ * const cookies = getCookies(headers);
4582
+ * console.log(cookies); // { full: "of", tasty: "chocolate" }
4583
+ * ```
4584
+ *
4585
+ * @param headers The headers instance to get cookies from
4586
+ * @return Object with cookie names as keys
4587
+ */
4588
+ function getCookies(headers) {
4589
+ const cookie = headers.get("Cookie");
4590
+ if (cookie != null) {
4591
+ const out = {};
4592
+ const c = cookie.split(";");
4593
+ for (const kv of c) {
4594
+ const [cookieKey, ...cookieVal] = kv.split("=");
4595
+ assert(cookieKey != null);
4596
+ const key = cookieKey.trim();
4597
+ out[key] = cookieVal.join("=");
4722
4598
  }
4723
- return await factory.createFacility(this, options);
4599
+ return out;
4724
4600
  }
4725
- async queryDatabaseObject(sql, params, client, dropErrorLog) {
4726
- try {
4727
- return await this.#databaseAccessor.queryDatabaseObject(sql, params, client);
4728
- }
4729
- catch (err) {
4730
- if (!dropErrorLog) {
4731
- this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
4732
- }
4733
- throw err;
4601
+ return {};
4602
+ }
4603
+ /**
4604
+ * Set the cookie header properly in the headers
4605
+ *
4606
+ * @example
4607
+ * ```ts
4608
+ * import {
4609
+ * Cookie,
4610
+ * setCookie,
4611
+ * } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
4612
+ *
4613
+ * const headers = new Headers();
4614
+ * const cookie: Cookie = { name: "Space", value: "Cat" };
4615
+ * setCookie(headers, cookie);
4616
+ *
4617
+ * const cookieHeader = headers.get("set-cookie");
4618
+ * console.log(cookieHeader); // Space=Cat
4619
+ * ```
4620
+ *
4621
+ * @param headers The headers instance to set the cookie to
4622
+ * @param cookie Cookie to set
4623
+ */
4624
+ function setCookie(headers, cookie) {
4625
+ // Parsing cookie headers to make consistent set-cookie header
4626
+ // ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
4627
+ const v = toString(cookie);
4628
+ if (v) {
4629
+ headers.append("Set-Cookie", v);
4630
+ }
4631
+ }
4632
+ /**
4633
+ * Set the cookie header with empty value in the headers to delete it
4634
+ *
4635
+ * > Note: Deleting a `Cookie` will set its expiration date before now. Forcing
4636
+ * > the browser to delete it.
4637
+ *
4638
+ * @example
4639
+ * ```ts
4640
+ * import { deleteCookie } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
4641
+ *
4642
+ * const headers = new Headers();
4643
+ * deleteCookie(headers, "deno");
4644
+ *
4645
+ * const cookieHeader = headers.get("set-cookie");
4646
+ * console.log(cookieHeader); // deno=; Expires=Thus, 01 Jan 1970 00:00:00 GMT
4647
+ * ```
4648
+ *
4649
+ * @param headers The headers instance to delete the cookie from
4650
+ * @param name Name of cookie
4651
+ * @param attributes Additional cookie attributes
4652
+ */
4653
+ function deleteCookie(headers, name, attributes) {
4654
+ setCookie(headers, {
4655
+ name: name,
4656
+ value: "",
4657
+ expires: new Date(0),
4658
+ ...attributes,
4659
+ });
4660
+ }
4661
+ function parseSetCookie(value) {
4662
+ const attrs = value.split(";").map((attr) => {
4663
+ const [key, ...values] = attr.trim().split("=");
4664
+ return [key, values.join("=")];
4665
+ });
4666
+ const cookie = {
4667
+ name: attrs[0][0],
4668
+ value: attrs[0][1],
4669
+ };
4670
+ for (const [key, value] of attrs.slice(1)) {
4671
+ switch (key.toLocaleLowerCase()) {
4672
+ case "expires":
4673
+ cookie.expires = new Date(value);
4674
+ break;
4675
+ case "max-age":
4676
+ cookie.maxAge = Number(value);
4677
+ if (cookie.maxAge < 0) {
4678
+ console.warn("Max-Age must be an integer superior or equal to 0. Cookie ignored.");
4679
+ return null;
4680
+ }
4681
+ break;
4682
+ case "domain":
4683
+ cookie.domain = value;
4684
+ break;
4685
+ case "path":
4686
+ cookie.path = value;
4687
+ break;
4688
+ case "secure":
4689
+ cookie.secure = true;
4690
+ break;
4691
+ case "httponly":
4692
+ cookie.httpOnly = true;
4693
+ break;
4694
+ case "samesite":
4695
+ cookie.sameSite = value;
4696
+ break;
4697
+ default:
4698
+ if (!Array.isArray(cookie.unparsed)) {
4699
+ cookie.unparsed = [];
4700
+ }
4701
+ cookie.unparsed.push([key, value].join("="));
4734
4702
  }
4735
4703
  }
4736
- async tryQueryDatabaseObject(sql, params, client, dropErrorLog) {
4737
- try {
4738
- return await this.queryDatabaseObject(sql, params, client);
4739
- }
4740
- catch (err) {
4741
- if (!dropErrorLog) {
4742
- this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
4743
- }
4704
+ if (cookie.name.startsWith("__Secure-")) {
4705
+ /** This requirement is mentioned in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie but not the RFC. */
4706
+ if (!cookie.secure) {
4707
+ console.warn("Cookies with names starting with `__Secure-` must be set with the secure flag. Cookie ignored.");
4708
+ return null;
4744
4709
  }
4745
- return [];
4746
- }
4747
- get middlewares() {
4748
- return this.#middlewares;
4749
4710
  }
4750
- async handleRequest(request, next) {
4751
- const rapidRequest = new RapidRequest(this, request);
4752
- await rapidRequest.parseBody();
4753
- const routeContext = new RouteContext(this, rapidRequest);
4754
- const { response } = routeContext;
4755
- try {
4756
- await this.#pluginManager.onPrepareRouteContext(routeContext);
4757
- await this.#buildedRoutes(routeContext, next);
4711
+ if (cookie.name.startsWith("__Host-")) {
4712
+ if (!cookie.secure) {
4713
+ console.warn("Cookies with names starting with `__Host-` must be set with the secure flag. Cookie ignored.");
4714
+ return null;
4758
4715
  }
4759
- catch (ex) {
4760
- let error;
4761
- if (lodash.isString(ex)) {
4762
- error = {
4763
- message: ex,
4764
- };
4765
- }
4766
- else {
4767
- error = { name: ex.name, message: ex.message, stack: ex.stack };
4768
- }
4769
- this.#logger.error("handle request error.", { error });
4770
- response.json({ error }, 500);
4716
+ if (cookie.domain !== undefined) {
4717
+ console.warn("Cookies with names starting with `__Host-` must not have a domain specified. Cookie ignored.");
4718
+ return null;
4771
4719
  }
4772
- if (!response.status && !response.body) {
4773
- response.json({
4774
- error: {
4775
- message: "No route handler was found to handle this request.",
4776
- },
4777
- }, 404);
4720
+ if (cookie.path !== "/") {
4721
+ console.warn("Cookies with names starting with `__Host-` must have path be `/`. Cookie has been ignored.");
4722
+ return null;
4778
4723
  }
4779
- return response.getResponse();
4780
- }
4781
- async beforeRunRouteActions(handlerContext) {
4782
- await this.#pluginManager.beforeRunRouteActions(handlerContext);
4783
4724
  }
4784
- async beforeCreateEntity(model, options) {
4785
- await this.#pluginManager.beforeCreateEntity(model, options);
4786
- }
4787
- async beforeUpdateEntity(model, options, currentEntity) {
4788
- await this.#pluginManager.beforeUpdateEntity(model, options, currentEntity);
4725
+ return cookie;
4726
+ }
4727
+ /**
4728
+ * Parse set-cookies of a header
4729
+ *
4730
+ * @example
4731
+ * ```ts
4732
+ * import { getSetCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
4733
+ *
4734
+ * const headers = new Headers([
4735
+ * ["Set-Cookie", "lulu=meow; Secure; Max-Age=3600"],
4736
+ * ["Set-Cookie", "booya=kasha; HttpOnly; Path=/"],
4737
+ * ]);
4738
+ *
4739
+ * const cookies = getSetCookies(headers);
4740
+ * console.log(cookies); // [{ name: "lulu", value: "meow", secure: true, maxAge: 3600 }, { name: "booya", value: "kahsa", httpOnly: true, path: "/ }]
4741
+ * ```
4742
+ *
4743
+ * @param headers The headers instance to get set-cookies from
4744
+ * @return List of cookies
4745
+ */
4746
+ function getSetCookies(headers) {
4747
+ // TODO(lino-levan): remove this ts-ignore when Typescript 5.2 lands in Deno
4748
+ // @ts-ignore Typescript's TS Dom types will be out of date until 5.2
4749
+ return headers
4750
+ .getSetCookie()
4751
+ /** Parse each `set-cookie` header separately */
4752
+ .map(parseSetCookie)
4753
+ /** Skip empty cookies */
4754
+ .filter(Boolean);
4755
+ }
4756
+
4757
+ const GlobalRequest = global.Request;
4758
+ class RapidRequest {
4759
+ #logger;
4760
+ #raw;
4761
+ #bodyParsed;
4762
+ #body;
4763
+ #headers;
4764
+ #parsedCookies;
4765
+ method;
4766
+ url;
4767
+ ip;
4768
+ constructor(server, req) {
4769
+ this.#logger = server.getLogger();
4770
+ this.#raw = req;
4771
+ this.method = req.method;
4772
+ this.url = new URL(req.url);
4773
+ this.#headers = req.headers;
4789
4774
  }
4790
- async #handleEntityEvent(eventName, sender, payload, routerContext) {
4791
- const { modelSingularCode, baseModelSingularCode } = payload;
4792
- if (!routerContext) {
4793
- routerContext = new RouteContext(this);
4794
- }
4795
- const entityWatchHandlerContext = {
4796
- server: this,
4797
- payload,
4798
- routerContext,
4799
- };
4800
- let emitter;
4801
- if (eventName === "entity.beforeCreate") {
4802
- emitter = this.#entityBeforeCreateEventEmitters;
4803
- }
4804
- else if (eventName === "entity.create") {
4805
- emitter = this.#entityCreateEventEmitters;
4806
- }
4807
- else if (eventName === "entity.beforeUpdate") {
4808
- emitter = this.#entityBeforeUpdateEventEmitters;
4775
+ async parseBody() {
4776
+ if (this.#bodyParsed) {
4777
+ this.#logger.warn("Request body has been parsed. 'parseBody()' method should not be called more than once.");
4778
+ return;
4809
4779
  }
4810
- else if (eventName === "entity.update") {
4811
- emitter = this.#entityUpdateEventEmitters;
4780
+ const requestMethod = this.method;
4781
+ if (requestMethod !== "POST" && requestMethod !== "PUT" && requestMethod !== "PATCH") {
4782
+ this.#body = null;
4783
+ this.#bodyParsed = true;
4784
+ return;
4812
4785
  }
4813
- else if (eventName === "entity.beforeDelete") {
4814
- emitter = this.#entityBeforeDeleteEventEmitters;
4786
+ const contentLength = parseInt(this.#headers.get("Content-Length") || "0", 10);
4787
+ if (!contentLength) {
4788
+ this.#body = null;
4789
+ this.#bodyParsed = true;
4790
+ return;
4815
4791
  }
4816
- else if (eventName === "entity.delete") {
4817
- emitter = this.#entityDeleteEventEmitters;
4792
+ const req = this.#raw;
4793
+ const contentType = this.#headers.get("Content-Type") || "application/json";
4794
+ if (contentType.includes("json")) {
4795
+ this.#body = {
4796
+ type: "json",
4797
+ value: await req.json(),
4798
+ };
4818
4799
  }
4819
- else if (eventName === "entity.addRelations") {
4820
- emitter = this.#entityAddRelationsEventEmitters;
4800
+ else if (contentType.startsWith("application/x-www-form-urlencoded")) {
4801
+ const bodyText = await req.text();
4802
+ this.#body = {
4803
+ type: "form",
4804
+ value: qs__default["default"].parse(bodyText),
4805
+ };
4821
4806
  }
4822
- else if (eventName === "entity.removeRelations") {
4823
- emitter = this.#entityRemoveRelationsEventEmitters;
4807
+ else if (contentType.startsWith("multipart/form-data")) {
4808
+ this.#body = {
4809
+ type: "form-data",
4810
+ value: await parseFormDataBody(req),
4811
+ };
4824
4812
  }
4825
- else if (eventName === "entity.beforeResponse") {
4826
- emitter = this.#entityBeforeResponseEventEmitters;
4813
+ this.#bodyParsed = true;
4814
+ }
4815
+ get rawRequest() {
4816
+ return this.#raw;
4817
+ }
4818
+ get headers() {
4819
+ return this.#headers;
4820
+ }
4821
+ get cookies() {
4822
+ if (!this.#parsedCookies) {
4823
+ this.#parsedCookies = getCookies(this.#headers);
4827
4824
  }
4828
- await emitter.emit(modelSingularCode, entityWatchHandlerContext);
4829
- if (baseModelSingularCode) {
4830
- await emitter.emit(baseModelSingularCode, entityWatchHandlerContext);
4825
+ return this.#parsedCookies;
4826
+ }
4827
+ get body() {
4828
+ if (!this.#bodyParsed) {
4829
+ throw new Error("Request body not parsed, you should call 'parseBody()' method before getting the body.");
4831
4830
  }
4831
+ return this.#body;
4832
4832
  }
4833
4833
  }
4834
4834
 
@@ -5372,9 +5372,9 @@ class MetaService {
5372
5372
  await server.tryQueryDatabaseObject(`COMMENT ON TABLE ${queryBuilder.quoteTable(model)} IS ${queryBuilder.formatValueToSqlLiteral(model.name)};`, []);
5373
5373
  }
5374
5374
  }
5375
- const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
5376
- FROM information_schema.columns c
5377
- INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
5375
+ const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
5376
+ FROM information_schema.columns c
5377
+ INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
5378
5378
  LEFT JOIN pg_catalog.pg_description d ON (d.objoid = st.relid and d.objsubid = c.ordinal_position);`;
5379
5379
  const columnsInDb = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
5380
5380
  for (const model of applicationConfig.models) {
@@ -9481,9 +9481,9 @@ class EntityAccessControlPlugin {
9481
9481
  if (!userId) {
9482
9482
  return;
9483
9483
  }
9484
- const actions = await server.queryDatabaseObject(`select distinct a.* from sys_actions a
9485
- inner join oc_role_sys_action_links ra on a.id = ra.action_id
9486
- inner join oc_role_user_links ru on ru.role_id = ra.role_id
9484
+ const actions = await server.queryDatabaseObject(`select distinct a.* from sys_actions a
9485
+ inner join oc_role_sys_action_links ra on a.id = ra.action_id
9486
+ inner join oc_role_user_links ru on ru.role_id = ra.role_id
9487
9487
  where ru.user_id = $1;`, [userId]);
9488
9488
  routeContext.state.allowedActions = actions.map((item) => item.code);
9489
9489
  }