@terreno/api 0.13.3 → 0.14.1

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 (172) hide show
  1. package/dist/__tests__/versionCheckPlugin.test.js +136 -3
  2. package/dist/api.arrayOperations.test.js +1 -0
  3. package/dist/api.d.ts +15 -4
  4. package/dist/api.errors.test.js +1 -0
  5. package/dist/api.hooks.test.js +1 -0
  6. package/dist/api.js +153 -104
  7. package/dist/api.query.test.js +1 -0
  8. package/dist/api.test.js +174 -0
  9. package/dist/auth.d.ts +10 -5
  10. package/dist/auth.js +163 -90
  11. package/dist/auth.test.js +159 -0
  12. package/dist/betterAuthApp.test.js +1 -0
  13. package/dist/betterAuthSetup.d.ts +5 -6
  14. package/dist/betterAuthSetup.js +30 -17
  15. package/dist/betterAuthSetup.test.js +1 -0
  16. package/dist/config.d.ts +48 -0
  17. package/dist/config.js +257 -0
  18. package/dist/config.test.d.ts +1 -0
  19. package/dist/config.test.js +328 -0
  20. package/dist/configuration.test.js +1 -0
  21. package/dist/configurationApp.d.ts +1 -1
  22. package/dist/configurationApp.js +17 -13
  23. package/dist/configurationPlugin.test.js +1 -0
  24. package/dist/consentApp.test.js +1 -0
  25. package/dist/envConfigurationPlugin.d.ts +2 -0
  26. package/dist/envConfigurationPlugin.js +173 -0
  27. package/dist/envConfigurationPlugin.test.d.ts +1 -0
  28. package/dist/envConfigurationPlugin.test.js +322 -0
  29. package/dist/errors.d.ts +18 -7
  30. package/dist/errors.js +111 -12
  31. package/dist/errors.test.js +16 -1
  32. package/dist/example.js +19 -7
  33. package/dist/expressServer.d.ts +10 -9
  34. package/dist/expressServer.js +62 -53
  35. package/dist/expressServer.test.js +165 -2
  36. package/dist/githubAuth.d.ts +2 -1
  37. package/dist/githubAuth.js +41 -26
  38. package/dist/githubAuth.test.js +1 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +4 -0
  41. package/dist/logger.d.ts +1 -1
  42. package/dist/logger.js +42 -20
  43. package/dist/models/versionConfig.d.ts +2 -0
  44. package/dist/models/versionConfig.js +8 -0
  45. package/dist/notifiers/googleChatNotifier.js +14 -16
  46. package/dist/notifiers/googleChatNotifier.test.js +1 -0
  47. package/dist/notifiers/slackNotifier.js +16 -14
  48. package/dist/notifiers/slackNotifier.test.js +41 -3
  49. package/dist/notifiers/zoomNotifier.js +7 -10
  50. package/dist/notifiers/zoomNotifier.test.js +1 -0
  51. package/dist/openApi.d.ts +1 -1
  52. package/dist/openApi.test.js +1 -0
  53. package/dist/openApiBuilder.d.ts +39 -6
  54. package/dist/openApiBuilder.js +1 -31
  55. package/dist/openApiBuilder.test.js +1 -0
  56. package/dist/openApiValidator.js +1 -0
  57. package/dist/openApiValidator.test.js +1 -0
  58. package/dist/permissions.d.ts +4 -4
  59. package/dist/permissions.js +67 -65
  60. package/dist/permissions.middleware.test.js +1 -0
  61. package/dist/permissions.test.js +1 -0
  62. package/dist/plugins.d.ts +5 -5
  63. package/dist/plugins.js +18 -9
  64. package/dist/plugins.test.js +1 -1
  65. package/dist/populate.d.ts +15 -8
  66. package/dist/populate.js +23 -24
  67. package/dist/populate.test.js +1 -0
  68. package/dist/realtime/changeStreamWatcher.d.ts +73 -0
  69. package/dist/realtime/changeStreamWatcher.js +724 -0
  70. package/dist/realtime/index.d.ts +6 -0
  71. package/dist/realtime/index.js +27 -0
  72. package/dist/realtime/queryMatcher.d.ts +14 -0
  73. package/dist/realtime/queryMatcher.js +250 -0
  74. package/dist/realtime/queryStore.d.ts +37 -0
  75. package/dist/realtime/queryStore.js +195 -0
  76. package/dist/realtime/realtime.test.d.ts +10 -0
  77. package/dist/realtime/realtime.test.js +3066 -0
  78. package/dist/realtime/realtimeApp.d.ts +93 -0
  79. package/dist/realtime/realtimeApp.js +560 -0
  80. package/dist/realtime/registry.d.ts +40 -0
  81. package/dist/realtime/registry.js +38 -0
  82. package/dist/realtime/socketUser.d.ts +10 -0
  83. package/dist/realtime/socketUser.js +17 -0
  84. package/dist/realtime/types.d.ts +100 -0
  85. package/dist/realtime/types.js +2 -0
  86. package/dist/requestContext.d.ts +37 -0
  87. package/dist/requestContext.js +344 -0
  88. package/dist/requestContext.test.d.ts +1 -0
  89. package/dist/requestContext.test.js +384 -0
  90. package/dist/terrenoApp.d.ts +8 -0
  91. package/dist/terrenoApp.js +50 -13
  92. package/dist/terrenoApp.test.js +194 -21
  93. package/dist/terrenoPlugin.d.ts +11 -0
  94. package/dist/tests/bunSetup.js +1 -0
  95. package/dist/tests.js +1 -1
  96. package/dist/transformers.d.ts +2 -2
  97. package/dist/transformers.js +5 -3
  98. package/dist/transformers.test.js +90 -0
  99. package/dist/types/consentResponse.d.ts +6 -3
  100. package/dist/versionCheckPlugin.d.ts +2 -0
  101. package/dist/versionCheckPlugin.js +18 -12
  102. package/package.json +4 -2
  103. package/src/__tests__/versionCheckPlugin.test.ts +94 -3
  104. package/src/api.arrayOperations.test.ts +1 -0
  105. package/src/api.errors.test.ts +1 -0
  106. package/src/api.hooks.test.ts +1 -0
  107. package/src/api.query.test.ts +1 -0
  108. package/src/api.test.ts +132 -0
  109. package/src/api.ts +199 -84
  110. package/src/auth.test.ts +160 -0
  111. package/src/auth.ts +120 -50
  112. package/src/betterAuthApp.test.ts +1 -0
  113. package/src/betterAuthSetup.test.ts +1 -0
  114. package/src/betterAuthSetup.ts +59 -22
  115. package/src/config.test.ts +255 -0
  116. package/src/config.ts +216 -0
  117. package/src/configuration.test.ts +1 -0
  118. package/src/configurationApp.ts +59 -24
  119. package/src/configurationPlugin.test.ts +1 -0
  120. package/src/consentApp.test.ts +1 -0
  121. package/src/envConfigurationPlugin.test.ts +143 -0
  122. package/src/envConfigurationPlugin.ts +100 -0
  123. package/src/errors.test.ts +19 -1
  124. package/src/errors.ts +118 -38
  125. package/src/example.ts +49 -21
  126. package/src/express.d.ts +18 -1
  127. package/src/expressServer.test.ts +147 -2
  128. package/src/expressServer.ts +80 -50
  129. package/src/githubAuth.test.ts +1 -0
  130. package/src/githubAuth.ts +59 -38
  131. package/src/index.ts +4 -0
  132. package/src/logger.ts +47 -17
  133. package/src/models/versionConfig.ts +13 -2
  134. package/src/notifiers/googleChatNotifier.test.ts +1 -0
  135. package/src/notifiers/googleChatNotifier.ts +7 -9
  136. package/src/notifiers/slackNotifier.test.ts +29 -3
  137. package/src/notifiers/slackNotifier.ts +9 -7
  138. package/src/notifiers/zoomNotifier.test.ts +1 -0
  139. package/src/notifiers/zoomNotifier.ts +8 -11
  140. package/src/openApi.test.ts +1 -0
  141. package/src/openApi.ts +4 -4
  142. package/src/openApiBuilder.test.ts +1 -0
  143. package/src/openApiBuilder.ts +14 -11
  144. package/src/openApiValidator.test.ts +1 -0
  145. package/src/openApiValidator.ts +3 -2
  146. package/src/permissions.middleware.test.ts +1 -0
  147. package/src/permissions.test.ts +1 -0
  148. package/src/permissions.ts +30 -25
  149. package/src/plugins.test.ts +1 -1
  150. package/src/plugins.ts +21 -14
  151. package/src/populate.test.ts +1 -0
  152. package/src/populate.ts +44 -36
  153. package/src/realtime/changeStreamWatcher.ts +572 -0
  154. package/src/realtime/index.ts +34 -0
  155. package/src/realtime/queryMatcher.ts +179 -0
  156. package/src/realtime/queryStore.ts +132 -0
  157. package/src/realtime/realtime.test.ts +2465 -0
  158. package/src/realtime/realtimeApp.ts +478 -0
  159. package/src/realtime/registry.ts +64 -0
  160. package/src/realtime/socketUser.ts +25 -0
  161. package/src/realtime/types.ts +112 -0
  162. package/src/requestContext.test.ts +321 -0
  163. package/src/requestContext.ts +368 -0
  164. package/src/terrenoApp.test.ts +137 -11
  165. package/src/terrenoApp.ts +64 -17
  166. package/src/terrenoPlugin.ts +12 -0
  167. package/src/tests/bunSetup.ts +1 -0
  168. package/src/tests.ts +7 -2
  169. package/src/transformers.test.ts +70 -2
  170. package/src/transformers.ts +15 -7
  171. package/src/types/consentResponse.ts +8 -10
  172. package/src/versionCheckPlugin.ts +15 -7
