@joystick.js/node-canary 0.0.0-canary.33 → 0.0.0-canary.331

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 (92) hide show
  1. package/_package.json +2 -3
  2. package/dist/action/class.js +21 -0
  3. package/dist/api/get.js +15 -13
  4. package/dist/api/set.js +15 -13
  5. package/dist/app/accounts/createMetadataTableColumns.js +12 -0
  6. package/dist/app/accounts/deleteUser.js +7 -0
  7. package/dist/app/accounts/generateSession.js +2 -4
  8. package/dist/app/accounts/getBrowserSafeUser.js +5 -2
  9. package/dist/app/accounts/hasLoginTokenExpired.js +1 -2
  10. package/dist/app/accounts/index.js +2 -0
  11. package/dist/app/accounts/login.js +6 -0
  12. package/dist/app/accounts/recoverPassword.js +3 -0
  13. package/dist/app/accounts/resetPassword.js +6 -0
  14. package/dist/app/accounts/signup.js +50 -7
  15. package/dist/app/accounts/verifyEmail.js +3 -0
  16. package/dist/app/cluster.js +26 -0
  17. package/dist/app/databases/generate_sql_from_object.js +60 -0
  18. package/dist/app/databases/mongodb/buildConnectionString.js +1 -1
  19. package/dist/app/databases/mongodb/createAccountsIndexes.js +18 -0
  20. package/dist/app/databases/mongodb/createSessionsIndexes.js +10 -0
  21. package/dist/app/databases/mongodb/index.js +1 -1
  22. package/dist/app/databases/mongodb/queries/accounts.js +8 -1
  23. package/dist/app/databases/mongodb/queries/queues.js +51 -28
  24. package/dist/app/databases/mongodb/queries/sessions.js +26 -0
  25. package/dist/app/databases/postgresql/createSessionsIndexes.js +10 -0
  26. package/dist/app/databases/postgresql/createSessionsTables.js +14 -0
  27. package/dist/app/databases/postgresql/handleCleanupQueues.js +36 -0
  28. package/dist/app/databases/postgresql/index.js +88 -2
  29. package/dist/app/databases/postgresql/queries/accounts.js +5 -2
  30. package/dist/app/databases/postgresql/queries/queues.js +83 -38
  31. package/dist/app/databases/postgresql/queries/sessions.js +43 -0
  32. package/dist/app/databases/queryMap.js +6 -2
  33. package/dist/app/databases/stringToSnakeCase.js +6 -0
  34. package/dist/app/getBrowserSafeRequest.js +3 -2
  35. package/dist/app/index.js +229 -78
  36. package/dist/app/initExpress.js +1 -1
  37. package/dist/app/middleware/csp.js +2 -2
  38. package/dist/app/middleware/getTranslations.js +64 -0
  39. package/dist/app/middleware/get_insecure_landing_page_html.js +71 -0
  40. package/dist/app/middleware/hmr/client.js +13 -9
  41. package/dist/app/middleware/index.js +6 -5
  42. package/dist/app/middleware/insecure.js +3 -4
  43. package/dist/app/middleware/render.js +8 -66
  44. package/dist/app/middleware/session.js +12 -11
  45. package/dist/app/queues/index.js +82 -32
  46. package/dist/app/registerGetters.js +5 -6
  47. package/dist/app/registerSetters.js +5 -6
  48. package/dist/app/runGetter.js +17 -5
  49. package/dist/app/runSessionQuery.js +15 -0
  50. package/dist/app/runSetter.js +17 -5
  51. package/dist/app/sanitizeAPIResponse.js +1 -1
  52. package/dist/app/validateInstanceToken.js +16 -0
  53. package/dist/app/validateSession.js +8 -3
  54. package/dist/app/validateUploaderOptions.js +3 -3
  55. package/dist/app/validateUploads.js +12 -1
  56. package/dist/email/send.js +7 -1
  57. package/dist/email/templates/reset-password.js +0 -1
  58. package/dist/fixture/index.js +40 -0
  59. package/dist/index.js +19 -0
  60. package/dist/lib/escapeKeyValuePair.js +13 -0
  61. package/dist/lib/formatAPIError.js +0 -1
  62. package/dist/lib/getBuildPath.js +1 -1
  63. package/dist/lib/getSSLCertificates.js +3 -3
  64. package/dist/lib/importFile.js +7 -0
  65. package/dist/lib/isValidJSONString.js +1 -1
  66. package/dist/lib/log.js +0 -3
  67. package/dist/lib/objectToSQLKeysString.js +1 -1
  68. package/dist/lib/objectToSQLValuesString.js +1 -1
  69. package/dist/lib/serializeQueryParameters.js +1 -1
  70. package/dist/lib/timestamps.js +47 -0
  71. package/dist/lib/wait.js +8 -0
  72. package/dist/push/logs/index.js +35 -17
  73. package/dist/settings/load.js +3 -5
  74. package/dist/ssr/compileCSS.js +4 -4
  75. package/dist/ssr/findComponentInTree.js +1 -1
  76. package/dist/ssr/getAPIForDataFunctions.js +35 -0
  77. package/dist/ssr/getDataFromComponent.js +15 -0
  78. package/dist/ssr/index.js +19 -45
  79. package/dist/ssr/replaceWhenTags.js +2 -3
  80. package/dist/ssr/setHeadTagsInHTML.js +3 -3
  81. package/dist/test/index.js +9 -0
  82. package/dist/test/trackFunctionCall.js +17 -0
  83. package/dist/validation/inputWithSchema/index.js +3 -3
  84. package/dist/validation/schema/index.js +5 -5
  85. package/dist/websockets/index.js +4 -0
  86. package/getSanitizedContext.js +43 -0
  87. package/package.json +2 -2
  88. package/dist/app/accounts/roles/index.test.js +0 -123
  89. package/dist/app/index.test.js +0 -575
  90. package/dist/app/middleware/sanitizeRequestParameters.js +0 -26
  91. package/dist/email/send.test.js +0 -37
  92. package/dist/validation/index.test.js +0 -463
