@terreno/api 0.20.2 → 0.22.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 (107) hide show
  1. package/.ai/guidelines/core.md +71 -0
  2. package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
  3. package/README.md +54 -1
  4. package/bunfig.toml +1 -1
  5. package/dist/__tests__/versionCheckPlugin.test.js +29 -7
  6. package/dist/actions.openApi.test.js +13 -11
  7. package/dist/api.js +98 -11
  8. package/dist/api.query.test.js +31 -1
  9. package/dist/api.test.js +211 -0
  10. package/dist/auth.test.js +418 -43
  11. package/dist/betterAuth.d.ts +1 -1
  12. package/dist/consentApp.test.js +1 -0
  13. package/dist/example.js +4 -4
  14. package/dist/expressServer.d.ts +0 -22
  15. package/dist/expressServer.js +1 -125
  16. package/dist/expressServer.test.js +90 -91
  17. package/dist/githubAuth.test.js +22 -22
  18. package/dist/logger.d.ts +154 -0
  19. package/dist/logger.js +445 -26
  20. package/dist/logger.test.js +435 -0
  21. package/dist/middleware.d.ts +7 -0
  22. package/dist/middleware.js +58 -1
  23. package/dist/middleware.test.js +159 -0
  24. package/dist/models/consentForm.js +2 -1
  25. package/dist/models/consentResponse.js +2 -1
  26. package/dist/models/versionConfig.js +2 -1
  27. package/dist/openApi.test.js +10 -17
  28. package/dist/openApiBuilder.d.ts +18 -0
  29. package/dist/openApiBuilder.js +21 -0
  30. package/dist/openApiBuilder.test.js +34 -10
  31. package/dist/permissions.test.js +10 -43
  32. package/dist/populate.test.js +10 -42
  33. package/dist/realtime/changeStreamWatcher.d.ts +4 -4
  34. package/dist/realtime/changeStreamWatcher.js +2 -4
  35. package/dist/realtime/queryMatcher.d.ts +1 -1
  36. package/dist/realtime/queryMatcher.js +39 -14
  37. package/dist/realtime/types.d.ts +3 -3
  38. package/dist/requestContext.d.ts +61 -0
  39. package/dist/requestContext.js +74 -0
  40. package/dist/secretProviders.test.js +335 -0
  41. package/dist/syncConsents.test.js +2 -2
  42. package/dist/terrenoApp.d.ts +27 -15
  43. package/dist/terrenoApp.js +24 -14
  44. package/dist/terrenoApp.test.js +52 -0
  45. package/dist/tests/bunSetup.js +66 -262
  46. package/dist/tests/createTestData.d.ts +9 -0
  47. package/dist/tests/createTestData.js +272 -0
  48. package/dist/tests/models.d.ts +71 -0
  49. package/dist/tests/models.js +134 -0
  50. package/dist/tests/mongoTestSetup.d.ts +7 -0
  51. package/dist/tests/mongoTestSetup.js +150 -0
  52. package/dist/tests/testEnv.d.ts +0 -0
  53. package/dist/tests/testEnv.js +6 -0
  54. package/dist/tests/testHelper.d.ts +22 -0
  55. package/dist/tests/testHelper.js +115 -0
  56. package/dist/tests/types.d.ts +29 -0
  57. package/dist/tests/types.js +2 -0
  58. package/dist/tests.d.ts +10 -78
  59. package/dist/tests.js +24 -241
  60. package/dist/transformers.test.js +14 -50
  61. package/package.json +18 -4
  62. package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
  63. package/src/__tests__/versionCheckPlugin.test.ts +43 -15
  64. package/src/actions.openApi.test.ts +12 -10
  65. package/src/api.query.test.ts +24 -1
  66. package/src/api.test.ts +169 -0
  67. package/src/api.ts +71 -0
  68. package/src/auth.test.ts +287 -39
  69. package/src/betterAuth.ts +1 -1
  70. package/src/consentApp.test.ts +1 -0
  71. package/src/example.ts +4 -4
  72. package/src/expressServer.test.ts +82 -85
  73. package/src/expressServer.ts +1 -213
  74. package/src/githubAuth.test.ts +22 -22
  75. package/src/logger.test.ts +466 -1
  76. package/src/logger.ts +477 -14
  77. package/src/middleware.test.ts +74 -2
  78. package/src/middleware.ts +57 -0
  79. package/src/models/consentForm.ts +3 -4
  80. package/src/models/consentResponse.ts +6 -4
  81. package/src/models/versionConfig.ts +3 -4
  82. package/src/openApi.test.ts +10 -17
  83. package/src/openApiBuilder.test.ts +27 -10
  84. package/src/openApiBuilder.ts +24 -0
  85. package/src/permissions.test.ts +8 -23
  86. package/src/populate.test.ts +7 -22
  87. package/src/realtime/changeStreamWatcher.ts +15 -10
  88. package/src/realtime/queryMatcher.ts +54 -27
  89. package/src/realtime/types.ts +4 -4
  90. package/src/requestContext.ts +86 -0
  91. package/src/secretProviders.test.ts +219 -1
  92. package/src/syncConsents.test.ts +1 -1
  93. package/src/terrenoApp.test.ts +38 -0
  94. package/src/terrenoApp.ts +37 -15
  95. package/src/tests/bunSetup.ts +22 -236
  96. package/src/tests/createTestData.ts +176 -0
  97. package/src/tests/models.ts +164 -0
  98. package/src/tests/mongoTestSetup.ts +69 -0
  99. package/src/tests/testEnv.ts +4 -0
  100. package/src/tests/testHelper.ts +57 -0
  101. package/src/tests/types.ts +35 -0
  102. package/src/tests.ts +40 -231
  103. package/src/transformers.test.ts +11 -30
  104. package/tsconfig.typedoc.json +4 -0
  105. package/dist/tests/index.d.ts +0 -1
  106. package/dist/tests/index.js +0 -17
  107. package/src/tests/index.ts +0 -1
