@terreno/api 0.11.4-beta.4 → 0.11.5

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.
@@ -71,6 +71,17 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
71
71
  }
72
72
  return to.concat(ar || Array.prototype.slice.call(from));
73
73
  };
74
+ var __values = (this && this.__values) || function(o) {
75
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
76
+ if (m) return m.call(o);
77
+ if (o && typeof o.length === "number") return {
78
+ next: function () {
79
+ if (o && i >= o.length) o = void 0;
80
+ return { value: o && o[i++], done: !o };
81
+ }
82
+ };
83
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
84
+ };
74
85
  var __importDefault = (this && this.__importDefault) || function (mod) {
75
86
  return (mod && mod.__esModule) ? mod : { "default": mod };
76
87
  };
@@ -662,5 +673,267 @@ var tests_1 = require("./tests");
662
673
  }
663
674
  });
664
675
  }); });
676
+ (0, bun_test_1.it)("re-throws when addRoutes throws during route initialization", function () {
677
+ var addRoutes = function () {
678
+ throw new Error("Route init boom");
679
+ };
680
+ (0, bun_test_1.expect)(function () {
681
+ return (0, expressServer_1.setupServer)({
682
+ addRoutes: addRoutes,
683
+ skipListen: true,
684
+ userModel: tests_1.UserModel,
685
+ });
686
+ }).toThrow("Route init boom");
687
+ });
688
+ });
689
+ (0, bun_test_1.describe)("wrapScript", function () {
690
+ var originalExit = process.exit;
691
+ (0, bun_test_1.beforeEach)(function () {
692
+ 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" });
693
+ // Prevent process.exit from killing the test runner
694
+ process.exit = (0, bun_test_1.mock)(function () {
695
+ throw new Error("__EXIT__");
696
+ });
697
+ });
698
+ (0, bun_test_1.afterEach)(function () {
699
+ process.exit = originalExit;
700
+ });
701
+ (0, bun_test_1.it)("calls process.exit(0) on success", function () { return __awaiter(void 0, void 0, void 0, function () {
702
+ var func;
703
+ return __generator(this, function (_a) {
704
+ switch (_a.label) {
705
+ case 0:
706
+ func = function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
707
+ return [2 /*return*/, "done"];
708
+ }); }); };
709
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { terminateTimeout: 0 })).rejects.toThrow("__EXIT__")];
710
+ case 1:
711
+ _a.sent();
712
+ (0, bun_test_1.expect)(process.exit).toHaveBeenCalledWith(0);
713
+ return [2 /*return*/];
714
+ }
715
+ });
716
+ }); });
717
+ (0, bun_test_1.it)("calls process.exit(1) on error", function () { return __awaiter(void 0, void 0, void 0, function () {
718
+ var func;
719
+ return __generator(this, function (_a) {
720
+ switch (_a.label) {
721
+ case 0:
722
+ func = function () { return __awaiter(void 0, void 0, void 0, function () {
723
+ return __generator(this, function (_a) {
724
+ throw new Error("script failed");
725
+ });
726
+ }); };
727
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { terminateTimeout: 0 })).rejects.toThrow("__EXIT__")];
728
+ case 1:
729
+ _a.sent();
730
+ (0, bun_test_1.expect)(process.exit).toHaveBeenCalledWith(1);
731
+ return [2 /*return*/];
732
+ }
733
+ });
734
+ }); });
735
+ (0, bun_test_1.it)("calls onFinish with the result", function () { return __awaiter(void 0, void 0, void 0, function () {
736
+ var finishResult, func, onFinish;
737
+ return __generator(this, function (_a) {
738
+ switch (_a.label) {
739
+ case 0:
740
+ func = function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
741
+ return [2 /*return*/, "result-value"];
742
+ }); }); };
743
+ onFinish = function (r) { return __awaiter(void 0, void 0, void 0, function () {
744
+ return __generator(this, function (_a) {
745
+ finishResult = r;
746
+ return [2 /*return*/];
747
+ });
748
+ }); };
749
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { onFinish: onFinish, terminateTimeout: 0 })).rejects.toThrow("__EXIT__")];
750
+ case 1:
751
+ _a.sent();
752
+ (0, bun_test_1.expect)(finishResult).toBe("result-value");
753
+ (0, bun_test_1.expect)(process.exit).toHaveBeenCalledWith(0);
754
+ return [2 /*return*/];
755
+ }
756
+ });
757
+ }); });
758
+ (0, bun_test_1.it)("sets up warn and terminate timeouts when terminateTimeout is not 0", function () { return __awaiter(void 0, void 0, void 0, function () {
759
+ var func;
760
+ return __generator(this, function (_a) {
761
+ switch (_a.label) {
762
+ case 0:
763
+ func = function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
764
+ return [2 /*return*/, "ok"];
765
+ }); }); };
766
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { terminateTimeout: 600 })).rejects.toThrow("__EXIT__")];
767
+ case 1:
768
+ _a.sent();
769
+ (0, bun_test_1.expect)(process.exit).toHaveBeenCalledWith(0);
770
+ return [2 /*return*/];
771
+ }
772
+ });
773
+ }); });
774
+ (0, bun_test_1.it)("passes slackChannel option through", function () { return __awaiter(void 0, void 0, void 0, function () {
775
+ var func;
776
+ return __generator(this, function (_a) {
777
+ switch (_a.label) {
778
+ case 0:
779
+ func = function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
780
+ return [2 /*return*/, "ok"];
781
+ }); }); };
782
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { slackChannel: "test-channel", terminateTimeout: 0 })).rejects.toThrow("__EXIT__")];
783
+ case 1:
784
+ _a.sent();
785
+ (0, bun_test_1.expect)(process.exit).toHaveBeenCalledWith(0);
786
+ return [2 /*return*/];
787
+ }
788
+ });
789
+ }); });
790
+ });
791
+ (0, bun_test_1.describe)("wrapScript", function () {
792
+ var originalExit = process.exit;
793
+ var originalSetTimeout = globalThis.setTimeout;
794
+ var timerIds = [];
795
+ (0, bun_test_1.beforeEach)(function () {
796
+ // biome-ignore lint/suspicious/noExplicitAny: Mock requires type override for process.exit.
797
+ process.exit = (0, bun_test_1.mock)(function () {
798
+ throw new Error("process.exit called");
799
+ });
800
+ // Capture timer handles so we can clear them after each test
801
+ globalThis.setTimeout = (function () {
802
+ var args = [];
803
+ for (var _i = 0; _i < arguments.length; _i++) {
804
+ args[_i] = arguments[_i];
805
+ }
806
+ var id = originalSetTimeout.apply(void 0, __spreadArray([], __read(args), false));
807
+ timerIds.push(id);
808
+ return id;
809
+ });
810
+ });
811
+ (0, bun_test_1.afterEach)(function () {
812
+ var e_1, _a;
813
+ try {
814
+ for (var timerIds_1 = __values(timerIds), timerIds_1_1 = timerIds_1.next(); !timerIds_1_1.done; timerIds_1_1 = timerIds_1.next()) {
815
+ var id = timerIds_1_1.value;
816
+ clearTimeout(id);
817
+ }
818
+ }
819
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
820
+ finally {
821
+ try {
822
+ if (timerIds_1_1 && !timerIds_1_1.done && (_a = timerIds_1.return)) _a.call(timerIds_1);
823
+ }
824
+ finally { if (e_1) throw e_1.error; }
825
+ }
826
+ timerIds.length = 0;
827
+ globalThis.setTimeout = originalSetTimeout;
828
+ process.exit = originalExit;
829
+ });
830
+ (0, bun_test_1.it)("runs a successful script and calls process.exit(0)", function () { return __awaiter(void 0, void 0, void 0, function () {
831
+ var func;
832
+ return __generator(this, function (_a) {
833
+ switch (_a.label) {
834
+ case 0:
835
+ func = (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
836
+ return [2 /*return*/, "done"];
837
+ }); }); });
838
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { terminateTimeout: 0 })).rejects.toThrow("process.exit called")];
839
+ case 1:
840
+ _a.sent();
841
+ (0, bun_test_1.expect)(func).toHaveBeenCalled();
842
+ (0, bun_test_1.expect)(process.exit).toHaveBeenCalledWith(0);
843
+ return [2 /*return*/];
844
+ }
845
+ });
846
+ }); });
847
+ (0, bun_test_1.it)("calls onFinish callback on success", function () { return __awaiter(void 0, void 0, void 0, function () {
848
+ var onFinish, func;
849
+ return __generator(this, function (_a) {
850
+ switch (_a.label) {
851
+ case 0:
852
+ onFinish = (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
853
+ return [2 /*return*/];
854
+ }); }); });
855
+ func = (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
856
+ return [2 /*return*/, "result"];
857
+ }); }); });
858
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { onFinish: onFinish, terminateTimeout: 0 })).rejects.toThrow("process.exit called")];
859
+ case 1:
860
+ _a.sent();
861
+ (0, bun_test_1.expect)(onFinish).toHaveBeenCalledWith("result");
862
+ return [2 /*return*/];
863
+ }
864
+ });
865
+ }); });
866
+ (0, bun_test_1.it)("calls process.exit(1) when script throws", function () { return __awaiter(void 0, void 0, void 0, function () {
867
+ var func;
868
+ return __generator(this, function (_a) {
869
+ switch (_a.label) {
870
+ case 0:
871
+ func = (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () {
872
+ return __generator(this, function (_a) {
873
+ throw new Error("script failure");
874
+ });
875
+ }); });
876
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { terminateTimeout: 0 })).rejects.toThrow("process.exit called")];
877
+ case 1:
878
+ _a.sent();
879
+ (0, bun_test_1.expect)(process.exit).toHaveBeenCalledWith(1);
880
+ return [2 /*return*/];
881
+ }
882
+ });
883
+ }); });
884
+ (0, bun_test_1.it)("sets up timeout warnings when terminateTimeout is not 0", function () { return __awaiter(void 0, void 0, void 0, function () {
885
+ var func;
886
+ return __generator(this, function (_a) {
887
+ switch (_a.label) {
888
+ case 0:
889
+ func = (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
890
+ return [2 /*return*/, "done"];
891
+ }); }); });
892
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func, { terminateTimeout: 600 })).rejects.toThrow("process.exit called")];
893
+ case 1:
894
+ _a.sent();
895
+ (0, bun_test_1.expect)(func).toHaveBeenCalled();
896
+ return [2 /*return*/];
897
+ }
898
+ });
899
+ }); });
900
+ (0, bun_test_1.it)("uses default terminateTimeout when not specified", function () { return __awaiter(void 0, void 0, void 0, function () {
901
+ var func;
902
+ return __generator(this, function (_a) {
903
+ switch (_a.label) {
904
+ case 0:
905
+ func = (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
906
+ return [2 /*return*/, "done"];
907
+ }); }); });
908
+ return [4 /*yield*/, (0, bun_test_1.expect)((0, expressServer_1.wrapScript)(func)).rejects.toThrow("process.exit called")];
909
+ case 1:
910
+ _a.sent();
911
+ (0, bun_test_1.expect)(func).toHaveBeenCalled();
912
+ return [2 /*return*/];
913
+ }
914
+ });
915
+ }); });
916
+ });
917
+ (0, bun_test_1.describe)("setupServer error handling", function () {
918
+ var originalEnv = process.env;
919
+ (0, bun_test_1.beforeEach)(function () {
920
+ process.env = __assign(__assign({}, originalEnv), { REFRESH_TOKEN_SECRET: "test-refresh-secret", SESSION_SECRET: "test-session-secret", TOKEN_EXPIRES_IN: "1h", TOKEN_ISSUER: "test-issuer", TOKEN_SECRET: "test-secret" });
921
+ });
922
+ (0, bun_test_1.afterEach)(function () {
923
+ process.env = originalEnv;
924
+ });
925
+ (0, bun_test_1.it)("catches and rethrows errors from initializeRoutes", function () {
926
+ var addRoutes = function () {
927
+ throw new Error("route initialization failed");
928
+ };
929
+ (0, bun_test_1.expect)(function () {
930
+ return (0, expressServer_1.setupServer)({
931
+ addRoutes: addRoutes,
932
+ skipListen: true,
933
+ // biome-ignore lint/suspicious/noExplicitAny: Test mock for UserModel.
934
+ userModel: tests_1.UserModel,
935
+ });
936
+ }).toThrow("route initialization failed");
937
+ });
665
938
  });
