@terreno/api 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/dist/__tests__/versionCheckPlugin.test.js +53 -3
  2. package/dist/api.arrayOperations.test.js +1 -0
  3. package/dist/api.asyncHandler.test.d.ts +1 -0
  4. package/dist/api.asyncHandler.test.js +236 -0
  5. package/dist/api.d.ts +15 -4
  6. package/dist/api.errors.test.js +1 -0
  7. package/dist/api.hooks.test.js +1 -0
  8. package/dist/api.js +153 -104
  9. package/dist/api.query.test.js +1 -0
  10. package/dist/api.test.js +174 -0
  11. package/dist/auth.d.ts +10 -5
  12. package/dist/auth.js +163 -90
  13. package/dist/auth.test.js +159 -0
  14. package/dist/betterAuthApp.test.js +1 -0
  15. package/dist/betterAuthSetup.d.ts +5 -6
  16. package/dist/betterAuthSetup.js +17 -14
  17. package/dist/betterAuthSetup.test.js +1 -0
  18. package/dist/config.d.ts +48 -0
  19. package/dist/config.js +248 -0
  20. package/dist/config.test.d.ts +1 -0
  21. package/dist/config.test.js +328 -0
  22. package/dist/configuration.test.js +1 -0
  23. package/dist/configurationApp.d.ts +1 -1
  24. package/dist/configurationApp.js +17 -13
  25. package/dist/configurationPlugin.test.js +1 -0
  26. package/dist/consentApp.test.js +1 -0
  27. package/dist/envConfigurationPlugin.d.ts +2 -0
  28. package/dist/envConfigurationPlugin.js +173 -0
  29. package/dist/envConfigurationPlugin.test.d.ts +1 -0
  30. package/dist/envConfigurationPlugin.test.js +322 -0
  31. package/dist/errors.d.ts +18 -7
  32. package/dist/errors.js +106 -10
  33. package/dist/errors.test.js +16 -1
  34. package/dist/example.js +16 -7
  35. package/dist/expressServer.d.ts +10 -9
  36. package/dist/expressServer.js +62 -53
  37. package/dist/expressServer.test.js +53 -2
  38. package/dist/githubAuth.d.ts +2 -1
  39. package/dist/githubAuth.js +41 -26
  40. package/dist/githubAuth.test.js +1 -0
  41. package/dist/index.d.ts +4 -0
  42. package/dist/index.js +4 -0
  43. package/dist/logger.d.ts +1 -1
  44. package/dist/logger.js +42 -20
  45. package/dist/models/versionConfig.d.ts +2 -0
  46. package/dist/models/versionConfig.js +8 -0
  47. package/dist/notifiers/googleChatNotifier.js +14 -16
  48. package/dist/notifiers/googleChatNotifier.test.js +1 -0
  49. package/dist/notifiers/slackNotifier.js +16 -14
  50. package/dist/notifiers/slackNotifier.test.js +41 -3
  51. package/dist/notifiers/zoomNotifier.js +7 -10
  52. package/dist/notifiers/zoomNotifier.test.js +1 -0
  53. package/dist/openApi.d.ts +1 -1
  54. package/dist/openApi.test.js +1 -0
  55. package/dist/openApiBuilder.d.ts +39 -6
  56. package/dist/openApiBuilder.js +1 -31
  57. package/dist/openApiBuilder.test.js +1 -0
  58. package/dist/openApiValidator.js +1 -0
  59. package/dist/openApiValidator.test.js +65 -0
  60. package/dist/permissions.d.ts +4 -4
  61. package/dist/permissions.js +67 -65
  62. package/dist/permissions.middleware.test.js +1 -0
  63. package/dist/permissions.test.js +1 -0
  64. package/dist/plugins.d.ts +5 -5
  65. package/dist/plugins.js +18 -9
  66. package/dist/plugins.test.js +1 -1
  67. package/dist/populate.d.ts +15 -8
  68. package/dist/populate.js +23 -24
  69. package/dist/populate.test.js +1 -0
  70. package/dist/realtime/changeStreamWatcher.d.ts +73 -0
  71. package/dist/realtime/changeStreamWatcher.js +720 -0
  72. package/dist/realtime/index.d.ts +6 -0
  73. package/dist/realtime/index.js +27 -0
  74. package/dist/realtime/queryMatcher.d.ts +14 -0
  75. package/dist/realtime/queryMatcher.js +250 -0
  76. package/dist/realtime/queryStore.d.ts +37 -0
  77. package/dist/realtime/queryStore.js +195 -0
  78. package/dist/realtime/realtime.test.d.ts +10 -0
  79. package/dist/realtime/realtime.test.js +2158 -0
  80. package/dist/realtime/realtimeApp.d.ts +93 -0
  81. package/dist/realtime/realtimeApp.js +560 -0
  82. package/dist/realtime/registry.d.ts +40 -0
  83. package/dist/realtime/registry.js +38 -0
  84. package/dist/realtime/socketUser.d.ts +10 -0
  85. package/dist/realtime/socketUser.js +17 -0
  86. package/dist/realtime/types.d.ts +100 -0
  87. package/dist/realtime/types.js +2 -0
  88. package/dist/requestContext.d.ts +37 -0
  89. package/dist/requestContext.js +344 -0
  90. package/dist/requestContext.test.d.ts +1 -0
  91. package/dist/requestContext.test.js +241 -0
  92. package/dist/terrenoApp.d.ts +8 -0
  93. package/dist/terrenoApp.js +50 -13
  94. package/dist/terrenoApp.test.js +194 -21
  95. package/dist/terrenoPlugin.d.ts +11 -0
  96. package/dist/tests/bunSetup.js +1 -0
  97. package/dist/tests.js +1 -1
  98. package/dist/transformers.d.ts +2 -2
  99. package/dist/transformers.js +5 -3
  100. package/dist/transformers.test.js +90 -0
  101. package/dist/types/consentResponse.d.ts +6 -3
  102. package/dist/versionCheckPlugin.d.ts +2 -0
  103. package/dist/versionCheckPlugin.js +18 -12
  104. package/package.json +4 -2
  105. package/src/__tests__/versionCheckPlugin.test.ts +37 -3
  106. package/src/api.arrayOperations.test.ts +1 -0
  107. package/src/api.asyncHandler.test.ts +177 -0
  108. package/src/api.errors.test.ts +1 -0
  109. package/src/api.hooks.test.ts +1 -0
  110. package/src/api.query.test.ts +1 -0
  111. package/src/api.test.ts +132 -0
  112. package/src/api.ts +199 -84
  113. package/src/auth.test.ts +160 -0
  114. package/src/auth.ts +120 -50
  115. package/src/betterAuthApp.test.ts +1 -0
  116. package/src/betterAuthSetup.test.ts +1 -0
  117. package/src/betterAuthSetup.ts +46 -19
  118. package/src/config.test.ts +255 -0
  119. package/src/config.ts +206 -0
  120. package/src/configuration.test.ts +1 -0
  121. package/src/configurationApp.ts +59 -24
  122. package/src/configurationPlugin.test.ts +1 -0
  123. package/src/consentApp.test.ts +1 -0
  124. package/src/envConfigurationPlugin.test.ts +143 -0
  125. package/src/envConfigurationPlugin.ts +100 -0
  126. package/src/errors.test.ts +19 -1
  127. package/src/errors.ts +94 -20
  128. package/src/example.ts +46 -21
  129. package/src/express.d.ts +18 -1
  130. package/src/expressServer.test.ts +50 -2
  131. package/src/expressServer.ts +80 -50
  132. package/src/githubAuth.test.ts +1 -0
  133. package/src/githubAuth.ts +59 -38
  134. package/src/index.ts +4 -0
  135. package/src/logger.ts +47 -17
  136. package/src/models/versionConfig.ts +13 -2
  137. package/src/notifiers/googleChatNotifier.test.ts +1 -0
  138. package/src/notifiers/googleChatNotifier.ts +7 -9
  139. package/src/notifiers/slackNotifier.test.ts +29 -3
  140. package/src/notifiers/slackNotifier.ts +9 -7
  141. package/src/notifiers/zoomNotifier.test.ts +1 -0
  142. package/src/notifiers/zoomNotifier.ts +8 -11
  143. package/src/openApi.test.ts +1 -0
  144. package/src/openApi.ts +4 -4
  145. package/src/openApiBuilder.test.ts +1 -0
  146. package/src/openApiBuilder.ts +14 -11
  147. package/src/openApiValidator.test.ts +59 -0
  148. package/src/openApiValidator.ts +3 -2
  149. package/src/permissions.middleware.test.ts +1 -0
  150. package/src/permissions.test.ts +1 -0
  151. package/src/permissions.ts +30 -25
  152. package/src/plugins.test.ts +1 -1
  153. package/src/plugins.ts +21 -14
  154. package/src/populate.test.ts +1 -0
  155. package/src/populate.ts +44 -36
  156. package/src/realtime/changeStreamWatcher.ts +568 -0
  157. package/src/realtime/index.ts +34 -0
  158. package/src/realtime/queryMatcher.ts +179 -0
  159. package/src/realtime/queryStore.ts +132 -0
  160. package/src/realtime/realtime.test.ts +1755 -0
  161. package/src/realtime/realtimeApp.ts +478 -0
  162. package/src/realtime/registry.ts +64 -0
  163. package/src/realtime/socketUser.ts +25 -0
  164. package/src/realtime/types.ts +112 -0
  165. package/src/requestContext.test.ts +196 -0
  166. package/src/requestContext.ts +368 -0
  167. package/src/terrenoApp.test.ts +137 -11
  168. package/src/terrenoApp.ts +64 -17
  169. package/src/terrenoPlugin.ts +12 -0
  170. package/src/tests/bunSetup.ts +1 -0
  171. package/src/tests.ts +7 -2
  172. package/src/transformers.test.ts +70 -2
  173. package/src/transformers.ts +15 -7
  174. package/src/types/consentResponse.ts +8 -10
  175. package/src/versionCheckPlugin.ts +15 -7
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
47
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
48
+ return new (P || (P = Promise))(function (resolve, reject) {
49
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
50
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
51
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
52
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
53
+ });
54
+ };
55
+ var __generator = (this && this.__generator) || function (thisArg, body) {
56
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
57
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
58
+ function verb(n) { return function (v) { return step([n, v]); }; }
59
+ function step(op) {
60
+ if (f) throw new TypeError("Generator is already executing.");
61
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
62
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
63
+ if (y = 0, t) op = [op[0] & 2, t.value];
64
+ switch (op[0]) {
65
+ case 0: case 1: t = op; break;
66
+ case 4: _.label++; return { value: op[1], done: false };
67
+ case 5: _.label++; y = op[1]; op = [0]; continue;
68
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
69
+ default:
70
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
71
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
72
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
73
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
74
+ if (t[2]) _.ops.pop();
75
+ _.trys.pop(); continue;
76
+ }
77
+ op = body.call(thisArg, _);
78
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
79
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
80
+ }
81
+ };
82
+ var __values = (this && this.__values) || function(o) {
83
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
84
+ if (m) return m.call(o);
85
+ if (o && typeof o.length === "number") return {
86
+ next: function () {
87
+ if (o && i >= o.length) o = void 0;
88
+ return { value: o && o[i++], done: !o };
89
+ }
90
+ };
91
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
92
+ };
93
+ var __read = (this && this.__read) || function (o, n) {
94
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
95
+ if (!m) return o;
96
+ var i = m.call(o), r, ar = [], e;
97
+ try {
98
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
99
+ }
100
+ catch (error) { e = { error: error }; }
101
+ finally {
102
+ try {
103
+ if (r && !r.done && (m = i["return"])) m.call(i);
104
+ }
105
+ finally { if (e) throw e.error; }
106
+ }
107
+ return ar;
108
+ };
109
+ var _a;
110
+ Object.defineProperty(exports, "__esModule", { value: true });
111
+ // biome-ignore-all lint/suspicious/noExplicitAny: test model typing
112
+ var bun_test_1 = require("bun:test");
113
+ var mongoose_1 = __importStar(require("mongoose"));
114
+ var config_1 = require("./config");
115
+ var envConfigurationPlugin_1 = require("./envConfigurationPlugin");
116
+ var testSchema = new mongoose_1.Schema({}, { strict: "throw" });
117
+ testSchema.plugin(envConfigurationPlugin_1.envConfigurationPlugin);
118
+ var TestEnvConfig = (_a = mongoose_1.default.models.TestEnvConfig) !== null && _a !== void 0 ? _a : mongoose_1.default.model("TestEnvConfig", testSchema);
119
+ var setupLoader = function () {
120
+ config_1.Config.setEnvLoader(function () { return __awaiter(void 0, void 0, void 0, function () {
121
+ var doc, out, _a, _b, _c, k, v;
122
+ var e_1, _d;
123
+ return __generator(this, function (_e) {
124
+ switch (_e.label) {
125
+ case 0: return [4 /*yield*/, TestEnvConfig.findOne({}).lean()];
126
+ case 1:
127
+ doc = (_e.sent());
128
+ if (!(doc === null || doc === void 0 ? void 0 : doc.env)) {
129
+ return [2 /*return*/, {}];
130
+ }
131
+ if (doc.env instanceof Map) {
132
+ out = {};
133
+ try {
134
+ for (_a = __values(doc.env), _b = _a.next(); !_b.done; _b = _a.next()) {
135
+ _c = __read(_b.value, 2), k = _c[0], v = _c[1];
136
+ out[k] = v;
137
+ }
138
+ }
139
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
140
+ finally {
141
+ try {
142
+ if (_b && !_b.done && (_d = _a.return)) _d.call(_a);
143
+ }
144
+ finally { if (e_1) throw e_1.error; }
145
+ }
146
+ return [2 /*return*/, out];
147
+ }
148
+ return [2 /*return*/, __assign({}, doc.env)];
149
+ }
150
+ });
151
+ }); });
152
+ };
153
+ (0, bun_test_1.describe)("envConfigurationPlugin", function () {
154
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
155
+ var _a;
156
+ return __generator(this, function (_b) {
157
+ switch (_b.label) {
158
+ case 0:
159
+ config_1.Config.clearRegistryForTesting();
160
+ config_1.Config.clearOverrides();
161
+ config_1.Config.setCachedEnv(null);
162
+ config_1.Config.setEnvLoader(null);
163
+ Reflect.deleteProperty(process.env, "TERRENO_PLUGIN_KEY");
164
+ config_1.Config.register("TERRENO_PLUGIN_KEY", { default: "fallback" });
165
+ return [4 /*yield*/, ((_a = mongoose_1.default.connection.db) === null || _a === void 0 ? void 0 : _a.collection("testenvconfigs").deleteMany({}))];
166
+ case 1:
167
+ _b.sent();
168
+ setupLoader();
169
+ return [2 /*return*/];
170
+ }
171
+ });
172
+ }); });
173
+ (0, bun_test_1.afterEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
174
+ var _a;
175
+ return __generator(this, function (_b) {
176
+ switch (_b.label) {
177
+ case 0:
178
+ config_1.Config.clearRegistryForTesting();
179
+ config_1.Config.clearOverrides();
180
+ config_1.Config.setCachedEnv(null);
181
+ config_1.Config.setEnvLoader(null);
182
+ Reflect.deleteProperty(process.env, "TERRENO_PLUGIN_KEY");
183
+ return [4 /*yield*/, ((_a = mongoose_1.default.connection.db) === null || _a === void 0 ? void 0 : _a.collection("testenvconfigs").deleteMany({}))];
184
+ case 1:
185
+ _b.sent();
186
+ return [2 /*return*/];
187
+ }
188
+ });
189
+ }); });
190
+ (0, bun_test_1.it)("adds an env Map field to the schema", function () {
191
+ var doc = new TestEnvConfig();
192
+ (0, bun_test_1.expect)(doc.env).toBeInstanceOf(Map);
193
+ });
194
+ (0, bun_test_1.it)("Config.refresh() loads values from the document", function () { return __awaiter(void 0, void 0, void 0, function () {
195
+ var doc;
196
+ return __generator(this, function (_a) {
197
+ switch (_a.label) {
198
+ case 0:
199
+ doc = new TestEnvConfig();
200
+ doc.env.set("TERRENO_PLUGIN_KEY", "fromDoc");
201
+ return [4 /*yield*/, doc.save()];
202
+ case 1:
203
+ _a.sent();
204
+ config_1.Config.setCachedEnv(null);
205
+ return [4 /*yield*/, config_1.Config.refresh()];
206
+ case 2:
207
+ _a.sent();
208
+ (0, bun_test_1.expect)(config_1.Config.get("TERRENO_PLUGIN_KEY")).toBe("fromDoc");
209
+ return [2 /*return*/];
210
+ }
211
+ });
212
+ }); });
213
+ (0, bun_test_1.it)("post-save hook refreshes the cache automatically", function () { return __awaiter(void 0, void 0, void 0, function () {
214
+ var doc;
215
+ return __generator(this, function (_a) {
216
+ switch (_a.label) {
217
+ case 0:
218
+ doc = new TestEnvConfig();
219
+ doc.env.set("TERRENO_PLUGIN_KEY", "first");
220
+ return [4 /*yield*/, doc.save()];
221
+ case 1:
222
+ _a.sent();
223
+ (0, bun_test_1.expect)(config_1.Config.get("TERRENO_PLUGIN_KEY")).toBe("first");
224
+ doc.env.set("TERRENO_PLUGIN_KEY", "second");
225
+ return [4 /*yield*/, doc.save()];
226
+ case 2:
227
+ _a.sent();
228
+ (0, bun_test_1.expect)(config_1.Config.get("TERRENO_PLUGIN_KEY")).toBe("second");
229
+ return [2 /*return*/];
230
+ }
231
+ });
232
+ }); });
233
+ (0, bun_test_1.it)("post-findOneAndUpdate hook refreshes the cache", function () { return __awaiter(void 0, void 0, void 0, function () {
234
+ var doc;
235
+ return __generator(this, function (_a) {
236
+ switch (_a.label) {
237
+ case 0:
238
+ doc = new TestEnvConfig();
239
+ doc.env.set("TERRENO_PLUGIN_KEY", "initial");
240
+ return [4 /*yield*/, doc.save()];
241
+ case 1:
242
+ _a.sent();
243
+ return [4 /*yield*/, TestEnvConfig.findOneAndUpdate({ _id: doc._id }, { env: new Map([["TERRENO_PLUGIN_KEY", "updated"]]) })];
244
+ case 2:
245
+ _a.sent();
246
+ (0, bun_test_1.expect)(config_1.Config.get("TERRENO_PLUGIN_KEY")).toBe("updated");
247
+ return [2 /*return*/];
248
+ }
249
+ });
250
+ }); });
251
+ (0, bun_test_1.it)("post-updateOne hook refreshes the cache", function () { return __awaiter(void 0, void 0, void 0, function () {
252
+ var doc;
253
+ return __generator(this, function (_a) {
254
+ switch (_a.label) {
255
+ case 0:
256
+ doc = new TestEnvConfig();
257
+ doc.env.set("TERRENO_PLUGIN_KEY", "initial");
258
+ return [4 /*yield*/, doc.save()];
259
+ case 1:
260
+ _a.sent();
261
+ return [4 /*yield*/, TestEnvConfig.updateOne({ _id: doc._id }, { env: new Map([["TERRENO_PLUGIN_KEY", "updatedViaUpdateOne"]]) })];
262
+ case 2:
263
+ _a.sent();
264
+ (0, bun_test_1.expect)(config_1.Config.get("TERRENO_PLUGIN_KEY")).toBe("updatedViaUpdateOne");
265
+ return [2 /*return*/];
266
+ }
267
+ });
268
+ }); });
269
+ (0, bun_test_1.it)("empty-string env values fall through to process.env", function () { return __awaiter(void 0, void 0, void 0, function () {
270
+ var doc;
271
+ return __generator(this, function (_a) {
272
+ switch (_a.label) {
273
+ case 0:
274
+ process.env.TERRENO_PLUGIN_KEY = "fromEnv";
275
+ doc = new TestEnvConfig();
276
+ doc.env.set("TERRENO_PLUGIN_KEY", "");
277
+ return [4 /*yield*/, doc.save()];
278
+ case 1:
279
+ _a.sent();
280
+ (0, bun_test_1.expect)(config_1.Config.get("TERRENO_PLUGIN_KEY")).toBe("fromEnv");
281
+ return [2 /*return*/];
282
+ }
283
+ });
284
+ }); });
285
+ (0, bun_test_1.it)("missing document yields registered defaults", function () { return __awaiter(void 0, void 0, void 0, function () {
286
+ return __generator(this, function (_a) {
287
+ switch (_a.label) {
288
+ case 0: return [4 /*yield*/, config_1.Config.refresh()];
289
+ case 1:
290
+ _a.sent();
291
+ (0, bun_test_1.expect)(config_1.Config.get("TERRENO_PLUGIN_KEY")).toBe("fallback");
292
+ return [2 /*return*/];
293
+ }
294
+ });
295
+ }); });
296
+ (0, bun_test_1.it)("refreshFromDoc handles null document via Mongoose hook when collection is empty", function () { return __awaiter(void 0, void 0, void 0, function () {
297
+ var _a;
298
+ return __generator(this, function (_b) {
299
+ switch (_b.label) {
300
+ case 0:
301
+ // Ensure collection is empty — no documents to find
302
+ return [4 /*yield*/, ((_a = mongoose_1.default.connection.db) === null || _a === void 0 ? void 0 : _a.collection("testenvconfigs").deleteMany({}))];
303
+ case 1:
304
+ // Ensure collection is empty — no documents to find
305
+ _b.sent();
306
+ // Override the cache so we can verify it gets cleared by the hook
307
+ config_1.Config.setCachedEnv({ TERRENO_PLUGIN_KEY: "stale" });
308
+ (0, bun_test_1.expect)(config_1.Config.get("TERRENO_PLUGIN_KEY")).toBe("stale");
309
+ // Trigger findOneAndUpdate hook on a non-existent doc — refreshFromDoc
310
+ // calls findOneOrNone which returns null, so mapToObject(undefined) runs
311
+ return [4 /*yield*/, TestEnvConfig.findOneAndUpdate({ _id: new mongoose_1.default.Types.ObjectId() }, { $set: { __v: 1 } })];
312
+ case 2:
313
+ // Trigger findOneAndUpdate hook on a non-existent doc — refreshFromDoc
314
+ // calls findOneOrNone which returns null, so mapToObject(undefined) runs
315
+ _b.sent();
316
+ // mapToObject(undefined) returns {}, so Config falls back to the registered default
317
+ (0, bun_test_1.expect)(config_1.Config.get("TERRENO_PLUGIN_KEY")).toBe("fallback");
318
+ return [2 /*return*/];
319
+ }
320
+ });
321
+ }); });
322
+ });
package/dist/errors.d.ts CHANGED
@@ -21,7 +21,7 @@ export interface APIErrorConstructor {
21
21
  meta?: {
22
22
  [id: string]: string;
23
23
  };
24
- error?: Error;
24
+ error?: unknown;
25
25
  disableExternalErrorTracking?: boolean;
26
26
  }
