@joystick.js/node-canary 0.0.0-canary.30 → 0.0.0-canary.301

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 (90) hide show
  1. package/_package.json +2 -2
  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/mongodb/buildConnectionString.js +1 -1
  18. package/dist/app/databases/mongodb/createAccountsIndexes.js +18 -0
  19. package/dist/app/databases/mongodb/createSessionsIndexes.js +10 -0
  20. package/dist/app/databases/mongodb/index.js +1 -1
  21. package/dist/app/databases/mongodb/queries/accounts.js +8 -1
  22. package/dist/app/databases/mongodb/queries/queues.js +34 -25
  23. package/dist/app/databases/mongodb/queries/sessions.js +26 -0
  24. package/dist/app/databases/postgresql/createSessionsIndexes.js +10 -0
  25. package/dist/app/databases/postgresql/createSessionsTables.js +14 -0
  26. package/dist/app/databases/postgresql/handleCleanupQueues.js +35 -0
  27. package/dist/app/databases/postgresql/index.js +9 -2
  28. package/dist/app/databases/postgresql/queries/accounts.js +5 -2
  29. package/dist/app/databases/postgresql/queries/queues.js +69 -36
  30. package/dist/app/databases/postgresql/queries/sessions.js +43 -0
  31. package/dist/app/databases/queryMap.js +6 -2
  32. package/dist/app/databases/stringToSnakeCase.js +6 -0
  33. package/dist/app/getBrowserSafeRequest.js +3 -2
  34. package/dist/app/index.js +214 -73
  35. package/dist/app/initExpress.js +1 -1
  36. package/dist/app/middleware/csp.js +2 -2
  37. package/dist/app/middleware/getTranslations.js +64 -0
  38. package/dist/app/middleware/get_insecure_landing_page_html.js +71 -0
  39. package/dist/app/middleware/hmr/client.js +13 -9
  40. package/dist/app/middleware/index.js +6 -5
  41. package/dist/app/middleware/insecure.js +3 -4
  42. package/dist/app/middleware/render.js +154 -0
  43. package/dist/app/middleware/session.js +12 -11
  44. package/dist/app/queues/index.js +74 -27
  45. package/dist/app/registerGetters.js +5 -6
  46. package/dist/app/registerSetters.js +5 -6
  47. package/dist/app/runGetter.js +17 -5
  48. package/dist/app/runSessionQuery.js +15 -0
  49. package/dist/app/runSetter.js +17 -5
  50. package/dist/app/sanitizeAPIResponse.js +1 -1
  51. package/dist/app/validateSession.js +8 -3
  52. package/dist/app/validateUploaderOptions.js +3 -3
  53. package/dist/app/validateUploads.js +12 -1
  54. package/dist/email/send.js +7 -1
  55. package/dist/email/templates/reset-password.js +0 -1
  56. package/dist/fixture/index.js +40 -0
  57. package/dist/index.js +15 -0
  58. package/dist/lib/escapeKeyValuePair.js +13 -0
  59. package/dist/lib/formatAPIError.js +0 -1
  60. package/dist/lib/getBuildPath.js +1 -1
  61. package/dist/lib/getSSLCertificates.js +3 -3
  62. package/dist/lib/importFile.js +7 -0
  63. package/dist/lib/isValidJSONString.js +1 -1
  64. package/dist/lib/log.js +0 -3
  65. package/dist/lib/objectToSQLKeysString.js +1 -1
  66. package/dist/lib/objectToSQLValuesString.js +1 -1
  67. package/dist/lib/serializeQueryParameters.js +1 -1
  68. package/dist/lib/timestamps.js +47 -0
  69. package/dist/lib/wait.js +8 -0
  70. package/dist/push/logs/index.js +6 -1
  71. package/dist/settings/load.js +3 -5
  72. package/dist/ssr/compileCSS.js +4 -4
  73. package/dist/ssr/findComponentInTree.js +1 -1
  74. package/dist/ssr/getAPIForDataFunctions.js +35 -0
  75. package/dist/ssr/getDataFromComponent.js +15 -0
  76. package/dist/ssr/index.js +19 -45
  77. package/dist/ssr/replaceWhenTags.js +2 -3
  78. package/dist/ssr/setHeadTagsInHTML.js +3 -3
  79. package/dist/test/index.js +9 -0
  80. package/dist/test/trackFunctionCall.js +17 -0
  81. package/dist/validation/inputWithSchema/index.js +3 -3
  82. package/dist/validation/schema/index.js +5 -5
  83. package/dist/websockets/index.js +4 -0
  84. package/getSanitizedContext.js +43 -0
  85. package/package.json +2 -1
  86. package/dist/app/accounts/roles/index.test.js +0 -123
  87. package/dist/app/index.test.js +0 -575
  88. package/dist/app/middleware/sanitizeRequestParameters.js +0 -21
  89. package/dist/email/send.test.js +0 -37
  90. package/dist/validation/index.test.js +0 -463