666
939
  });
@@ -1,5 +1,5 @@
1
- export declare function sendToGoogleChat(messageText: string, { channel, shouldThrow, env }?: {
1
+ export declare const sendToGoogleChat: (messageText: string, { channel, shouldThrow, env }?: {
2
2
  channel?: string;
3
3
  shouldThrow?: boolean;
4
4
  env?: string;
5
- }): Promise<void>;
5
+ }) => Promise<void>;
@@ -68,17 +68,46 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
68
68
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
69
69
  }
70
70
  };
71
+ var __read = (this && this.__read) || function (o, n) {
72
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
73
+ if (!m) return o;
74
+ var i = m.call(o), r, ar = [], e;
75
+ try {
76
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
77
+ }
78
+ catch (error) { e = { error: error }; }
79
+ finally {
80
+ try {
81
+ if (r && !r.done && (m = i["return"])) m.call(i);
82
+ }
83
+ finally { if (e) throw e.error; }
84
+ }
85
+ return ar;
86
+ };
87
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
88
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
89
+ if (ar || !(i in from)) {
90
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
91
+ ar[i] = from[i];
92
+ }
93
+ }
94
+ return to.concat(ar || Array.prototype.slice.call(from));
95
+ };
71
96
  var __importDefault = (this && this.__importDefault) || function (mod) {
72
97
  return (mod && mod.__esModule) ? mod : { "default": mod };
73
98
  };