27
27
  /**
@@ -54,22 +54,33 @@ export declare class APIError extends Error {
54
54
  header?: string;
55
55
  } | undefined;
56
56
  meta: {
57
- [id: string]: any;
57
+ [id: string]: unknown;
58
58
  } | undefined;
59
- error?: Error;
59
+ error?: unknown;
60
60
  disableExternalErrorTracking?: boolean;
61
61
  constructor(data: APIErrorConstructor);
62
62
  }
63
63
  export declare const errorsPlugin: (schema: Schema) => void;
64
- export declare const isAPIError: (error: Error) => error is APIError;
64
+ export declare const isAPIError: (error: unknown) => error is APIError;
65
+ /** Extract a human-readable message from an unknown error. */
66
+ export declare const errorMessage: (error: unknown) => string;
67
+ /** Extract a stack trace string from an unknown error. */
68
+ export declare const errorStack: (error: unknown) => string;
65
69
  /**
66
70
  * Safely extracts the disableExternalErrorTracking property from an error.
67
71
  * Works with both APIError instances and regular Error objects that may have
68
72
  * this property attached.
69
73
  */
70
74
  export declare const getDisableExternalErrorTracking: (error: unknown) => boolean | undefined;
71
- export declare const getAPIErrorBody: (error: APIError) => {
72
- [id: string]: any;
73
- };
75
+ export declare const getAPIErrorBody: (error: APIError) => Record<string, unknown>;
74
76
  export declare const apiUnauthorizedMiddleware: (err: Error, _req: Request, res: Response, next: NextFunction) => void;
