@ruiapp/rapid-core 0.8.20 → 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 +746 -741
  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 -26
  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",
@@ -2762,11 +2283,16 @@ async function validateEntity(server, model, entity) {
2762
2283
  for (const propCode in entity) {
2763
2284
  let prop = getEntityPropertyByCode(server, model, propCode);
2764
2285
  if (!prop) {
2765
- getEntityPropertyByFieldName(server, model, propCode);
2286
+ prop = getEntityPropertyByFieldName(server, model, propCode);
2766
2287
  }
2767
2288
  if (!prop) {
2768
2289
  continue;
2769
2290
  }
2291
+ if (propCode === "id") {
2292
+ if (lodash.isNil(entity[propCode])) {
2293
+ delete entity[propCode];
2294
+ }
2295
+ }
2770
2296
  if (prop.type === "date") {
2771
2297
  const originValue = entity[propCode];
2772
2298
  if (originValue) {
@@ -3289,8 +2815,8 @@ async function findManyRelationLinksViaLinkTable(options) {
3289
2815
  const command = `SELECT * FROM ${server.queryBuilder.quoteTable({
3290
2816
  schema: relationProperty.linkSchema,
3291
2817
  tableName: relationProperty.linkTableName,
3292
- })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])
3293
- ORDER BY id
2818
+ })} WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = ANY($1::int[])
2819
+ ORDER BY id
3294
2820
  `;
3295
2821
  const params = [mainEntityIds];
3296
2822
  const relationLinks = await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
@@ -3921,7 +3447,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
3921
3447
  await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
3922
3448
  schema: property.linkSchema,
3923
3449
  tableName: property.linkTableName,
3924
- })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1
3450
+ })} WHERE ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = $1
3925
3451
  AND ${server.queryBuilder.quoteObject(property.targetIdColumnName)} <> ALL($2::int[])`, [id, targetIdsToKeep], routeContext?.getDbTransactionClient());
3926
3452
  }
3927
3453
  else {
@@ -3950,8 +3476,8 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
3950
3476
  await server.queryDatabaseObject(`UPDATE ${server.queryBuilder.quoteTable({
3951
3477
  schema: relationModel.schema,
3952
3478
  tableName: relationModel.tableName,
3953
- })}
3954
- SET ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = null
3479
+ })}
3480
+ SET ${server.queryBuilder.quoteObject(property.selfIdColumnName)} = null
3955
3481
  WHERE id = ANY($1::int[])`, [targetIdsToRemove], routeContext?.getDbTransactionClient());
3956
3482
  }
3957
3483
  else {
@@ -4236,7 +3762,7 @@ async function deleteEntityById(server, dataAccessor, options, plugin) {
4236
3762
  await server.queryDatabaseObject(`DELETE FROM ${server.queryBuilder.quoteTable({
4237
3763
  schema: relationProperty.linkSchema,
4238
3764
  tableName: relationProperty.linkTableName,
4239
- })}
3765
+ })}
4240
3766
  WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
4241
3767
  }
4242
3768
  else {
@@ -4246,8 +3772,8 @@ async function deleteEntityById(server, dataAccessor, options, plugin) {
4246
3772
  await server.queryDatabaseObject(`UPDATE ${server.queryBuilder.quoteTable({
4247
3773
  schema: relationModel.schema,
4248
3774
  tableName: relationModel.tableName,
4249
- })}
4250
- SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = null
3775
+ })}
3776
+ SET ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = null
4251
3777
  WHERE ${server.queryBuilder.quoteObject(relationProperty.selfIdColumnName)} = $1`, [id], routeContext?.getDbTransactionClient());
4252
3778
  }
4253
3779
  }