74
99
  Object.defineProperty(exports, "__esModule", { value: true });
75
- exports.sendToGoogleChat = sendToGoogleChat;
100
+ exports.sendToGoogleChat = void 0;
76
101
  var Sentry = __importStar(require("@sentry/bun"));
77
102
  var axios_1 = __importDefault(require("axios"));
78
103
  var errors_1 = require("../errors");
79
104
  var logger_1 = require("../logger");
80
- function sendToGoogleChat(messageText_1) {
81
- return __awaiter(this, arguments, void 0, function (messageText, _a) {
105
+ var sendToGoogleChat = function (messageText_1) {
106
+ var args_1 = [];
107
+ for (var _i = 1; _i < arguments.length; _i++) {
108
+ args_1[_i - 1] = arguments[_i];
109
+ }
110
+ return __awaiter(void 0, __spreadArray([messageText_1], __read(args_1), false), void 0, function (messageText, _a) {
82
111
  var chatWebhooksString, msg, chatWebhooks, chatChannel, chatWebhookUrl, msg, formattedMessageText, error_1;
83
112
  var _b, _c, _d;
84
113
  var _e = _a === void 0 ? {} : _a, channel = _e.channel, _f = _e.shouldThrow, shouldThrow = _f === void 0 ? false : _f, env = _e.env;
@@ -127,4 +156,5 @@ function sendToGoogleChat(messageText_1) {
127
156
  }
128
157
  });
129
158
  });
130
- }
159
+ };
160
+ exports.sendToGoogleChat = sendToGoogleChat;
@@ -23,7 +23,7 @@
23
23
  * Logs errors to Sentry and logger when webhook is missing or request fails.
24
24
  * Uses Zoom's rich message format (format=full) with structured header and body.
25
25
  */