@@ -0,0 +1,71 @@
1
+ var get_insecure_landing_page_html_default = (hostname = "", path = "") => {
2
+ const url = `https://${hostname}${path}`;
3
+ return `
4
+ <html>
5
+ <head>
6
+ <title>Insecure Connection</title>
7
+ <style type="text/css">
8
+ body {
9
+ font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
10
+ font-size: 16px;
11
+ line-height: 24px;
12
+ background: #fafafa;
13
+ display: flex;
14
+ margin: 0;
15
+ padding: 20px;
16
+ justify-content: center;
17
+ }
18
+
19
+ .warning {
20
+ width: 100%;
21
+ max-width: 500px;
22
+ background: #fff;
23
+ border: 1px solid #eee;
24
+ border-radius: 3px;
25
+ padding: 35px;
26
+ align-self: flex-start;
27
+ }
28
+
29
+ .warning h1 {
30
+ color: #000;
31
+ font-size: 22px;
32
+ line-height: 30px;
33
+ margin: 0;
34
+ }
35
+
36
+ .warning p {
37
+ font-size: 16px;
38
+ line-height: 25px;
39
+ font-weight: 400;
40
+ color: #555;
41
+ margin: 10px 0 0;
42
+ }
43
+
44
+ .warning p a {
45
+ color: #555;
46
+ }
47
+
48
+ @media screen and (min-width: 768px) {
49
+ body {
50
+ padding: 40px;
51
+ }
52
+ }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <div class="warning">
57
+ <h1>Insecure Connection<h1>
58
+ <p>The site at this URL requires an SSL-secured connection. Please visit <a href="${url}">${url}</a> instead. You will be automatically redirected in 15 seconds.</p>
59
+ </div>
60
+ <script>
61
+ setTimeout(() => {
62
+ location.href = '${url}';
63
+ }, 15 * 1000);
64
+ <\/script>
65
+ </body>
66
+ </html>
67
+ `;
68
+ };
69
+ export {
70
+ get_insecure_landing_page_html_default as default
71
+ };
@@ -58,7 +58,7 @@ var client_default = (() => websocketClient({
58
58
  location.reload();
59
59
  }
60
60
  if (message?.settings) {
61
- window.__joystick_settings__ = JSON.stringify(message?.settings);
61
+ window.__joystick_settings__ = message?.settings;
62
62
  window.joystick.settings = message?.settings;
63
63
  }
64
64
  if (clientIndex) {
@@ -71,32 +71,36 @@ var client_default = (() => websocketClient({
71
71
  if (CSS) {
72
72
  const updatedCSS = document.createElement("link");
73
73
  updatedCSS.setAttribute("rel", "stylesheet");
74
- updatedCSS.setAttribute("href", "/_joystick/index.css");
74
+ updatedCSS.setAttribute("href", `/_joystick/index.css?v=${new Date().getTime()}`);
75
75
  document.head.appendChild(updatedCSS);
76
76
  }
77
77
  if (isFileChange && isPageInLayout) {
78
78
  (async () => {
79
79
  window.__joystick_childrenBeforeHMRUpdate__ = window.joystick?._internal?.tree?.instance?.children;
80
- const layoutComponentFile = await import(`${window.__joystick_layout__}?t=${new Date().getTime()}`);
81
- const pageComponentFile = await import(`${window.window.__joystick_layout_page_url__}?t=${new Date().getTime()}`);
80
+ const layoutComponentFile = await import(`${window.__joystick_layout__}?v=${new Date().getTime()}`).catch(() => {
81
+ location.reload();
82
+ });
83
+ const pageComponentFile = await import(`${window.window.__joystick_layout_page_url__}?t=${new Date().getTime()}`).catch(() => {
84
+ location.reload();
85
+ });
82
86
  const layout = layoutComponentFile.default;
83
87
  const page = pageComponentFile.default;
84
88
  window.joystick.mount(layout, Object.assign({ page }, window.__joystick_ssr_props__), document.getElementById("app"));
85
89
  if (connection.send) {
86
- const sessions = await fetch(`${location.origin}/api/_joystick/sessions`)?.then((response) => response.text());
87
- connection.send({ type: "HMR_UPDATE_COMPLETE", sessions });
90
+ connection.send({ type: "HMR_UPDATE_COMPLETE" });
88
91
  }
89
92
  })();
90
93
  }
91
94
  if (isFileChange && !isPageInLayout) {
92
95
  (async () => {
93
96
  window.__joystick_childrenBeforeHMRUpdate__ = window.joystick?._internal?.tree?.instance?.children;
94
- const pageComponentFile = await import(`${window.__joystick_page_url__}?t=${new Date().getTime()}`);
97
+ const pageComponentFile = await import(`${window.__joystick_page_url__}?v=${new Date().getTime()}`).catch(() => {
98
+ location.reload();
99
+ });
95
100
  const page = pageComponentFile.default;
96
101
  window.joystick.mount(page, Object.assign({}, window.__joystick_ssr_props__), document.getElementById("app"));
97
102
  if (connection.send) {
98
- const sessions = await fetch(`${location.origin}/api/_joystick/sessions`)?.then((response) => response.strin());
99
- connection.send({ type: "HMR_UPDATE_COMPLETE", sessions });
103
+ connection.send({ type: "HMR_UPDATE_COMPLETE" });
100
104
  }
101
105
  })();
102
106
  }
@@ -15,11 +15,10 @@ import runUserQuery from "../accounts/runUserQuery.js";
15
15
  import replaceBackslashesWithForwardSlashes from "../../lib/replaceBackslashesWithForwardSlashes.js";
16
16
  import replaceFileProtocol from "../../lib/replaceFileProtocol.js";
17
17
  import getBuildPath from "../../lib/getBuildPath.js";
18
- import sanitizeRequestParameters from "./sanitizeRequestParameters.js";
19
18
  import session from "./session.js";
20
19
  import csp from "./csp.js";
21
20
  const cwd = replaceFileProtocol(replaceBackslashesWithForwardSlashes(process.cwd()));
22
- const faviconPath = process.env.NODE_ENV === "test" ? `${cwd}/src/tests/mocks/app/public/favicon.ico` : "public/favicon.ico";
21
+ const faviconPath = "public/favicon.ico";
23
22
  var middleware_default = ({
24
23
  app,
25
24
  port,
@@ -27,7 +26,7 @@ var middleware_default = ({
27
26
  appInstance,
28
27
  cspConfig
29
28
  }) => {
30
- if (process.env.NODE_ENV === "production") {
29
+ if (process.env.NODE_ENV !== "development") {
31
30
  app.use(insecure);
32
31
  }
33
32
  const buildPath = getBuildPath();
@@ -42,8 +41,8 @@ var middleware_default = ({
42
41
  }
43
42
  next();
44
43
  });
45
- app.use(sanitizeRequestParameters);
46
44
  app.use(requestMethods);
45
+ app.enable("trust proxy");
47
46
  if (cspConfig) {
48
47
  app.use((req, res, next) => csp(req, res, next, cspConfig));
49
48
  }
@@ -73,7 +72,9 @@ var middleware_default = ({
73
72
  app.use(cookieParser());
74
73
  app.use(bodyParser(middlewareConfig?.bodyParser));
75
74
  app.use(cors(middlewareConfig?.cors, port));
76
- app.use((req, res, next) => session(req, res, next, appInstance));
75
+ if (process.databases?._sessions) {
76
+ app.use((req, res, next) => session(req, res, next));
77
+ }
77
78
  app.use(async (req, res, next) => {
78
79
  const loginTokenHasExpired = await hasLoginTokenExpired(res, req?.cookies?.joystickLoginToken, req?.cookies?.joystickLoginTokenExpiresAt);
79
80
  req.context = {
@@ -1,8 +1,7 @@
1
+ import get_insecure_landing_page_html from "./get_insecure_landing_page_html.js";
1
2
  var insecure_default = (req, res, next) => {
2
- const forwardedProtocol = req.get("x-forwarded-proto");
3
- const isForwardedFromHTTPS = forwardedProtocol && forwardedProtocol === "https";
4
- if (process.env.NODE_ENV != "development" && !req.secure && !isForwardedFromHTTPS) {
5
- return res.redirect("https://" + req.headers.host + req.url);
3
+ if (!req.secure) {
4
+ return res.send(get_insecure_landing_page_html(req?.headers?.host, req?.url));
6
5
  }
7
6
  next();
8
7
  };
@@ -9,6 +9,9 @@ import replaceFileProtocol from "../../lib/replaceFileProtocol.js";
9
9
  import replaceBackslashesWithForwardSlashes from "../../lib/replaceBackslashesWithForwardSlashes.js";
10
10
  import getBuildPath from "../../lib/getBuildPath.js";
11
11
  import escapeHTML from "../../lib/escapeHTML.js";
12
+ import escapeKeyValuePair from "../../lib/escapeKeyValuePair.js";
13
+ import importFile from "../../lib/importFile.js";
14
+ import getTranslations from "./getTranslations.js";
12
15
  const generateHash = (input = "") => {
13
16
  return crypto.createHash("sha256").update(input).digest("hex");
14
17
  };
@@ -66,71 +69,12 @@ const getCachedHTML = ({ cachePath, cacheFileName, currentDiff }) => {
66
69
  const getUrl = (request = {}) => {
67
70
  const [path = null] = request.url?.split("?");
68
71
  return {
69
- params: escapeHTML(request.params),
70
- query: escapeHTML(request.query),
72
+ params: escapeKeyValuePair(request.params),
73
+ query: escapeKeyValuePair(request.query),
71
74
  route: escapeHTML(request.route.path),
72
75
  path: escapeHTML(path)
73
76
  };
74
77
  };
75
- const getFile = async (buildPath = "") => {
76
- const file = await import(buildPath);
77
- return file.default;
78
- };
79
- const getTranslationsFile = async (languageFilePath = "", paths = "") => {
80
- const languageFile = await getFile(`${paths.build}/i18n/${languageFilePath}`);
81
- const isValidLanguageFile = languageFile && isObject(languageFile);
82
- if (isValidLanguageFile) {
83
- const translationsForPage = languageFile[paths.page];
84
- return translationsForPage ? translationsForPage : languageFile;
85
- }
86
- return {};
87
- };
88
- const getTranslations = async (paths = {}, languagePreferences = []) => {
89
- const languageFiles = fs.readdirSync(`${paths.build}/i18n`);
90
- let matchingFile = null;
91
- for (let i = 0; i < languagePreferences.length; i += 1) {
92
- const languageRegex = languagePreferences[i];
93
- const match = languageFiles.find((languageFile) => !!languageFile.match(languageRegex));
94
- if (match) {
95
- matchingFile = match;
96
- break;
97
- }
98
- }
99
- const translationsFile = await getTranslationsFile(matchingFile, paths);
100
- return translationsFile;
101
- };
102
- const getLanguagePreferenceRegexes = (userLanguage = "", browserLanguages = []) => {
103
- let languagePreferences = [];
104
- if (userLanguage) {
105
- languagePreferences.push(userLanguage);
106
- }
107
- const filteredBrowserLanguages = browserLanguages?.filter((language) => {
108
- return !language?.includes("*");
109
- });
110
- languagePreferences.push(...filteredBrowserLanguages);
111
- languagePreferences.push(settings?.config?.i18n?.defaultLanguage);
112
- return languagePreferences?.flatMap((language) => {
113
- const variants = [language];
114
- if (language?.length === 2) {
115
- variants.push(`${language.substring(0, 2)}-`);
116
- }
117
- if (language?.length > 2) {
118
- variants.push(`${language?.split("-")[0]}`);
119
- variants.push(`${language?.split("-")[0]}-`);
120
- }
121
- return variants;
122
- })?.map((languageString) => {
123
- const lastCharacter = languageString[languageString.length - 1];
124
- if (lastCharacter === "-") {
125
- return new RegExp(`^${languageString}[A-Z]+.js`, "g");
126
- }
127
- return new RegExp(`^${languageString}.js`, "g");
128
- });
129
- };
130
- const parseBrowserLanguages = (languages = "") => {
131
- const rawLanguages = languages.split(",");
132
- return rawLanguages?.map((rawLanguage) => rawLanguage.split(";")[0]);
133
- };
134
78
  var render_default = (req, res, next, appInstance = {}) => {
135
79
  res.render = async function(path = "", options = {}) {
136
80
  const urlFormattedForCache = req?.url?.split("/")?.filter((part) => !!part)?.join("_");
@@ -168,13 +112,11 @@ var render_default = (req, res, next, appInstance = {}) => {
168
112
  return res.send(cachedHTML);
169
113
  }
170
114
  }
171
- const pageFile = await getFile(pagePath);
115
+ const pageFile = await importFile(pagePath);
172
116
  const Page = pageFile;
173
- const layoutFile = layoutPath ? await getFile(layoutPath) : null;
117
+ const layoutFile = layoutPath ? await importFile(layoutPath) : null;
174
118
  const Layout = layoutFile;
175
- const browserLanguages = parseBrowserLanguages(req?.headers["accept-language"]);
176
- const languagePreferenceRegexes = getLanguagePreferenceRegexes(req?.context?.user?.language, browserLanguages);
177
- const translations = await getTranslations({ build: buildPath, page: path }, languagePreferenceRegexes);
119
+ const translations = await getTranslations({ build: buildPath, page: path }, req);
178
120
  const url = getUrl(req);
179
121
  const props = { ...options?.props || {} };
180
122
  if (layoutPath && fs.existsSync(layoutPath)) {
@@ -1,21 +1,22 @@
1
1
  import setCookie from "../../lib/setCookie.js";
2
- import generateId from "../../lib/generateId.js";
3
- var session_default = (req, res, next, appInstance = {}) => {
4
- let sessionId = req?.cookies?.joystickSession;
5
- if (!sessionId) {
6
- sessionId = generateId(32);
7
- setCookie(res, "joystickSession", sessionId);
8
- }
9
- if (!appInstance.sessions.get(sessionId)) {
10
- appInstance.sessions.set(sessionId, { id: sessionId, csrf: generateId(32) });
2
+ import runSessionQuery from "../runSessionQuery.js";
3
+ var session_default = async (req, res, next) => {
4
+ await runSessionQuery("delete_expired_sessions");
5
+ let session_id = req?.cookies?.joystickSession;
6
+ const existing_session = session_id ? await runSessionQuery("get_session", {
7
+ session_id
8
+ }) : null;
9
+ if (!existing_session) {
10
+ session_id = await runSessionQuery("create_session");
11
+ setCookie(res, "joystickSession", session_id);
11
12
  }
12
13
  req.cookies = {
13
14
  ...req?.cookies || {},
14
- joystickSession: sessionId
15
+ joystickSession: session_id
15
16
  };
16
17
  req.context = {
17
18
  ...req?.context || {},
18
- session: appInstance?.sessions?.get(sessionId)
19
+ session: await runSessionQuery("get_session", { session_id })
19
20
  };
20
21
  next();
21
22
  };
@@ -1,28 +1,34 @@
1
- import dayjs from "dayjs";
2
1
  import fs from "fs";
3
2
  import os from "os";
4
3
  import generateId from "../../lib/generateId";
5
4
  import getTargetDatabaseProvider from "../databases/getTargetDatabaseProvider";
6
5
  import queryMap from "../databases/queryMap";
6
+ import chalk from "chalk";
7
+ import timestamps from "../../lib/timestamps";
7
8
  class Queue {
8
9
  constructor(queueName = "", queueOptions = {}) {
10
+ this._initDatabase = this._initDatabase.bind(this);
9
11
  this.machineId = fs.readFileSync(`${os.homedir()}/.cheatcode/MACHINE_ID`, "utf-8")?.trim().replace(/\n/g, "");
10
12
  this.name = queueName;
11
13
  this.options = {
12
14
  concurrentJobs: 1,
13
15
  ...queueOptions
14
16
  };
15
- this._initDatabase();
16
- if (queueOptions?.runOnStartup) {
17
- this.run();
18
- }
17
+ this._initDatabase(this?.options?.external, this?.options?.database?.provider);
19
18
  }
20
- async _initDatabase() {
21
- const queuesDatabase = getTargetDatabaseProvider("queues");
22
- const db = queryMap[queuesDatabase]?.queues;
23
- if (db && typeof db === "object" && !Array.isArray(db)) {
24
- this.db = Object.entries(db)?.reduce((boundQueries = {}, [queryFunctionName, queryFunction]) => {
19
+ async _initDatabase(is_external = false, database_provider = null) {
20
+ const queuesDatabase = database_provider || getTargetDatabaseProvider("queues");
21
+ const queue_queries_for_database_provider = queryMap[queuesDatabase]?.queues;
22
+ const db = this._getDatabaseConnection();
23
+ console.log({
24
+ queuesDatabase,
25
+ queue_queries_for_database_provider,
26
+ db: db.namespace
27
+ });
28
+ if (db && queue_queries_for_database_provider && typeof queue_queries_for_database_provider === "object" && !Array.isArray(queue_queries_for_database_provider)) {
29
+ this.db = Object.entries(queue_queries_for_database_provider || {})?.reduce((boundQueries = {}, [queryFunctionName, queryFunction]) => {
25
30
  boundQueries[queryFunctionName] = queryFunction.bind({
31
+ db,
26
32
  machineId: this.machineId,
27
33
  queue: {
28
34
  name: this.name,
@@ -30,18 +36,43 @@ class Queue {
30
36
  }
31
37
  });
32
38
  return boundQueries;
33
- }, {});
34
- await this.db.initializeDatabase();
39
+ }, { _connection: db });
40
+ if (!is_external) {
41
+ await this.db.initializeDatabase(queuesDatabase);
42
+ if (this?.options?.runOnStartup) {
43
+ this.run();
44
+ }
45
+ }
35
46
  }
36
47
  }
37
- add(options = {}) {
38
- const nextRunAt2 = options?.nextRunAt === "now" || !options?.nextRunAt ? dayjs().format() : options?.nextRunAt;
39
- this.db.addJob({
48
+ _getDatabaseConnection() {
49
+ if (this?.options?.database) {
50
+ const { provider, name } = this?.options?.database;
51
+ const existing_connection = process.databases && process.databases[provider] && process.databases[provider][name];
52
+ if (!existing_connection) {
53
+ console.warn(chalk.red(`Connection to database ${provider}.${name} not found on process. Cannot start queue.`));
54
+ }
55
+ return existing_connection || null;
56
+ }
57
+ return process.databases._queues;
58
+ }
59
+ async add(options = {}) {
60
+ const nextRunAt = options?.nextRunAt === "now" || !options?.nextRunAt ? new Date().toISOString() : options?.nextRunAt;
61
+ const job_to_add = {
40
62
  _id: generateId(),
41
63
  status: "pending",
64
+ environment: process.env.NODE_ENV,
42
65
  ...options,
43
- nextRunAt: nextRunAt2
44
- });
66
+ nextRunAt
67
+ };
68
+ const job_definition = this.options.jobs[options?.job];
69
+ if (job_definition && typeof job_definition?.preflight?.onBeforeAdd === "function") {
70
+ const canAddJob = await job_definition?.preflight?.onBeforeAdd(job_to_add, this.db._connection, `queue_${this.name}`);
71
+ if (!canAddJob) {
72
+ return null;
73
+ }
74
+ }
75
+ this.db.addJob(job_to_add);
45
76
  }
46
77
  async _checkIfOkayToRunJobs() {
47
78
  const jobsRunning = await this._getNumberOfJobsRunning();
@@ -55,7 +86,7 @@ class Queue {
55
86
  return;
56
87
  }
57
88
  if (!this.options.retryJobsRunningBeforeRestart) {
58
- return this.db.setJobsForMachineIncomplete();
89
+ return this.db.deleteIncompleteJobsForMachine();
59
90
  }
60
91
  return this.db.setJobsForMachinePending();
61
92
  }
@@ -69,41 +100,60 @@ class Queue {
69
100
  const okayToRunJobs = await this._checkIfOkayToRunJobs();
70
101
  if (okayToRunJobs && !process.env.HALT_QUEUES) {
71
102
  const nextJob = await this.db.getNextJobToRun();
72
- this._handleNextJob(nextJob);
103
+ this.handleNextJob(nextJob);
73
104
  }
74
105
  }, 300);
75
106
  });
76
107
  }
77
- _handleNextJob(nextJob = {}) {
78
- if (nextJob && nextJob?.job && this.options.jobs[nextJob?.job] && typeof this.options.jobs[nextJob?.job]?.run === "function") {
108
+ async handleNextJob(nextJob = {}) {
109
+ const job_definition = this.options.jobs[nextJob?.job];
110
+ if (nextJob && nextJob?.job && job_definition && typeof job_definition?.run === "function") {
79
111
  try {
80
- this.options.jobs[nextJob.job].run(nextJob?.payload, {
112
+ if (typeof job_definition?.preflight?.okayToRun === "function") {
113
+ const okay_to_run = await job_definition?.preflight?.okayToRun(nextJob?.payload, nextJob);
114
+ if (!okay_to_run) {
115
+ return this._handleRequeueJob(nextJob, timestamps.get_future_time("seconds", job_definition?.preflight?.requeueDelayInSeconds || 10));
116
+ }
117
+ }
118
+ if (!isNaN(job_definition?.maxAttempts)) {
119
+ if (nextJob?.attempts >= parseInt(job_definition?.maxAttempts, 10)) {
120
+ if (typeof job_definition?.onMaxAttemptsExhausted === "function") {
121
+ await job_definition.onMaxAttemptsExhausted(nextJob);
122
+ }
123
+ return this._handleDeleteJob(nextJob?._id);
124
+ }
125
+ }
126
+ await this._logAttempt(nextJob?._id);
127
+ await job_definition.run(nextJob?.payload, {
81
128
  ...nextJob,
82
129
  queue: this,
83
130
  completed: () => this._handleJobCompleted(nextJob?._id),
84
- failed: (error) => this._handleJobFailed(nextJob?._id, error),
131
+ failed: (error) => this._handleJobFailed(nextJob, job_definition, error),
85
132
  delete: () => this._handleDeleteJob(nextJob?._id),
86
- requeue: (nextRunAt2 = "") => this._handleRequeueJob(nextJob, nextRunAt2)
133
+ requeue: (nextRunAt = "") => this._handleRequeueJob(nextJob, nextRunAt)
87
134
  });
88
135
  } catch (exception) {
89
- this._handleJobFailed(nextJob?._id, exception);
90
- if (this.options.jobs[nextJob.job]?.requeueOnFailure) {
91
- this._handleRequeueJob(nextRunAt, dayjs().add(10, "seconds").format());
92
- }
136
+ this._handleJobFailed(nextJob, job_definition, exception);
93
137
  }
94
138
  }
95
139
  }
140
+ _logAttempt(jobId = "") {
141
+ return this.db.logAttempt(jobId);
142
+ }
96
143
  _handleJobCompleted(jobId = "") {
97
144
  return this.db.setJobCompleted(jobId);
98
145
  }
99
- _handleJobFailed(jobId = "", error = "") {
100
- return this.db.setJobFailed(jobId, error);
146
+ _handleJobFailed(nextJob = {}, job_definition = {}, error = "") {
147
+ if (job_definition?.requeueOnFailure) {
148
+ return this._handleRequeueJob(nextJob, timestamps.get_future_time("seconds", 10));
149
+ }
150
+ return this.db.setJobFailed(nextJob?._id, error);
101
151
  }
102
152
  _handleDeleteJob(jobId = "") {
103
153
  return this.db.deleteJob(jobId);
104
154
  }
105
- _handleRequeueJob(job = {}, nextRunAt2 = dayjs().format()) {
106
- return this.db.requeueJob(job?._id, nextRunAt2);
155
+ _handleRequeueJob(job = {}, nextRunAt = new Date().toISOString()) {
156
+ return this.db.requeueJob(job?._id, nextRunAt);
107
157
  }
108
158
  list(status = "") {
109
159
  const query = {};
@@ -1,10 +1,6 @@
1
1
  import getAPIURLComponent from "./getAPIURLComponent";
2
2
  import getAPIContext from "./getAPIContext";
3
3
  import formatAPIError from "../lib/formatAPIError";
4
- import validate from "../validation/index.js";
5
- import getOutput from "./getOutput";
6
- import sanitizeAPIResponse from "./sanitizeAPIResponse";
7
- import { isObject } from "../validation/lib/typeValidators";
8
4
  import validateSession from "./validateSession.js";
9
5
  import runGetter from "./runGetter.js";
10
6
  var registerGetters_default = (express, getters = [], context = {}, APIOptions = {}, appInstance = {}) => {
@@ -12,7 +8,7 @@ var registerGetters_default = (express, getters = [], context = {}, APIOptions =
12
8
  if (app) {
13
9
  for (const [getterName, getterOptions] of getters) {
14
10
  app.get(`/api/_getters/${getAPIURLComponent(getterName)}`, ...Array.isArray(getterOptions?.middleware) ? getterOptions?.middleware : [], async (req, res) => {
15
- const isValidSession = validateSession(req, res, appInstance?.sessions);
11
+ const isValidSession = await validateSession(req, res);
16
12
  if (!isValidSession) {
17
13
  return;
18
14
  }
@@ -30,7 +26,10 @@ var registerGetters_default = (express, getters = [], context = {}, APIOptions =
30
26
  return res.status(200).send(JSON.stringify(response));
31
27
  }).catch((error) => {
32
28
  if (typeof error === "string") {
33
- return res.status(500).send(error);
29
+ const sanitized_error = error?.replace("[runGetter] ", "")?.replace("[runGetter.handleRunGetter] ", "");
30
+ return res.status(500).send(JSON.stringify({
31
+ errors: [formatAPIError(new Error(sanitized_error))]
32
+ }));
34
33
  }
35
34
  if (typeof error === "object" && !Array.isArray(error)) {
36
35
  return res.status(error?.errors && error?.errors[0]?.status || 400).send(JSON.stringify(error));
@@ -1,10 +1,6 @@
1
1
  import getAPIURLComponent from "./getAPIURLComponent.js";
2
2
  import getAPIContext from "./getAPIContext.js";
3
3
  import formatAPIError from "../lib/formatAPIError.js";
4
- import validate from "../validation/index.js";
5
- import getOutput from "./getOutput.js";
6
- import sanitizeAPIResponse from "./sanitizeAPIResponse.js";
7
- import { isObject } from "../validation/lib/typeValidators.js";
8
4
  import validateSession from "./validateSession.js";
9
5
  import runSetter from "./runSetter.js";
10
6
  var registerSetters_default = (express, setters = [], context = {}, APIOptions = {}, appInstance = {}) => {
@@ -12,7 +8,7 @@ var registerSetters_default = (express, setters = [], context = {}, APIOptions =
12
8
  if (app) {
13
9
  for (const [setterName, setterOptions] of setters) {
14
10
  app.post(`/api/_setters/${getAPIURLComponent(setterName)}`, ...Array.isArray(setterOptions?.middleware) ? setterOptions?.middleware : [], async (req, res) => {
15
- const isValidSession = validateSession(req, res, appInstance?.sessions);
11
+ const isValidSession = await validateSession(req, res);
16
12
  if (!isValidSession) {
17
13
  return;
18
14
  }
@@ -30,7 +26,10 @@ var registerSetters_default = (express, setters = [], context = {}, APIOptions =
30
26
  return res.status(200).send(JSON.stringify(response));
31
27
  }).catch((error) => {
32
28
  if (typeof error === "string") {
33
- return res.status(500).send(error);
29
+ const sanitized_error = error?.replace("[runSetter] ", "")?.replace("[runSetter.handleRunSetter] ", "");
30
+ return res.status(500).send(JSON.stringify({
31
+ errors: [formatAPIError(new Error(sanitized_error))]
32
+ }));
34
33
  }
35
34
  if (typeof error === "object" && !Array.isArray(error)) {
36
35
  return res.status(error?.errors && error?.errors[0]?.status || 400).send(JSON.stringify(error));
@@ -3,19 +3,31 @@ import formatAPIError from "../lib/formatAPIError.js";
3
3
  import { isObject } from "../validation/lib/typeValidators.js";
4
4
  import getOutput from "./getOutput.js";
5
5
  import sanitizeAPIResponse from "./sanitizeAPIResponse.js";
6
- const handleRunGetter = async (getterOptions = {}, input = {}, output = {}, context = {}, APIOptions = {}) => {
6
+ import trackFunctionCall from "../test/trackFunctionCall.js";
7
+ import getSanitizedContext from "../../getSanitizedContext.js";
8
+ const handleRunGetter = async (name = "", getterOptions = {}, input = {}, output = {}, context = {}, APIOptions = {}) => {
7
9
  try {
8
10
  const shouldDisableSanitizationForGetter = getterOptions?.sanitize === false;
9
11
  const shouldSanitizeOutput = (getterOptions?.sanitize || APIOptions?.sanitize) === true || isObject(APIOptions?.sanitize || getterOptions?.sanitize);
10
- const data = await getterOptions?.get(input, context) || {};
12
+ const sanitizedContext = getSanitizedContext(context);
13
+ trackFunctionCall(`node.api.getters.${name}`, [
14
+ input,
15
+ sanitizedContext
16
+ ]);
17
+ const data = await getterOptions?.get(input, context);
11
18
  const response = output ? getOutput(data, output) : data;
12
19
  return !shouldDisableSanitizationForGetter && shouldSanitizeOutput ? sanitizeAPIResponse(response, getterOptions?.sanitize || APIOptions?.sanitize) : response;
13
20
  } catch (exception) {
14
21
  throw new Error(`[runGetter.handleRunGetter] ${exception.message}`);
15
22
  }
16
23
  };
17
- const handleRunAuthorization = async (getterOptions = {}, input = {}, context = {}) => {
24
+ const handleRunAuthorization = async (name = "", getterOptions = {}, input = {}, context = {}) => {
18
25
  try {
26
+ const sanitizedContext = getSanitizedContext(context);
27
+ trackFunctionCall(`node.api.getters.${name}.authorized`, [
28
+ input,
29
+ sanitizedContext
30
+ ]);
19
31
  const authorization = await getterOptions?.authorized(input, context);
20
32
  if (typeof authorization === "boolean") {
21
33
  return authorization;
@@ -77,7 +89,7 @@ const runGetter = async (options, { resolve, reject }) => {
77
89
  }
78
90
  }
79
91
  if (typeof options?.getterOptions?.authorized === "function") {
80
- const authorized = await handleRunAuthorization(options?.getterOptions, options?.input, options?.context);
92
+ const authorized = await handleRunAuthorization(options?.getterName, options?.getterOptions, options?.input, options?.context);
81
93
  if (!authorized || typeof authorized === "string") {
82
94
  return reject({
83
95
  errors: [
@@ -87,7 +99,7 @@ const runGetter = async (options, { resolve, reject }) => {
87
99
  }
88
100
  }
89
101
  if (typeof options?.getterOptions?.get === "function") {
90
- const response = await handleRunGetter(options?.getterOptions, options?.input, options?.output, options?.context, options?.APIOptions);
102
+ const response = await handleRunGetter(options?.getterName, options?.getterOptions, options?.input, options?.output, options?.context, options?.APIOptions);
91
103
  return resolve(response);
92
104
  }
93
105
  resolve();
@@ -0,0 +1,15 @@
1
+ import queryMap from "./databases/queryMap";
2
+ import getTargetDatabaseConnection from "./databases/getTargetDatabaseConnection.js";
3
+ var runSessionQuery_default = async (queryName = "", inputs = {}) => {
4
+ const sessionsDatabase = getTargetDatabaseConnection("sessions");
5
+ const queryMapForDatabase = sessionsDatabase && queryMap && queryMap[sessionsDatabase?.provider] && queryMap[sessionsDatabase?.provider]?.sessions;
6
+ const query = queryMapForDatabase && queryMapForDatabase[queryName];
7
+ if (sessionsDatabase?.connection && query) {
8
+ const response = await queryMapForDatabase[queryName](inputs, sessionsDatabase?.connection);
9
+ return Promise.resolve(response);
10
+ }
11
+ return null;
12
+ };
13
+ export {
14
+ runSessionQuery_default as default
15
+ };