@terreno/api 0.3.1 → 0.4.2

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 (42) hide show
  1. package/dist/api.js +9 -8
  2. package/dist/betterAuthSetup.js +1 -1
  3. package/dist/configuration.test.d.ts +1 -0
  4. package/dist/configuration.test.js +699 -0
  5. package/dist/configurationApp.d.ts +91 -0
  6. package/dist/configurationApp.js +407 -0
  7. package/dist/configurationPlugin.d.ts +102 -0
  8. package/dist/configurationPlugin.js +285 -0
  9. package/dist/configurationPlugin.test.d.ts +1 -0
  10. package/dist/configurationPlugin.test.js +509 -0
  11. package/dist/example.js +1 -1
  12. package/dist/expressServer.js +5 -1
  13. package/dist/githubAuth.js +2 -2
  14. package/dist/index.d.ts +5 -0
  15. package/dist/index.js +5 -0
  16. package/dist/openApiCompat.d.ts +23 -0
  17. package/dist/openApiCompat.js +198 -0
  18. package/dist/scriptRunner.d.ts +52 -0
  19. package/dist/scriptRunner.js +231 -0
  20. package/dist/secretProviders.d.ts +47 -0
  21. package/dist/secretProviders.js +214 -0
  22. package/dist/terrenoApp.d.ts +25 -0
  23. package/dist/terrenoApp.js +49 -2
  24. package/dist/tests.d.ts +27 -9
  25. package/dist/tests.js +10 -1
  26. package/package.json +13 -13
  27. package/src/api.ts +9 -8
  28. package/src/betterAuthSetup.ts +2 -2
  29. package/src/configuration.test.ts +398 -0
  30. package/src/configurationApp.ts +359 -0
  31. package/src/configurationPlugin.test.ts +299 -0
  32. package/src/configurationPlugin.ts +288 -0
  33. package/src/example.ts +1 -1
  34. package/src/expressServer.ts +6 -1
  35. package/src/githubAuth.ts +4 -4
  36. package/src/index.ts +5 -0
  37. package/src/openApiCompat.ts +147 -0
  38. package/src/permissions.ts +1 -1
  39. package/src/scriptRunner.ts +219 -0
  40. package/src/secretProviders.ts +109 -0
  41. package/src/terrenoApp.ts +44 -2
  42. package/src/tests.ts +12 -1