package/dist/tests.js CHANGED
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -68,222 +35,38 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
68
35
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
69
36
  }
70
37
  };
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 __importDefault = (this && this.__importDefault) || function (mod) {
88
- return (mod && mod.__esModule) ? mod : { "default": mod };
89
- };
90
38
  Object.defineProperty(exports, "__esModule", { value: true });
91
- exports.setupDb = exports.authAsUser = exports.getBaseServer = exports.RequiredModel = exports.FoodModel = exports.StaffUserModel = exports.SuperUserModel = exports.UserModel = void 0;
92
- var express_1 = __importDefault(require("express"));
93
- var mongoose_1 = __importStar(require("mongoose"));
94
- var passport_local_mongoose_1 = __importDefault(require("passport-local-mongoose"));
95
- var qs_1 = __importDefault(require("qs"));
96
- var supertest_1 = __importDefault(require("supertest"));
97
- var logger_1 = require("./logger");
39
+ exports.loadTestData = exports.authAsUser = exports.getBaseServer = exports.UserModel = exports.setupTestData = exports.setupTestCache = exports.setupDb = exports.SuperUserModel = exports.StaffUserModel = exports.RequiredModel = exports.loadTestDataFromCache = exports.FoodModel = exports.createTestData = void 0;
40
+ var test_1 = require("@terreno/test");
98
41
  var openApiCompat_1 = require("./openApiCompat");