77
+ /**
78
+ * Converts Mongoose validation/cast errors into client-friendly APIErrors.
79
+ */
80
+ export declare const mongooseErrorToAPIError: (err: Error) => APIError | null;
75
81
  export declare const apiErrorMiddleware: (err: Error, _req: Request, res: Response, next: NextFunction) => void;
82
+ /**
83
+ * Final Express error handler for unexpected errors. Always returns JSON so
84
+ * clients (e.g. RTK Query) can parse the response.
85
+ */
86
+ export declare const apiFallthroughErrorMiddleware: (err: Error, _req: Request, res: Response, _next: NextFunction) => void;
package/dist/errors.js CHANGED
@@ -58,11 +58,27 @@ var __values = (this && this.__values) || function(o) {
58
58
  };
59
59
  throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
60
60
  };
61
+ var __read = (this && this.__read) || function (o, n) {
62
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
63
+ if (!m) return o;
64
+ var i = m.call(o), r, ar = [], e;
65
+ try {
66
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
67
+ }
68
+ catch (error) { e = { error: error }; }
69
+ finally {
70
+ try {
71
+ if (r && !r.done && (m = i["return"])) m.call(i);
72
+ }
73
+ finally { if (e) throw e.error; }
74
+ }
75
+ return ar;
76
+ };
61
77
  Object.defineProperty(exports, "__esModule", { value: true });