26
- export declare function sendToZoom({ header, body, subheader }: {
26
+ export declare const sendToZoom: ({ header, body, subheader }: {
27
27
  header: string;
28
28
  body: string;
29
29
  subheader?: string;
@@ -31,4 +31,4 @@ export declare function sendToZoom({ header, body, subheader }: {
31
31
  channel: string;
32
32
  shouldThrow?: boolean;
33
33
  env?: string;
34
- }): Promise<void>;
34
+ }) => Promise<void>;
@@ -72,7 +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
- exports.sendToZoom = sendToZoom;
75
+ exports.sendToZoom = void 0;
76
76
  var Sentry = __importStar(require("@sentry/bun"));
77
77
  var axios_1 = __importDefault(require("axios"));
78
78
  var errors_1 = require("../errors");
@@ -102,80 +102,78 @@ var logger_1 = require("../logger");
102
102
  * Logs errors to Sentry and logger when webhook is missing or request fails.
103
103
  * Uses Zoom's rich message format (format=full) with structured header and body.
104
104
  */
105
- function sendToZoom(_a, _b) {
106
- return __awaiter(this, arguments, void 0, function (_c, _d) {
107
- var zoomWebhooksString, msg, zoomWebhooks, zoomChannel, zoomWebhookUrl, msg, zoomToken, msg, messageBody, error_1;
108
- var _e, _f, _g, _h, _j, _k, _l, _m;
109
- var header = _c.header, body = _c.body, subheader = _c.subheader;
110
- var channel = _d.channel, _o = _d.shouldThrow, shouldThrow = _o === void 0 ? false : _o, env = _d.env;
111
- return __generator(this, function (_p) {
112
- switch (_p.label) {
113
- case 0:
114
- zoomWebhooksString = process.env.ZOOM_CHAT_WEBHOOKS;
115
- if (!zoomWebhooksString) {
116
- msg = "ZOOM_CHAT_WEBHOOKS not set. Zoom message not sent";
117
- Sentry.captureException(new Error(msg));
118
- logger_1.logger.error(msg);
119
- return [2 /*return*/];
120
- }
121
- zoomWebhooks = JSON.parse(zoomWebhooksString !== null && zoomWebhooksString !== void 0 ? zoomWebhooksString : "{}");
122
- zoomChannel = channel !== null && channel !== void 0 ? channel : "default";
123
- zoomWebhookUrl = (_f = (_e = zoomWebhooks[zoomChannel]) === null || _e === void 0 ? void 0 : _e.channel) !== null && _f !== void 0 ? _f : (_g = zoomWebhooks.default) === null || _g === void 0 ? void 0 : _g.channel;
124
- if (!zoomWebhookUrl) {
125
- msg = "No webhook url set in env for ".concat(zoomChannel, ". Zoom message not sent");
126
- Sentry.captureException(new Error(msg));
127
- logger_1.logger.error(msg);
128
- return [2 /*return*/];
129
- }
130
- zoomToken = (_j = (_h = zoomWebhooks[zoomChannel]) === null || _h === void 0 ? void 0 : _h.verificationToken) !== null && _j !== void 0 ? _j : (_k = zoomWebhooks.default) === null || _k === void 0 ? void 0 : _k.verificationToken;
131
- if (!zoomToken) {
132
- msg = "No verification token set in env for ".concat(zoomChannel, ". Zoom message not sent");
133
- Sentry.captureException(new Error(msg));
134
- logger_1.logger.error(msg);
135
- return [2 /*return*/];
136
- }
137
- messageBody = {
138
- body: [
139
- {
140
- text: body,
141
- type: "message",
142
- },
143
- ],
144
- head: {
145
- text: env ? "[".concat(env.toUpperCase(), "] ").concat(header) : header,
105
+ var sendToZoom = function (_a, _b) { return __awaiter(void 0, [_a, _b], void 0, function (_c, _d) {
106
+ var zoomWebhooksString, msg, zoomWebhooks, zoomChannel, zoomWebhookUrl, msg, zoomToken, msg, messageBody, error_1;
107
+ var _e, _f, _g, _h, _j, _k, _l, _m;
108
+ var header = _c.header, body = _c.body, subheader = _c.subheader;
109
+ var channel = _d.channel, _o = _d.shouldThrow, shouldThrow = _o === void 0 ? false : _o, env = _d.env;
110
+ return __generator(this, function (_p) {
111
+ switch (_p.label) {
112
+ case 0:
113
+ zoomWebhooksString = process.env.ZOOM_CHAT_WEBHOOKS;
114
+ if (!zoomWebhooksString) {
115
+ msg = "ZOOM_CHAT_WEBHOOKS not set. Zoom message not sent";
116
+ Sentry.captureException(new Error(msg));
117
+ logger_1.logger.error(msg);
118
+ return [2 /*return*/];
119
+ }
120
+ zoomWebhooks = JSON.parse(zoomWebhooksString !== null && zoomWebhooksString !== void 0 ? zoomWebhooksString : "{}");
121
+ zoomChannel = channel !== null && channel !== void 0 ? channel : "default";
122
+ zoomWebhookUrl = (_f = (_e = zoomWebhooks[zoomChannel]) === null || _e === void 0 ? void 0 : _e.channel) !== null && _f !== void 0 ? _f : (_g = zoomWebhooks.default) === null || _g === void 0 ? void 0 : _g.channel;
123
+ if (!zoomWebhookUrl) {
124
+ msg = "No webhook url set in env for ".concat(zoomChannel, ". Zoom message not sent");
125
+ Sentry.captureException(new Error(msg));
126
+ logger_1.logger.error(msg);
127
+ return [2 /*return*/];
128
+ }
129
+ zoomToken = (_j = (_h = zoomWebhooks[zoomChannel]) === null || _h === void 0 ? void 0 : _h.verificationToken) !== null && _j !== void 0 ? _j : (_k = zoomWebhooks.default) === null || _k === void 0 ? void 0 : _k.verificationToken;
130
+ if (!zoomToken) {
131
+ msg = "No verification token set in env for ".concat(zoomChannel, ". Zoom message not sent");
132
+ Sentry.captureException(new Error(msg));
133
+ logger_1.logger.error(msg);
134
+ return [2 /*return*/];
135
+ }
136
+ messageBody = {
137
+ body: [
138
+ {
139
+ text: body,
140
+ type: "message",
146
141
  },
142
+ ],
143
+ head: {
144
+ text: env ? "[".concat(env.toUpperCase(), "] ").concat(header) : header,
145
+ },
146
+ };
147
+ if (subheader) {
148
+ messageBody.head.sub_head = {
149
+ text: subheader,
147
150
  };
148
- // Add subheader if provided
149
- if (subheader) {
150
- messageBody.head.sub_head = {
151
- text: subheader,
152
- };
153
- }
154
- _p.label = 1;
155
- case 1:
156
- _p.trys.push([1, 3, , 4]);
157
- return [4 /*yield*/, axios_1.default.post("".concat(zoomWebhookUrl, "?format=full"), { content: messageBody }, {
158
- headers: {
159
- Authorization: zoomToken,
160
- "Content-Type": "application/json",
161
- },
162
- })];
163
- case 2:
164
- _p.sent();
165
- return [3 /*break*/, 4];
166
- case 3:
167
- error_1 = _p.sent();
168
- logger_1.logger.error("Error posting to Zoom: ".concat((_l = error_1.text) !== null && _l !== void 0 ? _l : error_1.message));
169
- Sentry.captureException(error_1);
170
- if (shouldThrow) {
171
- throw new errors_1.APIError({
172
- status: 500,
173
- title: "Error posting to Zoom: ".concat((_m = error_1.text) !== null && _m !== void 0 ? _m : error_1.message),
174
- });
175
- }
176
- return [3 /*break*/, 4];
177
- case 4: return [2 /*return*/];
178
- }
179
- });
151
+ }
152
+ _p.label = 1;
153
+ case 1:
154
+ _p.trys.push([1, 3, , 4]);
155
+ return [4 /*yield*/, axios_1.default.post("".concat(zoomWebhookUrl, "?format=full"), { content: messageBody }, {
156
+ headers: {
157
+ Authorization: zoomToken,
158
+ "Content-Type": "application/json",
159
+ },
160
+ })];
161
+ case 2:
162
+ _p.sent();
163
+ return [3 /*break*/, 4];
164
+ case 3:
165
+ error_1 = _p.sent();
166
+ logger_1.logger.error("Error posting to Zoom: ".concat((_l = error_1.text) !== null && _l !== void 0 ? _l : error_1.message));
167
+ Sentry.captureException(error_1);
168
+ if (shouldThrow) {
169
+ throw new errors_1.APIError({
170
+ status: 500,
171
+ title: "Error posting to Zoom: ".concat((_m = error_1.text) !== null && _m !== void 0 ? _m : error_1.message),
172
+ });
173
+ }
174
+ return [3 /*break*/, 4];
175
+ case 4: return [2 /*return*/];
176
+ }
180
177
  });
181
- }
178
+ }); };
179
+ exports.sendToZoom = sendToZoom;
@@ -33,7 +33,7 @@ import type { ModelRouterOptions } from "./api";
33
33
  * };
34
34
  * ```
35
35
  */
36
- export type OpenApiSchemaProperty = {
36
+ export interface OpenApiSchemaProperty {
37
37
  /** The JSON Schema type (e.g., "string", "number", "boolean", "object", "array") */
38
38
  type: string;
39
39
  /** Human-readable description of the property */
@@ -48,7 +48,7 @@ export type OpenApiSchemaProperty = {
48
48
  additionalProperties?: OpenApiSchemaProperty | boolean;
49
49
  /** Whether this property is required in the parent object */
50
50
  required?: boolean;
51
- };
51
+ }
52
52
  /**
53
53
  * Defines the top-level schema for request bodies and responses.
54
54
  *
@@ -107,7 +107,7 @@ export type OpenApiSchema = {
107
107
  * };
108
108
  * ```
109
109
  */
110
- export type OpenApiParameter = {
110
+ export interface OpenApiParameter {
111
111
  /** Location of the parameter */
112
112
  in: "query" | "path" | "header";
113
113
  /** Name of the parameter */
@@ -118,7 +118,7 @@ export type OpenApiParameter = {
118
118
  schema: OpenApiSchemaProperty;
119
119
  /** Human-readable description of the parameter */
120
120
  description?: string;
121
- };
121
+ }
122
122
  /**
123
123
  * Defines a response in an OpenAPI operation.
124
124
  *
@@ -143,7 +143,7 @@ export type OpenApiParameter = {
143
143
  * };
144
144
  * ```
145
145
  */
146
- export type OpenApiResponse = {
146
+ export interface OpenApiResponse {
147
147
  /** Human-readable description of the response */
148
148
  description: string;
149
149
  /** Content definitions keyed by media type */
@@ -152,7 +152,7 @@ export type OpenApiResponse = {
152
152
  schema: OpenApiSchema;
153
153
  };
154
154
  };
155
- };
155
+ }
156
156
  /**
157
157
  * Result from building OpenAPI middleware with schemas exposed.
158
158
  * Useful when you want to use the schemas with asyncHandler's validation.
@@ -485,4 +485,4 @@ export declare class OpenApiMiddlewareBuilder {
485
485
  * router.get("/analytics/stats", statsMiddleware, getStatsHandler);
486
486
  * ```
487
487
  */
488
- export declare function createOpenApiBuilder(options: Partial<ModelRouterOptions<unknown>>): OpenApiMiddlewareBuilder;
488
+ export declare const createOpenApiBuilder: (options: Partial<ModelRouterOptions<unknown>>) => OpenApiMiddlewareBuilder;
@@ -30,8 +30,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
30
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.OpenApiMiddlewareBuilder = void 0;
34
- exports.createOpenApiBuilder = createOpenApiBuilder;
33
+ exports.createOpenApiBuilder = exports.OpenApiMiddlewareBuilder = void 0;
35
34
  /**
36
35
  * OpenAPI Middleware Builder
37
36
  *
@@ -534,6 +533,7 @@ exports.OpenApiMiddlewareBuilder = OpenApiMiddlewareBuilder;
534
533
  * router.get("/analytics/stats", statsMiddleware, getStatsHandler);
535
534
  * ```
536
535
  */
537
- function createOpenApiBuilder(options) {
536
+ var createOpenApiBuilder = function (options) {
538
537
  return new OpenApiMiddlewareBuilder(options);
539
- }
538
+ };
539
+ exports.createOpenApiBuilder = createOpenApiBuilder;
package/package.json CHANGED
@@ -27,7 +27,6 @@
27
27
  "jsonwebtoken": "^9.0.2",
28
28
  "lodash": "^4.17.23",
29
29
  "luxon": "^3.7.2",
30
- "mongoose": "8.23.0",
31
30
  "mongoose-to-swagger": "^1.4.0",
32
31
  "ms": "^2.1.3",
33
32
  "on-finished": "^2.3.0",
@@ -62,6 +61,7 @@
62
61
  "@types/sinon": "^21.0.0",
63
62
  "@types/supertest": "^6.0.2",
64
63
  "mongodb-memory-server": "^11.0.1",
64
+ "mongoose": "8.23.0",
65
65
  "sinon": "^21.0.1",
66
66
  "supertest": "^7.0.0",
67
67
  "typedoc": "~0.28.17",
@@ -104,5 +104,5 @@
104
104
  "updateSnapshot": "bun test --update-snapshots"
105
105
  },
106
106
  "types": "dist/index.d.ts",
107
- "version": "0.11.4-beta.4"
107
+ "version": "0.11.5"
108
108
  }
@@ -1,4 +1,4 @@
1
- import {afterEach, beforeEach, describe, expect, it} from "bun:test";
1
+ import {afterEach, beforeEach, describe, expect, it, mock} from "bun:test";
2
2
  import express from "express";
3
3
  import supertest from "supertest";
4
4
 
@@ -9,6 +9,7 @@ import {
9
9
  logRequests,
10
10
  setupEnvironment,
11
11
  setupServer,
12
+ wrapScript,
12
13
  } from "./expressServer";
13
14
  import {UserModel} from "./tests";
14
15
 
@@ -568,5 +569,191 @@ describe("expressServer", () => {
568
569
 
569
570
  await supertest(app).get("/test").expect(200);
570
571
  });
572
+
573
+ it("re-throws when addRoutes throws during route initialization", () => {
574
+ const addRoutes = () => {
575
+ throw new Error("Route init boom");
576
+ };
577
+
578
+ expect(() =>
579
+ setupServer({
580
+ addRoutes,
581
+ skipListen: true,
582
+ userModel: UserModel as any,
583
+ })
584
+ ).toThrow("Route init boom");
585
+ });
586
+ });
587
+
588
+ describe("wrapScript", () => {
589
+ const originalExit = process.exit;
590
+
591
+ beforeEach(() => {
592
+ process.env = {
593
+ ...process.env,
594
+ REFRESH_TOKEN_SECRET: "test-refresh-secret",
595
+ SESSION_SECRET: "test-session-secret",
596
+ TOKEN_EXPIRES_IN: "1h",
597
+ TOKEN_ISSUER: "test-issuer",
598
+ TOKEN_SECRET: "test-secret",
599
+ };
600
+ // Prevent process.exit from killing the test runner
601
+ process.exit = mock(() => {
602
+ throw new Error("__EXIT__");
603
+ }) as unknown as typeof process.exit;
604
+ });
605
+
606
+ afterEach(() => {
607
+ process.exit = originalExit;
608
+ });
609
+
610
+ it("calls process.exit(0) on success", async () => {
611
+ const func = async () => "done";
612
+ await expect(wrapScript(func, {terminateTimeout: 0})).rejects.toThrow("__EXIT__");
613
+ expect(process.exit).toHaveBeenCalledWith(0);
614
+ });
615
+
616
+ it("calls process.exit(1) on error", async () => {
617
+ const func = async () => {
618
+ throw new Error("script failed");
619
+ };
620
+ await expect(wrapScript(func, {terminateTimeout: 0})).rejects.toThrow("__EXIT__");
621
+ expect(process.exit).toHaveBeenCalledWith(1);
622
+ });
623
+
624
+ it("calls onFinish with the result", async () => {
625
+ let finishResult: unknown;
626
+ const func = async () => "result-value";
627
+ const onFinish = async (r: unknown) => {
628
+ finishResult = r;
629
+ };
630
+ await expect(wrapScript(func, {onFinish, terminateTimeout: 0})).rejects.toThrow("__EXIT__");
631
+ expect(finishResult).toBe("result-value");
632
+ expect(process.exit).toHaveBeenCalledWith(0);
633
+ });
634
+
635
+ it("sets up warn and terminate timeouts when terminateTimeout is not 0", async () => {
636
+ const func = async () => "ok";
637
+ await expect(wrapScript(func, {terminateTimeout: 600})).rejects.toThrow("__EXIT__");
638
+ expect(process.exit).toHaveBeenCalledWith(0);
639
+ });
640
+
641
+ it("passes slackChannel option through", async () => {
642
+ const func = async () => "ok";
643
+ await expect(
644
+ wrapScript(func, {slackChannel: "test-channel", terminateTimeout: 0})
645
+ ).rejects.toThrow("__EXIT__");
646
+ expect(process.exit).toHaveBeenCalledWith(0);
647
+ });
648
+ });
649
+
650
+ describe("wrapScript", () => {
651
+ const originalExit = process.exit;
652
+ const originalSetTimeout = globalThis.setTimeout;
653
+ const timerIds: ReturnType<typeof setTimeout>[] = [];
654
+
655
+ beforeEach(() => {
656
+ // biome-ignore lint/suspicious/noExplicitAny: Mock requires type override for process.exit.
657
+ process.exit = mock(() => {
658
+ throw new Error("process.exit called");
659
+ }) as unknown as typeof process.exit;
660
+ // Capture timer handles so we can clear them after each test
661
+ globalThis.setTimeout = ((...args: Parameters<typeof setTimeout>) => {
662
+ const id = originalSetTimeout(...args);
663
+ timerIds.push(id);
664
+ return id;
665
+ }) as typeof setTimeout;
666
+ });
667
+
668
+ afterEach(() => {
669
+ for (const id of timerIds) {
670
+ clearTimeout(id);
671
+ }
672
+ timerIds.length = 0;
673
+ globalThis.setTimeout = originalSetTimeout;
674
+ process.exit = originalExit;
675
+ });
676
+
677
+ it("runs a successful script and calls process.exit(0)", async () => {
678
+ const func = mock(async () => "done");
679
+
680
+ await expect(wrapScript(func, {terminateTimeout: 0})).rejects.toThrow("process.exit called");
681
+
682
+ expect(func).toHaveBeenCalled();
683
+ expect(process.exit).toHaveBeenCalledWith(0);
684
+ });
685
+
686
+ it("calls onFinish callback on success", async () => {
687
+ const onFinish = mock(async () => {});
688
+ const func = mock(async () => "result");
689
+
690
+ await expect(wrapScript(func, {onFinish, terminateTimeout: 0})).rejects.toThrow(
691
+ "process.exit called"
692
+ );
693
+
694
+ expect(onFinish).toHaveBeenCalledWith("result");
695
+ });
696
+
697
+ it("calls process.exit(1) when script throws", async () => {
698
+ const func = mock(async () => {
699
+ throw new Error("script failure");
700
+ });
701
+
702
+ await expect(wrapScript(func, {terminateTimeout: 0})).rejects.toThrow("process.exit called");
703
+
704
+ expect(process.exit).toHaveBeenCalledWith(1);
705
+ });
706
+
707
+ it("sets up timeout warnings when terminateTimeout is not 0", async () => {
708
+ const func = mock(async () => "done");
709
+
710
+ await expect(wrapScript(func, {terminateTimeout: 600})).rejects.toThrow(
711
+ "process.exit called"
712
+ );
713
+
714
+ expect(func).toHaveBeenCalled();
715
+ });
716
+
717
+ it("uses default terminateTimeout when not specified", async () => {
718
+ const func = mock(async () => "done");
719
+
720
+ await expect(wrapScript(func)).rejects.toThrow("process.exit called");
721
+
722
+ expect(func).toHaveBeenCalled();
723
+ });
724
+ });
725
+
726
+ describe("setupServer error handling", () => {
727
+ const originalEnv = process.env;
728
+
729
+ beforeEach(() => {
730
+ process.env = {
731
+ ...originalEnv,
732
+ REFRESH_TOKEN_SECRET: "test-refresh-secret",
733
+ SESSION_SECRET: "test-session-secret",
734
+ TOKEN_EXPIRES_IN: "1h",
735
+ TOKEN_ISSUER: "test-issuer",
736
+ TOKEN_SECRET: "test-secret",
737
+ };
738
+ });
739
+
740
+ afterEach(() => {
741
+ process.env = originalEnv;
742
+ });
743
+
744
+ it("catches and rethrows errors from initializeRoutes", () => {
745
+ const addRoutes = () => {
746
+ throw new Error("route initialization failed");
747
+ };
748
+
749
+ expect(() =>
750
+ setupServer({
751
+ addRoutes,
752
+ skipListen: true,
753
+ // biome-ignore lint/suspicious/noExplicitAny: Test mock for UserModel.
754
+ userModel: UserModel as any,
755
+ })
756
+ ).toThrow("route initialization failed");
757
+ });
571
758
  });
572
759
  });