@@ -0,0 +1,214 @@
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
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __generator = (this && this.__generator) || function (thisArg, body) {
45
+ 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);
46
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
47
+ function verb(n) { return function (v) { return step([n, v]); }; }
48
+ function step(op) {
49
+ if (f) throw new TypeError("Generator is already executing.");
50
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
51
+ 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;
52
+ if (y = 0, t) op = [op[0] & 2, t.value];
53
+ switch (op[0]) {
54
+ case 0: case 1: t = op; break;
55
+ case 4: _.label++; return { value: op[1], done: false };
56
+ case 5: _.label++; y = op[1]; op = [0]; continue;
57
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
58
+ default:
59
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
60
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
61
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
62
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
63
+ if (t[2]) _.ops.pop();
64
+ _.trys.pop(); continue;
65
+ }
66
+ op = body.call(thisArg, _);
67
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
68
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
69
+ }
70
+ };
71
+ var __read = (this && this.__read) || function (o, n) {
72
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
73
+ if (!m) return o;
74
+ var i = m.call(o), r, ar = [], e;
75
+ try {
76
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
77
+ }
78
+ catch (error) { e = { error: error }; }
79
+ finally {
80
+ try {
81
+ if (r && !r.done && (m = i["return"])) m.call(i);
82
+ }
83
+ finally { if (e) throw e.error; }
84
+ }
85
+ return ar;
86
+ };
87
+ Object.defineProperty(exports, "__esModule", { value: true });
88
+ exports.GcpSecretProvider = exports.EnvSecretProvider = void 0;
89
+ var logger_1 = require("./logger");
90
+ /**
91
+ * Secret provider that reads secrets from environment variables.
92
+ * Useful for local development and testing.
93
+ *
94
+ * Maps secret names to environment variable names by converting to SCREAMING_SNAKE_CASE.
95
+ * e.g., "openai-api-key" → process.env.OPENAI_API_KEY
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * const provider = new EnvSecretProvider();
100
+ * // reads process.env.OPENAI_API_KEY
101
+ * const key = await provider.getSecret("openai-api-key");
102
+ * ```
103
+ */
104
+ var EnvSecretProvider = /** @class */ (function () {
105
+ function EnvSecretProvider() {
106
+ this.name = "env";
107
+ }
108
+ EnvSecretProvider.prototype.getSecret = function (secretName) {
109
+ return __awaiter(this, void 0, void 0, function () {
110
+ var envKey, value;
111
+ var _a;
112
+ return __generator(this, function (_b) {
113
+ envKey = secretName.replace(/[-.]/g, "_").toUpperCase();
114
+ value = (_a = process.env[envKey]) !== null && _a !== void 0 ? _a : null;
115
+ if (value === null) {
116
+ logger_1.logger.debug("EnvSecretProvider: no env var found for ".concat(secretName, " (tried ").concat(envKey, ")"));
117
+ }
118
+ return [2 /*return*/, value];
119
+ });
120
+ });
121
+ };
122
+ return EnvSecretProvider;
123
+ }());
124
+ exports.EnvSecretProvider = EnvSecretProvider;
125
+ /**
126
+ * Secret provider that reads secrets from Google Cloud Secret Manager.
127
+ *
128
+ * Requires `@google-cloud/secret-manager` to be installed.
129
+ * Resolves short names like "openai-api-key" to the full resource path
130
+ * `projects/{projectId}/secrets/{secretName}/versions/latest`.
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * const provider = new GcpSecretProvider({ projectId: "my-project" });
135
+ * const key = await provider.getSecret("openai-api-key");
136
+ * ```
137
+ */
138
+ var GcpSecretProvider = /** @class */ (function () {
139
+ function GcpSecretProvider(options) {
140
+ this.name = "gcp";
141
+ this.client = null;
142
+ this.projectId = options.projectId;
143
+ }
144
+ GcpSecretProvider.prototype.getClient = function () {
145
+ return __awaiter(this, void 0, void 0, function () {
146
+ var moduleName, mod, SecretManagerServiceClient, _a;
147
+ var _b, _c;
148
+ return __generator(this, function (_d) {
149
+ switch (_d.label) {
150
+ case 0:
151
+ if (!!this.client) return [3 /*break*/, 4];
152
+ _d.label = 1;
153
+ case 1:
154
+ _d.trys.push([1, 3, , 4]);
155
+ moduleName = "@google-cloud/secret-manager";
156
+ return [4 /*yield*/, Promise.resolve("".concat(/* webpackIgnore: true */ moduleName)).then(function (s) { return __importStar(require(s)); })];
157
+ case 2:
158
+ mod = _d.sent();
159
+ SecretManagerServiceClient = (_b = mod.SecretManagerServiceClient) !== null && _b !== void 0 ? _b : (_c = mod.default) === null || _c === void 0 ? void 0 : _c.SecretManagerServiceClient;
160
+ this.client = new SecretManagerServiceClient();
161
+ return [3 /*break*/, 4];
162
+ case 3:
163
+ _a = _d.sent();
164
+ throw new Error("GcpSecretProvider requires @google-cloud/secret-manager. Install it with: bun add @google-cloud/secret-manager");
165
+ case 4: return [2 /*return*/, this.client];
166
+ }
167
+ });
168
+ });
169
+ };
170
+ GcpSecretProvider.prototype.getSecret = function (secretName) {
171
+ return __awaiter(this, void 0, void 0, function () {
172
+ var client, resourceName, _a, version, payload, error_1;
173
+ var _b;
174
+ return __generator(this, function (_c) {
175
+ switch (_c.label) {
176
+ case 0: return [4 /*yield*/, this.getClient()];
177
+ case 1:
178
+ client = _c.sent();
179
+ if (secretName.startsWith("projects/")) {
180
+ resourceName = secretName.endsWith("/versions/latest")
181
+ ? secretName
182
+ : "".concat(secretName, "/versions/latest");
183
+ }
184
+ else {
185
+ resourceName = "projects/".concat(this.projectId, "/secrets/").concat(secretName, "/versions/latest");
186
+ }
187
+ _c.label = 2;
188
+ case 2:
189
+ _c.trys.push([2, 4, , 5]);
190
+ return [4 /*yield*/, client.accessSecretVersion({ name: resourceName })];
191
+ case 3:
192
+ _a = __read.apply(void 0, [_c.sent(), 1]), version = _a[0];
193
+ payload = (_b = version.payload) === null || _b === void 0 ? void 0 : _b.data;
194
+ if (!payload) {
195
+ logger_1.logger.warn("GcpSecretProvider: secret ".concat(secretName, " has no payload"));
196
+ return [2 /*return*/, null];
197
+ }
198
+ return [2 /*return*/, typeof payload === "string" ? payload : new TextDecoder().decode(payload)];
199
+ case 4:
200
+ error_1 = _c.sent();
201
+ if ((error_1 === null || error_1 === void 0 ? void 0 : error_1.code) === 5) {
202
+ // NOT_FOUND
203
+ logger_1.logger.warn("GcpSecretProvider: secret ".concat(secretName, " not found"));
204
+ return [2 /*return*/, null];
205
+ }
206
+ throw error_1;
207
+ case 5: return [2 /*return*/];
208
+ }
209
+ });
210
+ });
211
+ };
212
+ return GcpSecretProvider;
213
+ }());
214
+ exports.GcpSecretProvider = GcpSecretProvider;
@@ -2,6 +2,7 @@ import * as Sentry from "@sentry/bun";
2
2
  import express from "express";