@@ -4344,11 +3870,11 @@ class EntityManager {
4344
3870
  const command = `INSERT INTO ${queryBuilder.quoteTable({
4345
3871
  schema: relationProperty.linkSchema,
4346
3872
  tableName: relationProperty.linkTableName,
4347
- })} (${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)})
4348
- SELECT $1, $2 WHERE NOT EXISTS (
4349
- SELECT ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}, ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}
4350
- FROM ${queryBuilder.quoteTable({ schema: relationProperty.linkSchema, tableName: relationProperty.linkTableName })}
4351
- 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
4352
3878
  )`;
4353
3879
  const params = [id, relation.id];
4354
3880
  await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
@@ -4388,7 +3914,7 @@ class EntityManager {
4388
3914
  const { queryBuilder } = server;
4389
3915
  if (relationProperty.linkTableName) {
4390
3916
  for (const relation of relations) {
4391
- 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 })}
4392
3918
  WHERE ${queryBuilder.quoteObject(relationProperty.selfIdColumnName)}=$1 AND ${queryBuilder.quoteObject(relationProperty.targetIdColumnName)}=$2;`;
4393
3919
  const params = [id, relation.id];
4394
3920
  await server.queryDatabaseObject(command, params, routeContext?.getDbTransactionClient());
@@ -4535,295 +4061,774 @@ class RapidServer {
4535
4061
  }
4536
4062
  }
4537
4063
  }
4538
- if (routes) {
4539
- for (const route of routes) {
4540
- const originalRoute = lodash.find(this.#applicationConfig.routes, (item) => item.code == route.code);
4541
- if (originalRoute) {
4542
- originalRoute.name = route.name;
4543
- originalRoute.actions = route.actions;
4544
- }
4545
- else {
4546
- this.#applicationConfig.routes.push(route);
4547
- }
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 });
4548
4264
  }
4549
4265
  }
4266
+ return [];
4550
4267
  }
4551
- appendModelProperties(modelSingularCode, properties) {
4552
- const originalModel = lodash.find(this.#applicationConfig.models, (item) => item.singularCode == modelSingularCode);
4553
- if (!originalModel) {
4554
- 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);
4555
4278
  }
4556
- const originalProperties = originalModel.properties;
4557
- for (const property of properties) {
4558
- const originalProperty = lodash.find(originalProperties, (item) => item.code == property.code);
4559
- if (originalProperty) {
4560
- originalProperty.name = property.name;
4279
+ catch (ex) {
4280
+ let error;
4281
+ if (lodash.isString(ex)) {
4282
+ error = {
4283
+ message: ex,
4284
+ };
4561
4285
  }
4562
4286
  else {
4563
- originalProperties.push(property);
4287
+ error = { name: ex.name, message: ex.message, stack: ex.stack };
4564
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);
4565
4298
  }
4299
+ return response.getResponse();
4566
4300
  }
4567
- registerActionHandler(plugin, options) {
4568
- const handler = lodash.bind(options.handler, null, plugin);
4569
- this.#actionHandlersMapByCode.set(options.code, handler);
4301
+ async beforeRunRouteActions(handlerContext) {
4302
+ await this.#pluginManager.beforeRunRouteActions(handlerContext);
4570
4303
  }
4571
- getActionHandlerByCode(code) {
4572
- return this.#actionHandlersMapByCode.get(code);
4304
+ async beforeCreateEntity(model, options) {
4305
+ await this.#pluginManager.beforeCreateEntity(model, options);
4573
4306
  }
4574
- registerMiddleware(middleware) {
4575
- this.#middlewares.push(middleware);
4307
+ async beforeUpdateEntity(model, options, currentEntity) {
4308
+ await this.#pluginManager.beforeUpdateEntity(model, options, currentEntity);
4576
4309
  }
4577
- getDataAccessor(options) {
4578
- const { namespace, singularCode } = options;
4579
- let dataAccessor = this.#cachedDataAccessors.get(singularCode);
4580
- if (dataAccessor) {
4581
- return dataAccessor;
4310
+ async #handleEntityEvent(eventName, sender, payload, routerContext) {
4311
+ const { modelSingularCode, baseModelSingularCode } = payload;
4312
+ if (!routerContext) {
4313
+ routerContext = new RouteContext(this);
4582
4314
  }
4583
- const model = this.getModel(options);
4584
- if (!model) {
4585
- 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;
4586
4323
  }
4587
- dataAccessor = new DataAccessor(this, this.#databaseAccessor, {
4588
- model,
4589
- queryBuilder: this.queryBuilder,
4590
- });
4591
- this.#cachedDataAccessors.set(singularCode, dataAccessor);
4592
- 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}`);
4593
4486
  }
