@terreno/api 0.0.18 → 0.1.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 (48) hide show
  1. package/.claude/CLAUDE.local.md +204 -0
  2. package/.cursor/rules/00-root.mdc +338 -0
  3. package/.github/copilot-instructions.md +333 -0
  4. package/AGENTS.md +23 -3
  5. package/README.md +73 -3
  6. package/dist/api.d.ts +68 -1
  7. package/dist/api.js +139 -4
  8. package/dist/api.test.js +906 -2
  9. package/dist/auth.js +3 -1
  10. package/dist/errors.js +14 -11
  11. package/dist/example.js +7 -7
  12. package/dist/githubAuth.test.js +3 -3
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +2 -0
  15. package/dist/openApi.test.js +8 -5
  16. package/dist/openApiBuilder.d.ts +69 -1
  17. package/dist/openApiBuilder.js +109 -5
  18. package/dist/openApiValidator.d.ts +296 -0
  19. package/dist/openApiValidator.js +698 -0
  20. package/dist/openApiValidator.test.d.ts +1 -0
  21. package/dist/openApiValidator.test.js +346 -0
  22. package/dist/plugins.test.js +3 -3
  23. package/dist/terrenoPlugin.d.ts +4 -0
  24. package/dist/terrenoPlugin.js +2 -0
  25. package/dist/tests.js +34 -24
  26. package/package.json +4 -1
  27. package/src/__snapshots__/openApi.test.ts.snap +399 -0
  28. package/src/__snapshots__/openApiBuilder.test.ts.snap +108 -0
  29. package/src/api.test.ts +743 -2
  30. package/src/api.ts +209 -3
  31. package/src/auth.ts +3 -1
  32. package/src/errors.ts +14 -11
  33. package/src/example.ts +7 -7
  34. package/src/githubAuth.test.ts +3 -3
  35. package/src/index.ts +2 -0
  36. package/src/openApi.test.ts +8 -5
  37. package/src/openApiBuilder.ts +188 -15
  38. package/src/openApiValidator.test.ts +241 -0
  39. package/src/openApiValidator.ts +860 -0
  40. package/src/plugins.test.ts +3 -3
  41. package/src/terrenoPlugin.ts +5 -0
  42. package/src/tests.ts +34 -24
  43. package/.cursorrules +0 -107
  44. package/.windsurfrules +0 -107
  45. package/dist/response.d.ts +0 -0
  46. package/dist/response.js +0 -1
  47. package/index.ts +0 -1
  48. package/src/response.ts +0 -0