3
3
  import type { ModelRouterRegistration } from "./api";
4
4
  import { type UserModel as UserMongooseModel } from "./auth";
5
+ import { type ConfigurationAppOptions } from "./configurationApp";
5
6
  import { type AuthOptions } from "./expressServer";
6
7
  import { type GitHubAuthOptions } from "./githubAuth";
7
8
  import { type LoggingOptions } from "./logger";
@@ -94,6 +95,7 @@ export declare class TerrenoApp {
94
95
  private options;
95
96
  private registrations;
96
97
  private middlewareFns;
98
+ private configurationApp;
97
99
  /**
98
100
  * Create a new TerrenoApp builder.
99
101
  *
@@ -139,6 +141,29 @@ export declare class TerrenoApp {
139
141
  * ```
140
142
  */
141
143
  addMiddleware(fn: express.RequestHandler | ((app: express.Application) => void)): this;
144
+ /**
145
+ * Register a configuration model with the application.
146
+ *
147
+ * Adds configuration management endpoints that expose the model's schema
148
+ * as metadata, and provide GET/PATCH endpoints for reading and updating
149
+ * the singleton configuration document. Nested subschemas become separate
150
+ * sections in the admin UI.
151
+ *
152
+ * All configuration endpoints require admin authentication.
153
+ *
154
+ * @param model - Mongoose model with configurationPlugin applied
155
+ * @param options - Optional configuration (basePath, fieldOverrides)
156
+ * @returns This TerrenoApp instance for method chaining
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const app = new TerrenoApp({ userModel: User })
161
+ * .configure(AppConfig)
162
+ * .register(todoRouter)
163
+ * .start();
164
+ * ```
165
+ */
166
+ configure(model: import("mongoose").Model<any>, options?: Omit<ConfigurationAppOptions, "model">): this;
142
167
  /**
143
168
  * Build the Express application without starting the server.
144
169
  *
@@ -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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
14
  if (k2 === undefined) k2 = k;
4
15
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -54,10 +65,12 @@ var cors_1 = __importDefault(require("cors"));
54
65
  var express_1 = __importDefault(require("express"));
55
66
  var qs_1 = __importDefault(require("qs"));
56
67
  var auth_1 = require("./auth");
68
+ var configurationApp_1 = require("./configurationApp");
57
69
  var errors_1 = require("./errors");
58
70
  var expressServer_1 = require("./expressServer");
59
71
  var githubAuth_1 = require("./githubAuth");
60
72
  var logger_1 = require("./logger");
73
+ var openApiCompat_1 = require("./openApiCompat");
61
74
  var openApiEtag_1 = require("./openApiEtag");
62
75
  /**
63
76
  * Fluent API for building Express applications with Terreno framework.
@@ -128,6 +141,7 @@ var TerrenoApp = /** @class */ (function () {
128
141
  function TerrenoApp(options) {
129
142
  this.registrations = [];
130
143
  this.middlewareFns = [];
144
+ this.configurationApp = null;
131
145
  this.options = options;
132
146
  }
133
147
  /**
@@ -175,6 +189,32 @@ var TerrenoApp = /** @class */ (function () {
175
189
  this.middlewareFns.push(fn);
176
190
  return this;
177
191
  };
192
+ /**
193
+ * Register a configuration model with the application.
194
+ *
195
+ * Adds configuration management endpoints that expose the model's schema
196
+ * as metadata, and provide GET/PATCH endpoints for reading and updating
197
+ * the singleton configuration document. Nested subschemas become separate
198
+ * sections in the admin UI.
199
+ *
200
+ * All configuration endpoints require admin authentication.
201
+ *
202
+ * @param model - Mongoose model with configurationPlugin applied
203
+ * @param options - Optional configuration (basePath, fieldOverrides)
204
+ * @returns This TerrenoApp instance for method chaining
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * const app = new TerrenoApp({ userModel: User })
209
+ * .configure(AppConfig)
210
+ * .register(todoRouter)
211
+ * .start();
212
+ * ```
213
+ */
214
+ TerrenoApp.prototype.configure = function (model, options) {
215
+ this.configurationApp = new configurationApp_1.ConfigurationApp(__assign({ model: model }, options));
216
+ return this;
217
+ };
178
218
  /**
179
219
  * Build the Express application without starting the server.
180
220
  *
@@ -204,6 +244,8 @@ var TerrenoApp = /** @class */ (function () {
204
244
  (0, logger_1.setupLogging)(this.options.loggingOptions);
205
245
  var app = (0, express_1.default)();
206
246
  var options = this.options;
247
+ // Record mount paths on layers for Express 5 → OpenAPI compat
248
+ (0, openApiCompat_1.patchAppUse)(app);
207
249
  app.set("query parser", function (str) { var _a; return qs_1.default.parse(str, { arrayLimit: (_a = options.arrayLimit) !== null && _a !== void 0 ? _a : 200 }); });
208
250
  app.use((0, cors_1.default)({ credentials: true, origin: (_c = options.corsOrigin) !== null && _c !== void 0 ? _c : "*" }));
209
251
  try {
@@ -227,7 +269,7 @@ var TerrenoApp = /** @class */ (function () {
227
269
  }
228
270
  finally { if (e_1) throw e_1.error; }
229
271
  }
230
- app.use(express_1.default.json());
272
+ app.use(express_1.default.json({ limit: "50mb" }));
231
273
  // Auth routes (login/signup/refresh_token) before JWT middleware
232
274
  (0, auth_1.addAuthRoutes)(app, options.userModel, options.authOptions);
233
275
  (0, auth_1.setupAuth)(app, options.userModel);
@@ -240,7 +282,7 @@ var TerrenoApp = /** @class */ (function () {
240
282
  next();
241
283
  });
242
284
  // Sentry scopes
243
- app.all("*", function (req, _res, next) {
285
+ app.use(function (req, _res, next) {
244
286
  var _a;
245
287
  var transactionId = req.header("X-Transaction-ID");
246
288
  var sessionId = req.header("X-Session-ID");
@@ -256,6 +298,7 @@ var TerrenoApp = /** @class */ (function () {
256
298
  next();
257
299
  });
258
300
  // OpenAPI
301
+ app.use(openApiCompat_1.openApiCompatMiddleware);
259
302
  app.use(openApiEtag_1.openApiEtagMiddleware);
260
303
  var oapi = (0, openapi_1.default)({
261
304
  info: {
@@ -275,6 +318,10 @@ var TerrenoApp = /** @class */ (function () {
275
318
  (0, githubAuth_1.setupGitHubAuth)(app, options.userModel, options.githubAuth);
276
319
  (0, githubAuth_1.addGitHubAuthRoutes)(app, options.userModel, options.githubAuth, options.authOptions);
277
320
  }
321
+ // Mount configuration app if configured
322
+ if (this.configurationApp) {
323
+ this.configurationApp.register(app);
324
+ }
278
325
  try {
279
326
  // Mount registered model routers and plugins
280
327
  for (var _f = __values(this.registrations), _g = _f.next(); !_g.done; _g = _f.next()) {
package/dist/tests.d.ts CHANGED
@@ -46,36 +46,54 @@ export interface Food {
46
46
  likes: boolean;
47
47
  }[];
48
48
  }
49
- export declare const UserModel: mongoose.Model<User, {}, {}, {}, mongoose.Document<unknown, {}, User, {}, {}> & User & {
49
+ export declare const UserModel: mongoose.Model<User, {}, {}, {}, mongoose.Document<unknown, {}, User, {}, mongoose.DefaultSchemaOptions> & User & {
50
50
  _id: mongoose.Types.ObjectId;
51
51
  } & {
52
52
  __v: number;
53
- }, any>;
54
- export declare const SuperUserModel: mongoose.Model<User & SuperUser, {}, {}, {}, mongoose.Document<unknown, {}, User & SuperUser, {}, {}> & User & SuperUser & {
53
+ } & {
54
+ id: string;
55
+ }, any, User>;
56
+ export declare const SuperUserModel: mongoose.Model<User & SuperUser, {}, {}, {
57
+ id: string;
58
+ }, mongoose.Document<unknown, {}, User & SuperUser, {
59
+ id: string;
60
+ }, mongoose.DefaultSchemaOptions> & Omit<User & SuperUser & {
55
61
  _id: mongoose.Types.ObjectId;
56
62
  } & {
57
63
  __v: number;
58
- }, any>;
59
- export declare const StaffUserModel: mongoose.Model<User & StaffUser, {}, {}, {}, mongoose.Document<unknown, {}, User & StaffUser, {}, {}> & User & StaffUser & {
64
+ }, "id"> & {
65
+ id: string;
66
+ }, any, User & SuperUser>;
67
+ export declare const StaffUserModel: mongoose.Model<User & StaffUser, {}, {}, {
68
+ id: string;
69
+ }, mongoose.Document<unknown, {}, User & StaffUser, {
70
+ id: string;
71
+ }, mongoose.DefaultSchemaOptions> & Omit<User & StaffUser & {
60
72
  _id: mongoose.Types.ObjectId;
61
73
  } & {
62
74
  __v: number;
63
- }, any>;
75
+ }, "id"> & {
76
+ id: string;
77
+ }, any, User & StaffUser>;
64
78
  export declare const FoodModel: Model<Food>;
65
79
  interface RequiredField {
66
80
  name: string;
67
81
  about?: string;
68
82
  }
69
- export declare const RequiredModel: mongoose.Model<RequiredField, {}, {}, {}, mongoose.Document<unknown, {}, RequiredField, {}, {}> & RequiredField & {
83
+ export declare const RequiredModel: mongoose.Model<RequiredField, {}, {}, {}, mongoose.Document<unknown, {}, RequiredField, {}, mongoose.DefaultSchemaOptions> & RequiredField & {
70
84
  _id: mongoose.Types.ObjectId;
71
85
  } & {
72
86
  __v: number;
73
- }, any>;
87
+ } & {
88
+ id: string;
89
+ }, any, RequiredField>;
74
90
  export declare function getBaseServer(): Express;
75
91
  export declare function authAsUser(app: express.Application, type: "admin" | "notAdmin"): Promise<TestAgent>;
76
- export declare function setupDb(): Promise<(mongoose.Document<unknown, {}, User, {}, {}> & User & {
92
+ export declare function setupDb(): Promise<(mongoose.Document<unknown, {}, User, {}, mongoose.DefaultSchemaOptions> & User & {
77
93
  _id: mongoose.Types.ObjectId;
78
94
  } & {
79
95
  __v: number;
96
+ } & {
97
+ id: string;
80
98
  })[]>;
81
99
  export {};
package/dist/tests.js CHANGED
@@ -95,8 +95,10 @@ exports.setupDb = setupDb;
95
95
  var express_1 = __importDefault(require("express"));
96
96
  var mongoose_1 = __importStar(require("mongoose"));
97
97
  var passport_local_mongoose_1 = __importDefault(require("passport-local-mongoose"));
98
+ var qs_1 = __importDefault(require("qs"));
98
99
  var supertest_1 = __importDefault(require("supertest"));
99
100
  var logger_1 = require("./logger");
101
+ var openApiCompat_1 = require("./openApiCompat");
100
102
  var plugins_1 = require("./plugins");
101
103
  var userSchema = new mongoose_1.Schema({
102
104
  admin: { default: false, description: "Whether the user has admin privileges", type: Boolean },
@@ -189,7 +191,14 @@ var requiredSchema = new mongoose_1.Schema({
189
191
  exports.RequiredModel = (0, mongoose_1.model)("Required", requiredSchema);
190
192
  function getBaseServer() {
191
193
  var app = (0, express_1.default)();
192
- app.all("/*", function (req, res, next) {
194
+ app.set("query parser", function (str) { return qs_1.default.parse(str, { arrayLimit: 200 }); });
195
+ // Express 5 defaults to 'simple' query parser (Node querystring) which doesn't
196
+ // support nested bracket notation like name[$regex]=Green. Use qs to match
197
+ // what setupServer() configures.
198
+ app.set("query parser", function (str) { return qs_1.default.parse(str, { arrayLimit: 200 }); });
199
+ // Record mount paths on layers for Express 5 → OpenAPI compat
200
+ (0, openApiCompat_1.patchAppUse)(app);
201
+ app.use(function (req, res, next) {
193
202
  res.header("Access-Control-Allow-Origin", "*");
194
203
  res.header("Access-Control-Allow-Headers", "*");
195
204
  // intercepts OPTIONS method
package/package.json CHANGED
@@ -14,13 +14,13 @@
14
14
  "axios": "^1.13.2",
15
15
  "cors": "^2.8.5",
16
16
  "cron": "^4.3.4",
17
- "expo-server-sdk": "^3.14.0",
18
- "express": "^4.21.2",
17
+ "expo-server-sdk": "^6.0.0",
18
+ "express": "^5.2.1",
19
19
  "generaterr": "^1.5.0",
20
20
  "jsonwebtoken": "^9.0.2",
21
21
  "lodash": "^4.17.23",
22
22
  "luxon": "^3.7.2",
23
- "mongoose": "8.18.1",
23
+ "mongoose": "9.2.3",
24
24
  "mongoose-to-swagger": "^1.4.0",
25
25
  "ms": "^2.1.3",
26
26
  "on-finished": "^2.3.0",
@@ -37,28 +37,28 @@
37
37
  "description": "Styled after the Django & Django REST Framework, a batteries-include framework for building REST APIs with Node/Express/Mongoose.",
38
38
  "devDependencies": {
39
39
  "@biomejs/biome": "^2.3.6",
40
- "@types/bcrypt": "^5.0.2",
40
+ "@types/bcrypt": "^6.0.0",
41
41
  "@types/bun": "^1.2.4",
42
42
  "@types/cors": "^2.8.17",
43
43
  "@types/cron": "^2.4.3",
44
- "@types/express": "^4.17.21",
44
+ "@types/express": "^5.0.6",
45
45
  "@types/jsonwebtoken": "^9.0.9",
46
46
  "@types/lodash": "^4.17.15",
47
47
  "@types/mongodb": "^4.0.7",
48
- "@types/node": "^22.13.5",
48
+ "@types/node": "^25.3.0",
49
49
  "@types/on-finished": "^2.3.4",
50
50
  "@types/passport": "^1.0.17",
51
51
  "@types/passport-anonymous": "^1.0.5",
52
52
  "@types/passport-github2": "^1.2.9",
53
53
  "@types/passport-jwt": "^4.0.1",
54
54
  "@types/passport-local": "^1.0.38",
55
- "@types/sinon": "^17.0.4",
55
+ "@types/sinon": "^21.0.0",
56
56
  "@types/supertest": "^6.0.2",
57
57
  "mongodb-memory-server": "^11.0.1",
58
- "sinon": "^19.0.2",
58
+ "sinon": "^21.0.1",
59
59
  "supertest": "^7.0.0",
60
- "typedoc": "~0.27.9",
61
- "typescript": "5.8.2"
60
+ "typedoc": "~0.28.17",
61
+ "typescript": "5.9.3"
62
62
  },
63
63
  "homepage": "https://github.com/FlourishHealth/terreno#readme",
64
64
  "keywords": [
@@ -71,7 +71,7 @@
71
71
  "main": "dist/index.js",
72
72
  "name": "@terreno/api",
73
73
  "peerDependencies": {
74
- "mongoose": "^8.0.0"
74
+ "mongoose": "^9.0.0"
75
75
  },
76
76
  "repository": {
77
77
  "type": "git",
@@ -88,10 +88,10 @@
88
88
  "lint": "biome check ./src",
89
89
  "lint:fix": "biome check --write ./src",
90
90
  "lint:unsafefix": "biome check --fix --unsafe ./src",
91
- "test": "bun test --preload ./src/tests/bunSetup.ts",
91
+ "test": "bun test --preload ./src/tests/bunSetup.ts --update-snapshots",
92
92
  "test:ci": "bun test --preload ./src/tests/bunSetup.ts",
93
93
  "updateSnapshot": "bun test --update-snapshots"
94
94
  },
95
95
  "types": "dist/index.d.ts",
96
- "version": "0.3.1"
96
+ "version": "0.4.2"
97
97
  }
package/src/api.ts CHANGED
@@ -992,9 +992,12 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
992
992
  });
993
993
  }
994
994
 
995
+ const field = req.params.field as string;
996
+ const itemId = req.params.itemId as string;
997
+
995
998
  // We apply the operation *before* the hooks. As far as the callers are concerned, this should
996
999
  // be like PATCHing the field and replacing the whole thing.
997
- if (operation !== "DELETE" && req.body[req.params.field] === undefined) {
1000
+ if (operation !== "DELETE" && req.body[field] === undefined) {
998
1001
  throw new APIError({
999
1002
  status: 400,
1000
1003
  title: `Malformed body, array operations should have a single, top level key, got: ${Object.keys(
@@ -1003,28 +1006,26 @@ function _buildModelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>):
1003
1006
  });
1004
1007
  }
1005
1008
 
1006
- const field = req.params.field;
1007
-
1008
1009
  const array = [...doc[field]];
1009
1010
  if (operation === "POST") {
1010
1011
  array.push(req.body[field]);
1011
1012
  } else if (operation === "PATCH" || operation === "DELETE") {
1012
1013
  // Check for subschema vs String array:
1013
1014
  let index;
1014
- if (isValidObjectId(req.params.itemId)) {
1015
- index = array.findIndex((x: any) => x.id === req.params.itemId);
1015
+ if (isValidObjectId(itemId)) {
1016
+ index = array.findIndex((x: any) => x.id === itemId);
1016
1017
  } else {
1017
- index = array.findIndex((x: string) => x === req.params.itemId);
1018
+ index = array.findIndex((x: string) => x === itemId);
1018
1019
  }
1019
1020
  if (index === -1) {
1020
1021
  throw new APIError({
1021
1022
  status: 404,
1022
- title: `Could not find ${field}/${req.params.itemId}`,
1023
+ title: `Could not find ${field}/${itemId}`,
1023
1024
  });
1024
1025
  }
1025
1026
  // For PATCHing an item by ID, we need to merge the objects so we don't override the _id or
1026
1027
  // other parts of the subdocument.
1027
- if (operation === "PATCH" && isValidObjectId(req.params.itemId)) {
1028
+ if (operation === "PATCH" && isValidObjectId(itemId)) {
1028
1029
  Object.assign(array[index], req.body[field]);
1029
1030
  } else if (operation === "PATCH") {
1030
1031
  // For PATCHing a string array, we can replace the whole object.
@@ -87,7 +87,7 @@ export const createBetterAuth = (options: CreateBetterAuthOptions): BetterAuthIn
87
87
  trustedOrigins: config.trustedOrigins ?? [],
88
88
  });
89
89
 
90
- return auth;
90
+ return auth as any;
91
91
  };
92
92
 
93
93
  /**
@@ -205,7 +205,7 @@ export const mountBetterAuthRoutes = (
205
205
  const handler = toNodeHandler(auth);
206
206
 
207
207
  // Mount at the base path with wildcard
208
- app.all(`${basePath}/*`, (req, res) => {
208
+ app.all(`${basePath}/*path`, (req, res) => {
209
209
  return handler(req, res);
210
210
  });
211
211