99
- var plugins_1 = require("./plugins");
100
- var userSchema = new mongoose_1.Schema({
101
- admin: { default: false, description: "Whether the user has admin privileges", type: Boolean },
102
- age: { description: "The user's age", type: Number },
103
- name: { description: "The user's display name", type: String },
104
- username: { description: "The user's username", type: String },
105
- });
106
- userSchema.plugin(passport_local_mongoose_1.default, {
107
- attemptsField: "attempts",
108
- interval: process.env.NODE_ENV === "test" ? 1 : 100,
109
- limitAttempts: true,
110
- maxAttempts: 3,
111
- maxInterval: process.env.NODE_ENV === "test" ? 1 : 300000,
112
- usernameCaseInsensitive: true,
113
- usernameField: "email",
114
- });
115
- // userSchema.plugin(tokenPlugin);
116
- userSchema.plugin(plugins_1.createdUpdatedPlugin);
117
- userSchema.plugin(plugins_1.isDisabledPlugin);
118
- userSchema.methods.postCreate = function (body) {
119
- return __awaiter(this, void 0, void 0, function () {
120
- return __generator(this, function (_a) {
121
- this.age = body.age;
122
- return [2 /*return*/, this.save()];
123
- });
124
- });
125
- };
126
- exports.UserModel = (0, mongoose_1.model)("User", userSchema);
127
- var superUserSchema = new mongoose_1.Schema({
128
- superTitle: { description: "The super user's title", required: true, type: String },
129
- });
130
- exports.SuperUserModel = exports.UserModel.discriminator("SuperUser", superUserSchema);
131
- var staffUserSchema = new mongoose_1.Schema({
132
- department: {
133
- description: "The department the staff member belongs to",
134
- required: true,
135
- type: String,
136
- },
137
- });
138
- exports.StaffUserModel = exports.UserModel.discriminator("Staff", staffUserSchema);
139
- var foodCategorySchema = new mongoose_1.Schema({
140
- name: { description: "The name of the food category", type: String },
141
- show: { description: "Whether this category is visible", type: Boolean },
142
- }, { timestamps: { createdAt: "created", updatedAt: "updated" } });
143
- var likesSchema = new mongoose_1.Schema({
144
- likes: { description: "Whether the user liked the item", type: Boolean },
145
- userId: { description: "The user who liked the item", ref: "User", type: "ObjectId" },
146
- });
147
- var foodSchema = new mongoose_1.Schema({
148
- calories: { description: "Number of calories in the food", type: Number },
149
- categories: { description: "Categories this food belongs to", type: [foodCategorySchema] },
150
- created: { description: "When this food was created", type: Date },
151
- eatenBy: [
152
- {
153
- description: "Users who have eaten this food",
154
- ref: "User",
155
- required: true,
156
- type: mongoose_1.Schema.Types.ObjectId,
157
- },
158
- ],
159
- // biome-ignore lint/suspicious/noExplicitAny: DateOnly is a custom SchemaType not recognized by Mongoose's built-in type definitions
160
- expiration: { description: "Expiration date of the food", type: plugins_1.DateOnly },
161
- hidden: {
162
- default: false,
163
- description: "Whether this food is hidden from listings",
164
- type: Boolean,
165
- },
166
- lastEatenWith: {
167
- description: "Map of user names to dates they last ate this food with",
168
- of: Date,
169
- type: Map,
170
- },
171
- likesIds: { description: "User likes for this food", required: true, type: [likesSchema] },
172
- name: { description: "The name of the food", type: String },
173
- ownerId: { description: "The user who owns this food entry", ref: "User", type: "ObjectId" },
174
- source: {
175
- dateAdded: { description: "When the source was added", type: String },
176
- href: { description: "URL of the source", type: String },
177
- name: { description: "Name of the source", type: String },
178
- },
179
- tags: { description: "Tags associated with this food", type: [String] },
180
- }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
181
- foodSchema.virtual("description").get(function () {
182
- return "".concat(this.name, " has ").concat(this.calories, " calories");
183
- });
184
- exports.FoodModel = (0, mongoose_1.model)("Food", foodSchema);
185
- var requiredSchema = new mongoose_1.Schema({
186
- about: { description: "Information about the item", type: String },
187
- name: { description: "The name of the item", required: true, type: String },
188
- });
189
- exports.RequiredModel = (0, mongoose_1.model)("Required", requiredSchema);
42
+ var createTestData_1 = require("./tests/createTestData");
43
+ Object.defineProperty(exports, "createTestData", { enumerable: true, get: function () { return createTestData_1.createTestData; } });
44
+ var models_1 = require("./tests/models");
45
+ Object.defineProperty(exports, "FoodModel", { enumerable: true, get: function () { return models_1.FoodModel; } });
46
+ Object.defineProperty(exports, "RequiredModel", { enumerable: true, get: function () { return models_1.RequiredModel; } });
47
+ Object.defineProperty(exports, "StaffUserModel", { enumerable: true, get: function () { return models_1.StaffUserModel; } });
48
+ Object.defineProperty(exports, "SuperUserModel", { enumerable: true, get: function () { return models_1.SuperUserModel; } });
49
+ Object.defineProperty(exports, "UserModel", { enumerable: true, get: function () { return models_1.UserModel; } });
50
+ var mongoTestSetup_1 = require("./tests/mongoTestSetup");
51
+ Object.defineProperty(exports, "loadTestDataFromCache", { enumerable: true, get: function () { return mongoTestSetup_1.loadTestDataFromCache; } });
52
+ Object.defineProperty(exports, "setupTestCache", { enumerable: true, get: function () { return mongoTestSetup_1.setupTestCache; } });
53
+ var testHelper_1 = require("./tests/testHelper");
54
+ Object.defineProperty(exports, "setupDb", { enumerable: true, get: function () { return testHelper_1.setupDb; } });
55
+ Object.defineProperty(exports, "setupTestData", { enumerable: true, get: function () { return testHelper_1.setupTestData; } });
190
56
  var getBaseServer = function () {
191
- var app = (0, express_1.default)();
192
- app.set("query parser", function (str) { return qs_1.default.parse(str, { arrayLimit: 200 }); });
193
- // Express 5 defaults to 'simple' query parser (Node querystring) which doesn't
194
- // support nested bracket notation like name[$regex]=Green. Use qs to match
195
- // what setupServer() configures.
196
- app.set("query parser", function (str) { return qs_1.default.parse(str, { arrayLimit: 200 }); });
197
- // Record mount paths on layers for Express 5 → OpenAPI compat
198
- (0, openApiCompat_1.patchAppUse)(app);
199
- app.use(function (req, res, next) {
200
- res.header("Access-Control-Allow-Origin", "*");
201
- res.header("Access-Control-Allow-Headers", "*");
202
- // intercepts OPTIONS method
203
- if (req.method === "OPTIONS") {
204
- res.send(200);
205
- }
206
- else {
207
- next();
208
- }
57
+ return (0, test_1.getBaseServer)({
58
+ patchOpenApiCompat: openApiCompat_1.patchAppUse,
209
59
  });
210
- app.use(express_1.default.json());
211
- return app;
212
60
  };
213
61
  exports.getBaseServer = getBaseServer;
214
62
  var authAsUser = function (app, type) { return __awaiter(void 0, void 0, void 0, function () {
215
- var email, password, agent, res;
63
+ var email, password;
216
64
  return __generator(this, function (_a) {
217
- switch (_a.label) {
218
- case 0:
219
- email = type === "admin" ? "admin@example.com" : "notAdmin@example.com";
220
- password = type === "admin" ? "securePassword" : "password";
221
- agent = supertest_1.default.agent(app);
222
- return [4 /*yield*/, agent.post("/auth/login").send({ email: email, password: password }).expect(200)];
223
- case 1:
224
- res = _a.sent();
225
- return [4 /*yield*/, agent.set("authorization", "Bearer ".concat(res.body.data.token))];
226
- case 2:
227
- _a.sent();
228
- return [2 /*return*/, agent];
229
- }
65
+ email = type === "admin" ? "admin@example.com" : "notAdmin@example.com";
66
+ password = type === "admin" ? "securePassword" : "password";
67
+ return [2 /*return*/, (0, test_1.authAsUser)(app, { email: email, password: password })];
230
68
  });
231
69
  }); };
232
70
  exports.authAsUser = authAsUser;
233
- var setupDb = function () { return __awaiter(void 0, void 0, void 0, function () {
234
- var _a, notAdmin, admin, adminOther, error_1;
235
- return __generator(this, function (_b) {
236
- switch (_b.label) {
237
- case 0: return [4 /*yield*/, mongoose_1.default
238
- .connect("mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000")
239
- .catch(logger_1.logger.catch)];
240
- case 1:
241
- _b.sent();
242
- process.env.REFRESH_TOKEN_SECRET = "refresh_secret";
243
- process.env.TOKEN_SECRET = "secret";
244
- process.env.TOKEN_EXPIRES_IN = "30m";
245
- process.env.TOKEN_ISSUER = "example.com";
246
- process.env.SESSION_SECRET = "session";
247
- // Broken out of the try/catch below so you can test the catch logger by shutting down mongo.
248
- return [4 /*yield*/, Promise.all([exports.UserModel.deleteMany({}), exports.FoodModel.deleteMany({})]).catch(logger_1.logger.catch)];
249
- case 2:
250
- // Broken out of the try/catch below so you can test the catch logger by shutting down mongo.
251
- _b.sent();
252
- _b.label = 3;
253
- case 3:
254
- _b.trys.push([3, 11, , 12]);
255
- return [4 /*yield*/, Promise.all([
256
- exports.UserModel.create({ email: "notAdmin@example.com", name: "Not Admin" }),
257
- exports.UserModel.create({ admin: true, email: "admin@example.com", name: "Admin" }),
258
- exports.UserModel.create({ admin: true, email: "admin+other@example.com", name: "Admin Other" }),
259
- ])];
260
- case 4:
261
- _a = __read.apply(void 0, [_b.sent(), 3]), notAdmin = _a[0], admin = _a[1], adminOther = _a[2];
262
- return [4 /*yield*/, notAdmin.setPassword("password")];
263
- case 5:
264
- _b.sent();
265
- return [4 /*yield*/, notAdmin.save()];
266
- case 6:
267
- _b.sent();
268
- return [4 /*yield*/, admin.setPassword("securePassword")];
269
- case 7:
270
- _b.sent();
271
- return [4 /*yield*/, admin.save()];
272
- case 8:
273
- _b.sent();
274
- return [4 /*yield*/, adminOther.setPassword("otherPassword")];
275
- case 9:
276
- _b.sent();
277
- return [4 /*yield*/, adminOther.save()];
278
- case 10:
279
- _b.sent();
280
- return [2 /*return*/, [admin, notAdmin, adminOther]];
281
- case 11:
282
- error_1 = _b.sent();
283
- logger_1.logger.error("Error setting up DB", error_1);
284
- throw error_1;
285
- case 12: return [2 /*return*/];
286
- }
287
- });
288
- }); };
289
- exports.setupDb = setupDb;
71
+ var mongoTestSetup_2 = require("./tests/mongoTestSetup");
72
+ Object.defineProperty(exports, "loadTestData", { enumerable: true, get: function () { return mongoTestSetup_2.loadTestData; } });
@@ -46,22 +46,6 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
46
46
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
47
  }
48
48
  };
49
- var __read = (this && this.__read) || function (o, n) {
50
- var m = typeof Symbol === "function" && o[Symbol.iterator];
51
- if (!m) return o;
52
- var i = m.call(o), r, ar = [], e;
53
- try {
54
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
55
- }
56
- catch (error) { e = { error: error }; }
57
- finally {
58
- try {
59
- if (r && !r.done && (m = i["return"])) m.call(i);
60
- }
61
- finally { if (e) throw e.error; }
62
- }
63
- return ar;
64
- };
65
49
  var __importDefault = (this && this.__importDefault) || function (mod) {
66
50
  return (mod && mod.__esModule) ? mod : { "default": mod };
67
51
  };
@@ -81,37 +65,16 @@ var transformers_1 = require("./transformers");
81
65
  var server;
82
66
  var app;
83
67
  (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
84
- var _a;
85
- return __generator(this, function (_b) {
86
- switch (_b.label) {
68
+ var testData;
69
+ return __generator(this, function (_a) {
70
+ switch (_a.label) {
87
71
  case 0:
88
72
  process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
89
- return [4 /*yield*/, (0, tests_1.setupDb)()];
73
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
90
74
  case 1:
91
- _a = __read.apply(void 0, [_b.sent(), 2]), admin = _a[0], notAdmin = _a[1];
92
- return [4 /*yield*/, Promise.all([
93
- tests_1.FoodModel.create({
94
- calories: 1,
95
- created: new Date(),
96
- name: "Spinach",
97
- ownerId: notAdmin._id,
98
- }),
99
- tests_1.FoodModel.create({
100
- calories: 100,
101
- created: Date.now() - 10,
102
- hidden: true,
103
- name: "Apple",
104
- ownerId: admin._id,
105
- }),
106
- tests_1.FoodModel.create({
107
- calories: 100,
108
- created: Date.now() - 10,
109
- name: "Carrots",
110
- ownerId: admin._id,
111
- }),
112
- ])];
113
- case 2:
114
- _b.sent();
75
+ testData = _a.sent();
76
+ admin = testData.users.admin;
77
+ notAdmin = testData.users.notAdmin;
115
78
  app = (0, tests_1.getBaseServer)();
116
79
  (0, auth_1.setupAuth)(app, tests_1.UserModel);
117
80
  (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
@@ -156,7 +119,7 @@ var transformers_1 = require("./transformers");
156
119
  return [4 /*yield*/, agent.get("/food").expect(200)];
157
120
  case 2:
158
121
  foodRes = _a.sent();
159
- (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(2);
122
+ (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(3);
160
123
  return [2 /*return*/];
161
124
  }
162
125
  });
@@ -171,7 +134,7 @@ var transformers_1 = require("./transformers");
171
134
  return [4 /*yield*/, agent.get("/food").expect(200)];
172
135
  case 2:
173
136
  foodRes = _a.sent();
174
- (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(3);
137
+ (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(4);
175
138
  return [2 /*return*/];
176
139
  }
177
140
  });
@@ -186,7 +149,7 @@ var transformers_1 = require("./transformers");
186
149
  return [4 /*yield*/, agent.get("/food").expect(200)];
187
150
  case 2:
188
151
  foodRes = _a.sent();
189
- (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(3);
152
+ (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(4);
190
153
  spinach = foodRes.body.data.find(function (food) { return food.name === "Spinach"; });
191
154
  (0, bun_test_1.expect)(spinach.created).toBeDefined();
192
155
  (0, bun_test_1.expect)(spinach.id).toBeDefined();
@@ -227,7 +190,7 @@ var transformers_1 = require("./transformers");
227
190
  return [4 /*yield*/, agent.get("/food").expect(200)];
228
191
  case 2:
229
192
  foodRes = _a.sent();
230
- (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(2);
193
+ (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(3);
231
194
  spinach = foodRes.body.data.find(function (food) { return food.name === "Spinach"; });
232
195
  (0, bun_test_1.expect)(spinach.id).toBeDefined();
233
196
  (0, bun_test_1.expect)(spinach.name).toBe("Spinach");
@@ -289,7 +252,7 @@ var transformers_1 = require("./transformers");
289
252
  return [4 /*yield*/, agent.get("/food").expect(200)];
290
253
  case 2:
291
254
  foodRes = _a.sent();
292
- (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(2);
255
+ (0, bun_test_1.expect)(foodRes.body.data).toHaveLength(3);
293
256
  spinach = foodRes.body.data.find(function (food) { return food.name === "Spinach"; });
294
257
  (0, bun_test_1.expect)(spinach.id).toBeDefined();
295
258
  (0, bun_test_1.expect)(spinach.name).toBe("Spinach");
@@ -358,9 +321,10 @@ var transformers_1 = require("./transformers");
358
321
  case 0: return [4 /*yield*/, server.get("/food")];
359
322
  case 1:
360
323
  res = _a.sent();
361
- (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
324
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(3);
362
325
  (0, bun_test_1.expect)(res.body.data.find(function (f) { return f.name === "Spinach"; })).toBeDefined();
363
326
  (0, bun_test_1.expect)(res.body.data.find(function (f) { return f.name === "Carrots"; })).toBeDefined();
327
+ (0, bun_test_1.expect)(res.body.data.find(function (f) { return f.name === "Pizza"; })).toBeDefined();
364
328
  return [2 /*return*/];
365
329
  }
366
330
  });
package/package.json CHANGED
@@ -46,6 +46,7 @@
46
46
  "description": "Styled after the Django & Django REST Framework, a batteries-include framework for building REST APIs with Node/Express/Mongoose.",
47
47
  "devDependencies": {
48
48
  "@biomejs/biome": "^2.3.6",
49
+ "@terreno/test": "workspace:*",
49
50
  "@types/bcrypt": "^6.0.0",
50
51
  "@types/bun": "^1.2.4",
51
52
  "@types/cors": "^2.8.17",
@@ -80,6 +81,16 @@
80
81
  ],
81
82
  "license": "Apache-2.0",
82
83
  "main": "dist/index.js",
84
+ "exports": {
85
+ ".": {
86
+ "types": "./dist/index.d.ts",
87
+ "default": "./dist/index.js"
88
+ },
89
+ "./testing": {
90
+ "types": "./dist/tests.d.ts",
91
+ "default": "./dist/tests.js"
92
+ }
93
+ },
83
94
  "name": "@terreno/api",
84
95
  "publishConfig": {
85
96
  "access": "public"
@@ -96,18 +107,21 @@
96
107
  "json5": "2.2.3"
97
108
  },
98
109
  "scripts": {
99
- "compile": "bun tsc",
110
+ "compile": "node ../.github/scripts/compile-workspace-deps.js && bun tsc",
100
111
  "compile:watch": "bun tsc -w",
101
112
  "dev": "bun tsc -w",
102
113
  "docs": "typedoc --out docs src/index.ts",
103
114
  "lint": "biome check ./src",
104
115
  "lint:fix": "biome check --write ./src",
105
116
  "lint:unsafefix": "biome check --fix --unsafe ./src",
106
- "test": "bun test --preload ./src/tests/bunSetup.ts --update-snapshots",
107
- "test:ci": "bun test --preload ./src/tests/bunSetup.ts",
117
+ "test": "bun test --update-snapshots",
118
+ "test:ci": "bun test",
108
119
  "test:coverage": "bun run ../scripts/check-coverage.ts",
120
+ "test:cache:clean": "bun ./src/tests/mongoTestSetup.ts clean",
121
+ "test:cache:setup": "bun ./src/tests/mongoTestSetup.ts setup",
122
+ "test:cache:status": "bun ./src/tests/mongoTestSetup.ts status",
109
123
  "updateSnapshot": "bun test --update-snapshots"
110
124
  },
111
125
  "types": "dist/index.d.ts",
112
- "version": "0.20.2"
126
+ "version": "0.22.0"
113
127
  }
@@ -910,6 +910,7 @@ exports[`OpenApiMiddlewareBuilder snapshot tests matches OpenAPI spec snapshot 1
910
910
  "/food/stats": {
911
911
  "get": {
912
912
  "description": "Returns aggregated statistics about food items",
913
+ "operationId": "getFoodStats",
913
914
  "parameters": [
914
915
  {
915
916
  "description": "Filter by food category",
@@ -30,19 +30,27 @@ describe("VersionCheckPlugin", () => {
30
30
  it("returns ok when no VersionConfig exists", async () => {
31
31
  const res = await app.get("/version-check").query({platform: "web", version: 100});
32
32
  expect(res.status).toBe(200);
33
- expect(res.body).toEqual({pollingIntervalMs: 86400000, status: "ok"});
33
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
34
+ expect(res.body).toEqual(
35
+ expect.objectContaining({
36
+ pollingIntervalMs: 86400000,
37
+ status: "ok",
38
+ })
39
+ );
34
40
  });
35
41
 
36
42
  it("returns ok when version param is missing", async () => {
37
43
  const res = await app.get("/version-check");
38
44
  expect(res.status).toBe(200);
39
- expect(res.body).toEqual({status: "ok"});
45
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
46
+ expect(res.body).toEqual(expect.objectContaining({status: "ok"}));
40
47
  });
41
48
 
42
49
  it("returns ok when version param is invalid", async () => {
43
50
  const res = await app.get("/version-check").query({platform: "web", version: "invalid"});
44
51
  expect(res.status).toBe(200);
45
- expect(res.body).toEqual({status: "ok"});
52
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
53
+ expect(res.body).toEqual(expect.objectContaining({status: "ok"}));
46
54
  });
47
55
 
48
56
  it("returns ok when client version >= warning and required (web)", async () => {
@@ -53,12 +61,15 @@ describe("VersionCheckPlugin", () => {
53
61
 
54
62
  const res = await app.get("/version-check").query({platform: "web", version: 150});
55
63
  expect(res.status).toBe(200);
56
- expect(res.body).toEqual({
57
- pollingIntervalMs: 86400000,
58
- requiredVersion: 50,
59
- status: "ok",
60
- warningVersion: 100,
61
- });
64
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
65
+ expect(res.body).toEqual(
66
+ expect.objectContaining({
67
+ pollingIntervalMs: 86400000,
68
+ requiredVersion: 50,
69
+ status: "ok",
70
+ warningVersion: 100,
71
+ })
72
+ );
62
73
  });
63
74
 
64
75
  it("returns warning when client version < warning (web)", async () => {
@@ -70,6 +81,7 @@ describe("VersionCheckPlugin", () => {
70
81
 
71
82
  const res = await app.get("/version-check").query({platform: "web", version: 80});
72
83
  expect(res.status).toBe(200);
84
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
73
85
  expect(res.body.status).toBe("warning");
74
86
  expect(res.body.message).toBe("Please update!");
75
87
  });
@@ -84,6 +96,7 @@ describe("VersionCheckPlugin", () => {
84
96
 
85
97
  const res = await app.get("/version-check").query({platform: "web", version: 50});
86
98
  expect(res.status).toBe(200);
99
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
87
100
  expect(res.body.status).toBe("required");
88
101
  expect(res.body.message).toBe("Update required");
89
102
  expect(res.body.updateUrl).toBe("https://example.com/update");
@@ -98,9 +111,11 @@ describe("VersionCheckPlugin", () => {
98
111
  });
99
112
 
100
113
  const webRes = await app.get("/version-check").query({platform: "web", version: 100});
114
+ expect(webRes.body.requestId).toBe(webRes.headers["x-request-id"]);
101
115
  expect(webRes.body.status).toBe("ok");
102
116
 
103
117
  const mobileRes = await app.get("/version-check").query({platform: "mobile", version: 100});
118
+ expect(mobileRes.body.requestId).toBe(mobileRes.headers["x-request-id"]);
104
119
  expect(mobileRes.body.status).toBe("required");
105
120
  });
106
121
 
@@ -111,6 +126,7 @@ describe("VersionCheckPlugin", () => {
111
126
  });
112
127
 
113
128
  const res = await app.get("/version-check").query({platform: "invalid", version: 50});
129
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
114
130
  expect(res.body.status).toBe("required");
115
131
  });
116
132
 
@@ -122,12 +138,15 @@ describe("VersionCheckPlugin", () => {
122
138
 
123
139
  const res = await app.get("/version-check").query({platform: "web", version: 100});
124
140
  expect(res.status).toBe(200);
125
- expect(res.body).toEqual({
126
- pollingIntervalMs: 86400000,
127
- requiredVersion: 50,
128
- status: "ok",
129
- warningVersion: 100,
130
- });
141
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
142
+ expect(res.body).toEqual(
143
+ expect.objectContaining({
144
+ pollingIntervalMs: 86400000,
145
+ requiredVersion: 50,
146
+ status: "ok",
147
+ warningVersion: 100,
148
+ })
149
+ );
131
150
  });
132
151
 
133
152
  it("returns pollingIntervalMs from config pollingIntervalMinutes", async () => {
@@ -139,6 +158,7 @@ describe("VersionCheckPlugin", () => {
139
158
 
140
159
  const res = await app.get("/version-check").query({platform: "web", version: 100});
141
160
  expect(res.status).toBe(200);
161
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
142
162
  expect(res.body.pollingIntervalMs).toBe(3600000);
143
163
  });
144
164
 
@@ -150,6 +170,7 @@ describe("VersionCheckPlugin", () => {
150
170
 
151
171
  const res = await app.get("/version-check").query({platform: "web", version: 100});
152
172
  expect(res.status).toBe(200);
173
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
153
174
  expect(res.body.pollingIntervalMs).toBe(86400000);
154
175
  });
155
176
 
@@ -161,6 +182,7 @@ describe("VersionCheckPlugin", () => {
161
182
 
162
183
  const res = await app.get("/version-check?version=50&platform=web");
163
184
  expect(res.status).toBe(200);
185
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
164
186
  expect(res.body.status).toBe("required");
165
187
  });
166
188
 
@@ -172,6 +194,7 @@ describe("VersionCheckPlugin", () => {
172
194
 
173
195
  const res = await app.get("/version-check").query({platform: "web", version: 50});
174
196
  expect(res.status).toBe(200);
197
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
175
198
  expect(res.body.status).toBe("warning");
176
199
  expect(res.body.message).toBe(
177
200
  "A new version is available. Please update for the best experience."
@@ -186,6 +209,7 @@ describe("VersionCheckPlugin", () => {
186
209
 
187
210
  const res = await app.get("/version-check").query({platform: "web", version: 50});
188
211
  expect(res.status).toBe(200);
212
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
189
213
  expect(res.body.status).toBe("required");
190
214
  expect(res.body.message).toBe(
191
215
  "This version is no longer supported. Please update to continue."
@@ -200,6 +224,7 @@ describe("VersionCheckPlugin", () => {
200
224
 
201
225
  const res = await app.get("/version-check").query({platform: "web", version: 100});
202
226
  expect(res.status).toBe(200);
227
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
203
228
  expect(res.body.status).toBe("warning");
204
229
  });
205
230
 
@@ -210,12 +235,14 @@ describe("VersionCheckPlugin", () => {
210
235
  });
211
236
 
212
237
  const warningRes = await app.get("/version-check").query({platform: "web", version: 150});
238
+ expect(warningRes.body.requestId).toBe(warningRes.headers["x-request-id"]);
213
239
  expect(warningRes.body.status).toBe("warning");
214
240
  expect(warningRes.body.message).toBe(
215
241
  "A new version is available. Please update for the best experience."
216
242
  );
217
243
 
218
244
  const requiredRes = await app.get("/version-check").query({platform: "web", version: 50});
245
+ expect(requiredRes.body.requestId).toBe(requiredRes.headers["x-request-id"]);
219
246
  expect(requiredRes.body.status).toBe("required");
220
247
  expect(requiredRes.body.message).toBe(
221
248
  "This version is no longer supported. Please update to continue."
@@ -225,6 +252,7 @@ describe("VersionCheckPlugin", () => {
225
252
  it("handles numeric version parameter", async () => {
226
253
  const res = await app.get("/version-check?version=50&platform=web");
227
254
  expect(res.status).toBe(200);
255
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
228
256
  });
229
257
  });
230
258