@@ -0,0 +1,346 @@
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ 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);
24
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ 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;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
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
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
66
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
67
+ if (ar || !(i in from)) {
68
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
69
+ ar[i] = from[i];
70
+ }
71
+ }
72
+ return to.concat(ar || Array.prototype.slice.call(from));
73
+ };
74
+ Object.defineProperty(exports, "__esModule", { value: true });
75
+ var bun_test_1 = require("bun:test");
76
+ var api_1 = require("./api");
77
+ var auth_1 = require("./auth");
78
+ var openApiValidator_1 = require("./openApiValidator");
79
+ var permissions_1 = require("./permissions");
80
+ var tests_1 = require("./tests");
81
+ // RequiredModel has a clean schema that AJV can compile (no non-standard types).
82
+ // It has: name (String, required), about (String, optional)
83
+ var requiredRouterOptions = {
84
+ permissions: {
85
+ create: [permissions_1.Permissions.IsAuthenticated],
86
+ delete: [permissions_1.Permissions.IsAdmin],
87
+ list: [permissions_1.Permissions.IsAuthenticated],
88
+ read: [permissions_1.Permissions.IsAuthenticated],
89
+ update: [permissions_1.Permissions.IsAuthenticated],
90
+ },
91
+ queryFields: ["name"],
92
+ sort: "-name",
93
+ };
94
+ var setupFreshApp = function () { return __awaiter(void 0, void 0, void 0, function () {
95
+ var freshApp;
96
+ return __generator(this, function (_a) {
97
+ freshApp = (0, tests_1.getBaseServer)();
98
+ (0, auth_1.setupAuth)(freshApp, tests_1.UserModel);
99
+ (0, auth_1.addAuthRoutes)(freshApp, tests_1.UserModel);
100
+ return [2 /*return*/, freshApp];
101
+ });
102
+ }); };
103
+ (0, bun_test_1.describe)("openApiValidator", function () {
104
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
105
+ return __generator(this, function (_a) {
106
+ switch (_a.label) {
107
+ case 0:
108
+ (0, openApiValidator_1.resetOpenApiValidatorConfig)();
109
+ return [4 /*yield*/, (0, tests_1.setupDb)()];
110
+ case 1:
111
+ _a.sent();
112
+ return [4 /*yield*/, tests_1.RequiredModel.deleteMany({})];
113
+ case 2:
114
+ _a.sent();
115
+ return [2 /*return*/];
116
+ }
117
+ });
118
+ }); });
119
+ (0, bun_test_1.afterEach)(function () {
120
+ (0, openApiValidator_1.resetOpenApiValidatorConfig)();
121
+ });
122
+ (0, bun_test_1.describe)("isConfigured flag", function () {
123
+ (0, bun_test_1.it)("is false by default", function () {
124
+ (0, bun_test_1.expect)((0, openApiValidator_1.isOpenApiValidatorConfigured)()).toBe(false);
125
+ });
126
+ (0, bun_test_1.it)("becomes true after configureOpenApiValidator()", function () {
127
+ (0, openApiValidator_1.configureOpenApiValidator)();
128
+ (0, bun_test_1.expect)((0, openApiValidator_1.isOpenApiValidatorConfigured)()).toBe(true);
129
+ });
130
+ (0, bun_test_1.it)("resets to false after resetOpenApiValidatorConfig()", function () {
131
+ (0, openApiValidator_1.configureOpenApiValidator)();
132
+ (0, bun_test_1.expect)((0, openApiValidator_1.isOpenApiValidatorConfigured)()).toBe(true);
133
+ (0, openApiValidator_1.resetOpenApiValidatorConfig)();
134
+ (0, bun_test_1.expect)((0, openApiValidator_1.isOpenApiValidatorConfigured)()).toBe(false);
135
+ });
136
+ });
137
+ (0, bun_test_1.describe)("no-op when not configured", function () {
138
+ (0, bun_test_1.it)("does not strip or validate when not configured", function () { return __awaiter(void 0, void 0, void 0, function () {
139
+ var freshApp, admin, res;
140
+ return __generator(this, function (_a) {
141
+ switch (_a.label) {
142
+ case 0: return [4 /*yield*/, setupFreshApp()];
143
+ case 1:
144
+ freshApp = _a.sent();
145
+ freshApp.use("/required", (0, api_1.modelRouter)(tests_1.RequiredModel, requiredRouterOptions));
146
+ return [4 /*yield*/, (0, tests_1.authAsUser)(freshApp, "admin")];
147
+ case 2:
148
+ admin = _a.sent();
149
+ return [4 /*yield*/, admin.post("/required").send({ name: "Apple" }).expect(201)];
150
+ case 3:
151
+ res = _a.sent();
152
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Apple");
153
+ return [2 /*return*/];
154
+ }
155
+ });
156
+ }); });
157
+ });
158
+ (0, bun_test_1.describe)("active after configuration", function () {
159
+ (0, bun_test_1.it)("strips extra properties when removeAdditional is true", function () { return __awaiter(void 0, void 0, void 0, function () {
160
+ var freshApp, admin, res;
161
+ return __generator(this, function (_a) {
162
+ switch (_a.label) {
163
+ case 0:
164
+ (0, openApiValidator_1.configureOpenApiValidator)({ removeAdditional: true });
165
+ return [4 /*yield*/, setupFreshApp()];
166
+ case 1:
167
+ freshApp = _a.sent();
168
+ freshApp.use("/required", (0, api_1.modelRouter)(tests_1.RequiredModel, requiredRouterOptions));
169
+ return [4 /*yield*/, (0, tests_1.authAsUser)(freshApp, "admin")];
170
+ case 2:
171
+ admin = _a.sent();
172
+ return [4 /*yield*/, admin
173
+ .post("/required")
174
+ .send({ fakeField: "this should be stripped", name: "Apple" })
175
+ .expect(201)];
176
+ case 3:
177
+ res = _a.sent();
178
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Apple");
179
+ (0, bun_test_1.expect)(res.body.data.fakeField).toBeUndefined();
180
+ return [2 /*return*/];
181
+ }
182
+ });
183
+ }); });
184
+ (0, bun_test_1.it)("rejects missing required fields", function () { return __awaiter(void 0, void 0, void 0, function () {
185
+ var freshApp, admin, res;
186
+ return __generator(this, function (_a) {
187
+ switch (_a.label) {
188
+ case 0:
189
+ (0, openApiValidator_1.configureOpenApiValidator)();
190
+ return [4 /*yield*/, setupFreshApp()];
191
+ case 1:
192
+ freshApp = _a.sent();
193
+ freshApp.use("/required", (0, api_1.modelRouter)(tests_1.RequiredModel, requiredRouterOptions));
194
+ return [4 /*yield*/, (0, tests_1.authAsUser)(freshApp, "admin")];
195
+ case 2:
196
+ admin = _a.sent();
197
+ return [4 /*yield*/, admin.post("/required").send({ about: "no name" }).expect(400)];
198
+ case 3:
199
+ res = _a.sent();
200
+ (0, bun_test_1.expect)(res.body.title).toBe("Request validation failed");
201
+ return [2 /*return*/];
202
+ }
203
+ });
204
+ }); });
205
+ });
206
+ (0, bun_test_1.describe)("onAdditionalPropertiesRemoved hook", function () {
207
+ (0, bun_test_1.it)("fires callback with removed property names", function () { return __awaiter(void 0, void 0, void 0, function () {
208
+ var removedProps, freshApp, admin;
209
+ return __generator(this, function (_a) {
210
+ switch (_a.label) {
211
+ case 0:
212
+ removedProps = [];
213
+ (0, openApiValidator_1.configureOpenApiValidator)({
214
+ onAdditionalPropertiesRemoved: function (props) {
215
+ removedProps.push.apply(removedProps, __spreadArray([], __read(props), false));
216
+ },
217
+ removeAdditional: true,
218
+ });
219
+ return [4 /*yield*/, setupFreshApp()];
220
+ case 1:
221
+ freshApp = _a.sent();
222
+ freshApp.use("/required", (0, api_1.modelRouter)(tests_1.RequiredModel, requiredRouterOptions));
223
+ return [4 /*yield*/, (0, tests_1.authAsUser)(freshApp, "admin")];
224
+ case 2:
225
+ admin = _a.sent();
226
+ return [4 /*yield*/, admin
227
+ .post("/required")
228
+ .send({ extraA: "stripped", extraB: "also stripped", name: "Apple" })
229
+ .expect(201)];
230
+ case 3:
231
+ _a.sent();
232
+ (0, bun_test_1.expect)(removedProps).toContain("extraA");
233
+ (0, bun_test_1.expect)(removedProps).toContain("extraB");
234
+ return [2 /*return*/];
235
+ }
236
+ });
237
+ }); });
238
+ });
239
+ (0, bun_test_1.describe)("per-route validation: false override", function () {
240
+ (0, bun_test_1.it)("skips validation when validation is false", function () { return __awaiter(void 0, void 0, void 0, function () {
241
+ var freshApp, admin, res;
242
+ return __generator(this, function (_a) {
243
+ switch (_a.label) {
244
+ case 0:
245
+ (0, openApiValidator_1.configureOpenApiValidator)({ removeAdditional: true });
246
+ return [4 /*yield*/, setupFreshApp()];
247
+ case 1:
248
+ freshApp = _a.sent();
249
+ freshApp.use("/required", (0, api_1.modelRouter)(tests_1.RequiredModel, __assign(__assign({}, requiredRouterOptions), { validation: false })));
250
+ return [4 /*yield*/, (0, tests_1.authAsUser)(freshApp, "admin")];
251
+ case 2:
252
+ admin = _a.sent();
253
+ return [4 /*yield*/, admin
254
+ .post("/required")
255
+ .send({ fakeField: "not stripped", name: "Apple" })
256
+ .expect(201)];
257
+ case 3:
258
+ res = _a.sent();
259
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Apple");
260
+ return [2 /*return*/];
261
+ }
262
+ });
263
+ }); });
264
+ });
265
+ (0, bun_test_1.describe)("sanitization of non-standard mongoose-to-swagger types", function () {
266
+ (0, bun_test_1.it)("validates models with ObjectId and DateOnly fields after sanitization", function () { return __awaiter(void 0, void 0, void 0, function () {
267
+ var freshApp, admin, res;
268
+ return __generator(this, function (_a) {
269
+ switch (_a.label) {
270
+ case 0:
271
+ (0, openApiValidator_1.configureOpenApiValidator)({ removeAdditional: true });
272
+ return [4 /*yield*/, setupFreshApp()];
273
+ case 1:
274
+ freshApp = _a.sent();
275
+ freshApp.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
276
+ permissions: {
277
+ create: [permissions_1.Permissions.IsAuthenticated],
278
+ delete: [permissions_1.Permissions.IsAdmin],
279
+ list: [permissions_1.Permissions.IsAuthenticated],
280
+ read: [permissions_1.Permissions.IsAuthenticated],
281
+ update: [permissions_1.Permissions.IsAuthenticated],
282
+ },
283
+ queryFields: ["name", "calories", "hidden"],
284
+ sort: "-created",
285
+ }));
286
+ return [4 /*yield*/, (0, tests_1.authAsUser)(freshApp, "admin")];
287
+ case 2:
288
+ admin = _a.sent();
289
+ return [4 /*yield*/, admin
290
+ .post("/food")
291
+ .send({ calories: 100, likesIds: [], name: "Apple", source: { name: "Test" } })
292
+ .expect(201)];
293
+ case 3:
294
+ res = _a.sent();
295
+ (0, bun_test_1.expect)(res.body.data.name).toBe("Apple");
296
+ return [2 /*return*/];
297
+ }
298
+ });
299
+ }); });
300
+ });
301
+ (0, bun_test_1.describe)("buildQuerySchemaFromFields", function () {
302
+ (0, bun_test_1.it)("always includes limit, page, and sort", function () {
303
+ var schema = (0, openApiValidator_1.buildQuerySchemaFromFields)(tests_1.FoodModel, []);
304
+ (0, bun_test_1.expect)(schema.limit).toBeDefined();
305
+ (0, bun_test_1.expect)(schema.page).toBeDefined();
306
+ (0, bun_test_1.expect)(schema.sort).toBeDefined();
307
+ });
308
+ (0, bun_test_1.it)("includes queryFields from model schema", function () {
309
+ var schema = (0, openApiValidator_1.buildQuerySchemaFromFields)(tests_1.FoodModel, ["name", "calories"]);
310
+ (0, bun_test_1.expect)(schema.name).toBeDefined();
311
+ (0, bun_test_1.expect)(schema.calories).toBeDefined();
312
+ (0, bun_test_1.expect)(schema.hidden).toBeUndefined();
313
+ });
314
+ (0, bun_test_1.it)("marks query fields as not required", function () {
315
+ var schema = (0, openApiValidator_1.buildQuerySchemaFromFields)(tests_1.FoodModel, ["name"]);
316
+ (0, bun_test_1.expect)(schema.name.required).toBe(false);
317
+ });
318
+ });
319
+ (0, bun_test_1.describe)("validateRequestBody middleware", function () {
320
+ (0, bun_test_1.it)("is a no-op when not configured", function () {
321
+ (0, openApiValidator_1.resetOpenApiValidatorConfig)();
322
+ var middleware = (0, openApiValidator_1.validateRequestBody)({
323
+ name: { required: true, type: "string" },
324
+ });
325
+ var nextCalled = false;
326
+ var req = { body: {} };
327
+ var res = {};
328
+ var next = function () {
329
+ nextCalled = true;
330
+ };
331
+ middleware(req, res, next);
332
+ (0, bun_test_1.expect)(nextCalled).toBe(true);
333
+ });
334
+ (0, bun_test_1.it)("validates when configured", function () {
335
+ (0, openApiValidator_1.configureOpenApiValidator)();
336
+ var middleware = (0, openApiValidator_1.validateRequestBody)({
337
+ name: { required: true, type: "string" },
338
+ });
339
+ var req = { body: {}, method: "POST", path: "/test" };
340
+ var res = {};
341
+ (0, bun_test_1.expect)(function () {
342
+ middleware(req, res, function () { });
343
+ }).toThrow();
344
+ });
345
+ });
346
+ });
@@ -64,9 +64,9 @@ var permissions_1 = require("./permissions");
64
64
  var plugins_1 = require("./plugins");