@@ -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
  };
@@ -0,0 +1,154 @@
1
+ import fs from "fs";
2
+ import dayjs from "dayjs";
3
+ import crypto from "crypto";
4
+ import ssr from "../../ssr/index.js";
5
+ import { isObject } from "../../validation/lib/typeValidators";
6
+ import settings from "../../settings";
7
+ import generateErrorPage from "../../lib/generateErrorPage.js";
8
+ import replaceFileProtocol from "../../lib/replaceFileProtocol.js";
9
+ import replaceBackslashesWithForwardSlashes from "../../lib/replaceBackslashesWithForwardSlashes.js";
10
+ import getBuildPath from "../../lib/getBuildPath.js";
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";
15
+ const generateHash = (input = "") => {
16
+ return crypto.createHash("sha256").update(input).digest("hex");
17
+ };
18
+ const getCacheDiff = async (diffFunction = null) => {
19
+ if (diffFunction) {
20
+ const diff = await diffFunction();
21
+ const diffHash = typeof diff === "string" ? generateHash(diff) : null;
22
+ return diffHash;
23
+ }
24
+ return null;
25
+ };
26
+ const writeCacheFileToDisk = ({
27
+ expiresAfterMinutes = "",
28
+ cachePath = "",
29
+ cacheFileName = "index",
30
+ currentDiff = null,
31
+ html = ""
32
+ }) => {
33
+ const expiresAt = dayjs().add(expiresAfterMinutes, "minutes").unix();
34
+ fs.mkdir(`${cachePath}/_cache`, { recursive: true }, () => {
35
+ fs.writeFile(`${cachePath}/_cache/${cacheFileName}_${expiresAt}.html`, html, (error) => {
36
+ if (error) {
37
+ console.warn(error);
38
+ }
39
+ });
40
+ if (currentDiff) {
41
+ fs.writeFile(`${cachePath}/_cache/diff_${expiresAt}`, currentDiff, (error) => {
42
+ if (error) {
43
+ console.warn(error);
44
+ }
45
+ });
46
+ }
47
+ });
48
+ };
49
+ const getCachedHTML = ({ cachePath, cacheFileName, currentDiff }) => {
50
+ const files = fs.existsSync(`${cachePath}/_cache`) ? fs.readdirSync(`${cachePath}/_cache`) : [];
51
+ const cacheFile = files?.find((file) => file?.includes(cacheFileName));
52
+ const cacheFileExpiresAtUnix = cacheFile?.replace(`${cacheFileName}_`, "").replace(".html", "");
53
+ const existingDiff = fs.existsSync(`${cachePath}/_cache/diff_${cacheFileExpiresAtUnix}`) ? fs.readFileSync(`${cachePath}/_cache/diff_${cacheFileExpiresAtUnix}`, "utf-8") : null;
54
+ const cacheFileDiffHasChanged = existingDiff !== currentDiff;
55
+ const cacheFileExpiresAtHasPassed = dayjs().isAfter(dayjs.unix(parseInt(cacheFileExpiresAtUnix)));
56
+ const cacheFileHasExpired = cacheFileDiffHasChanged || cacheFileExpiresAtHasPassed;
57
+ if (cacheFileDiffHasChanged || cacheFileExpiresAtHasPassed) {
58
+ fs.unlink(`${cachePath}/_cache/${cacheFile}`, (error) => {
59
+ if (error)
60
+ return;
61
+ });
62
+ fs.unlink(`${cachePath}/_cache/diff_${cacheFileExpiresAtUnix}`, (error) => {
63
+ if (error)
64
+ return;
65
+ });
66
+ }
67
+ return cacheFile && !cacheFileHasExpired ? fs.readFileSync(`${cachePath}/_cache/${cacheFile}`, "utf-8") : null;
68
+ };
69
+ const getUrl = (request = {}) => {
70
+ const [path = null] = request.url?.split("?");
71
+ return {
72
+ params: escapeKeyValuePair(request.params),
73
+ query: escapeKeyValuePair(request.query),
74
+ route: escapeHTML(request.route.path),
75
+ path: escapeHTML(path)
76
+ };
77
+ };
78
+ var render_default = (req, res, next, appInstance = {}) => {
79
+ res.render = async function(path = "", options = {}) {
80
+ const urlFormattedForCache = req?.url?.split("/")?.filter((part) => !!part)?.join("_");
81
+ const buildPathForEnvironment = getBuildPath();
82
+ const buildPath = replaceFileProtocol(replaceBackslashesWithForwardSlashes(`${process.cwd().replace(buildPathForEnvironment, "")}/${buildPathForEnvironment}`));
83
+ const pagePath = `${buildPath}${path}`;
84
+ const layoutPath = options.layout ? `${buildPath}${options.layout}` : null;
85
+ const pagePathParts = `${buildPathForEnvironment}${path}`?.split("/")?.filter((part) => !!part);
86
+ const cachePath = pagePathParts?.slice(0, pagePathParts.length - 1)?.join("/");
87
+ let currentDiff;
88
+ if (!fs.existsSync(pagePath)) {
89
+ return res.status(404).send(generateErrorPage({
90
+ type: "pageNotFound",
91
+ path: `res.render('${path}')`,
92
+ frame: null,
93
+ stack: `A page component at the path ${path} could not be found.`
94
+ }));
95
+ }
96
+ if (layoutPath && !fs.existsSync(layoutPath)) {
97
+ return res.status(404).send(generateErrorPage({
98
+ type: "layoutNotFound",
99
+ path: `res.render('${path}', { layout: '${options.layout}' })`,
100
+ frame: null,
101
+ stack: `A layout component at the path ${options.layout} could not be found.`
102
+ }));
103
+ }
104
+ if (options?.cache?.expiresAfterMinutes) {
105
+ currentDiff = typeof options?.cache?.diff === "function" ? await getCacheDiff(options?.cache?.diff) : null;
106
+ const cachedHTML = await getCachedHTML({
107
+ cachePath,
108
+ cacheFileName: urlFormattedForCache?.trim() === "" ? "index" : urlFormattedForCache,
109
+ currentDiff
110
+ });
111
+ if (cachedHTML) {
112
+ return res.send(cachedHTML);
113
+ }
114
+ }
115
+ const pageFile = await importFile(pagePath);
116
+ const Page = pageFile;
117
+ const layoutFile = layoutPath ? await importFile(layoutPath) : null;
118
+ const Layout = layoutFile;
119
+ const translations = await getTranslations({ build: buildPath, page: path }, req);
120
+ const url = getUrl(req);
121
+ const props = { ...options?.props || {} };
122
+ if (layoutPath && fs.existsSync(layoutPath)) {
123
+ props.page = Page;
124
+ }
125
+ const html = await ssr({
126
+ componentFunction: Layout || Page,
127
+ req,
128
+ props,
129
+ url,
130
+ translations,
131
+ attributes: options?.attributes,
132
+ email: false,
133
+ baseHTMLPath: null,
134
+ layoutComponentPath: options?.layout,
135
+ pageComponentPath: path?.substring(0, 1) === "/" ? path?.replace("/", "") : path,
136
+ head: options?.head,
137
+ api: appInstance?.options?.api
138
+ });
139
+ if (options?.cache?.expiresAfterMinutes) {
140
+ writeCacheFileToDisk({
141
+ expiresAfterMinutes: parseInt(options?.cache?.expiresAfterMinutes),
142
+ cachePath,
143
+ cacheFileName: urlFormattedForCache?.trim() === "" ? "index" : urlFormattedForCache,
144
+ currentDiff,
145
+ html
146
+ });
147
+ }
148
+ return res.status(200).send(html);
149
+ };
150
+ next();
151
+ };
152
+ export {
153
+ render_default as default
154
+ };
@@ -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,40 @@
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();
17
+ this._validateConfiguration(() => {
18
+ this._initDatabase(this?.options?.external, this?.options?.database?.provider);
19
+ });
20
+ }
21
+ _validateConfiguration(callback = null) {
22
+ if (this?.options?.external) {
23
+ if (!this?.options?.database?.provider || !this?.options?.database?.name) {
24
+ console.warn(chalk.red(`Cannot connect to external queue ${this.name}. Must pass database.provider and database.name in queue configuration.`));
25
+ return;
26
+ }
18
27
  }
28
+ callback();
19
29
  }
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]) => {
30
+ async _initDatabase(is_external = false, database_provider = null) {
31
+ const queuesDatabase = database_provider || getTargetDatabaseProvider("queues");
32
+ const queue_queries_for_database_provider = queryMap[queuesDatabase]?.queues;
33
+ const db = this._getDatabaseConnection();
34
+ if (db && queue_queries_for_database_provider && typeof queue_queries_for_database_provider === "object" && !Array.isArray(queue_queries_for_database_provider)) {
35
+ this.db = Object.entries(queue_queries_for_database_provider || {})?.reduce((boundQueries = {}, [queryFunctionName, queryFunction]) => {
25
36
  boundQueries[queryFunctionName] = queryFunction.bind({
37
+ db,
26
38
  machineId: this.machineId,
27
39
  queue: {
28
40
  name: this.name,
@@ -31,16 +43,32 @@ class Queue {
31
43
  });
32
44
  return boundQueries;
33
45
  }, {});
34
- await this.db.initializeDatabase();
46
+ if (!is_external) {
47
+ await this.db.initializeDatabase(queuesDatabase);
48
+ if (this?.options?.runOnStartup) {
49
+ this.run();
50
+ }
51
+ }
52
+ }
53
+ }
54
+ _getDatabaseConnection() {
55
+ if (this?.options?.database) {
56
+ const { provider, name } = this?.options?.database;
57
+ const existing_connection = process.databases && process.databases[provider] && process.databases[provider][name];
58
+ if (!existing_connection) {
59
+ console.warn(chalk.red(`Connection to database ${provider}.${name} not found on process. Cannot start queue.`));
60
+ }
61
+ return existing_connection || null;
35
62
  }
63
+ return process.databases._queues;
36
64
  }
37
65
  add(options = {}) {
38
- const nextRunAt2 = options?.nextRunAt === "now" || !options?.nextRunAt ? dayjs().format() : options?.nextRunAt;
66
+ const nextRunAt = options?.nextRunAt === "now" || !options?.nextRunAt ? new Date().toISOString() : options?.nextRunAt;
39
67
  this.db.addJob({
40
68
  _id: generateId(),
41
69
  status: "pending",
42
70
  ...options,
43
- nextRunAt: nextRunAt2
71
+ nextRunAt
44
72
  });
45
73
  }
46
74
  async _checkIfOkayToRunJobs() {
@@ -55,7 +83,7 @@ class Queue {
55
83
  return;
56
84
  }
57
85
  if (!this.options.retryJobsRunningBeforeRestart) {
58
- return this.db.setJobsForMachineIncomplete();
86
+ return this.db.deleteIncompleteJobsForMachine();
59
87
  }
60
88
  return this.db.setJobsForMachinePending();
61
89
  }
@@ -69,41 +97,60 @@ class Queue {
69
97
  const okayToRunJobs = await this._checkIfOkayToRunJobs();
70
98
  if (okayToRunJobs && !process.env.HALT_QUEUES) {
71
99
  const nextJob = await this.db.getNextJobToRun();
72
- this._handleNextJob(nextJob);
100
+ this.handleNextJob(nextJob);
73
101
  }
74
102
  }, 300);
75
103
  });