@@ -86,10 +86,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
86
86
  return (mod && mod.__esModule) ? mod : { "default": mod };
87
87
  };
88
88
  Object.defineProperty(exports, "__esModule", { value: true });
89
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
89
90
  var bun_test_1 = require("bun:test");
91
+ var node_stream_1 = require("node:stream");
90
92
  var express_1 = __importDefault(require("express"));
91
93
  var supertest_1 = __importDefault(require("supertest"));
94
+ var winston_1 = __importDefault(require("winston"));
92
95
  var expressServer_1 = require("./expressServer");
96
+ var logger_1 = require("./logger");
93
97
  var tests_1 = require("./tests");
94
98
  (0, bun_test_1.describe)("expressServer", function () {
95
99
  (0, bun_test_1.describe)("setupEnvironment", function () {
@@ -122,6 +126,55 @@ var tests_1 = require("./tests");
122
126
  });
123
127
  });
124
128
  (0, bun_test_1.describe)("logRequests", function () {
129
+ (0, bun_test_1.it)("attaches request and session context to route logs", function () { return __awaiter(void 0, void 0, void 0, function () {
130
+ var logs, logStream, transport, app, res, parsedLog;
131
+ return __generator(this, function (_a) {
132
+ switch (_a.label) {
133
+ case 0:
134
+ logs = [];
135
+ logStream = new node_stream_1.Writable({
136
+ write: function (chunk, _encoding, callback) {
137
+ logs.push(chunk.toString());
138
+ callback();
139
+ },
140
+ });
141
+ transport = new winston_1.default.transports.Stream({
142
+ format: winston_1.default.format.json(),
143
+ stream: logStream,
144
+ });
145
+ app = (0, expressServer_1.setupServer)({
146
+ addRoutes: function (router) {
147
+ router.get("/context-test", function (req, res) {
148
+ logger_1.logger.info("context route log");
149
+ return res.json({ requestId: req.requestId, sessionId: req.sessionId });
150
+ });
151
+ },
152
+ logRequests: false,
153
+ skipListen: true,
154
+ userModel: tests_1.UserModel,
155
+ });
156
+ logger_1.winstonLogger.add(transport);
157
+ return [4 /*yield*/, (0, supertest_1.default)(app)
158
+ .get("/context-test")
159
+ .set("X-Request-ID", "req-123")
160
+ .set("X-Session-ID", "session-123")
161
+ .expect(200)];
162
+ case 1:
163
+ res = _a.sent();
164
+ (0, bun_test_1.expect)(res.headers["x-request-id"]).toBe("req-123");
165
+ (0, bun_test_1.expect)(res.headers["x-session-id"]).toBe("session-123");
166
+ (0, bun_test_1.expect)(res.body).toEqual({ requestId: "req-123", sessionId: "session-123" });
167
+ parsedLog = logs
168
+ .map(function (entry) { return JSON.parse(entry); })
169
+ .find(function (entry) { return entry.message === "context route log"; });
170
+ (0, bun_test_1.expect)(parsedLog).toBeDefined();
171
+ (0, bun_test_1.expect)(parsedLog.requestId).toBe("req-123");
172
+ (0, bun_test_1.expect)(parsedLog.sessionId).toBe("session-123");
173
+ logger_1.winstonLogger.remove(transport);
174
+ return [2 /*return*/];
175
+ }
176
+ });
177
+ }); });
125
178
  (0, bun_test_1.it)("logs request with admin user type", function () {
126
179
  var req = {
127
180
  body: {},
@@ -793,7 +846,6 @@ var tests_1 = require("./tests");
793
846
  var originalSetTimeout = globalThis.setTimeout;
794
847
  var timerIds = [];
795
848
  (0, bun_test_1.beforeEach)(function () {
796
- // biome-ignore lint/suspicious/noExplicitAny: Mock requires type override for process.exit.
797
849
  process.exit = (0, bun_test_1.mock)(function () {
798
850
  throw new Error("process.exit called");
799
851
  });
@@ -914,6 +966,118 @@ var tests_1 = require("./tests");
914
966
  });
915
967
  }); });
916
968
  });