65
65
  var tests_1 = require("./tests");
66
66
  var stuffSchema = new mongoose_1.Schema({
67
- date: plugins_1.DateOnly,
68
- name: String,
69
- ownerId: String,
67
+ date: { description: "The date associated with this item", type: plugins_1.DateOnly },
68
+ name: { description: "The name of the item", type: String },
69
+ ownerId: { description: "The user who owns this item", type: String },
70
70
  });
71
71
  stuffSchema.plugin(plugins_1.isDeletedPlugin);
72
72
  stuffSchema.plugin(plugins_1.findOneOrNone);
@@ -0,0 +1,4 @@
1
+ import type express from "express";
2
+ export interface TerrenoPlugin {
3
+ register(app: express.Application): void;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/tests.js CHANGED
@@ -99,10 +99,10 @@ var supertest_1 = __importDefault(require("supertest"));
99
99
  var logger_1 = require("./logger");
100
100
  var plugins_1 = require("./plugins");
101
101
  var userSchema = new mongoose_1.Schema({
102
- admin: { default: false, type: Boolean },
103
- age: Number,
104
- name: String,
105
- username: String,
102
+ admin: { default: false, description: "Whether the user has admin privileges", type: Boolean },
103
+ age: { description: "The user's age", type: Number },
104
+ name: { description: "The user's display name", type: String },
105
+ username: { description: "The user's username", type: String },
106
106
  });
107
107
  userSchema.plugin(passport_local_mongoose_1.default, {
108
108
  attemptsField: "attempts",
@@ -126,55 +126,65 @@ userSchema.methods.postCreate = function (body) {
126
126
  };
127
127
  exports.UserModel = (0, mongoose_1.model)("User", userSchema);
128
128
  var superUserSchema = new mongoose_1.Schema({
129
- superTitle: { required: true, type: String },
129
+ superTitle: { description: "The super user's title", required: true, type: String },
130
130
  });
131
131
  exports.SuperUserModel = exports.UserModel.discriminator("SuperUser", superUserSchema);
132
132
  var staffUserSchema = new mongoose_1.Schema({
133
- department: { required: true, type: String },
133
+ department: {
134
+ description: "The department the staff member belongs to",
135
+ required: true,
136
+ type: String,
137
+ },
134
138
  });
135
139
  exports.StaffUserModel = exports.UserModel.discriminator("Staff", staffUserSchema);
136
140
  var foodCategorySchema = new mongoose_1.Schema({
137
- name: String,
138
- show: Boolean,
141
+ name: { description: "The name of the food category", type: String },
142
+ show: { description: "Whether this category is visible", type: Boolean },
139
143
  }, { timestamps: { createdAt: "created", updatedAt: "updated" } });
140
144
  var likesSchema = new mongoose_1.Schema({
141
- likes: Boolean,
142
- userId: { ref: "User", type: "ObjectId" },
145
+ likes: { description: "Whether the user liked the item", type: Boolean },
146
+ userId: { description: "The user who liked the item", ref: "User", type: "ObjectId" },
143
147
  });
144
148
  var foodSchema = new mongoose_1.Schema({
145
- calories: Number,
146
- categories: [foodCategorySchema],
147
- created: Date,
149
+ calories: { description: "Number of calories in the food", type: Number },
150
+ categories: { description: "Categories this food belongs to", type: [foodCategorySchema] },
151
+ created: { description: "When this food was created", type: Date },
148
152
  eatenBy: [
149
153
  {
154
+ description: "Users who have eaten this food",
150
155
  ref: "User",
151
156
  required: true,
152
157
  type: mongoose_1.Schema.Types.ObjectId,
153
158
  },
154
159
  ],
155
- expiration: plugins_1.DateOnly,
156
- hidden: { default: false, type: Boolean },
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
+ },
157
166
  lastEatenWith: {
167
+ description: "Map of user names to dates they last ate this food with",
158
168
  of: Date,
159
169
  type: Map,
160
170
  },
161
- likesIds: { required: true, type: [likesSchema] },
162
- name: String,
163
- ownerId: { ref: "User", type: "ObjectId" },
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" },
164
174
  source: {
165
- dateAdded: String,
166
- href: String,
167
- name: String,
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 },
168
178
  },
169
- tags: [String],
179
+ tags: { description: "Tags associated with this food", type: [String] },
170
180
  }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
171
181
  foodSchema.virtual("description").get(function () {
172
182
  return "".concat(this.name, " has ").concat(this.calories, " calories");
173
183
  });
174
184
  exports.FoodModel = (0, mongoose_1.model)("Food", foodSchema);
175
185
  var requiredSchema = new mongoose_1.Schema({
176
- about: String,
177
- name: { required: true, type: String },
186
+ about: { description: "Information about the item", type: String },
187
+ name: { description: "The name of the item", required: true, type: String },
178
188
  });
179
189
  exports.RequiredModel = (0, mongoose_1.model)("Required", requiredSchema);
180
190
  function getBaseServer() {
package/package.json CHANGED
@@ -8,6 +8,8 @@
8
8
  "@sentry/profiling-node": "^10.25.0",
9
9
  "@types/qs": "^6.14.0",
10
10
  "@wesleytodd/openapi": "^1.1.0",
11
+ "ajv": "^8.17.1",
12
+ "ajv-formats": "^3.0.1",
11
13
  "axios": "^1.13.2",
12
14
  "cors": "^2.8.5",
13
15
  "cron": "^4.3.4",
@@ -77,6 +79,7 @@
77
79
  },
78
80
  "scripts": {
79
81
  "compile": "bun tsc",
82
+ "compile:watch": "bun tsc -w",
80
83
  "dev": "bun tsc -w",
81
84
  "docs": "typedoc --out docs src/index.ts",
82
85
  "lint": "biome check ./src",
@@ -87,5 +90,5 @@
87
90
  "updateSnapshot": "bun test --update-snapshots"
88
91
  },
89
92
  "types": "dist/index.d.ts",
90
- "version": "0.0.18"
93
+ "version": "0.1.0"
91
94
  }