@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
@@ -10,6 +10,39 @@ var __assign = (this && this.__assign) || function () {
10
10
  };
11
11
  return __assign.apply(this, arguments);
12
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
+ })();
13
46
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
47
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
48
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -67,11 +100,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
67
100
  };
68
101
  Object.defineProperty(exports, "__esModule", { value: true });
69
102
  var bun_test_1 = require("bun:test");
103
+ var mongoose_1 = __importStar(require("mongoose"));
70
104
  var supertest_1 = __importDefault(require("supertest"));
71
105
  var api_1 = require("./api");
106
+ var configurationPlugin_1 = require("./configurationPlugin");
72
107
  var permissions_1 = require("./permissions");
108
+ var plugins_1 = require("./plugins");
73
109
  var terrenoApp_1 = require("./terrenoApp");
74
110
  var tests_1 = require("./tests");
111
+ var typedUserModel = tests_1.UserModel;
75
112
  (0, bun_test_1.describe)("TerrenoApp", function () {
76
113
  var originalEnv = process.env;
77
114
  (0, bun_test_1.beforeEach)(function () {
@@ -84,7 +121,7 @@ var tests_1 = require("./tests");
84
121
  (0, bun_test_1.it)("returns an express application without listening", function () {
85
122
  var app = new terrenoApp_1.TerrenoApp({
86
123
  skipListen: true,
87
- userModel: tests_1.UserModel,
124
+ userModel: typedUserModel,
88
125
  }).build();
89
126
  (0, bun_test_1.expect)(app).toBeDefined();
90
127
  });
@@ -92,7 +129,7 @@ var tests_1 = require("./tests");
92
129
  var app = new terrenoApp_1.TerrenoApp({
93
130
  corsOrigin: "https://example.com",
94
131
  skipListen: true,
95
- userModel: tests_1.UserModel,
132
+ userModel: typedUserModel,
96
133
  }).build();
97
134
  (0, bun_test_1.expect)(app).toBeDefined();
98
135
  });
@@ -101,7 +138,7 @@ var tests_1 = require("./tests");
101
138
  (0, bun_test_1.it)("returns an express application with skipListen", function () {
102
139
  var app = new terrenoApp_1.TerrenoApp({
103
140
  skipListen: true,
104
- userModel: tests_1.UserModel,
141
+ userModel: typedUserModel,
105
142
  }).start();
106
143
  (0, bun_test_1.expect)(app).toBeDefined();
107
144
  });
@@ -109,20 +146,20 @@ var tests_1 = require("./tests");
109
146
  (0, bun_test_1.describe)("register with modelRouter", function () {
110
147
  var admin;
111
148
  (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
112
- var _a;
113
- return __generator(this, function (_b) {
114
- switch (_b.label) {
149
+ var _d;
150
+ return __generator(this, function (_e) {
151
+ switch (_e.label) {
115
152
  case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
116
153
  case 1:
117
- _a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
154
+ _d = __read.apply(void 0, [_e.sent(), 1]), admin = _d[0];
118
155
  return [2 /*return*/];
119
156
  }
120
157
  });
121
158
  }); });
122
159
  (0, bun_test_1.it)("mounts model router at the specified path", function () { return __awaiter(void 0, void 0, void 0, function () {
123
160
  var foodRegistration, app, agent, res;
124
- return __generator(this, function (_a) {
125
- switch (_a.label) {
161
+ return __generator(this, function (_d) {
162
+ switch (_d.label) {
126
163
  case 0:
127
164
  foodRegistration = (0, api_1.modelRouter)("/food", tests_1.FoodModel, {
128
165
  allowAnonymous: true,
@@ -139,7 +176,7 @@ var tests_1 = require("./tests");
139
176
  (0, bun_test_1.expect)(foodRegistration.path).toBe("/food");
140
177
  app = new terrenoApp_1.TerrenoApp({
141
178
  skipListen: true,
142
- userModel: tests_1.UserModel,
179
+ userModel: typedUserModel,
143
180
  })
144
181
  .register(foodRegistration)
145
182
  .build();
@@ -150,13 +187,13 @@ var tests_1 = require("./tests");
150
187
  source: { name: "Nature" },
151
188
  })];
152
189
  case 1:
153
- _a.sent();
190
+ _d.sent();
154
191
  return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
155
192
  case 2:
156
- agent = _a.sent();
193
+ agent = _d.sent();
157
194
  return [4 /*yield*/, agent.get("/food").expect(200)];
158
195
  case 3:
159
- res = _a.sent();
196
+ res = _d.sent();
160
197
  (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
161
198
  (0, bun_test_1.expect)(res.body.data[0].name).toBe("Apple");
162
199
  return [2 /*return*/];
@@ -165,7 +202,7 @@ var tests_1 = require("./tests");
165
202
  }); });
166
203
  (0, bun_test_1.it)("supports chaining multiple registrations", function () { return __awaiter(void 0, void 0, void 0, function () {
167
204
  var foodRegistration, app;
168
- return __generator(this, function (_a) {
205
+ return __generator(this, function (_d) {
169
206
  foodRegistration = (0, api_1.modelRouter)("/food", tests_1.FoodModel, {
170
207
  allowAnonymous: true,
171
208
  permissions: {
@@ -178,7 +215,7 @@ var tests_1 = require("./tests");
178
215
  });
179
216
  app = new terrenoApp_1.TerrenoApp({
180
217
  skipListen: true,
181
- userModel: tests_1.UserModel,
218
+ userModel: typedUserModel,
182
219
  })
183
220
  .register(foodRegistration)
184
221
  .build();
@@ -195,12 +232,11 @@ var tests_1 = require("./tests");
195
232
  };
196
233
  var app = new terrenoApp_1.TerrenoApp({
197
234
  skipListen: true,
198
- userModel: tests_1.UserModel,
235
+ userModel: typedUserModel,
199
236
  })
200
237
  .register(plugin)
201
238
  .build();
202
239
  (0, bun_test_1.expect)(registerFn).toHaveBeenCalledTimes(1);
203
- // Verify the plugin received the express app
204
240
  var calledWith = registerFn.mock.calls[0][0];
205
241
  (0, bun_test_1.expect)(calledWith).toBe(app);
206
242
  });
@@ -208,8 +244,8 @@ var tests_1 = require("./tests");
208
244
  (0, bun_test_1.describe)("addMiddleware", function () {
209
245
  (0, bun_test_1.it)("runs request handler middleware", function () { return __awaiter(void 0, void 0, void 0, function () {
210
246
  var middlewareCalled, middleware, app;
211
- return __generator(this, function (_a) {
212
- switch (_a.label) {
247
+ return __generator(this, function (_d) {
248
+ switch (_d.label) {
213
249
  case 0:
214
250
  middlewareCalled = false;
215
251
  middleware = function (_req, _res, next) {
@@ -218,19 +254,156 @@ var tests_1 = require("./tests");
218
254
  };
219
255
  app = new terrenoApp_1.TerrenoApp({
220
256
  skipListen: true,
221
- userModel: tests_1.UserModel,
257
+ userModel: typedUserModel,
222
258
  })
223
259
  .addMiddleware(middleware)
224
260
  .build();
225
261
  return [4 /*yield*/, (0, supertest_1.default)(app).get("/nonexistent").expect(404)];
226
262
  case 1:
227
- _a.sent();
263
+ _d.sent();
228
264
  (0, bun_test_1.expect)(middlewareCalled).toBe(true);
229
265
  return [2 /*return*/];
230
266
  }
231
267
  });
232
268
  }); });
233
269
  });
270
+ (0, bun_test_1.describe)("configure", function () {
271
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
272
+ return __generator(this, function (_d) {
273
+ switch (_d.label) {
274
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
275
+ case 1:
276
+ _d.sent();
277
+ return [2 /*return*/];
278
+ }
279
+ });
280
+ }); });
281
+ (0, bun_test_1.it)("mounts configuration routes when configure() is called", function () { return __awaiter(void 0, void 0, void 0, function () {
282
+ var cfgSchema, modelName, CfgModel, app, agent, res;
283
+ return __generator(this, function (_d) {
284
+ switch (_d.label) {
285
+ case 0:
286
+ cfgSchema = new mongoose_1.Schema({ siteName: { default: "My Site", description: "Site name", type: String } }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
287
+ cfgSchema.plugin(configurationPlugin_1.configurationPlugin);
288
+ cfgSchema.plugin(plugins_1.createdUpdatedPlugin);
289
+ modelName = "CfgModel_".concat(Date.now());
290
+ CfgModel = mongoose_1.default.model(modelName, cfgSchema);
291
+ app = new terrenoApp_1.TerrenoApp({
292
+ skipListen: true,
293
+ userModel: typedUserModel,
294
+ })
295
+ .configure(CfgModel)
296
+ .build();
297
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
298
+ case 1:
299
+ agent = _d.sent();
300
+ return [4 /*yield*/, agent.get("/configuration/meta")];
301
+ case 2:
302
+ res = _d.sent();
303
+ (0, bun_test_1.expect)(res.status).toBe(200);
304
+ return [2 /*return*/];
305
+ }
306
+ });
307
+ }); });
308
+ (0, bun_test_1.it)("supports custom basePath via configure options", function () { return __awaiter(void 0, void 0, void 0, function () {
309
+ var cfgSchema2, modelName, CfgModel2, app, agent, res;
310
+ return __generator(this, function (_d) {
311
+ switch (_d.label) {
312
+ case 0:
313
+ cfgSchema2 = new mongoose_1.Schema({ siteName: { default: "Test", description: "Site name", type: String } }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
314
+ cfgSchema2.plugin(configurationPlugin_1.configurationPlugin);
315
+ cfgSchema2.plugin(plugins_1.createdUpdatedPlugin);
316
+ modelName = "CfgModel2_".concat(Date.now());
317
+ CfgModel2 = mongoose_1.default.model(modelName, cfgSchema2);
318
+ app = new terrenoApp_1.TerrenoApp({
319
+ skipListen: true,
320
+ userModel: typedUserModel,
321
+ })
322
+ .configure(CfgModel2, { basePath: "/settings" })
323
+ .build();
324
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
325
+ case 1:
326
+ agent = _d.sent();
327
+ return [4 /*yield*/, agent.get("/settings/meta")];
328
+ case 2:
329
+ res = _d.sent();
330
+ (0, bun_test_1.expect)(res.status).toBe(200);
331
+ return [2 /*return*/];
332
+ }
333
+ });
334
+ }); });
335
+ });
336
+ (0, bun_test_1.describe)("fallthrough error handler", function () {
337
+ (0, bun_test_1.it)("returns 500 for non-API errors", function () { return __awaiter(void 0, void 0, void 0, function () {
338
+ var plugin, app, res;
339
+ return __generator(this, function (_d) {
340
+ switch (_d.label) {
341
+ case 0:
342
+ plugin = {
343
+ register: function (pluginApp) {
344
+ pluginApp.get("/trigger-fallthrough", function (_req, _res) {
345
+ throw new Error("unexpected failure");
346
+ });
347
+ },
348
+ };
349
+ app = new terrenoApp_1.TerrenoApp({
350
+ skipListen: true,
351
+ userModel: typedUserModel,
352
+ })
353
+ .register(plugin)
354
+ .build();
355
+ return [4 /*yield*/, (0, supertest_1.default)(app).get("/trigger-fallthrough")];
356
+ case 1:
357
+ res = _d.sent();
358
+ (0, bun_test_1.expect)(res.status).toBe(500);
359
+ return [2 /*return*/];
360
+ }
361
+ });
362
+ }); });
363
+ });
364
+ (0, bun_test_1.describe)("start with listen", function () {
365
+ (0, bun_test_1.it)("starts and listens on the configured port", function () { return __awaiter(void 0, void 0, void 0, function () {
366
+ var port, app;
367
+ return __generator(this, function (_d) {
368
+ port = "19876";
369
+ process.env.PORT = port;
370
+ app = new terrenoApp_1.TerrenoApp({
371
+ userModel: typedUserModel,
372
+ }).start();
373
+ (0, bun_test_1.expect)(app).toBeDefined();
374
+ return [2 /*return*/];
375
+ });
376
+ }); });
377
+ });
378
+ (0, bun_test_1.describe)("addMiddleware with app-configuring function", function () {
379
+ (0, bun_test_1.it)("invokes a function that receives the express app (fn.length > 3)", function () { return __awaiter(void 0, void 0, void 0, function () {
380
+ var receivedApp, configFn, app;
381
+ return __generator(this, function (_d) {
382
+ configFn = function (_appInstance, _a, _b, _c) {
383
+ receivedApp = _appInstance;
384
+ };
385
+ app = new terrenoApp_1.TerrenoApp({
386
+ skipListen: true,
387
+ userModel: typedUserModel,
388
+ })
389
+ .addMiddleware(configFn)
390
+ .build();
391
+ (0, bun_test_1.expect)(app).toBeDefined();
392
+ (0, bun_test_1.expect)(receivedApp).toBe(app);
393
+ return [2 /*return*/];
394
+ });
395
+ }); });
396
+ });
397
+ (0, bun_test_1.describe)("logRequests option", function () {
398
+ (0, bun_test_1.it)("disables request logging when logRequests is false", function () {
399
+ var app = new terrenoApp_1.TerrenoApp({
400
+ logRequests: false,
401
+ skipListen: true,
402
+ userModel: typedUserModel,
403
+ }).build();
404
+ (0, bun_test_1.expect)(app).toBeDefined();
405
+ });
406
+ });
234
407
  (0, bun_test_1.describe)("modelRouter overload", function () {
235
408
  (0, bun_test_1.it)("returns ModelRouterRegistration when path is provided", function () {
236
409
  var result = (0, api_1.modelRouter)("/food", tests_1.FoodModel, {
@@ -1,3 +1,4 @@
1
+ import type http from "node:http";
1
2
  import type express from "express";
2
3
  /**
3
4
  * Interface for plugins that can be registered with TerrenoApp.
@@ -35,4 +36,14 @@ export interface TerrenoPlugin {
35
36
  * @param app - The Express application instance to register with
36
37
  */
37
38
  register(app: express.Application, openApi?: unknown): void;
39
+ /**
40
+ * Called after the HTTP server is created but before it starts listening.
41
+ * Use this to attach services that need the raw HTTP server, such as
42
+ * Socket.io or other WebSocket libraries.
43
+ *
44
+ * Only called when using `TerrenoApp.start()` (not `build()`).
45
+ *
46
+ * @param server - The Node.js HTTP server instance
47
+ */
48
+ onServerCreated?(server: http.Server): void;
38
49
  }
@@ -64,6 +64,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
64
64
  return (mod && mod.__esModule) ? mod : { "default": mod };
65
65
  };
66
66
  Object.defineProperty(exports, "__esModule", { value: true });
67
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
67
68
  var bun_test_1 = require("bun:test");
68
69
  var node_stream_1 = require("node:stream");
69
70
  var mongoose_1 = __importDefault(require("mongoose"));
package/dist/tests.js CHANGED
@@ -156,7 +156,7 @@ var foodSchema = new mongoose_1.Schema({
156
156
  type: mongoose_1.Schema.Types.ObjectId,
157
157
  },
158
158
  ],
159
- // noExplicitAny: DateOnly is a custom SchemaType not recognized by Mongoose's built-in type definitions
159
+ // biome-ignore lint/suspicious/noExplicitAny: DateOnly is a custom SchemaType not recognized by Mongoose's built-in type definitions
160
160
  expiration: { description: "Expiration date of the food", type: plugins_1.DateOnly },
161
161
  hidden: {
162
162
  default: false,
@@ -17,9 +17,9 @@ export declare const AdminOwnerTransformer: <T>(options: {
17
17
  adminWriteFields?: string[];
18
18
  }) => TerrenoTransformer<T>;
19
19
  export declare const transform: <T>(options: ModelRouterOptions<T>, data: Partial<T> | Partial<T>[], method: "create" | "update", user?: User) => Partial<T> | (Partial<T> | undefined)[] | undefined;
20
- export declare const serialize: <T>(req: express.Request, options: ModelRouterOptions<T>, data: (Document & T) | (Document & T)[]) => Partial<T> | (Partial<T> | undefined)[] | undefined;
20
+ export declare const serialize: <T>(req: express.Request, options: ModelRouterOptions<T>, data: (Document<unknown, unknown, unknown> & T) | (Document<unknown, unknown, unknown> & T)[]) => Partial<T> | (Partial<T> | undefined)[] | undefined;
21
21
  /**
22
22
  * Default response handler for modelRouter. Calls toObject on each doc and returns the result,
23
23
  * using transformers.serializer if provided.
24
24
  */
25
- export declare const defaultResponseHandler: <T>(doc: (Document & T) | (Document & T)[] | null, method: "list" | "create" | "read" | "update", request: express.Request, options: ModelRouterOptions<T>) => Promise<Partial<T> | (Partial<T> | undefined)[] | null | undefined>;
25
+ export declare const defaultResponseHandler: <T>(doc: (Document<unknown, unknown, unknown> & T) | (Document<unknown, unknown, unknown> & T)[] | null, method: "list" | "create" | "read" | "update", request: express.Request, options: ModelRouterOptions<T>) => Promise<Partial<T> | (Partial<T> | undefined)[] | null | undefined>;
@@ -79,7 +79,8 @@ var getUserType = function (user, obj) {
79
79
  if (user === null || user === void 0 ? void 0 : user.admin) {
80
80
  return "admin";
81
81
  }
82
- if (obj && user && String(obj === null || obj === void 0 ? void 0 : obj.ownerId) === String(user === null || user === void 0 ? void 0 : user.id)) {
82
+ var withOwner = obj;
83
+ if (withOwner && user && String(withOwner === null || withOwner === void 0 ? void 0 : withOwner.ownerId) === String(user === null || user === void 0 ? void 0 : user.id)) {
83
84
  return "owner";
84
85
  }
85
86
  if (user === null || user === void 0 ? void 0 : user.id) {
@@ -94,8 +95,9 @@ var AdminOwnerTransformer = function (options) {
94
95
  try {
95
96
  for (var fields_1 = __values(fields), fields_1_1 = fields_1.next(); !fields_1_1.done; fields_1_1 = fields_1.next()) {
96
97
  var field = fields_1_1.value;
97
- if (obj[field] !== undefined) {
98
- newData[field] = obj[field];
98
+ var key = field;
99
+ if (obj[key] !== undefined) {
100
+ newData[key] = obj[key];
99
101
  }
100
102
  }
101
103
  }
@@ -1,4 +1,15 @@
1
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
+ };
2
13
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
14
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
15
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -55,10 +66,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
55
66
  return (mod && mod.__esModule) ? mod : { "default": mod };
56
67
  };
57
68
  Object.defineProperty(exports, "__esModule", { value: true });
69
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
58
70
  var bun_test_1 = require("bun:test");
59
71
  var supertest_1 = __importDefault(require("supertest"));
60
72
  var api_1 = require("./api");
61
73
  var auth_1 = require("./auth");
74
+ var errors_1 = require("./errors");
62
75
  var permissions_1 = require("./permissions");
63
76
  var tests_1 = require("./tests");
64
77
  var transformers_1 = require("./transformers");
@@ -368,3 +381,80 @@ var transformers_1 = require("./transformers");
368
381
  });
369
382
  }); });
370
383
  });
384
+ (0, bun_test_1.describe)("transform (deprecated helper)", function () {
385
+ var mockTransformFn = function (obj, _method) { return (__assign(__assign({}, obj), { name: "".concat(obj.name, "_transformed") })); };
386
+ (0, bun_test_1.it)("returns data unchanged when no transformer is configured", function () {
387
+ var options = { permissions: {} };
388
+ var data = { name: "Apple" };
389
+ (0, bun_test_1.expect)((0, transformers_1.transform)(options, data, "create")).toEqual(data);
390
+ });
391
+ (0, bun_test_1.it)("transforms a single object", function () {
392
+ var options = {
393
+ transformer: { transform: mockTransformFn },
394
+ };
395
+ var result = (0, transformers_1.transform)(options, { name: "Apple" }, "create");
396
+ (0, bun_test_1.expect)(result.name).toBe("Apple_transformed");
397
+ });
398
+ (0, bun_test_1.it)("transforms an array of objects", function () {
399
+ var options = {
400
+ transformer: { transform: mockTransformFn },
401
+ };
402
+ var data = [{ name: "Apple" }, { name: "Banana" }];
403
+ var result = (0, transformers_1.transform)(options, data, "update");
404
+ (0, bun_test_1.expect)(result).toHaveLength(2);
405
+ (0, bun_test_1.expect)(result[0].name).toBe("Apple_transformed");
406
+ (0, bun_test_1.expect)(result[1].name).toBe("Banana_transformed");
407
+ });
408
+ });
409
+ (0, bun_test_1.describe)("defaultResponseHandler", function () {
410
+ (0, bun_test_1.it)("returns null when doc is null", function () { return __awaiter(void 0, void 0, void 0, function () {
411
+ var options, req, result;
412
+ return __generator(this, function (_a) {
413
+ switch (_a.label) {
414
+ case 0:
415
+ options = { permissions: {} };
416
+ req = {};
417
+ return [4 /*yield*/, (0, transformers_1.defaultResponseHandler)(null, "read", req, options)];
418
+ case 1:
419
+ result = _a.sent();
420
+ (0, bun_test_1.expect)(result).toBeNull();
421
+ return [2 /*return*/];
422
+ }
423
+ });
424
+ }); });
425
+ (0, bun_test_1.it)("throws APIError when serialize throws", function () { return __awaiter(void 0, void 0, void 0, function () {
426
+ var options, fakeDoc, req, err_1;
427
+ return __generator(this, function (_a) {
428
+ switch (_a.label) {
429
+ case 0:
430
+ options = {
431
+ transformer: {
432
+ serialize: function () {
433
+ throw new Error("serialize boom");
434
+ },
435
+ },
436
+ };
437
+ fakeDoc = {
438
+ _id: "abc",
439
+ toObject: function () { return ({ name: "Apple" }); },
440
+ };
441
+ req = {};
442
+ _a.label = 1;
443
+ case 1:
444
+ _a.trys.push([1, 3, , 4]);
445
+ return [4 /*yield*/, (0, transformers_1.defaultResponseHandler)(fakeDoc, "read", req, options)];
446
+ case 2:
447
+ _a.sent();
448
+ (0, bun_test_1.expect)(true).toBe(false);
449
+ return [3 /*break*/, 4];
450
+ case 3:
451
+ err_1 = _a.sent();
452
+ (0, bun_test_1.expect)(err_1).toBeInstanceOf(errors_1.APIError);
453
+ (0, bun_test_1.expect)(err_1.status).toBe(400);
454
+ (0, bun_test_1.expect)(err_1.title).toContain("Error serializing read response");
455
+ return [3 /*break*/, 4];
456
+ case 4: return [2 /*return*/];
457
+ }
458
+ });
459
+ }); });
460
+ });
@@ -1,8 +1,11 @@
1
1
  import type mongoose from "mongoose";
2
2
  import type { FindExactlyOnePlugin, FindOneOrNonePlugin } from "../plugins";
3
- export type ConsentResponseMethods = {};
4
- export type ConsentResponseStatics = FindExactlyOnePlugin<ConsentResponseDocument> & FindOneOrNonePlugin<ConsentResponseDocument>;
5
- export type ConsentResponseModel = mongoose.Model<ConsentResponseDocument, object, ConsentResponseMethods> & ConsentResponseStatics;
3
+ export interface ConsentResponseMethods {
4
+ }
5
+ export interface ConsentResponseStatics extends FindExactlyOnePlugin<ConsentResponseDocument>, FindOneOrNonePlugin<ConsentResponseDocument> {
6
+ }
7
+ export interface ConsentResponseModel extends mongoose.Model<ConsentResponseDocument, object, ConsentResponseMethods>, ConsentResponseStatics {
8
+ }
6
9
  export interface ConsentResponseDocument extends mongoose.Document {
7
10
  _id: mongoose.Types.ObjectId;
8
11
  userId: mongoose.Types.ObjectId;
@@ -3,6 +3,8 @@ import type { TerrenoPlugin } from "./terrenoPlugin";
3
3
  export type VersionCheckStatus = "ok" | "warning" | "required";
4
4
  export interface VersionCheckResponse {
5
5
  message?: string;
6
+ /** How often the client should poll for updates, in milliseconds. */
7
+ pollingIntervalMs?: number;
6
8
  requiredVersion?: number;
7
9
  status: VersionCheckStatus;
8
10
  updateUrl?: string;
@@ -41,6 +41,7 @@ var api_1 = require("./api");
41
41
  var versionConfig_1 = require("./models/versionConfig");
42
42
  var DEFAULT_WARNING_MESSAGE = "A new version is available. Please update for the best experience.";
43
43
  var DEFAULT_REQUIRED_MESSAGE = "This version is no longer supported. Please update to continue.";
44
+ var DEFAULT_POLLING_INTERVAL_MINUTES = 1440;
44
45
  /**
45
46
  * TerrenoPlugin that adds a public GET /version-check endpoint for upgrade enforcement.
46
47
  * Compares client build number against admin-configured thresholds per platform.
@@ -52,26 +53,30 @@ var VersionCheckPlugin = /** @class */ (function () {
52
53
  var _this = this;
53
54
  app.get("/version-check", (0, api_1.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
54
55
  var versionParam, platform, version, platformNormalized, config, requiredVersion, warningVersion, response;
55
- var _a, _b, _c, _d, _e, _f;
56
- return __generator(this, function (_g) {
57
- switch (_g.label) {
56
+ var _a, _b, _c, _d, _e, _f, _g;
57
+ return __generator(this, function (_h) {
58
+ switch (_h.label) {
58
59
  case 0:
59
60
  versionParam = req.query.version;
60
61
  platform = req.query.platform;
61
- version = typeof versionParam === "string"
62
- ? parseInt(versionParam, 10)
63
- : typeof versionParam === "number"
64
- ? versionParam
65
- : undefined;
62
+ if (typeof versionParam === "string") {
63
+ version = parseInt(versionParam, 10);
64
+ }
65
+ else if (typeof versionParam === "number") {
66
+ version = versionParam;
67
+ }
66
68
  if (version === undefined || Number.isNaN(version)) {
67
69
  return [2 /*return*/, res.json({ status: "ok" })];
68
70
  }
69
71
  platformNormalized = platform === "web" || platform === "mobile" ? platform : "web";
70
72
  return [4 /*yield*/, versionConfig_1.VersionConfig.findOneOrNone({ _singleton: "config" })];
71
73
  case 1:
72
- config = _g.sent();
74
+ config = _h.sent();
73
75
  if (!config) {
74
- return [2 /*return*/, res.json({ status: "ok" })];
76
+ return [2 /*return*/, res.json({
77
+ pollingIntervalMs: DEFAULT_POLLING_INTERVAL_MINUTES * 60 * 1000,
78
+ status: "ok",
79
+ })];
75
80
  }
76
81
  requiredVersion = platformNormalized === "web"
77
82
  ? ((_a = config.webRequiredVersion) !== null && _a !== void 0 ? _a : 0)
@@ -80,6 +85,7 @@ var VersionCheckPlugin = /** @class */ (function () {
80
85
  ? ((_c = config.webWarningVersion) !== null && _c !== void 0 ? _c : 0)
81
86
  : ((_d = config.mobileWarningVersion) !== null && _d !== void 0 ? _d : 0);
82
87
  response = {
88
+ pollingIntervalMs: ((_e = config.pollingIntervalMinutes) !== null && _e !== void 0 ? _e : DEFAULT_POLLING_INTERVAL_MINUTES) * 60 * 1000,
83
89
  requiredVersion: requiredVersion > 0 ? requiredVersion : undefined,
84
90
  status: "ok",
85
91
  updateUrl: config.updateUrl || undefined,
@@ -87,11 +93,11 @@ var VersionCheckPlugin = /** @class */ (function () {
87
93
  };
88
94
  if (requiredVersion > 0 && version < requiredVersion) {
89
95
  response.status = "required";
90
- response.message = (_e = config.requiredMessage) !== null && _e !== void 0 ? _e : DEFAULT_REQUIRED_MESSAGE;
96
+ response.message = (_f = config.requiredMessage) !== null && _f !== void 0 ? _f : DEFAULT_REQUIRED_MESSAGE;
91
97
  }
92
98
  else if (warningVersion > 0 && version < warningVersion) {
93
99
  response.status = "warning";
94
- response.message = (_f = config.warningMessage) !== null && _f !== void 0 ? _f : DEFAULT_WARNING_MESSAGE;
100
+ response.message = (_g = config.warningMessage) !== null && _g !== void 0 ? _g : DEFAULT_WARNING_MESSAGE;
95
101
  }
96
102
  return [2 /*return*/, res.json(response)];
97
103
  }
package/package.json CHANGED
@@ -37,7 +37,9 @@
37
37
  "passport-local": "^1.0.0",
38
38
  "passport-local-mongoose": "^9.0.1",
39
39
  "qs": "^6.14.1",
40
+ "@thream/socketio-jwt": "^3.1.4",
40
41
  "scmp": "^2.1.0",
42
+ "socket.io": "^4.8.1",
41
43
  "winston": "^3.18.3"
42
44
  },
43
45
  "description": "Styled after the Django & Django REST Framework, a batteries-include framework for building REST APIs with Node/Express/Mongoose.",
@@ -81,7 +83,7 @@
81
83
  "access": "public"
82
84
  },
83
85
  "peerDependencies": {
84
- "mongoose": "^8.0.0"
86
+ "mongoose": "^8.0.0 || ^9.0.0"
85
87
  },
86
88
  "repository": {
87
89
  "type": "git",
@@ -104,5 +106,5 @@
104
106
  "updateSnapshot": "bun test --update-snapshots"
105
107
  },
106
108
  "types": "dist/index.d.ts",
107
- "version": "0.13.2"
109
+ "version": "0.14.0"
108
110
  }