@@ -4,10 +4,10 @@ import axios from "axios";
4
4
  import {APIError} from "../errors";
5
5
  import {logger} from "../logger";
6
6
 
7
- export async function sendToGoogleChat(
7
+ export const sendToGoogleChat = async (
8
8
  messageText: string,
9
9
  {channel, shouldThrow = false, env}: {channel?: string; shouldThrow?: boolean; env?: string} = {}
10
- ) {
10
+ ) => {
11
11
  const chatWebhooksString = process.env.GOOGLE_CHAT_WEBHOOKS;
12
12
  if (!chatWebhooksString) {
13
13
  const msg = "GOOGLE_CHAT_WEBHOOKS not set. Google Chat message not sent";
@@ -44,4 +44,4 @@ export async function sendToGoogleChat(
44
44
  });
45
45
  }
46
46
  }
47
- }
47
+ };
@@ -29,10 +29,10 @@ import {logger} from "../logger";
29
29
  * Logs errors to Sentry and logger when webhook is missing or request fails.
30
30
  * Uses Zoom's rich message format (format=full) with structured header and body.
31
31
  */
32
- export async function sendToZoom(
32
+ export const sendToZoom = async (
33
33
  {header, body, subheader}: {header: string; body: string; subheader?: string},
34
34
  {channel, shouldThrow = false, env}: {channel: string; shouldThrow?: boolean; env?: string}
35
- ) {
35
+ ) => {
36
36
  const zoomWebhooksString = process.env.ZOOM_CHAT_WEBHOOKS;
37
37
  if (!zoomWebhooksString) {
38
38
  const msg = "ZOOM_CHAT_WEBHOOKS not set. Zoom message not sent";
@@ -45,7 +45,6 @@ export async function sendToZoom(
45
45
  );
46
46
 
47
47
  const zoomChannel = channel ?? "default";
48
- // Use format full
49
48
  const zoomWebhookUrl = zoomWebhooks[zoomChannel]?.channel ?? zoomWebhooks.default?.channel;
50
49
 
51
50
  if (!zoomWebhookUrl) {
@@ -64,7 +63,6 @@ export async function sendToZoom(
64
63
  return;
65
64
  }
66
65
 
67
- // Build the message structure
68
66
  const messageBody: {
69
67
  head: {text: string; sub_head?: {text: string}};
70
68
  body: Array<{type: string; text: string}>;
@@ -80,7 +78,6 @@ export async function sendToZoom(
80
78
  },
81
79
  };
82
80
 
83
- // Add subheader if provided
84
81
  if (subheader) {
85
82
  messageBody.head.sub_head = {
86
83
  text: subheader,
@@ -108,4 +105,4 @@ export async function sendToZoom(
108
105
  });
109
106
  }
110
107
  }
111
- }
108
+ };
@@ -75,7 +75,7 @@ import {
75
75
  * };
76
76
  * ```
77
77
  */
78
- export type OpenApiSchemaProperty = {
78
+ export interface OpenApiSchemaProperty {
79
79
  /** The JSON Schema type (e.g., "string", "number", "boolean", "object", "array") */
80
80
  type: string;
81
81
  /** Human-readable description of the property */
@@ -90,7 +90,7 @@ export type OpenApiSchemaProperty = {
90
90
  additionalProperties?: OpenApiSchemaProperty | boolean;
91
91
  /** Whether this property is required in the parent object */
92
92
  required?: boolean;
93
- };
93
+ }
94
94
 
95
95
  /**
96
96
  * Defines the top-level schema for request bodies and responses.
@@ -151,7 +151,7 @@ export type OpenApiSchema = {
151
151
  * };
152
152
  * ```
153
153
  */
154
- export type OpenApiParameter = {
154
+ export interface OpenApiParameter {
155
155
  /** Location of the parameter */
156
156
  in: "query" | "path" | "header";
157
157
  /** Name of the parameter */
@@ -162,7 +162,7 @@ export type OpenApiParameter = {
162
162
  schema: OpenApiSchemaProperty;
163
163
  /** Human-readable description of the parameter */
164
164
  description?: string;
165
- };
165
+ }
166
166
 
167
167
  /**
168
168
  * Defines a response in an OpenAPI operation.
@@ -188,7 +188,7 @@ export type OpenApiParameter = {
188
188
  * };
189
189
  * ```
190
190
  */
191
- export type OpenApiResponse = {
191
+ export interface OpenApiResponse {
192
192
  /** Human-readable description of the response */
193
193
  description: string;
194
194
  /** Content definitions keyed by media type */
@@ -197,7 +197,7 @@ export type OpenApiResponse = {
197
197
  schema: OpenApiSchema;
198
198
  };
199
199
  };
200
- };
200
+ }
201
201
 
202
202
  /**
203
203
  * Internal configuration object for the OpenAPI middleware builder.
@@ -817,8 +817,8 @@ export class OpenApiMiddlewareBuilder {
817
817
  * router.get("/analytics/stats", statsMiddleware, getStatsHandler);
818
818
  * ```
819
819
  */
820
- export function createOpenApiBuilder(
820
+ export const createOpenApiBuilder = (
821
821
  options: Partial<ModelRouterOptions<unknown>>
822
- ): OpenApiMiddlewareBuilder {
822
+ ): OpenApiMiddlewareBuilder => {
823
823
  return new OpenApiMiddlewareBuilder(options);
824
- }
824
+ };