76
104
  }
77
- _handleNextJob(nextJob = {}) {
78
- if (nextJob && nextJob?.job && this.options.jobs[nextJob?.job] && typeof this.options.jobs[nextJob?.job]?.run === "function") {
105
+ async handleNextJob(nextJob = {}) {
106
+ const job_definition = this.options.jobs[nextJob?.job];
107
+ if (nextJob && nextJob?.job && job_definition && typeof job_definition?.run === "function") {
79
108
  try {
80
- this.options.jobs[nextJob.job].run(nextJob?.payload, {
109
+ if (typeof job_definition?.preflight?.okayToRun === "function") {
110
+ const okay_to_run = await job_definition?.preflight?.okayToRun(nextJob?.payload, nextJob);
111
+ if (!okay_to_run) {
112
+ return this._handleRequeueJob(nextJob, timestamps.get_future_time("seconds", job_definition?.preflight?.requeueDelayInSeconds || 10));
113
+ }
114
+ }
115
+ if (!isNaN(job_definition?.maxAttempts)) {
116
+ if (nextJob?.attempts >= parseInt(job_definition?.maxAttempts, 10)) {
117
+ if (typeof job_definition?.onMaxAttemptsExhausted === "function") {
118
+ await job_definition.onMaxAttemptsExhausted(nextJob);
119
+ }
120
+ return this._handleDeleteJob(nextJob?._id);
121
+ }
122
+ }
123
+ await this._logAttempt(nextJob?._id);
124
+ await job_definition.run(nextJob?.payload, {
81
125
  ...nextJob,
82
126
  queue: this,
83
127
  completed: () => this._handleJobCompleted(nextJob?._id),
84
- failed: (error) => this._handleJobFailed(nextJob?._id, error),
128
+ failed: (error) => this._handleJobFailed(nextJob, job_definition, error),
85
129
  delete: () => this._handleDeleteJob(nextJob?._id),
86
- requeue: (nextRunAt2 = "") => this._handleRequeueJob(nextJob, nextRunAt2)
130
+ requeue: (nextRunAt = "") => this._handleRequeueJob(nextJob, nextRunAt)
87
131
  });
88
132
  } 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
- }
133
+ this._handleJobFailed(nextJob, job_definition, exception);
93
134
  }
94
135
  }
95
136
  }
137
+ _logAttempt(jobId = "") {
138
+ return this.db.logAttempt(jobId);
139
+ }
96
140
  _handleJobCompleted(jobId = "") {
97
141
  return this.db.setJobCompleted(jobId);
98
142
  }
99
- _handleJobFailed(jobId = "", error = "") {
100
- return this.db.setJobFailed(jobId, error);
143
+ _handleJobFailed(nextJob = {}, job_definition = {}, error = "") {
144
+ if (job_definition?.requeueOnFailure) {
145
+ return this._handleRequeueJob(nextJob, timestamps.get_future_time("seconds", 10));
146
+ }
147
+ return this.db.setJobFailed(nextJob?._id, error);
101
148
  }
102
149
  _handleDeleteJob(jobId = "") {
103
150
  return this.db.deleteJob(jobId);
104
151
  }
105
- _handleRequeueJob(job = {}, nextRunAt2 = dayjs().format()) {
106
- return this.db.requeueJob(job?._id, nextRunAt2);
152
+ _handleRequeueJob(job = {}, nextRunAt = new Date().toISOString()) {
153
+ return this.db.requeueJob(job?._id, nextRunAt);
107
154
  }
108
155
  list(status = "") {
109
156
  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
+ };
@@ -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 handleRunSetter = async (setterOptions = {}, input = {}, output = {}, context = {}, APIOptions = {}) => {
6
+ import trackFunctionCall from "../test/trackFunctionCall.js";
7
+ import getSanitizedContext from "../../getSanitizedContext.js";
8
+ const handleRunSetter = async (name = "", setterOptions = {}, input = {}, output = {}, context = {}, APIOptions = {}) => {
7
9
  try {
8
10
  const shouldDisableSanitizationForSetter = setterOptions?.sanitize === false;
9
11
  const shouldSanitizeOutput = (setterOptions?.sanitize || APIOptions?.sanitize) === true || isObject(APIOptions?.sanitize || setterOptions?.sanitize);
10
- const data = await setterOptions?.set(input, context) || {};
12
+ const sanitizedContext = getSanitizedContext(context);
13
+ trackFunctionCall(`node.api.setters.${name}`, [
14
+ input,
15
+ sanitizedContext
16
+ ]);
17
+ const data = await setterOptions?.set(input, context);
11
18
  const response = output ? getOutput(data, output) : data;
12
19
  return !shouldDisableSanitizationForSetter && shouldSanitizeOutput ? sanitizeAPIResponse(response, setterOptions?.sanitize || APIOptions?.sanitize) : response;
13
20
  } catch (exception) {
14
21
  throw new Error(`[runSetter.handleRunSetter] ${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.setters.${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 runSetter = async (options, { resolve, reject }) => {
77
89
  }
78
90
  }
79
91
  if (typeof options?.setterOptions?.authorized === "function") {
80
- const authorized = await handleRunAuthorization(options?.setterOptions, options?.input, options?.context);
92
+ const authorized = await handleRunAuthorization(options?.setterName, options?.setterOptions, options?.input, options?.context);
81
93
  if (!authorized || typeof authorized === "string") {
82
94
  return reject({
83
95
  errors: [
@@ -87,7 +99,7 @@ const runSetter = async (options, { resolve, reject }) => {
87
99
  }
88
100
  }
89
101
  if (typeof options?.setterOptions?.set === "function") {
90
- const response = await handleRunSetter(options?.setterOptions, options?.input, options?.output, options?.context, options?.APIOptions);
102
+ const response = await handleRunSetter(options?.setterName, options?.setterOptions, options?.input, options?.output, options?.context, options?.APIOptions);
91
103
  return resolve(response);
92
104
  }
93
105
  resolve();