4594
- getModel(options) {
4595
- if (options.namespace) {
4596
- return this.#applicationConfig?.models.find((e) => e.namespace === options.namespace && e.singularCode === options.singularCode);
4597
- }
4598
- return this.#applicationConfig?.models.find((e) => e.singularCode === options.singularCode);
4487
+ if (cookie.domain) {
4488
+ validateDomain(cookie.domain);
4489
+ out.push(`Domain=${cookie.domain}`);
4599
4490
  }
4600
- getEntityManager(singularCode) {
4601
- let entityManager = this.#cachedEntityManager.get(singularCode);
4602
- if (entityManager) {
4603
- return entityManager;
4604
- }
4605
- const dataAccessor = this.getDataAccessor({ singularCode });
4606
- entityManager = new EntityManager(this, dataAccessor);
4607
- this.#cachedEntityManager.set(singularCode, entityManager);
4608
- return entityManager;
4491
+ if (cookie.sameSite) {
4492
+ out.push(`SameSite=${cookie.sameSite}`);
4609
4493
  }
4610
- registerEventHandler(eventName, listener) {
4611
- this.#eventManager.on(eventName, listener);
4494
+ if (cookie.path) {
4495
+ validatePath(cookie.path);
4496
+ out.push(`Path=${cookie.path}`);
4612
4497
  }
4613
- registerEntityWatcher(entityWatcher) {
4614
- 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}`);
4615
4502
  }
4616
- async emitEvent(event) {
4617
- const { eventName, payload, sender, routeContext: routerContext } = event;
4618
- this.#logger.debug(`Emitting '${eventName}' event.`, { eventName });
4619
- this.#logger.verbose(`Event payload: `, { payload });
4620
- await this.#eventManager.emit(eventName, sender, payload, routerContext);
4621
- // TODO: should move this logic into metaManager
4622
- // if (
4623
- // (eventName === "entity.create" || eventName === "entity.update" ||
4624
- // eventName === "entity.delete") &&
4625
- // payload.namespace === "meta"
4626
- // ) {
4627
- // await this.configureApplication();
4628
- // }
4503
+ if (cookie.unparsed) {
4504
+ out.push(cookie.unparsed.join("; "));
4629
4505
  }
4630
- registerService(name, service) {
4631
- 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}".`);
4632
4515
  }
4633
- getService(name) {
4634
- 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;
4635
4525
  }
4636
- registerCronJob(job) {
4637
- const jobDuplicate = lodash.find(this.#cronJobs, (item) => item.code === job.code);
4638
- if (jobDuplicate) {
4639
- 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 + "'");
4640
4530
  }
4641
- this.#cronJobs.push(job);
4642
- }
4643
- listCronJobs() {
4644
- return [...this.#cronJobs, ...this.#appCronJobs];
4645
4531
  }
4646
- async start() {
4647
- this.#logger.info("Starting rapid server...");
4648
- const pluginManager = this.#pluginManager;
4649
- await pluginManager.loadPlugins(this.#plugins);
4650
- await pluginManager.initPlugins();
4651
- await pluginManager.registerMiddlewares();
4652
- await pluginManager.registerActionHandlers();
4653
- await pluginManager.registerEventHandlers();
4654
- await pluginManager.registerMessageHandlers();
4655
- await pluginManager.registerTaskProcessors();
4656
- this.#entityWatchers = this.#entityWatchers.concat(this.#appEntityWatchers);
4657
- for (const entityWatcher of this.#entityWatchers) {
4658
- if (entityWatcher.eventName === "entity.beforeCreate") {
4659
- this.#entityBeforeCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4660
- }
4661
- else if (entityWatcher.eventName === "entity.create") {
4662
- this.#entityCreateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4663
- }
4664
- else if (entityWatcher.eventName === "entity.beforeUpdate") {
4665
- this.#entityBeforeUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4666
- }
4667
- else if (entityWatcher.eventName === "entity.update") {
4668
- this.#entityUpdateEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4669
- }
4670
- else if (entityWatcher.eventName === "entity.beforeDelete") {
4671
- this.#entityBeforeDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4672
- }
4673
- else if (entityWatcher.eventName === "entity.delete") {
4674
- this.#entityDeleteEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4675
- }
4676
- else if (entityWatcher.eventName === "entity.addRelations") {
4677
- this.#entityAddRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4678
- }
4679
- else if (entityWatcher.eventName === "entity.removeRelations") {
4680
- this.#entityRemoveRelationsEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4681
- }
4682
- else if (entityWatcher.eventName === "entity.beforeResponse") {
4683
- this.#entityBeforeResponseEventEmitters.on(entityWatcher.modelSingularCode, entityWatcher.handler);
4684
- }
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 + "'");
4685
4550
  }
4686
- await this.configureApplication();
4687
- if (!this.#disableCronJobs) {
4688
- 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));
4689
4553
  }
4690
- this.#logger.info(`Rapid server ready.`);
4691
- await pluginManager.onApplicationReady(this.#applicationConfig);
4692
4554
  }
4693
- async configureApplication() {
4694
- this.#applicationConfig = lodash.cloneDeep(this.#bootstrapApplicationConfig);
4695
- const pluginManager = this.#pluginManager;
4696
- await pluginManager.onLoadingApplication(this.#applicationConfig);
4697
- await pluginManager.configureModels(this.#applicationConfig);
4698
- await pluginManager.configureModelProperties(this.#applicationConfig);
4699
- await pluginManager.configureServices(this.#applicationConfig);
4700
- await pluginManager.configureRoutes(this.#applicationConfig);
4701
- // TODO: check application configuration.
4702
- await pluginManager.onApplicationLoaded(this.#applicationConfig);
4703
- 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;
4704
4564
  }
4705
- registerFacilityFactory(factory) {
4706
- 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);
4707
4569
  }
4708
- async getFacility(name, options, nullIfUnknownFacility) {
4709
- const factory = this.#facilityFactories.get(name);
4710
- if (!factory) {
4711
- if (nullIfUnknownFacility) {
4712
- return null;
4713
- }
4714
- else {
4715
- throw new Error(`Failed to get facility. Unknown facility name: ${name}`);
4716
- }
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("=");
4717
4598
  }
4718
- return await factory.createFacility(this, options);
4599
+ return out;
4719
4600
  }
4720
- async queryDatabaseObject(sql, params, client, dropErrorLog) {
4721
- try {
4722
- return await this.#databaseAccessor.queryDatabaseObject(sql, params, client);
4723
- }
4724
- catch (err) {
4725
- if (!dropErrorLog) {
4726
- this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
4727
- }
4728
- 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("="));
4729
4702
  }
4730
4703
  }
4731
- async tryQueryDatabaseObject(sql, params, client, dropErrorLog) {
4732
- try {
4733
- return await this.queryDatabaseObject(sql, params, client);
4734
- }
4735
- catch (err) {
4736
- if (!dropErrorLog) {
4737
- this.#logger.error("Failed to query database object.", { errorMessage: err.message, sql, params });
4738
- }
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;
4739
4709
  }
4740
- return [];
4741
- }
4742
- get middlewares() {
4743
- return this.#middlewares;
4744
4710
  }
4745
- async handleRequest(request, next) {
4746
- const rapidRequest = new RapidRequest(this, request);
4747
- await rapidRequest.parseBody();
4748
- const routeContext = new RouteContext(this, rapidRequest);
4749
- const { response } = routeContext;
4750
- try {
4751
- await this.#pluginManager.onPrepareRouteContext(routeContext);
4752
- 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;
4753
4715
  }
4754
- catch (ex) {
4755
- let error;
4756
- if (lodash.isString(ex)) {
4757
- error = {
4758
- message: ex,
4759
- };
4760
- }
4761
- else {
4762
- error = { name: ex.name, message: ex.message, stack: ex.stack };
4763
- }
4764
- this.#logger.error("handle request error.", { error });
4765
- 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;
4766
4719
  }
4767
- if (!response.status && !response.body) {
4768
- response.json({
4769
- error: {
4770
- message: "No route handler was found to handle this request.",
4771
- },
4772
- }, 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;
4773
4723
  }
4774
- return response.getResponse();
4775
- }
4776
- async beforeRunRouteActions(handlerContext) {
4777
- await this.#pluginManager.beforeRunRouteActions(handlerContext);
4778
4724
  }
4779
- async beforeCreateEntity(model, options) {
4780
- await this.#pluginManager.beforeCreateEntity(model, options);
4781
- }
4782
- async beforeUpdateEntity(model, options, currentEntity) {
4783
- 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;
4784
4774
  }
4785
- async #handleEntityEvent(eventName, sender, payload, routerContext) {
4786
- const { modelSingularCode, baseModelSingularCode } = payload;
4787
- if (!routerContext) {
4788
- routerContext = new RouteContext(this);
4789
- }
4790
- const entityWatchHandlerContext = {
4791
- server: this,
4792
- payload,
4793
- routerContext,
4794
- };
4795
- let emitter;
4796
- if (eventName === "entity.beforeCreate") {
4797
- emitter = this.#entityBeforeCreateEventEmitters;
4798
- }
4799
- else if (eventName === "entity.create") {
4800
- emitter = this.#entityCreateEventEmitters;
4801
- }
4802
- else if (eventName === "entity.beforeUpdate") {
4803
- 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;
4804
4779
  }
4805
- else if (eventName === "entity.update") {
4806
- 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;
4807
4785
  }
4808
- else if (eventName === "entity.beforeDelete") {
4809
- 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;
4810
4791
  }
4811
- else if (eventName === "entity.delete") {
4812
- 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
+ };
4813
4799
  }
4814
- else if (eventName === "entity.addRelations") {
4815
- 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
+ };
4816
4806
  }
4817
- else if (eventName === "entity.removeRelations") {
4818
- emitter = this.#entityRemoveRelationsEventEmitters;
4807
+ else if (contentType.startsWith("multipart/form-data")) {
4808
+ this.#body = {
4809
+ type: "form-data",
4810
+ value: await parseFormDataBody(req),
4811
+ };
4819
4812
  }
4820
- else if (eventName === "entity.beforeResponse") {
4821
- 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);
4822
4824
  }
4823
- await emitter.emit(modelSingularCode, entityWatchHandlerContext);
4824
- if (baseModelSingularCode) {
4825
- 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.");
4826
4830
  }
4831
+ return this.#body;
4827
4832
  }
4828
4833
  }
4829
4834
 
@@ -5367,9 +5372,9 @@ class MetaService {
5367
5372
  await server.tryQueryDatabaseObject(`COMMENT ON TABLE ${queryBuilder.quoteTable(model)} IS ${queryBuilder.formatValueToSqlLiteral(model.name)};`, []);
5368
5373
  }
5369
5374
  }
5370
- 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
5371
- FROM information_schema.columns c
5372
- 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)
5373
5378
  LEFT JOIN pg_catalog.pg_description d ON (d.objoid = st.relid and d.objsubid = c.ordinal_position);`;
5374
5379
  const columnsInDb = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
5375
5380
  for (const model of applicationConfig.models) {
@@ -9476,9 +9481,9 @@ class EntityAccessControlPlugin {
9476
9481
  if (!userId) {
9477
9482
  return;
9478
9483
  }
9479
- const actions = await server.queryDatabaseObject(`select distinct a.* from sys_actions a
9480
- inner join oc_role_sys_action_links ra on a.id = ra.action_id
9481
- 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
9482
9487
  where ru.user_id = $1;`, [userId]);
9483
9488
  routeContext.state.allowedActions = actions.map((item) => item.code);
9484
9489
  }