969
+ (0, bun_test_1.describe)("setupServer with listen", function () {
970
+ var originalEnv = process.env;
971
+ var http = require("node:http");
972
+ var activeServer = null;
973
+ var originalListen = null;
974
+ (0, bun_test_1.beforeEach)(function () {
975
+ process.env = __assign(__assign({}, originalEnv), { PORT: "0", REFRESH_TOKEN_SECRET: "test-refresh-secret", SESSION_SECRET: "test-session-secret", TOKEN_EXPIRES_IN: "1h", TOKEN_ISSUER: "test-issuer", TOKEN_SECRET: "test-secret" });
976
+ originalListen = http.Server.prototype.listen;
977
+ http.Server.prototype.listen = function () {
978
+ var args = [];
979
+ for (var _i = 0; _i < arguments.length; _i++) {
980
+ args[_i] = arguments[_i];
981
+ }
982
+ activeServer = this;
983
+ return originalListen.apply(this, args);
984
+ };
985
+ });
986
+ (0, bun_test_1.afterEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
987
+ return __generator(this, function (_a) {
988
+ switch (_a.label) {
989
+ case 0:
990
+ process.env = originalEnv;
991
+ http.Server.prototype.listen = originalListen;
992
+ if (!activeServer) return [3 /*break*/, 2];
993
+ return [4 /*yield*/, new Promise(function (resolve) { return activeServer.close(function () { return resolve(); }); })];
994
+ case 1:
995
+ _a.sent();
996
+ activeServer = null;
997
+ _a.label = 2;
998
+ case 2: return [2 /*return*/];
999
+ }
1000
+ });
1001
+ }); });
1002
+ (0, bun_test_1.it)("starts listening on a port when skipListen is false", function () { return __awaiter(void 0, void 0, void 0, function () {
1003
+ var addRoutes, app;
1004
+ return __generator(this, function (_a) {
1005
+ switch (_a.label) {
1006
+ case 0:
1007
+ addRoutes = function () { };
1008
+ app = (0, expressServer_1.setupServer)({
1009
+ addRoutes: addRoutes,
1010
+ skipListen: false,
1011
+ userModel: tests_1.UserModel,
1012
+ });
1013
+ (0, bun_test_1.expect)(app).toBeDefined();
1014
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 100); })];
1015
+ case 1:
1016
+ _a.sent();
1017
+ return [2 /*return*/];
1018
+ }
1019
+ });
1020
+ }); });
1021
+ });
1022
+ (0, bun_test_1.describe)("wrapScript timeout callbacks", function () {
1023
+ var originalEnv = process.env;
1024
+ var originalExit = process.exit;
1025
+ var originalSetTimeout = globalThis.setTimeout;
1026
+ var timerIds = [];
1027
+ var timerCallbacks = [];
1028
+ (0, bun_test_1.beforeEach)(function () {
1029
+ process.env = __assign(__assign({}, process.env), { REFRESH_TOKEN_SECRET: "test-refresh-secret", SESSION_SECRET: "test-session-secret", TOKEN_EXPIRES_IN: "1h", TOKEN_ISSUER: "test-issuer", TOKEN_SECRET: "test-secret" });
1030
+ process.exit = (0, bun_test_1.mock)(function () {
1031
+ throw new Error("__EXIT__");
1032
+ });
1033
+ timerCallbacks.length = 0;
1034
+ timerIds.length = 0;
1035
+ globalThis.setTimeout = (function (cb, delay) {
1036
+ timerCallbacks.push({ callback: cb, delay: delay });
1037
+ var id = originalSetTimeout(cb, delay);
1038
+ timerIds.push(id);
1039
+ return id;
1040
+ });
1041
+ });
1042
+ (0, bun_test_1.afterEach)(function () {
1043
+ var e_2, _a;
1044
+ try {
1045
+ for (var timerIds_2 = __values(timerIds), timerIds_2_1 = timerIds_2.next(); !timerIds_2_1.done; timerIds_2_1 = timerIds_2.next()) {
1046
+ var id = timerIds_2_1.value;
1047
+ clearTimeout(id);
1048
+ }
1049
+ }
1050
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
1051
+ finally {
1052
+ try {
1053
+ if (timerIds_2_1 && !timerIds_2_1.done && (_a = timerIds_2.return)) _a.call(timerIds_2);
1054
+ }
1055
+ finally { if (e_2) throw e_2.error; }
1056
+ }
1057
+ globalThis.setTimeout = originalSetTimeout;
1058
+ process.exit = originalExit;
1059
+ process.env = originalEnv;
1060
+ });
1061
+ (0, bun_test_1.it)("registers warn and terminate timeouts with correct delays", function () { return __awaiter(void 0, void 0, void 0, function () {
1062
+ var func, warnTimer, closeTimer;
1063
+ return __generator(this, function (_a) {
1064
+ switch (_a.label) {
1065
+ case 0:
1066
+ func = function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
1067
+ return [2 /*return*/, "ok"];
1068
+ }); }); };
1069
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { terminateTimeout: 100 })).rejects.toThrow("__EXIT__")];
1070
+ case 1:
1071
+ _a.sent();
1072
+ warnTimer = timerCallbacks.find(function (t) { return t.delay === 50000; });
1073
+ closeTimer = timerCallbacks.find(function (t) { return t.delay === 100000; });
1074
+ (0, bun_test_1.expect)(warnTimer).toBeDefined();
1075
+ (0, bun_test_1.expect)(closeTimer).toBeDefined();
1076
+ return [2 /*return*/];
1077
+ }
1078
+ });
1079
+ }); });
1080
+ });
917
1081
  (0, bun_test_1.describe)("setupServer error handling", function () {
918
1082
  var originalEnv = process.env;
919
1083
  (0, bun_test_1.beforeEach)(function () {
@@ -930,7 +1094,6 @@ var tests_1 = require("./tests");
930
1094
  return (0, expressServer_1.setupServer)({
931
1095
  addRoutes: addRoutes,
932
1096
  skipListen: true,
933
- // biome-ignore lint/suspicious/noExplicitAny: Test mock for UserModel.
934
1097
  userModel: tests_1.UserModel,
935
1098
  });
936
1099
  }).toThrow("route initialization failed");
@@ -1,4 +1,5 @@
1
1
  import type express from "express";
2
+ import type { Schema } from "mongoose";
2
3
  import { type Profile } from "passport-github2";
3
4
  import { type UserModel } from "./auth";
4
5
  import type { AuthOptions } from "./expressServer";
@@ -46,7 +47,7 @@ export interface GitHubUserFields {
46
47
  * userSchema.plugin(githubUserPlugin);
47
48
  * ```
48
49
  */
49
- export declare const githubUserPlugin: (schema: any) => void;
50
+ export declare const githubUserPlugin: (schema: Schema<any, any, any, any>) => void;
50
51
  /**
51
52
  * Sets up GitHub OAuth authentication strategy.
52
53
  * Call this after setupAuth() in your server initialization.
@@ -58,12 +58,19 @@ var plugins_1 = require("./plugins");
58
58
  * userSchema.plugin(githubUserPlugin);
59
59
  * ```
60
60
  */
61
+ // biome-ignore lint/suspicious/noExplicitAny: Schema generics must be loose to accept arbitrary consumer schemas
61
62
  var githubUserPlugin = function (schema) {
62
63
  schema.add({
63
- githubAvatarUrl: { type: String },
64
- githubId: { index: true, sparse: true, type: String, unique: true },
65
- githubProfileUrl: { type: String },
66
- githubUsername: { type: String },
64
+ githubAvatarUrl: { description: "GitHub avatar image URL", type: String },
65
+ githubId: {
66
+ description: "GitHub user ID",
67
+ index: true,
68
+ sparse: true,
69
+ type: String,
70
+ unique: true,
71
+ },
72
+ githubProfileUrl: { description: "GitHub profile URL", type: String },
73
+ githubUsername: { description: "GitHub username", type: String },
67
74
  });
68
75
  };
69
76
  exports.githubUserPlugin = githubUserPlugin;
@@ -74,14 +81,16 @@ exports.githubUserPlugin = githubUserPlugin;
74
81
  var setupGitHubAuth = function (_app, userModel, githubOptions) {
75
82
  var _a;
76
83
  var scope = (_a = githubOptions.scope) !== null && _a !== void 0 ? _a : ["user:email"];
77
- passport_1.default.use("github", new passport_github2_1.Strategy({
84
+ passport_1.default.use("github",
85
+ // biome-ignore lint/suspicious/noExplicitAny: passport-github2's typed constructor overloads don't match passReqToCallback variant
86
+ new passport_github2_1.Strategy({
78
87
  callbackURL: githubOptions.callbackURL,
79
88
  clientID: githubOptions.clientId,
80
89
  clientSecret: githubOptions.clientSecret,
81
90
  passReqToCallback: true,
82
91
  scope: scope,
83
- }, (function (req, accessToken, refreshToken, profile, done) { return __awaiter(void 0, void 0, void 0, function () {
84
- var existingUser, user, githubId, existingGitHubUser, user, email, existingEmailUser, newUser, error_1;
92
+ }, function (req, accessToken, refreshToken, profile, done) { return __awaiter(void 0, void 0, void 0, function () {
93
+ var existingUser, user, githubId, existingGitHubUser, user, userWithGitHub, email, existingEmailUser, emailUserWithGitHub, newUser, error_1;
85
94
  var _a, _b, _c, _d, _e, _f, _g, _h;
86
95
  return __generator(this, function (_j) {
87
96
  switch (_j.label) {
@@ -113,10 +122,11 @@ var setupGitHubAuth = function (_app, userModel, githubOptions) {
113
122
  case 4:
114
123
  user = _j.sent();
115
124
  if (!user) return [3 /*break*/, 6];
116
- user.githubId = githubId;
117
- user.githubUsername = profile.username;
118
- user.githubProfileUrl = profile.profileUrl;
119
- user.githubAvatarUrl = (_b = (_a = profile.photos) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value;
125
+ userWithGitHub = user;
126
+ userWithGitHub.githubId = githubId;
127
+ userWithGitHub.githubUsername = profile.username;
128
+ userWithGitHub.githubProfileUrl = profile.profileUrl;
129
+ userWithGitHub.githubAvatarUrl = (_b = (_a = profile.photos) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value;
120
130
  return [4 /*yield*/, user.save()];
121
131
  case 5:
122
132
  _j.sent();
@@ -134,10 +144,11 @@ var setupGitHubAuth = function (_app, userModel, githubOptions) {
134
144
  existingEmailUser = _j.sent();
135
145
  if (!existingEmailUser) return [3 /*break*/, 11];
136
146
  if (!(githubOptions.allowAccountLinking !== false)) return [3 /*break*/, 10];
137
- existingEmailUser.githubId = githubId;
138
- existingEmailUser.githubUsername = profile.username;
139
- existingEmailUser.githubProfileUrl = profile.profileUrl;
140
- existingEmailUser.githubAvatarUrl = (_f = (_e = profile.photos) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.value;
147
+ emailUserWithGitHub = existingEmailUser;
148
+ emailUserWithGitHub.githubId = githubId;
149
+ emailUserWithGitHub.githubUsername = profile.username;
150
+ emailUserWithGitHub.githubProfileUrl = profile.profileUrl;
151
+ emailUserWithGitHub.githubAvatarUrl = (_f = (_e = profile.photos) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.value;
141
152
  return [4 /*yield*/, existingEmailUser.save()];
142
153
  case 9:
143
154
  _j.sent();
@@ -166,7 +177,7 @@ var setupGitHubAuth = function (_app, userModel, githubOptions) {
166
177
  case 14: return [2 /*return*/];
167
178
  }
168
179
  });
169
- }); })));
180
+ }); }));
170
181
  };
171
182
  exports.setupGitHubAuth = setupGitHubAuth;
172
183
  /**
@@ -182,11 +193,13 @@ var addGitHubAuthRoutes = function (app, userModel, githubOptions, authOptions)
182
193
  var router = (0, express_1.Router)();
183
194
  // Initiate GitHub OAuth flow
184
195
  router.get("/github", function (req, _res, next) {
196
+ var _a;
185
197
  // Store the return URL in session or query for redirect after auth
186
198
  var returnTo = req.query.returnTo;
187
199
  if (returnTo) {
188
- req.session = req.session || {};
189
- req.session.returnTo = returnTo;
200
+ var reqWithSession = req;
201
+ reqWithSession.session = (_a = reqWithSession.session) !== null && _a !== void 0 ? _a : {};
202
+ reqWithSession.session.returnTo = returnTo;
190
203
  }
191
204
  next();
192
205
  }, passport_1.default.authenticate("github", { session: false }));
@@ -208,11 +221,11 @@ var addGitHubAuthRoutes = function (app, userModel, githubOptions, authOptions)
208
221
  // If there's a return URL, redirect with tokens as query params
209
222
  if (returnTo) {
210
223
  url = new URL(returnTo);
211
- url.searchParams.set("token", tokens.token || "");
224
+ url.searchParams.set("token", (_b = tokens.token) !== null && _b !== void 0 ? _b : "");
212
225
  if (tokens.refreshToken) {
213
226
  url.searchParams.set("refreshToken", tokens.refreshToken);
214
227
  }
215
- url.searchParams.set("userId", ((_c = (_b = req.user) === null || _b === void 0 ? void 0 : _b._id) === null || _c === void 0 ? void 0 : _c.toString()) || "");
228
+ url.searchParams.set("userId", ((_c = req.user) === null || _c === void 0 ? void 0 : _c._id) ? String(req.user._id) : "");
216
229
  return [2 /*return*/, res.redirect(url.toString())];
217
230
  }
218
231
  // Otherwise return JSON response
@@ -250,7 +263,7 @@ var addGitHubAuthRoutes = function (app, userModel, githubOptions, authOptions)
250
263
  }, passport_1.default.authenticate("github", { session: false }));
251
264
  // Unlink GitHub from user account
252
265
  router.delete("/github/unlink", passport_1.default.authenticate("jwt", { session: false }), function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
253
- var user, hasPassword, error_3;
266
+ var user, userWithAuth, hasPassword, userWithGitHub, error_3;
254
267
  return __generator(this, function (_a) {
255
268
  switch (_a.label) {
256
269
  case 0:
@@ -266,16 +279,18 @@ var addGitHubAuthRoutes = function (app, userModel, githubOptions, authOptions)
266
279
  if (!user) {
267
280
  return [2 /*return*/, res.status(404).json({ message: "User not found" })];
268
281
  }
269
- hasPassword = !!user.hash || !!user.salt;
282
+ userWithAuth = user;
283
+ hasPassword = !!userWithAuth.hash || !!userWithAuth.salt;
270
284
  if (!hasPassword) {
271
285
  return [2 /*return*/, res.status(400).json({
272
286
  message: "Cannot unlink GitHub account without another authentication method. Set a password first.",
273
287
  })];
274
288
  }
275
- user.githubId = undefined;
276
- user.githubUsername = undefined;
277
- user.githubProfileUrl = undefined;
278
- user.githubAvatarUrl = undefined;
289
+ userWithGitHub = user;
290
+ userWithGitHub.githubId = undefined;
291
+ userWithGitHub.githubUsername = undefined;
292
+ userWithGitHub.githubProfileUrl = undefined;
293
+ userWithGitHub.githubAvatarUrl = undefined;
279
294
  return [4 /*yield*/, user.save()];
280
295
  case 3:
281
296
  _a.sent();
@@ -72,6 +72,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
72
72
  return (mod && mod.__esModule) ? mod : { "default": mod };
73
73
  };
74
74
  Object.defineProperty(exports, "__esModule", { value: true });
75
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
75
76
  var bun_test_1 = require("bun:test");
76
77
  var mongoose_1 = __importStar(require("mongoose"));
77
78
  var passport_1 = __importDefault(require("passport"));
package/dist/index.d.ts CHANGED
@@ -3,9 +3,11 @@ export * from "./auth";
3
3
  export * from "./betterAuth";
4
4
  export * from "./betterAuthApp";
5
5
  export * from "./betterAuthSetup";
6
+ export * from "./config";
6
7
  export * from "./configurationApp";
7
8
  export * from "./configurationPlugin";
8
9
  export * from "./consentApp";
10
+ export * from "./envConfigurationPlugin";
9
11
  export * from "./errors";
10
12
  export * from "./expressServer";
11
13
  export * from "./githubAuth";
@@ -22,6 +24,8 @@ export * from "./openApiValidator";
22
24
  export * from "./permissions";
23
25
  export * from "./plugins";
24
26
  export * from "./populate";
27
+ export * from "./realtime";
28
+ export * from "./requestContext";
25
29
  export * from "./scriptRunner";
26
30
  export * from "./secretProviders";
27
31
  export * from "./syncConsents";
package/dist/index.js CHANGED
@@ -19,9 +19,11 @@ __exportStar(require("./auth"), exports);
19
19
  __exportStar(require("./betterAuth"), exports);
20
20
  __exportStar(require("./betterAuthApp"), exports);
21
21
  __exportStar(require("./betterAuthSetup"), exports);
22
+ __exportStar(require("./config"), exports);
22
23
  __exportStar(require("./configurationApp"), exports);
23
24
  __exportStar(require("./configurationPlugin"), exports);
24
25
  __exportStar(require("./consentApp"), exports);
26
+ __exportStar(require("./envConfigurationPlugin"), exports);
25
27
  __exportStar(require("./errors"), exports);
26
28
  __exportStar(require("./expressServer"), exports);
27
29
  __exportStar(require("./githubAuth"), exports);
@@ -38,6 +40,8 @@ __exportStar(require("./openApiValidator"), exports);
38
40
  __exportStar(require("./permissions"), exports);
39
41
  __exportStar(require("./plugins"), exports);
40
42
  __exportStar(require("./populate"), exports);
43
+ __exportStar(require("./realtime"), exports);
44
+ __exportStar(require("./requestContext"), exports);
41
45
  __exportStar(require("./scriptRunner"), exports);
42
46
  __exportStar(require("./secretProviders"), exports);
43
47
  __exportStar(require("./syncConsents"), exports);
package/dist/logger.d.ts CHANGED
@@ -20,4 +20,4 @@ export interface LoggingOptions {
20
20
  logSlowRequestsReadMs?: number;
21
21
  logSlowRequestsWriteMs?: number;
22
22
  }
23
- export declare function setupLogging(options?: LoggingOptions): void;
23
+ export declare const setupLogging: (options?: LoggingOptions) => void;
package/dist/logger.js CHANGED
@@ -83,50 +83,70 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
83
83
  return (mod && mod.__esModule) ? mod : { "default": mod };
84
84
  };
85
85
  Object.defineProperty(exports, "__esModule", { value: true });
86
- exports.logger = exports.winstonLogger = void 0;
87
- exports.setupLogging = setupLogging;
86
+ exports.setupLogging = exports.logger = exports.winstonLogger = void 0;
88
87
  var node_fs_1 = __importDefault(require("node:fs"));
89
88
  var node_util_1 = require("node:util");
90
89
  var Sentry = __importStar(require("@sentry/bun"));
91
90
  var winston_1 = __importDefault(require("winston"));
92
- function isPrimitive(val) {
91
+ var requestContext_1 = require("./requestContext");
92
+ var isPrimitive = function (val) {
93
93
  return val === null || (typeof val !== "object" && typeof val !== "function");
94
- }
95
- function formatWithInspect(val) {
94
+ };
95
+ var formatWithInspect = function (val) {
96
96
  var prefix = isPrimitive(val) ? "" : "\n";
97
97
  var shouldFormat = typeof val !== "string";
98
98
  return prefix + (shouldFormat ? (0, node_util_1.inspect)(val, { colors: true, depth: null }) : val);
99
- }
99
+ };
100
+ var addRequestContextFormat = winston_1.default.format(function (info) {
101
+ var context = (0, requestContext_1.getCurrentLogContext)();
102
+ return __assign(__assign({}, context), info);
103
+ });
104
+ var formatContext = function (info) {
105
+ var contextParts = [
106
+ info.requestId ? "requestId=".concat(info.requestId) : undefined,
107
+ info.jobId ? "jobId=".concat(info.jobId) : undefined,
108
+ info.sessionId ? "sessionId=".concat(info.sessionId) : undefined,
109
+ info.userId ? "userId=".concat(info.userId) : undefined,
110
+ info.traceId ? "traceId=".concat(info.traceId) : undefined,
111
+ ].filter(Boolean);
112
+ if (contextParts.length === 0) {
113
+ return "";
114
+ }
115
+ return " ".concat(contextParts.join(" "));
116
+ };
100
117
  // Winston doesn't operate like console.log by default, e.g. `logger.error('error',
101
118
  // error)` only prints the message and no args. Add handling for all the args,
102
119
  // while also supporting splat logging.
103
- function printf(timestamp) {
120
+ var printf = function (timestamp) {
104
121
  if (timestamp === void 0) { timestamp = false; }
105
122
  return function (info) {
106
123
  var msg = formatWithInspect(info.message);
107
- var splatArgs = (info[Symbol.for("splat")] || []);
124
+ var splatKey = Symbol.for("splat");
125
+ var splatArgs = (info[splatKey] || []);
108
126
  var rest = splatArgs.map(function (data) { return formatWithInspect(data); }).join(" ");
127
+ var context = formatContext(info);
109
128
  if (timestamp) {
110
- return "".concat(info.timestamp, " - ").concat(info.level, ": ").concat(msg, " ").concat(rest);
129
+ return "".concat(info.timestamp, " - ").concat(info.level, ": ").concat(msg).concat(context, " ").concat(rest);
111
130
  }
112
- return "".concat(info.level, ": ").concat(msg, " ").concat(rest);
131
+ return "".concat(info.level, ": ").concat(msg).concat(context, " ").concat(rest);
113
132
  };
114
- }
133
+ };
115
134
  // Setup a global, default rejection handler.
116
135
  winston_1.default.add(new winston_1.default.transports.Console({
117
136
  debugStdout: true,
118
- format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.simple(), winston_1.default.format.printf(printf(false))),
137
+ format: winston_1.default.format.combine(addRequestContextFormat(), winston_1.default.format.colorize(), winston_1.default.format.simple(), winston_1.default.format.printf(printf(false))),
119
138
  handleExceptions: true,
120
139
  handleRejections: true,
121
140
  level: "error",
122
141
  }));
123
142
  // Setup a default console logger.
124
143
  exports.winstonLogger = winston_1.default.createLogger({
144
+ format: addRequestContextFormat(),
125
145
  level: "debug",
126
146
  transports: [
127
147
  new winston_1.default.transports.Console({
128
148
  debugStdout: true,
129
- format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.simple(), winston_1.default.format.printf(printf(false))),
149
+ format: winston_1.default.format.combine(addRequestContextFormat(), winston_1.default.format.colorize(), winston_1.default.format.simple(), winston_1.default.format.printf(printf(false))),
130
150
  handleExceptions: true,
131
151
  handleRejections: true,
132
152
  level: "debug",
@@ -134,11 +154,12 @@ exports.winstonLogger = winston_1.default.createLogger({
134
154
  ],
135
155
  });
136
156
  // Helper function to send logs to Sentry if enabled
137
- function sendToSentry(message, level) {
157
+ var sendToSentry = function (message, level) {
138
158
  if (process.env.USE_SENTRY_LOGGING === "true" && Sentry.logger) {
139
- Sentry.logger[level](message);
159
+ var logWithContext = Sentry.logger[level];
160
+ logWithContext(message, (0, requestContext_1.getCurrentLogContext)());
140
161
  }
141
- }
162
+ };
142
163
  exports.logger = {
143
164
  // simple way to log a caught exception. e.g. promise().catch(logger.catch)
144
165
  catch: function (e) {
@@ -186,12 +207,12 @@ exports.logger = {
186
207
  sendToSentry(msg, "warn");
187
208
  },
188
209
  };
189
- function setupLogging(options) {
210
+ var setupLogging = function (options) {
190
211
  var _a, e_1, _b;
191
212
  var _c, _d;
192
213
  exports.winstonLogger.clear();
193
214
  if (!(options === null || options === void 0 ? void 0 : options.disableConsoleLogging)) {
194
- var formats = [winston_1.default.format.simple()];
215
+ var formats = [addRequestContextFormat(), winston_1.default.format.simple()];
195
216
  if (!(options === null || options === void 0 ? void 0 : options.disableConsoleColors)) {
196
217
  formats.push(winston_1.default.format.colorize());
197
218
  }
@@ -211,7 +232,7 @@ function setupLogging(options) {
211
232
  colorize: false,
212
233
  compress: true,
213
234
  dirname: logDirectory,
214
- format: winston_1.default.format.simple(),
235
+ format: winston_1.default.format.combine(addRequestContextFormat(), winston_1.default.format.simple()),
215
236
  // 30 days of retention
216
237
  maxFiles: 30,
217
238
  // 50MB max file size
@@ -246,4 +267,5 @@ function setupLogging(options) {
246
267
  finally { if (e_1) throw e_1.error; }
247
268
  }
248
269
  }
249
- }
270
+ };
271
+ exports.setupLogging = setupLogging;
@@ -8,6 +8,8 @@ export interface VersionConfigDocument extends mongoose.Document {
8
8
  warningMessage: string;
9
9
  requiredMessage: string;
10
10
  updateUrl?: string;
11
+ /** How often clients should poll for version updates, in minutes. Defaults to 1440 (24 hours). */
12
+ pollingIntervalMinutes: number;
11
13
  created?: Date;
12
14
  updated?: Date;
13
15
  }
@@ -19,6 +19,12 @@ var versionConfigSchema = new mongoose_1.default.Schema({
19
19
  min: 0,
20
20
  type: Number,
21
21
  },
22
+ pollingIntervalMinutes: {
23
+ default: 1440,
24
+ description: "How often clients poll for version updates, in minutes (default: 1440 = 24 hours)",
25
+ min: 1,
26
+ type: Number,
27
+ },
22
28
  requiredMessage: {
23
29
  default: "This version is no longer supported. Please update to continue.",
24
30
  description: "Message shown on the blocking screen",
@@ -62,5 +68,7 @@ versionConfigSchema.add({
62
68
  });
63
69
  versionConfigSchema.index({ _singleton: 1 }, { unique: true });
64
70
  versionConfigSchema.plugin(plugins_1.createdUpdatedPlugin);
71
+ versionConfigSchema.plugin(plugins_1.isDeletedPlugin);
65
72
  versionConfigSchema.plugin(plugins_1.findOneOrNone);
73
+ versionConfigSchema.plugin(plugins_1.findExactlyOne);
66
74
  exports.VersionConfig = mongoose_1.default.model("VersionConfig", versionConfigSchema);
@@ -108,17 +108,16 @@ var sendToGoogleChat = function (messageText_1) {
108
108
  args_1[_i - 1] = arguments[_i];
109
109
  }
110
110
  return __awaiter(void 0, __spreadArray([messageText_1], __read(args_1), false), void 0, function (messageText, _a) {
111
- var chatWebhooksString, msg, chatWebhooks, chatChannel, chatWebhookUrl, msg, formattedMessageText, error_1, errorObj;
112
- var _b, _c, _d;
113
- var _e = _a === void 0 ? {} : _a, channel = _e.channel, _f = _e.shouldThrow, shouldThrow = _f === void 0 ? false : _f, env = _e.env;
114
- return __generator(this, function (_g) {
115
- switch (_g.label) {
111
+ var chatWebhooksString, msg, chatWebhooks, chatChannel, chatWebhookUrl, msg, formattedMessageText, error_1, message;
112
+ var _b;
113
+ var _c = _a === void 0 ? {} : _a, channel = _c.channel, _d = _c.shouldThrow, shouldThrow = _d === void 0 ? false : _d, env = _c.env;
114
+ return __generator(this, function (_e) {
115
+ switch (_e.label) {
116
116
  case 0:
117
117
  chatWebhooksString = process.env.GOOGLE_CHAT_WEBHOOKS;
118
118
  if (!chatWebhooksString) {
119
119
  msg = "GOOGLE_CHAT_WEBHOOKS not set. Google Chat message not sent";
120
- Sentry.captureException(new Error(msg));
121
- logger_1.logger.error(msg);
120
+ Sentry.captureException(new errors_1.APIError({ status: 500, title: msg }));
122
121
  return [2 /*return*/];
123
122
  }
124
123
  chatWebhooks = JSON.parse(chatWebhooksString !== null && chatWebhooksString !== void 0 ? chatWebhooksString : "{}");
@@ -126,30 +125,29 @@ var sendToGoogleChat = function (messageText_1) {
126
125
  chatWebhookUrl = (_b = chatWebhooks[chatChannel]) !== null && _b !== void 0 ? _b : chatWebhooks.default;
127
126
  if (!chatWebhookUrl) {
128
127
  msg = "No webhook url set in env for ".concat(chatChannel, ". Google Chat message not sent");
129
- Sentry.captureException(new Error(msg));
130
- logger_1.logger.error(msg);
128
+ Sentry.captureException(new errors_1.APIError({ status: 500, title: msg }));
131
129
  return [2 /*return*/];
132
130
  }
133
131
  formattedMessageText = messageText;
134
132
  if (env) {
135
133
  formattedMessageText = "[".concat(env.toUpperCase(), "] ").concat(messageText);
136
134
  }
137
- _g.label = 1;
135
+ _e.label = 1;
138
136
  case 1:
139
- _g.trys.push([1, 3, , 4]);
137
+ _e.trys.push([1, 3, , 4]);
140
138
  return [4 /*yield*/, axios_1.default.post(chatWebhookUrl, { text: formattedMessageText })];
141
139
  case 2:
142
- _g.sent();
140
+ _e.sent();
143
141
  return [3 /*break*/, 4];
144
142
  case 3:
145
- error_1 = _g.sent();
146
- errorObj = error_1;
147
- logger_1.logger.error("Error posting to Google Chat: ".concat((_c = errorObj.text) !== null && _c !== void 0 ? _c : errorObj.message));
143
+ error_1 = _e.sent();
144
+ message = (0, errors_1.errorMessage)(error_1);
145
+ logger_1.logger.error("Error posting to Google Chat: ".concat(message));
148
146
  Sentry.captureException(error_1);
149
147
  if (shouldThrow) {
150
148
  throw new errors_1.APIError({
151
149
  status: 500,
152
- title: "Error posting to Google Chat: ".concat((_d = errorObj.text) !== null && _d !== void 0 ? _d : errorObj.message),
150
+ title: "Error posting to Google Chat: ".concat(message),
153
151
  });
154
152
  }
155
153
  return [3 /*break*/, 4];