62
- exports.apiErrorMiddleware = exports.apiUnauthorizedMiddleware = exports.getAPIErrorBody = exports.getDisableExternalErrorTracking = exports.isAPIError = exports.errorsPlugin = exports.APIError = void 0;
78
+ exports.apiFallthroughErrorMiddleware = exports.apiErrorMiddleware = exports.mongooseErrorToAPIError = exports.apiUnauthorizedMiddleware = exports.getAPIErrorBody = exports.getDisableExternalErrorTracking = exports.errorStack = exports.errorMessage = exports.isAPIError = exports.errorsPlugin = exports.APIError = void 0;
63
79
  // https://jsonapi.org/format/#errors
64
80
  var Sentry = __importStar(require("@sentry/bun"));
65
- var mongoose_1 = require("mongoose");
81
+ var mongoose_1 = __importStar(require("mongoose"));
66
82
  var logger_1 = require("./logger");
67
83
  /**
68
84
  * APIError is a simple way to throw an error in an API route and control what is shown and the
@@ -81,9 +97,10 @@ var logger_1 = require("./logger");
81
97
  var APIError = /** @class */ (function (_super) {
82
98
  __extends(APIError, _super);
83
99
  function APIError(data) {
84
- var _a, _b;
100
+ var _this = this;
101
+ var errorStack = data.error instanceof Error && data.error.stack ? "\n".concat(data.error.stack) : "";
85
102
  // Include details in when the error is printed to the console or sent to Sentry.
86
- var _this = _super.call(this, "".concat(data.title).concat(data.detail ? ": ".concat(data.detail) : "").concat(data.error ? "\n".concat(data.error.stack) : "")) || this;
103
+ _this = _super.call(this, "".concat(data.title).concat(data.detail ? ": ".concat(data.detail) : "").concat(errorStack)) || this;
87
104
  _this.name = "APIError";
88
105
  var title = data.title, id = data.id, links = data.links, status = data.status, code = data.code, detail = data.detail, source = data.source, meta = data.meta, fields = data.fields, error = data.error;
89
106
  if (!status) {
@@ -106,7 +123,8 @@ var APIError = /** @class */ (function (_super) {
106
123
  _this.meta.fields = fields;
107
124
  }
108
125
  _this.error = error;
109
- var logMessage = "APIError(".concat(status, "): ").concat(title, " ").concat(detail ? detail : "").concat(((_a = data.error) === null || _a === void 0 ? void 0 : _a.stack) ? "\n".concat((_b = data.error) === null || _b === void 0 ? void 0 : _b.stack) : "");
126
+ var dataErrorStack = data.error instanceof Error && data.error.stack ? "\n".concat(data.error.stack) : "";
127
+ var logMessage = "APIError(".concat(status, "): ").concat(title, " ").concat(detail ? detail : "").concat(dataErrorStack);
110
128
  if (data.disableExternalErrorTracking) {
111
129
  logger_1.logger.warn(logMessage);
112
130
  }
@@ -148,9 +166,25 @@ var errorsPlugin = function (schema) {
148
166
  };
149
167
  exports.errorsPlugin = errorsPlugin;
150
168
  var isAPIError = function (error) {
151
- return error.name === "APIError";
169
+ return error instanceof Error && error.name === "APIError";
152
170
  };
153
171
  exports.isAPIError = isAPIError;
172
+ /** Extract a human-readable message from an unknown error. */
173
+ var errorMessage = function (error) {
174
+ if (error instanceof Error) {
175
+ return error.message;
176
+ }
177
+ return String(error);
178
+ };
179
+ exports.errorMessage = errorMessage;
180
+ /** Extract a stack trace string from an unknown error. */
181
+ var errorStack = function (error) {
182
+ if (error instanceof Error && error.stack) {
183
+ return error.stack;
184
+ }
185
+ return String(error);
186
+ };
187
+ exports.errorStack = errorStack;
154
188
  /**
155
189
  * Safely extracts the disableExternalErrorTracking property from an error.
156
190
  * Works with both APIError instances and regular Error objects that may have
@@ -174,6 +208,7 @@ exports.getDisableExternalErrorTracking = getDisableExternalErrorTracking;
174
208
  var getAPIErrorBody = function (error) {
175
209
  var e_1, _a;
176
210
  var errorData = { status: error.status, title: error.title };
211
+ var indexable = error;
177
212
  try {
178
213
  for (var _b = __values([
179
214
  "id",
@@ -186,8 +221,8 @@ var getAPIErrorBody = function (error) {
186
221
  "disableExternalErrorTracking",
187
222
  ]), _c = _b.next(); !_c.done; _c = _b.next()) {
188
223
  var key = _c.value;
189
- if (error[key]) {
190
- errorData[key] = error[key];
224
+ if (indexable[key]) {
225
+ errorData[key] = indexable[key];
191
226
  }
192
227
  }
193
228
  }
@@ -211,15 +246,76 @@ var apiUnauthorizedMiddleware = function (err, _req, res, next) {
211
246
  }
212
247
  };
213
248
  exports.apiUnauthorizedMiddleware = apiUnauthorizedMiddleware;
249
+ /**
250
+ * Converts Mongoose validation/cast errors into client-friendly APIErrors.
251
+ */
252
+ var mongooseErrorToAPIError = function (err) {
253
+ var e_2, _a, _b;
254
+ var _c, _d;
255
+ if (err instanceof mongoose_1.default.Error.ValidationError) {
256
+ var fields = {};
257
+ try {
258
+ for (var _e = __values(Object.entries(err.errors)), _f = _e.next(); !_f.done; _f = _e.next()) {
259
+ var _g = __read(_f.value, 2), path = _g[0], subErr = _g[1];
260
+ fields[path] = subErr.message;
261
+ }
262
+ }
263
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
264
+ finally {
265
+ try {
266
+ if (_f && !_f.done && (_a = _e.return)) _a.call(_e);
267
+ }
268
+ finally { if (e_2) throw e_2.error; }
269
+ }
270
+ return new APIError({
271
+ detail: err.message,
272
+ disableExternalErrorTracking: true,
273
+ fields: fields,
274
+ status: 400,
275
+ title: "Validation failed",
276
+ });
277
+ }
278
+ if (err instanceof mongoose_1.default.Error.CastError) {
279
+ var path = (_c = err.path) !== null && _c !== void 0 ? _c : "field";
280
+ return new APIError({
281
+ detail: "Invalid value for ".concat(path),
282
+ disableExternalErrorTracking: true,
283
+ fields: (_b = {},
284
+ _b[path] = "Expected ".concat((_d = err.kind) !== null && _d !== void 0 ? _d : "a valid value", ", got ").concat(JSON.stringify(err.value)),
285
+ _b),
286
+ status: 400,
287
+ title: "Validation failed",
288
+ });
289
+ }
290
+ return null;
291
+ };
292
+ exports.mongooseErrorToAPIError = mongooseErrorToAPIError;
214
293
  var apiErrorMiddleware = function (err, _req, res, next) {
215
294
  if ((0, exports.isAPIError)(err)) {
216
295
  if (!err.disableExternalErrorTracking) {
217
296
  Sentry.captureException(err);
218
297
  }
219
298
  res.status(err.status).json((0, exports.getAPIErrorBody)(err)).send();
299
+ return;
220
300
  }
221
- else {
222
- next(err);
301
+ var mongooseError = (0, exports.mongooseErrorToAPIError)(err);
302
+ if (mongooseError) {
303
+ res.status(mongooseError.status).json((0, exports.getAPIErrorBody)(mongooseError)).send();
304
+ return;
223
305
  }
306
+ next(err);
224
307
  };
225
308
  exports.apiErrorMiddleware = apiErrorMiddleware;
309
+ /**
310
+ * Final Express error handler for unexpected errors. Always returns JSON so
311
+ * clients (e.g. RTK Query) can parse the response.
312
+ */
313
+ var apiFallthroughErrorMiddleware = function (err, _req, res, _next) {
314
+ logger_1.logger.error("Fallthrough error: ".concat(err).concat(err.stack ? "\n".concat(err.stack) : ""));
315
+ Sentry.captureException(err);
316
+ if (res.headersSent) {
317
+ return;
318
+ }
319
+ res.status(500).json({ status: 500, title: "Internal server error" }).send();
320
+ };
321
+ exports.apiFallthroughErrorMiddleware = apiFallthroughErrorMiddleware;
@@ -35,7 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  var bun_test_1 = require("bun:test");
37
37
  var Sentry = __importStar(require("@sentry/bun"));
38
- var mongoose_1 = require("mongoose");
38
+ var mongoose_1 = __importStar(require("mongoose"));
39
39
  var errors_1 = require("./errors");
40
40
  var buildResponse = function () {
41
41
  var res = {
@@ -277,4 +277,19 @@ var buildResponse = function () {
277
277
  (0, bun_test_1.expect)(next).toHaveBeenCalledWith(err);
278
278
  (0, bun_test_1.expect)(res.status).not.toHaveBeenCalled();
279
279
  });
280
+ (0, bun_test_1.it)("converts Mongoose CastError to a 400 APIError response", function () {
281
+ var err = new mongoose_1.default.Error.CastError("Number", "not-a-number", "general.maxUploadSizeMb");
282
+ (0, errors_1.apiErrorMiddleware)(err, req, res, next);
283
+ (0, bun_test_1.expect)(res.status).toHaveBeenCalledWith(400);
284
+ (0, bun_test_1.expect)(res.json).toHaveBeenCalledWith(bun_test_1.expect.objectContaining({
285
+ meta: bun_test_1.expect.objectContaining({
286
+ fields: bun_test_1.expect.objectContaining({
287
+ "general.maxUploadSizeMb": bun_test_1.expect.stringContaining("Expected Number"),
288
+ }),
289
+ }),
290
+ status: 400,
291
+ title: "Validation failed",
292
+ }));
293
+ (0, bun_test_1.expect)(next).not.toHaveBeenCalled();
294
+ });
280
295
  });
package/dist/example.js CHANGED
@@ -67,7 +67,8 @@ mongoose_1.default
67
67
  var userSchema = new mongoose_1.Schema({
68
68
  admin: { default: false, description: "Whether the user has admin privileges", type: Boolean },
69
69
  username: { description: "The user's username", type: String },
70
- });
70
+ }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
71
+ // biome-ignore lint/suspicious/noExplicitAny: passport-local-mongoose's plugin type is incompatible with mongoose Schema generics
71
72
  userSchema.plugin(passport_local_mongoose_1.default, { usernameField: "email" });
72
73
  userSchema.plugin(plugins_1.createdUpdatedPlugin);
73
74
  userSchema.plugin(plugins_1.baseUserPlugin);
@@ -75,12 +76,20 @@ var UserModel = (0, mongoose_1.model)("User", userSchema);
75
76
  var schema = new mongoose_1.Schema({
76
77
  calories: { description: "Number of calories in the food", type: Number },
77
78
  created: { description: "When this food was created", type: Date },
78
- hidden: { default: false, description: "Whether this food is hidden from listings", type: Boolean },
79
+ hidden: {
80
+ default: false,
81
+ description: "Whether this food is hidden from listings",
82
+ type: Boolean,
83
+ },
79
84
  name: { description: "The name of the food", type: String },
80
85
  ownerId: { description: "The user who owns this food entry", ref: "User", type: "ObjectId" },
81
- });
86
+ }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
87
+ schema.plugin(plugins_1.createdUpdatedPlugin);
88
+ schema.plugin(plugins_1.isDeletedPlugin);
89
+ schema.plugin(plugins_1.findOneOrNone);
90
+ schema.plugin(plugins_1.findExactlyOne);
82
91
  var FoodModel = (0, mongoose_1.model)("Food", schema);
83
- function getBaseServer() {
92
+ var getBaseServer = function () {
84
93
  var app = (0, express_1.default)();
85
94
  app.use(function (req, res, next) {
86
95
  res.header("Access-Control-Allow-Origin", "*");
@@ -96,7 +105,7 @@ function getBaseServer() {
96
105
  app.use(express_1.default.json());
97
106
  (0, auth_1.setupAuth)(app, UserModel);
98
107
  (0, auth_1.addAuthRoutes)(app, UserModel);
99
- function addRoutes(router, options) {
108
+ var addRoutes = function (router, options) {
100
109
  router.use("/food", (0, api_1.modelRouter)(FoodModel, __assign(__assign({}, options), { openApiOverwrite: {
101
110
  get: { responses: { 200: { description: "Get all the food" } } },
102
111
  }, permissions: {
@@ -106,7 +115,7 @@ function getBaseServer() {
106
115
  read: [permissions_1.Permissions.IsAny],
107
116
  update: [permissions_1.Permissions.IsOwner],
108
117
  }, queryFields: ["name", "calories", "created", "ownerId", "hidden"] })));
109
- }
118
+ };
110
119
  return (0, expressServer_1.setupServer)({
111
120
  addRoutes: addRoutes,
112
121
  loggingOptions: {
@@ -114,5 +123,5 @@ function getBaseServer() {
114
123
  },
115
124
  userModel: UserModel,
116
125
  });
117
- }
126
+ };
118
127
  getBaseServer();